
























@@ -15,19 +15,23 @@ const MEMORY_TAG_QUICK_RE = /<\s*\/?\s*relevant[-_]memories\b/i;
1515 * This stateful pass hides content from an opening tag through the matching
1616 * closing tag, or to end-of-string if the stream was truncated mid-tag.
1717 */
18-const TOOL_CALL_QUICK_RE = /<\s*\/?\s*(?:tool_call|tool_result|function_calls?|tool_calls)\b/i;
18+const TOOL_CALL_QUICK_RE =
19+/<\s*\/?\s*(?:tool_call|tool_result|function_calls?|function|tool_calls)\b/i;
1920const TOOL_CALL_TAG_NAMES = new Set([
2021"tool_call",
2122"tool_result",
2223"function_call",
2324"function_calls",
25+"function",
2426"tool_calls",
2527]);
2628const TOOL_CALL_JSON_PAYLOAD_START_RE =
2729/^(?:\s+[A-Za-z_:][-A-Za-z0-9_:.]*\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))*\s*(?:\r?\n\s*)?[[{]/;
2830const TOOL_CALL_XML_PAYLOAD_START_RE =
2931/^\s*(?:\r?\n\s*)?<(?:function|invoke|parameters?|arguments?)\b/i;
303233+type ToolCallPayloadKind = "json" | "xml" | null;
34+3135function endsInsideQuotedString(text: string, start: number, end: number): boolean {
3236let quoteChar: "'" | '"' | null = null;
3337let isEscaped = false;
@@ -108,9 +112,36 @@ function findTagCloseIndex(text: string, start: number): number {
108112return -1;
109113}
110114111-function looksLikeToolCallPayloadStart(text: string, start: number): boolean {
115+function detectToolCallPayloadKind(text: string, start: number): ToolCallPayloadKind {
112116const rest = text.slice(start);
113-return TOOL_CALL_JSON_PAYLOAD_START_RE.test(rest) || TOOL_CALL_XML_PAYLOAD_START_RE.test(rest);
117+if (TOOL_CALL_JSON_PAYLOAD_START_RE.test(rest)) {
118+return "json";
119+}
120+if (TOOL_CALL_XML_PAYLOAD_START_RE.test(rest)) {
121+return "xml";
122+}
123+return null;
124+}
125+126+function isLikelyStandaloneFunctionToolCall(
127+text: string,
128+tagStart: number,
129+tag: ParsedToolCallTag,
130+): boolean {
131+if (tag.tagName !== "function" || tag.isClose || tag.isSelfClosing || tag.isTruncated) {
132+return false;
133+}
134+135+if (!/\bname\s*=/.test(text.slice(tag.contentStart, tag.end))) {
136+return false;
137+}
138+139+let idx = tagStart - 1;
140+while (idx >= 0 && (text[idx] === " " || text[idx] === "\t")) {
141+idx -= 1;
142+}
143+144+return idx < 0 || text[idx] === "\n" || text[idx] === "\r" || /[.!?:]/.test(text[idx]);
114145}
115146116147function parseToolCallTagAt(text: string, start: number): ParsedToolCallTag | null {
@@ -174,7 +205,9 @@ export function stripToolCallXmlTags(text: string): string {
174205let result = "";
175206let lastIndex = 0;
176207let inToolCallBlock = false;
177-let toolCallContentStart = 0;
208+let toolCallBlockContentStart = 0;
209+let toolCallBlockNeedsQuoteBalance = false;
210+let toolCallBlockStart = 0;
178211let toolCallBlockTagName: string | null = null;
179212const visibleTagBalance = new Map<string, number>();
180213@@ -216,13 +249,19 @@ export function stripToolCallXmlTags(text: string): string {
216249continue;
217250}
218251const payloadStart = tag.isTruncated ? tag.contentStart : tag.end;
219-const hasToolCallPayloadStart =
220-tag.tagName === "tool_call"
221- ? looksLikeToolCallPayloadStart(text, payloadStart)
222- : TOOL_CALL_JSON_PAYLOAD_START_RE.test(text.slice(payloadStart));
223-if (!tag.isClose && hasToolCallPayloadStart) {
252+const payloadKind =
253+tag.tagName === "tool_call" || tag.tagName === "function"
254+ ? detectToolCallPayloadKind(text, payloadStart)
255+ : TOOL_CALL_JSON_PAYLOAD_START_RE.test(text.slice(payloadStart))
256+ ? "json"
257+ : null;
258+const shouldStripStandaloneFunction =
259+tag.tagName !== "function" || isLikelyStandaloneFunctionToolCall(text, idx, tag);
260+if (!tag.isClose && payloadKind && shouldStripStandaloneFunction) {
224261inToolCallBlock = true;
225-toolCallContentStart = tag.end;
262+toolCallBlockContentStart = tag.end;
263+toolCallBlockNeedsQuoteBalance = payloadKind === "json";
264+toolCallBlockStart = idx;
226265toolCallBlockTagName = tag.tagName;
227266if (tag.isTruncated) {
228267lastIndex = text.length;
@@ -242,9 +281,11 @@ export function stripToolCallXmlTags(text: string): string {
242281tag.isClose &&
243282(tag.tagName === toolCallBlockTagName ||
244283(toolCallBlockTagName === "tool_result" && tag.tagName === "tool_call")) &&
245-!endsInsideQuotedString(text, toolCallContentStart, idx)
284+(!toolCallBlockNeedsQuoteBalance ||
285+!endsInsideQuotedString(text, toolCallBlockContentStart, idx))
246286) {
247287inToolCallBlock = false;
288+toolCallBlockNeedsQuoteBalance = false;
248289toolCallBlockTagName = null;
249290}
250291@@ -254,6 +295,8 @@ export function stripToolCallXmlTags(text: string): string {
254295255296if (!inToolCallBlock) {
256297result += text.slice(lastIndex);
298+} else if (toolCallBlockTagName === "function") {
299+result += text.slice(toolCallBlockStart);
257300}
258301259302return result;
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。