Files
meshtastic_mqtt_server/toolrouter/tools.go
T
2026-06-17 23:54:38 +08:00

141 lines
4.6 KiB
Go

package toolrouter
import (
"context"
"fmt"
"strings"
"time"
"meshtastic_mqtt_server/agenttool"
"meshtastic_mqtt_server/llm"
"meshtastic_mqtt_server/stream"
"meshtastic_mqtt_server/toolmanager"
"github.com/volcengine/volcengine-go-sdk/service/arkruntime/model"
)
// AgentTool represents a tool that can be called by the LLM
type AgentTool struct {
name string
definition *model.Tool
execute func(context.Context, string) (string, error)
}
// NewAgentTool creates a new agent tool
func NewAgentTool(name string, definition *model.Tool, execute func(context.Context, string) (string, error)) AgentTool {
return AgentTool{name: name, definition: definition, execute: execute}
}
// Name returns the tool name
func (t AgentTool) Name() string { return t.name }
// Definition returns the tool definition
func (t AgentTool) Definition() *model.Tool { return t.definition }
// AvailableAgentTools returns all available agent tools
func AvailableAgentTools(state *State, profile *llm.Profile, manager *toolmanager.Manager, emit stream.EmitFunc) []AgentTool {
return availableAgentTools(state, profile, manager, emit)
}
func availableAgentTools(state *State, profile *llm.Profile, manager *toolmanager.Manager, emit stream.EmitFunc) []AgentTool {
if state == nil || state.cfg == nil || !state.cfg.Enabled || manager == nil {
return nil
}
loaded := manager.Tools()
tools := make([]AgentTool, 0, len(loaded))
for _, tool := range loaded {
if tool == nil {
continue
}
if agentTool, ok := buildAgentTool(tool, "", profile, emit); ok {
tools = append(tools, agentTool)
}
}
return tools
}
func buildAgentTool(tool agenttool.LoadedTool, description string, profile *llm.Profile, emit stream.EmitFunc) (AgentTool, bool) {
if tool == nil || !tool.Enabled() {
return AgentTool{}, false
}
definition := tool.ToolDefinition(description)
if definition == nil || definition.Function == nil {
return AgentTool{}, false
}
name := tool.Name()
return AgentTool{
name: name,
definition: definition,
execute: func(ctx context.Context, args string) (string, error) {
runtime := agenttool.Runtime{
Profile: profile,
Now: time.Now(),
Emit: wrapAgentEmit(emit),
}
return tool.Execute(ctx, args, runtime)
},
}, true
}
func wrapAgentEmit(emit stream.EmitFunc) agenttool.EmitFunc {
if emit == nil {
return nil
}
return func(frame any) {
switch value := frame.(type) {
case agenttool.Frame:
emit(stream.Frame{Type: value.Type, Tool: value.Tool, Stage: value.Stage, Status: value.Status, Message: value.Message, Data: value.Data, Error: value.Error, Text: value.Text})
case stream.Frame:
emit(value)
}
}
}
// ExecuteAgentToolCall executes a tool call and returns the result
func ExecuteAgentToolCall(ctx context.Context, call *model.ToolCall, tools map[string]AgentTool, emit stream.EmitFunc) string {
if call == nil || call.Type != model.ToolTypeFunction {
result := "工具调用无效:仅支持 function 类型工具。"
if emit != nil {
emit(stream.Frame{Type: "trace", Tool: "agent_tools", Stage: "execute", Status: "error", Message: result})
}
return result
}
toolName := call.Function.Name
if emit != nil {
emit(stream.Frame{Type: "trace", Tool: toolName, Stage: "arguments", Status: "running", Message: "准备执行工具", Data: map[string]any{"tool_call_id": call.ID, "arguments": call.Function.Arguments}})
}
tool, ok := tools[toolName]
if !ok {
result := fmt.Sprintf("工具调用失败:未知工具 %s。", toolName)
if emit != nil {
emit(stream.Frame{Type: "trace", Tool: toolName, Stage: "execute", Status: "error", Message: result})
}
return result
}
started := time.Now()
result, err := tool.execute(ctx, call.Function.Arguments)
durationMs := time.Since(started).Milliseconds()
if err != nil {
messageText := fmt.Sprintf("工具 %s 执行失败:%v", tool.name, err)
if emit != nil {
emit(stream.Frame{Type: "trace", Tool: tool.name, Stage: "execute", Status: "error", Message: "工具执行失败", Data: map[string]any{"tool_call_id": call.ID, "duration_ms": durationMs, "error": err.Error()}})
}
return messageText
}
if strings.TrimSpace(result) == "" {
result = fmt.Sprintf("工具 %s 执行完成,但没有返回内容。", tool.name)
}
if emit != nil {
emit(stream.Frame{Type: "trace", Tool: tool.name, Stage: "result", Status: "success", Message: "工具执行完成", Data: map[string]any{"tool_call_id": call.ID, "duration_ms": durationMs, "result_preview": truncateString(result, 1200)}})
}
return result
}
func truncateString(s string, max int) string {
if len(s) <= max {
return s
}
return s[:max] + "..."
}