From 8ecece1572c4c7500a48ec7efc12737334f6ef2e Mon Sep 17 00:00:00 2001 From: nyoung Date: Thu, 8 Oct 2026 17:41:28 +0800 Subject: [PATCH] =?UTF-8?q?fix(prompt):=20=E5=B7=A5=E5=85=B7=E6=8C=87?= =?UTF-8?q?=E4=BB=A4=E6=98=BE=E5=BC=8F=E7=A6=81=E6=AD=A2=20DSML=20?= =?UTF-8?q?=E6=A0=87=E8=AE=B0=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 模型会退回 DeepSeek 原生 DSML 方言(<|DSML|> calls> / invoke / parameter)代替 JSON 输出工具调用,而网关的流式(extractableToolJSON)、 解析(TryParseToolCalls)、续写(ToolCallTruncated)三层都只认 JSON: DSML 回复既不会被转成 tool_calls,也不会触发续写,整段标记连同被截断 的正文一起当普通文本透传给客户端。 按既定决策不做 DSML 解析兼容,改为在注入的 TOOL INSTRUCTIONS 中显式 禁止该方言、声明只接受 JSON 格式。三接口共用 BuildPrompt 注入点, 一处改动同时生效。 新增红绿回归测试 TestToolsPromptForbidsDSML(先复现红,加文案后转绿)。 --- internal/openai/prompt.go | 7 +++++++ internal/openai/prompt_test.go | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/internal/openai/prompt.go b/internal/openai/prompt.go index d8d66ab..8918e05 100644 --- a/internal/openai/prompt.go +++ b/internal/openai/prompt.go @@ -237,6 +237,13 @@ func toolsPrompt(tools []ToolDef, toolChoice json.RawMessage) string { "", `{"tool_calls": [{"id": "call_xxx", "type": "function", "function": {"name": "tool_name", "arguments": "{\"param\": \"value\"}"}}]}`, "", + // DeepSeek 模型有时会退回自己的 DSML 标记方言(<|DSML|> calls> / + // invoke / parameter),网关不解析该方言,整段会被当普通文本透传, + // 客户端收不到 tool_calls——指令层显式禁止,只认上面的 JSON。 + "Never call tools with DSML/XML-style markup (for example `<|DSML|> calls>` or " + + "`<|DSML|> invoke name=\"...\">`). Those tags are discarded — only the " + + "JSON format above is accepted.", + "", "Available tools:", } for i, tool := range tools { diff --git a/internal/openai/prompt_test.go b/internal/openai/prompt_test.go index f629609..bd0be05 100644 --- a/internal/openai/prompt_test.go +++ b/internal/openai/prompt_test.go @@ -36,6 +36,24 @@ func TestBuildPromptWithTools(t *testing.T) { } } +// 红测试:上游模型曾用 DeepSeek 原生 DSML 标记(<|DSML|> calls> / invoke / +// parameter)代替 JSON 输出工具调用,网关三层(流式、解析、续写)都只认 +// JSON,整段标记被当普通文本发给客户端。指令层必须显式禁止该方言。 +func TestToolsPromptForbidsDSML(t *testing.T) { + prompt := BuildPrompt([]ChatMessage{ + {Role: "user", Content: MessageContent{Text: "Hi"}}, + }, PromptOptions{Tools: []ToolDef{{ + Type: "function", + Function: Function{Name: "write", Description: "Write a file"}, + }}}) + if !strings.Contains(prompt, "DSML") { + t.Error("tool instructions must explicitly forbid DSML markup") + } + if !strings.Contains(prompt, "Never") { + t.Error("tool instructions must state the DSML ban as a prohibition") + } +} + func TestBuildPromptAppendsSamplingConstraints(t *testing.T) { temp := 0.4 max := 512