fix(prompt): 工具指令显式禁止 DSML 标记输出

模型会退回 DeepSeek 原生 DSML 方言(<|DSML|> calls> / invoke /
parameter)代替 JSON 输出工具调用,而网关的流式(extractableToolJSON)、
解析(TryParseToolCalls)、续写(ToolCallTruncated)三层都只认 JSON:
DSML 回复既不会被转成 tool_calls,也不会触发续写,整段标记连同被截断
的正文一起当普通文本透传给客户端。

按既定决策不做 DSML 解析兼容,改为在注入的 TOOL INSTRUCTIONS 中显式
禁止该方言、声明只接受 JSON 格式。三接口共用 BuildPrompt 注入点,
一处改动同时生效。

新增红绿回归测试 TestToolsPromptForbidsDSML(先复现红,加文案后转绿)。
This commit is contained in:
nyoung committed 2026-10-08 17:41:28 +08:00
1 parent 68ba2aa431
commit 8ecece1572
2 files changed
+25

No files matched your search

+7
View File
@@ -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 {
+18
View File
@@ -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