fix(toolcall): pass gates and align go/js multi-layer parser

This commit is contained in:
CJACK.
2026-03-09 19:16:28 +08:00
parent bd72b91f27
commit 12d5f136d5
9 changed files with 355 additions and 30 deletions

View File

@@ -9,6 +9,7 @@ const {
buildToolCallCandidates,
parseToolCallsPayload,
parseMarkupToolCalls,
parseTextKVToolCalls,
} = require('./parse_payload');
const TOOL_NAME_LOOSE_PATTERN = /[^a-z0-9]+/g;
@@ -53,13 +54,23 @@ function parseToolCallsDetailed(text, toolNames) {
if (parsed.length === 0) {
parsed = parseMarkupToolCalls(c);
}
if (parsed.length === 0) {
parsed = parseTextKVToolCalls(c);
}
if (parsed.length > 0) {
result.sawToolCallSyntax = true;
break;
}
}
if (parsed.length === 0) {
return result;
parsed = parseMarkupToolCalls(sanitized);
if (parsed.length === 0) {
parsed = parseTextKVToolCalls(sanitized);
if (parsed.length === 0) {
return result;
}
}
result.sawToolCallSyntax = true;
}
const filtered = filterToolCallsDetailed(parsed, toolNames);
@@ -90,6 +101,9 @@ function parseStandaloneToolCallsDetailed(text, toolNames) {
if (parsed.length === 0) {
parsed = parseMarkupToolCalls(trimmed);
}
if (parsed.length === 0) {
parsed = parseTextKVToolCalls(trimmed);
}
if (parsed.length === 0) {
return result;
}
@@ -207,7 +221,8 @@ function looksLikeToolCallSyntax(text) {
return lower.includes('tool_calls')
|| lower.includes('<tool_call')
|| lower.includes('<function_call')
|| lower.includes('<invoke');
|| lower.includes('<invoke')
|| lower.includes('function.name:');
}
module.exports = {

View File

@@ -18,6 +18,7 @@ const TOOL_CALL_MARKUP_ARGS_PATTERNS = [
/<(?:[a-z0-9_:-]+:)?args\b[^>]*>([\s\S]*?)<\/(?:[a-z0-9_:-]+:)?args>/i,
/<(?:[a-z0-9_:-]+:)?params\b[^>]*>([\s\S]*?)<\/(?:[a-z0-9_:-]+:)?params>/i,
];
const TEXT_KV_NAME_PATTERN = /function\.name:\s*([a-zA-Z0-9_.-]+)/gi;
const {
toStringSafe,
@@ -141,6 +142,47 @@ function parseMarkupToolCalls(text) {
return out;
}
function parseTextKVToolCalls(text) {
const raw = toStringSafe(text);
if (!raw) {
return [];
}
const out = [];
const matches = [...raw.matchAll(TEXT_KV_NAME_PATTERN)];
if (matches.length === 0) {
return out;
}
for (let i = 0; i < matches.length; i += 1) {
const match = matches[i];
const name = toStringSafe(match[1]).trim();
if (!name) {
continue;
}
const nameEnd = match.index + toStringSafe(match[0]).length;
const searchEnd = i + 1 < matches.length ? matches[i + 1].index : raw.length;
const searchArea = raw.slice(nameEnd, searchEnd);
const argIdx = searchArea.indexOf('function.arguments:');
if (argIdx < 0) {
continue;
}
const argStart = nameEnd + argIdx + 'function.arguments:'.length;
const bracePos = raw.slice(argStart, searchEnd).indexOf('{');
if (bracePos < 0) {
continue;
}
const objStart = argStart + bracePos;
const obj = extractJSONObjectFrom(raw, objStart);
if (!obj.ok) {
continue;
}
out.push({
name,
input: parseToolCallInput(raw.slice(objStart, obj.end)),
});
}
return out;
}
function parseMarkupSingleToolCall(attrs, inner) {
const embedded = parseToolCallsPayload(inner);
if (embedded.length > 0) {
@@ -317,4 +359,5 @@ module.exports = {
buildToolCallCandidates,
parseToolCallsPayload,
parseMarkupToolCalls,
parseTextKVToolCalls,
};