Skip to content

Commit 99ac320

Browse files
committed
Update streaming and parse-xml-tool-calls
1 parent 4a54188 commit 99ac320

2 files changed

Lines changed: 42 additions & 4 deletions

File tree

src/parse-xml-tool-calls.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ function hasFunctionCalls(text) {
2626
|| /^\[tool_calls\]\s*\n[\s\S]*?- name:/im.test(text);
2727
}
2828

29+
function hasFunctionCallOpenTag(text) {
30+
if (typeof text !== 'string') return false;
31+
return /<function_calls\s*>/i.test(text)
32+
|| /\[function_calls\]/i.test(text)
33+
|| /\[tool_calls\]\s*$/m.test(text);
34+
}
35+
2936
// 从文本中提取所有 function_calls / tool_calls 块
3037
function extractFunctionCallsBlocks(text) {
3138
if (typeof text !== 'string') return [];
@@ -486,6 +493,7 @@ function extractTextToolCallField(block, fieldName) {
486493

487494
module.exports = {
488495
hasFunctionCalls,
496+
hasFunctionCallOpenTag,
489497
extractFunctionCallsBlocks,
490498
parseFunctionCallsBlock,
491499
parseXmlToolCallsFromText,

src/streaming.js

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const crypto = require('crypto');
22
const { normalizeError, createHttpError } = require('./errors');
33
const { normalizeUsage, sanitizeAssistantContent } = require('./helpers');
4+
const { hasFunctionCallOpenTag, stripXmlToolCalls } = require('./parse-xml-tool-calls');
45

56
function writeSseEvent(response, payload) {
67
response.write(`data: ${payload}\n\n`);
@@ -36,6 +37,8 @@ function beginOpenAiStream(response, model, options = {}) {
3637
usage: undefined,
3738
sawContent: false,
3839
accumulatedContent: '',
40+
contentBuffer: '',
41+
inToolCallMode: false,
3942
onComplete: typeof options.onComplete === 'function' ? options.onComplete : null,
4043
toolCallDetector: typeof options.toolCallDetector === 'function' ? options.toolCallDetector : null
4144
};
@@ -54,12 +57,31 @@ function normalizeStreamDelta(event) {
5457
function emitContentDelta(response, streamState, delta) {
5558
if (!delta) return;
5659

57-
streamState.sawContent = true;
5860
streamState.accumulatedContent += delta;
61+
streamState.contentBuffer += delta;
5962

60-
writeSseEvent(response, JSON.stringify(
61-
buildChunk(streamState.id, streamState.created, streamState.model, { content: delta }, null)
62-
));
63+
if (!streamState.inToolCallMode && hasFunctionCallOpenTag(streamState.accumulatedContent)) {
64+
streamState.inToolCallMode = true;
65+
streamState.contentBuffer = '';
66+
return;
67+
}
68+
69+
if (streamState.inToolCallMode) {
70+
streamState.contentBuffer = '';
71+
return;
72+
}
73+
74+
const shouldFlush = streamState.contentBuffer.length >= 40
75+
|| /\n/.test(streamState.contentBuffer)
76+
|| (streamState.contentBuffer.length > 5 && /[\.\!\?\。\!\?]\s*$/.test(streamState.contentBuffer));
77+
78+
if (shouldFlush && streamState.contentBuffer) {
79+
streamState.sawContent = true;
80+
writeSseEvent(response, JSON.stringify(
81+
buildChunk(streamState.id, streamState.created, streamState.model, { content: streamState.contentBuffer }, null)
82+
));
83+
streamState.contentBuffer = '';
84+
}
6385
}
6486

6587
function writeOpenAiContentChunk(response, streamState, event) {
@@ -72,6 +94,14 @@ function writeOpenAiContentChunk(response, streamState, event) {
7294
}
7395

7496
async function finishOpenAiStream(response, streamState) {
97+
if (streamState.contentBuffer && !streamState.inToolCallMode) {
98+
streamState.sawContent = true;
99+
writeSseEvent(response, JSON.stringify(
100+
buildChunk(streamState.id, streamState.created, streamState.model, { content: streamState.contentBuffer }, null)
101+
));
102+
streamState.contentBuffer = '';
103+
}
104+
75105
const toolCalls = streamState.toolCallDetector
76106
? streamState.toolCallDetector(streamState.accumulatedContent)
77107
: null;

0 commit comments

Comments
 (0)