Files
nofx/mcp/request.go
tinkle-community 9fcf44af65 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.
2026-03-08 17:10:07 +08:00

98 lines
3.8 KiB
Go

package mcp
// Message represents a conversation message.
// Supports plain messages (Role+Content), assistant tool-call messages (ToolCalls),
// and tool result messages (Role="tool", ToolCallID, Content).
type Message struct {
Role string `json:"role"` // "system", "user", "assistant", "tool"
Content string `json:"content,omitempty"` // Text content (omitted when ToolCalls present)
ToolCalls []ToolCall `json:"tool_calls,omitempty"` // Set by assistant when calling tools
ToolCallID string `json:"tool_call_id,omitempty"` // Set on role="tool" result messages
}
// ToolCall is a single function call requested by the LLM.
type ToolCall struct {
ID string `json:"id"` // Unique call ID (e.g. "call_abc123")
Type string `json:"type"` // Always "function"
Function ToolCallFunction `json:"function"` // Function name and JSON-serialised arguments
}
// ToolCallFunction holds the function name and raw JSON arguments string.
type ToolCallFunction struct {
Name string `json:"name"` // Function name
Arguments string `json:"arguments"` // JSON-encoded argument object
}
// LLMResponse is returned by CallWithRequestFull and carries both the assistant
// text reply (Content) and any structured tool calls (ToolCalls).
// Exactly one of the two fields will be non-empty for a well-formed response.
type LLMResponse struct {
Content string // Plain-text reply (final answer)
ToolCalls []ToolCall // Structured tool invocations
}
// Tool represents a tool/function that AI can call
type Tool struct {
Type string `json:"type"` // Usually "function"
Function FunctionDef `json:"function"` // Function definition
}
// FunctionDef function definition
type FunctionDef struct {
Name string `json:"name"` // Function name
Description string `json:"description,omitempty"` // Function description
Parameters map[string]any `json:"parameters,omitempty"` // Parameter schema (JSON Schema)
}
// Request AI API request (supports advanced features)
type Request struct {
// Basic fields
Model string `json:"model"` // Model name
Messages []Message `json:"messages"` // Conversation message list
Stream bool `json:"stream,omitempty"` // Whether to stream response
// Optional parameters (for fine-grained control)
Temperature *float64 `json:"temperature,omitempty"` // Temperature (0-2), controls randomness
MaxTokens *int `json:"max_tokens,omitempty"` // Maximum token count
TopP *float64 `json:"top_p,omitempty"` // Nucleus sampling parameter (0-1)
FrequencyPenalty *float64 `json:"frequency_penalty,omitempty"` // Frequency penalty (-2 to 2)
PresencePenalty *float64 `json:"presence_penalty,omitempty"` // Presence penalty (-2 to 2)
Stop []string `json:"stop,omitempty"` // Stop sequences
// Advanced features
Tools []Tool `json:"tools,omitempty"` // Available tools list
ToolChoice string `json:"tool_choice,omitempty"` // Tool choice strategy ("auto", "none", {"type": "function", "function": {"name": "xxx"}})
}
// NewMessage creates a message
func NewMessage(role, content string) Message {
return Message{
Role: role,
Content: content,
}
}
// NewSystemMessage creates a system message
func NewSystemMessage(content string) Message {
return Message{
Role: "system",
Content: content,
}
}
// NewUserMessage creates a user message
func NewUserMessage(content string) Message {
return Message{
Role: "user",
Content: content,
}
}
// NewAssistantMessage creates an assistant message
func NewAssistantMessage(content string) Message {
return Message{
Role: "assistant",
Content: content,
}
}