Files
2026-06-17 13:13:06 +08:00

163 lines
5.2 KiB
Go

package toolrouter
import (
"context"
"fmt"
"strings"
"time"
agents "aichat/agenttool"
"aichat/completion"
"aichat/config"
"aichat/llm"
"aichat/message"
"aichat/stream"
"aichat/toolmanager"
"aichat/utils"
"github.com/volcengine/volcengine-go-sdk/service/arkruntime/model"
)
type AgentTool struct {
name string
definition *model.Tool
execute func(context.Context, string) (string, error)
}
func NewAgentTool(name string, definition *model.Tool, execute func(context.Context, string) (string, error)) AgentTool {
return AgentTool{name: name, definition: definition, execute: execute}
}
func (t AgentTool) Name() string { return t.name }
func (t AgentTool) Definition() *model.Tool { return t.definition }
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
}
overrides := map[string]config.ToolRouteConfig{}
for _, item := range state.cfg.Tools {
name := strings.ToLower(strings.TrimSpace(item.Name))
if name != "" {
overrides[name] = item
}
}
if len(state.cfg.Tools) > 0 {
tools := make([]AgentTool, 0, len(state.cfg.Tools))
for _, item := range state.cfg.Tools {
name := strings.ToLower(strings.TrimSpace(item.Name))
if name == "" || !item.Enabled {
continue
}
tool, ok := manager.Get(name)
if !ok {
continue
}
if agentTool, ok := buildAgentTool(tool, strings.TrimSpace(item.Description), profile, emit); ok {
tools = append(tools, agentTool)
}
}
return tools
}
loaded := manager.Tools()
tools := make([]AgentTool, 0, len(loaded))
for _, tool := range loaded {
if tool == nil {
continue
}
item := overrides[tool.Name()]
if agentTool, ok := buildAgentTool(tool, strings.TrimSpace(item.Description), profile, emit); ok {
tools = append(tools, agentTool)
}
}
return tools
}
func buildAgentTool(tool agents.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 := agents.Runtime{
Profile: profile,
Now: time.Now(),
Emit: wrapAgentEmit(emit),
}
if profile != nil {
runtime.CompleteText = func(ctx context.Context, prompt string, maxTokens int) (string, error) {
return completion.CompleteText(ctx, profile, []message.ChatMessage{{Role: "system", Content: prompt}}, maxTokens)
}
}
return tool.Execute(ctx, args, runtime)
},
}, true
}
func wrapAgentEmit(emit stream.EmitFunc) agents.EmitFunc {
if emit == nil {
return nil
}
return func(frame any) {
switch value := frame.(type) {
case agents.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)
}
}
}
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": utils.TruncateString(result, 1200)}})
}
return result
}