From 0f1985af4a3b38609fdd730705ba82aa45017dfa Mon Sep 17 00:00:00 2001 From: huangxun Date: Mon, 9 Mar 2026 15:00:16 +0800 Subject: [PATCH] =?UTF-8?q?feat(util):=20=E5=A2=9E=E5=8A=A0=E5=AF=B9?= =?UTF-8?q?=E6=B7=B7=E6=9D=82=E6=96=87=E6=9C=AC=E4=B8=AD=20Tool=20Call=20?= =?UTF-8?q?=E7=9A=84=20fallback=20=E8=A7=A3=E6=9E=90=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 引入 parseTextKVToolCalls 解析器以处理混杂文本或带历史记录套壳(如 [TOOL_CALL_HISTORY])输出的函数调用提取。 - 将其作为 JSON 和 XML 的 fallback 解析手段集成到主流程。 - 添加单元测试用例且更新相关语义说明文档。 --- docs/toolcall-semantics.md | 3 +- internal/util/toolcalls_parse.go | 14 ++++++- internal/util/toolcalls_textkv.go | 55 ++++++++++++++++++++++++++ internal/util/toolcalls_textkv_test.go | 52 ++++++++++++++++++++++++ 4 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 internal/util/toolcalls_textkv.go create mode 100644 internal/util/toolcalls_textkv_test.go diff --git a/docs/toolcall-semantics.md b/docs/toolcall-semantics.md index 50165a9..b386762 100644 --- a/docs/toolcall-semantics.md +++ b/docs/toolcall-semantics.md @@ -19,7 +19,8 @@ This document defines the cross-runtime contract for `ParseToolCallsDetailed` / - first `{` to last `}` object slice. 3. Parse each candidate in order: - JSON payload parser (`tool_calls`, list, single call object), - - markup parser (``, ``, ``; supports attributes + nested fields). + - XML/Markup parser (``, ``, ``; supports attributes + nested fields), + - Text KV fallback parser (`function.name: ` ... `function.arguments: {json}`). 4. Stop at first candidate that yields at least one call. ## Name normalization policy diff --git a/internal/util/toolcalls_parse.go b/internal/util/toolcalls_parse.go index 53eac8e..d08ffe7 100644 --- a/internal/util/toolcalls_parse.go +++ b/internal/util/toolcalls_parse.go @@ -45,6 +45,9 @@ func ParseToolCallsDetailed(text string, availableToolNames []string) ToolCallPa if len(tc) == 0 { tc = parseMarkupToolCalls(candidate) } + if len(tc) == 0 { + tc = parseTextKVToolCalls(candidate) + } if len(tc) > 0 { parsed = tc result.SawToolCallSyntax = true @@ -54,7 +57,10 @@ func ParseToolCallsDetailed(text string, availableToolNames []string) ToolCallPa if len(parsed) == 0 { parsed = parseXMLToolCalls(text) if len(parsed) == 0 { - return result + parsed = parseTextKVToolCalls(text) + if len(parsed) == 0 { + return result + } } result.SawToolCallSyntax = true } @@ -93,6 +99,9 @@ func ParseStandaloneToolCallsDetailed(text string, availableToolNames []string) if len(parsed) == 0 { parsed = parseMarkupToolCalls(candidate) } + if len(parsed) == 0 { + parsed = parseTextKVToolCalls(candidate) + } if len(parsed) > 0 { result.SawToolCallSyntax = true calls, rejectedNames := filterToolCallsDetailed(parsed, availableToolNames) @@ -207,7 +216,8 @@ func looksLikeToolCallSyntax(text string) bool { return strings.Contains(lower, "tool_calls") || strings.Contains(lower, "