Skip to content

Commit 6608f4f

Browse files
committed
AIX: Gemini: collapse empty text in between reasoning
1 parent 93378ad commit 6608f4f

File tree

5 files changed

+19
-12
lines changed

5 files changed

+19
-12
lines changed

src/modules/aix/client/ContentReassembler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -317,13 +317,13 @@ export class ContentReassembler {
317317

318318
}
319319

320-
private onAppendReasoningText({ _t /*, weak*/ }: Extract<AixWire_Particles.PartParticleOp, { p: 'tr_' }>): void {
320+
private onAppendReasoningText({ _t, restart }: Extract<AixWire_Particles.PartParticleOp, { p: 'tr_' }>): void {
321321
// Break text accumulation
322322
this.currentTextFragmentIndex = null;
323323

324324
// append to existing ModelAuxVoidFragment if possible
325325
const currentFragment = this.accumulator.fragments[this.accumulator.fragments.length - 1];
326-
if (currentFragment && isVoidFragment(currentFragment) && isModelAuxPart(currentFragment.part)) {
326+
if (!restart && currentFragment && isVoidFragment(currentFragment) && isModelAuxPart(currentFragment.part)) {
327327
const appendedPart = { ...currentFragment.part, aText: (currentFragment.part.aText || '') + _t } satisfies DVoidModelAuxPart;
328328
this.accumulator.fragments[this.accumulator.fragments.length - 1] = { ...currentFragment, part: appendedPart };
329329
return;

src/modules/aix/server/api/aix.wiretypes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -671,7 +671,7 @@ export namespace AixWire_Particles {
671671

672672
export type PartParticleOp =
673673
| { p: '❤' } // heart beat
674-
| { p: 'tr_', _t: string, weak?: 'tag' } // reasoning text, incremental; could be a 'weak' detection, e.g. heuristic from '<think>' rather than API-provided
674+
| { p: 'tr_', _t: string, weak?: 'tag', restart?: boolean } // reasoning text, incremental; could be a 'weak' detection, e.g. heuristic from '<think>' rather than API-provided
675675
| { p: 'trs', signature: string } // reasoning signature
676676
| { p: 'trr_', _data: string } // reasoning raw (or redacted) data
677677
// | { p: 'ii', mimeType: string, i_b64?: string /* never undefined */ }

src/modules/aix/server/dispatch/chatGenerate/ChatGenerateTransmitter.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -265,15 +265,16 @@ export class ChatGenerateTransmitter implements IParticleTransmitter {
265265
}
266266

267267
/** Appends reasoning text, which is its own kind of content */
268-
appendReasoningText(textChunk: string, weak?: Extract<AixWire_Particles.PartParticleOp, { p: 'tr_' }>['weak']) {
268+
appendReasoningText(textChunk: string, options?: { weak?: 'tag', restart?: boolean }) {
269269
// NOTE: don't skip on empty chunks, as we want to transition states
270270
// if there was another Part in the making, queue it
271271
if (this.currentPart)
272272
this.endMessagePart();
273273
this.currentPart = {
274274
p: 'tr_',
275275
_t: textChunk,
276-
...(weak ? { weak } : {}),
276+
...(options?.weak ? { weak: options.weak } : {}),
277+
...(options?.restart ? { restart: true } : {}),
277278
};
278279
// [throttle] send it immediately for now
279280
this._queueParticleS();
@@ -326,12 +327,12 @@ export class ChatGenerateTransmitter implements IParticleTransmitter {
326327
const closingIdx = remaining.indexOf('</think>');
327328
if (closingIdx >= 0) {
328329
const reasoningText = remaining.substring(0, closingIdx);
329-
this.appendReasoningText(reasoningText, 'tag');
330+
this.appendReasoningText(reasoningText, { weak: 'tag' });
330331
this.isThinkingText = false;
331332
remaining = remaining.substring(closingIdx + '</think>'.length);
332333
// this is the only branch that can still loop
333334
} else {
334-
this.appendReasoningText(remaining, 'tag');
335+
this.appendReasoningText(remaining, { weak: 'tag' });
335336
return;
336337
}
337338
} else {

src/modules/aix/server/dispatch/chatGenerate/parsers/IParticleTransmitter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export interface IParticleTransmitter {
2323
appendText(textChunk: string): void;
2424

2525
/** Appends reasoning text, creating a part if missing [throttled] */
26-
appendReasoningText(textChunk: string, weak?: 'tag'): void;
26+
appendReasoningText(textChunk: string, options?: { weak?: 'tag', restart?: boolean }): void;
2727

2828
/** Sets a reasoning signature, associated with the current reasoning text */
2929
setReasoningSignature(signature: string): void;

src/modules/aix/server/dispatch/chatGenerate/parsers/gemini.parser.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { geminiConvertPCM2WAV } from './gemini.audioutils';
99

1010

1111
// configuration
12+
const COLLAPSE_EMPTY_TEXT_PARTS = true;
1213
const ENABLE_RECITATIONS_AS_CITATIONS = false;
1314

1415

@@ -34,6 +35,7 @@ export function createGeminiGenerateContentResponseParser(requestedModelName: st
3435
let sentRequestedModelName = false;
3536
let sentActualModelName = false;
3637
let timeToFirstEvent: number;
38+
let collapsedTextPartForReasoning = false;
3739
let skipComputingTotalsOnce = isStreaming;
3840
let groundingIndexNumber = 0;
3941

@@ -118,14 +120,18 @@ export function createGeminiGenerateContentResponseParser(requestedModelName: st
118120
// <- TextPart
119121
case 'text' in mPart:
120122
// [Gemini, 2025-01-23] CoT support
121-
if (mPart.thought)
122-
pt.appendReasoningText(mPart.text || '');
123-
else {
123+
if (mPart.thought) {
124+
pt.appendReasoningText(mPart.text || '', collapsedTextPartForReasoning ? { restart: true } : undefined);
125+
collapsedTextPartForReasoning = false;
126+
} else {
124127
// NOTE: considering the below, but not yet
125128
// don't send an empty text part, which may happen in between reasoning parts
126129
// and this way we can merge them
127130
// if (mPart.text?.length)
128-
pt.appendText(mPart.text || '');
131+
if (!COLLAPSE_EMPTY_TEXT_PARTS || mPart.text)
132+
pt.appendText(mPart.text || '');
133+
else
134+
collapsedTextPartForReasoning = true;
129135
}
130136
break;
131137

0 commit comments

Comments
 (0)