Nemotron-3-Nano-WebGPU / src /utils /think-parser.ts
Xenova's picture
Xenova HF Staff
Upload 88 files
f672a5d verified
export interface ThinkDelta {
type: "reasoning" | "content";
textDelta: string;
}
export class ThinkStreamParser {
reasoning = "";
content = "";
private inThink = false;
private buffer = "";
private static readonly OPEN_TAG = "<think>";
private static readonly CLOSE_TAG = "</think>";
push(text: string): ThinkDelta[] {
const deltas: ThinkDelta[] = [];
this.buffer += text;
while (this.buffer.length > 0) {
if (this.inThink) {
const closeIndex = this.buffer.indexOf(ThinkStreamParser.CLOSE_TAG);
if (closeIndex !== -1) {
const chunk = this.buffer.slice(0, closeIndex);
if (chunk) {
this.reasoning += chunk;
deltas.push({ type: "reasoning", textDelta: chunk });
}
this.buffer = this.buffer.slice(
closeIndex + ThinkStreamParser.CLOSE_TAG.length,
);
this.inThink = false;
continue;
}
const safeLength = this.getSafeFlushLength(
this.buffer,
ThinkStreamParser.CLOSE_TAG,
);
if (safeLength > 0) {
const chunk = this.buffer.slice(0, safeLength);
this.reasoning += chunk;
deltas.push({ type: "reasoning", textDelta: chunk });
this.buffer = this.buffer.slice(safeLength);
}
break;
}
const openIndex = this.buffer.indexOf(ThinkStreamParser.OPEN_TAG);
if (openIndex !== -1) {
const chunk = this.buffer.slice(0, openIndex);
if (chunk) {
this.content += chunk;
deltas.push({ type: "content", textDelta: chunk });
}
this.buffer = this.buffer.slice(
openIndex + ThinkStreamParser.OPEN_TAG.length,
);
this.inThink = true;
continue;
}
const safeLength = this.getSafeFlushLength(
this.buffer,
ThinkStreamParser.OPEN_TAG,
);
if (safeLength > 0) {
const chunk = this.buffer.slice(0, safeLength);
this.content += chunk;
deltas.push({ type: "content", textDelta: chunk });
this.buffer = this.buffer.slice(safeLength);
}
break;
}
return deltas;
}
flush(): ThinkDelta[] {
if (!this.buffer) return [];
const deltas: ThinkDelta[] = [];
if (this.inThink) {
this.reasoning += this.buffer;
deltas.push({ type: "reasoning", textDelta: this.buffer });
} else {
this.content += this.buffer;
deltas.push({ type: "content", textDelta: this.buffer });
}
this.buffer = "";
return deltas;
}
private getSafeFlushLength(buffer: string, tag: string): number {
for (
let overlap = Math.min(buffer.length, tag.length - 1);
overlap > 0;
overlap--
) {
if (buffer.endsWith(tag.slice(0, overlap))) {
return buffer.length - overlap;
}
}
return buffer.length;
}
}