refactor(agent): replace XML api_call with native function calling

Migrate the Telegram bot agent from an XML tag hack (<api_call>) to
OpenAI-native function calling via CallWithRequestFull.

Key changes:
- mcp/interface.go: add parseMCPResponseFull to clientHooks interface
- mcp/client.go: route callWithRequestFull through hooks for overridability
- mcp/claude_client.go: override parseMCPResponseFull for Claude response
  format (tool_use blocks instead of choices[].message.tool_calls)
- telegram/agent/agent.go: rewrite Run() to use CallWithRequestFull;
  define api_request tool with JSON Schema; implement tool-call loop
  with role="tool" result messages; remove XML parsing entirely
- telegram/agent/apicall.go: remove parseAPICall (dead code)
- telegram/agent/prompt.go: simplify — remove XML format instructions,
  replace with concise api_request tool usage instructions
- telegram/agent/agent_test.go: rebuild all tests using LLMResponse
  objects; add TestNarrationStructurallyImpossible, TestOnChunkCalledWithFinalReply,
  TestToolCallIDPropagated; remove XML-specific tests

Architecture advantage: with native function calling, the LLM returns
EITHER ToolCalls OR Content — never both. Narration is now structurally
impossible at the protocol level, not just enforced by prompt rules.

All 11 agent tests pass. mcp package tests pass.
This commit is contained in:
tinkle-community
2026-03-08 17:10:07 +08:00
parent 5f47dd13db
commit 9fcf44af65
8 changed files with 472 additions and 320 deletions

View File

@@ -13,35 +13,22 @@ func BuildAgentPrompt(apiDocs, userID string) string {
- All API calls are made on behalf of this user
- When asked "which user / username / email" — answer with this user ID directly, no API call needed
## Tool: api_call
When you need to call the API, your ENTIRE response must be ONLY the tag — nothing else:
<api_call>{"method":"GET","path":"/api/xxx","body":{}}</api_call>
When you have a final answer (no more API calls needed), reply with plain text — NO tag at all.
ABSOLUTE RULES — violation = broken product:
- 【ZERO NARRATION】Your response is EITHER the api_call tag alone OR a final text reply. NEVER both except api_call at the very end.
- NEVER output ANY text before an api_call tag. No "好的", no "现在", no "我将", no "Let me", no "I will", no "正在", no "Creating...", no ellipsis, NOTHING.
- NEVER more than one <api_call> tag per response
## Tool: api_request
Use the api_request tool to call the NOFX REST API:
- method: "GET" | "POST" | "PUT" | "DELETE"
- path: API path; query params go in the path: /api/positions?trader_id=xxx
- body: JSON object (use {} for GET requests)
- query parameters go in the path: /api/positions?trader_id=xxx
## NOFX API Documentation
%s
## Behavior Rules
1. 【SILENT ACTION】When you need to call an API: output ONLY the <api_call> tag. Zero words before it.
2. Only ONE <api_call> tag per response, always alone with nothing else
3. After getting an API result, decide: call another API (output tag only) or give final reply (text only)
4. If the API returns success (2xx), the operation succeeded — do not retry
5. Reply in the same language the user used (中文→中文, English→English)
6. Keep final replies concise — show results, not process
7. Ask for ALL missing required info in ONE message — never ask one field at a time
8. When user provides enough info, act immediately — no confirmation needed
9. Be decisive — infer intent from context, use schema to fill in smart defaults
1. Reply in the same language the user used (中文→中文, English→English)
2. Keep final replies concise — show results, not process
3. Ask for ALL missing required info in ONE message — never ask one field at a time
4. When user provides enough info, act immediately — no confirmation needed
5. Be decisive — infer intent from context, use schema to fill in smart defaults
## Verification Rule (CRITICAL)
After ANY PUT or POST that creates or modifies a resource:
@@ -56,7 +43,6 @@ After ANY PUT or POST that creates or modifies a resource:
- "AI model not enabled": tell user to enable the model first via PUT /api/models
- "Exchange not enabled": tell user to enable the exchange first
- 5xx: server error, ask user to try again
- stream interrupted / unavailable: apologize briefly and ask user to retry
## Account State (injected at conversation start)
At the start of each new conversation, a [Current Account State] block is provided with: