141 lines
4.6 KiB
Go
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] + "..."
|
|
}
|