mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-04 16:35:27 +08:00
refactor: optimize tool-calling prompt instructions and examples for improved model adherence
This commit is contained in:
@@ -53,7 +53,7 @@ func injectToolPrompt(messages []map[string]any, tools []any, policy util.ToolCh
|
||||
if len(toolSchemas) == 0 {
|
||||
return messages, names
|
||||
}
|
||||
toolPrompt := "You have access to these tools:\n\n" + strings.Join(toolSchemas, "\n\n") + "\n\nWhen you need to use tools, output ONLY XML using this canonical format:\n<tool_calls>\n <tool_call>\n <tool_name>tool_name</tool_name>\n <parameters>{\"param\":\"value\"}</parameters>\n </tool_call>\n</tool_calls>\n\n【EXAMPLE】\nUser: Please check the weather in Beijing and Shanghai, and update my todo list.\nAssistant:\n<tool_calls>\n <tool_call>\n <tool_name>get_weather</tool_name>\n <parameters>{\"city\":\"Beijing\"}</parameters>\n </tool_call>\n <tool_call>\n <tool_name>get_weather</tool_name>\n <parameters>{\"city\":\"Shanghai\"}</parameters>\n </tool_call>\n <tool_call>\n <tool_name>update_todo</tool_name>\n <parameters>{\"todos\":[{\"content\":\"Buy milk\"},{\"content\":\"Write report\"}]}</parameters>\n </tool_call>\n</tool_calls>\n\nIMPORTANT:\n1) If calling tools, output ONLY XML in the canonical format above. Do NOT include any extra text.\n2) Do NOT wrap tool-call XML in markdown/code fences (for example, do not use triple backticks).\n3) `<parameters>` MUST be strict JSON object text. Use double quotes for all JSON keys/strings.\n4) If calling multiple tools, emit multiple `<tool_call>` blocks under one `<tool_calls>` root.\n5) After receiving a tool result, you MUST use it to produce the final answer.\n6) Only call another tool when the previous result is missing required data or returned an error."
|
||||
toolPrompt := "You have access to these tools:\n\n" + strings.Join(toolSchemas, "\n\n") + "\n\n" + buildToolCallInstructions(names)
|
||||
if policy.Mode == util.ToolChoiceRequired {
|
||||
toolPrompt += "\n7) For this response, you MUST call at least one tool from the allowed list."
|
||||
}
|
||||
@@ -73,6 +73,90 @@ func injectToolPrompt(messages []map[string]any, tools []any, policy util.ToolCh
|
||||
return messages, names
|
||||
}
|
||||
|
||||
// buildToolCallInstructions generates the tool-calling instruction block
|
||||
// with attention-optimized structure: rules → negative examples → positive
|
||||
// examples (using real tool names) → anchor. This ordering exploits the
|
||||
// transformer's recency bias: the last tokens before generation starts
|
||||
// carry the strongest influence on the model's first output tokens.
|
||||
func buildToolCallInstructions(toolNames []string) string {
|
||||
// Pick real tool names for examples; fall back to generic names.
|
||||
ex1 := "read_file"
|
||||
ex2 := "write_to_file"
|
||||
ex3 := "ask_followup_question"
|
||||
used := map[string]bool{}
|
||||
for _, n := range toolNames {
|
||||
switch {
|
||||
case !used["ex1"] && (n == "read_file" || n == "list_files" || n == "search_files"):
|
||||
ex1 = n
|
||||
used["ex1"] = true
|
||||
case !used["ex2"] && (n == "write_to_file" || n == "apply_diff" || n == "execute_command"):
|
||||
ex2 = n
|
||||
used["ex2"] = true
|
||||
case !used["ex3"] && (n == "ask_followup_question" || n == "attempt_completion" || n == "update_todo_list"):
|
||||
ex3 = n
|
||||
used["ex3"] = true
|
||||
}
|
||||
}
|
||||
|
||||
return `TOOL CALL FORMAT — FOLLOW EXACTLY:
|
||||
|
||||
When calling tools, emit ONLY raw XML. No text before, no text after, no markdown fences.
|
||||
|
||||
<tool_calls>
|
||||
<tool_call>
|
||||
<tool_name>TOOL_NAME_HERE</tool_name>
|
||||
<parameters>{"key":"value"}</parameters>
|
||||
</tool_call>
|
||||
</tool_calls>
|
||||
|
||||
RULES:
|
||||
1) Output ONLY the XML above when calling tools. Do NOT mix tool XML with regular text.
|
||||
2) <parameters> MUST contain a strict JSON object. All JSON keys and strings use double quotes.
|
||||
3) Multiple tools → multiple <tool_call> blocks inside ONE <tool_calls> root.
|
||||
4) Do NOT wrap the XML in markdown code fences (no triple backticks).
|
||||
5) After receiving a tool result, use it directly. Only call another tool if the result is insufficient.
|
||||
6) If you want to say something AND call a tool, output text first, then the XML block on its own.
|
||||
|
||||
❌ WRONG — Do NOT do these:
|
||||
Wrong 1 — mixed text and XML:
|
||||
I'll read the file for you. <tool_calls><tool_call>...
|
||||
Wrong 2 — code fence wrapping:
|
||||
` + "```xml\n <tool_calls>...\n ```" + `
|
||||
Wrong 3 — missing <tool_calls> wrapper:
|
||||
<tool_call><tool_name>` + ex1 + `</tool_name><parameters>{}</parameters></tool_call>
|
||||
|
||||
✅ CORRECT EXAMPLES:
|
||||
|
||||
Example A — Single tool:
|
||||
<tool_calls>
|
||||
<tool_call>
|
||||
<tool_name>` + ex1 + `</tool_name>
|
||||
<parameters>{"path":"src/main.go"}</parameters>
|
||||
</tool_call>
|
||||
</tool_calls>
|
||||
|
||||
Example B — Two tools in parallel:
|
||||
<tool_calls>
|
||||
<tool_call>
|
||||
<tool_name>` + ex1 + `</tool_name>
|
||||
<parameters>{"path":"config.json"}</parameters>
|
||||
</tool_call>
|
||||
<tool_call>
|
||||
<tool_name>` + ex2 + `</tool_name>
|
||||
<parameters>{"path":"output.txt","content":"Hello world"}</parameters>
|
||||
</tool_call>
|
||||
</tool_calls>
|
||||
|
||||
Example C — Tool with complex JSON parameters (newlines in values use \n):
|
||||
<tool_calls>
|
||||
<tool_call>
|
||||
<tool_name>` + ex3 + `</tool_name>
|
||||
<parameters>{"question":"Which approach do you prefer?","follow_up":[{"text":"Option A"},{"text":"Option B"}]}</parameters>
|
||||
</tool_call>
|
||||
</tool_calls>
|
||||
|
||||
Remember: Output ONLY the <tool_calls>...</tool_calls> XML block when calling tools.`
|
||||
}
|
||||
func formatIncrementalStreamToolCallDeltas(deltas []toolCallDelta, ids map[int]string) []map[string]any {
|
||||
if len(deltas) == 0 {
|
||||
return nil
|
||||
|
||||
@@ -71,19 +71,19 @@ func TestBuildOpenAIFinalPrompt_VercelPreparePathKeepsFinalAnswerInstruction(t *
|
||||
}
|
||||
|
||||
finalPrompt, _ := buildOpenAIFinalPrompt(messages, tools, "")
|
||||
if !strings.Contains(finalPrompt, "After receiving a tool result, you MUST use it to produce the final answer.") {
|
||||
if !strings.Contains(finalPrompt, "After receiving a tool result, use it directly.") {
|
||||
t.Fatalf("vercel prepare finalPrompt missing final-answer instruction: %q", finalPrompt)
|
||||
}
|
||||
if !strings.Contains(finalPrompt, "Only call another tool when the previous result is missing required data or returned an error.") {
|
||||
if !strings.Contains(finalPrompt, "Only call another tool if the result is insufficient.") {
|
||||
t.Fatalf("vercel prepare finalPrompt missing retry guard instruction: %q", finalPrompt)
|
||||
}
|
||||
if !strings.Contains(finalPrompt, "output ONLY XML using this canonical format") {
|
||||
if !strings.Contains(finalPrompt, "TOOL CALL FORMAT") {
|
||||
t.Fatalf("vercel prepare finalPrompt missing xml format instruction: %q", finalPrompt)
|
||||
}
|
||||
if !strings.Contains(finalPrompt, "Do NOT wrap tool-call XML in markdown/code fences") {
|
||||
if !strings.Contains(finalPrompt, "Do NOT wrap the XML in markdown code fences") {
|
||||
t.Fatalf("vercel prepare finalPrompt missing no-fence xml instruction: %q", finalPrompt)
|
||||
}
|
||||
if strings.Contains(finalPrompt, "```xml") || strings.Contains(finalPrompt, "```json") {
|
||||
if strings.Contains(finalPrompt, "```json") {
|
||||
t.Fatalf("vercel prepare finalPrompt should not require fenced tool calls: %q", finalPrompt)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user