// Tool represents a function that can be called by the agent. type Tool interface { // Name is the name of the tool, as it would be called by the model. Name() string // Description is a description of the tool's purpose, inputs, and outputs. Description() string // Parameters returns the JSON schema for the tool's arguments. Parameters() any // Execute runs the tool with the given arguments and returns the output. // The args are expected to be a JSON string. Execute(args string) (string, error) }
// Message is a single message in a chat completion request. type Message struct { Role string`json:"role"` Content string`json:"content,omitempty"` ToolCalls []ToolCall `json:"tool_calls,omitempty"` ToolCallID string`json:"tool_call_id,omitempty"` }
// ToolCall represents a complete tool call. type ToolCall struct { ID string`json:"id"` Type string`json:"type"` Function struct { Name string`json:"name"` Arguments string`json:"arguments"` } `json:"function"` }
// ToolCallDelta represents a chunk of a tool call from the stream. type ToolCallDelta struct { Index int`json:"index"` ID string`json:"id,omitempty"` Type string`json:"type,omitempty"` Function struct { Name string`json:"name,omitempty"` Arguments string`json:"arguments,omitempty"` } `json:"function,omitempty"` }
// Tool represents the definition of a tool that can be called. type Tool struct { Type string`json:"type"` Function struct { Name string`json:"name"` Description string`json:"description"` Parameters any `json:"parameters"` } `json:"function"` }
修改流式调用的核心方法 CompletionStream,在流式输出中聚合工具调用命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// Aggregate tool calls iflen(choice.Delta.ToolCalls) > 0 { for _, toolCallDelta := range choice.Delta.ToolCalls { iflen(toolCalls) <= toolCallDelta.Index { // Expand the slice if a new tool call index appears toolCalls = append(toolCalls, make([]ToolCall, toolCallDelta.Index-len(toolCalls)+1)...) } call := &toolCalls[toolCallDelta.Index] if toolCallDelta.ID != "" { call.ID = toolCallDelta.ID } if toolCallDelta.Type != "" { call.Type = toolCallDelta.Type } call.Function.Name += toolCallDelta.Function.Name call.Function.Arguments += toolCallDelta.Function.Arguments } }
// Agent is the core logic unit of the application. It is UI-independent. type Agent struct { client *Client modelName string toolRegistry map[string]tools.Tool
// State messages []Message pendingToolCalls []ToolCall confirmingToolCall ToolCall isConfirming bool
// Live state for streaming lastStreamedContent string }
toolCall := a.pendingToolCalls[0] tool, ok := a.toolRegistry[toolCall.Function.Name] if !ok { returnfunc() tea.Msg { return ErrorMsg{Err: fmt.Errorf("tool %s not found in registry", toolCall.Function.Name)} } }
// model is the state of our TUI application. type model struct { viewport viewport.Model textarea textarea.Model agent *llm.Agent // The new core logic handler sub chan tea.Msg // Channel for receiving streaming messages loading bool lastContent string// Stores the live content of the current streaming message err error availableHeight int// Available height for the viewport }
case llm.AssistantToolCallMsg: cmd = m.agent.HandleToolCallRequest(msg) m.viewport.SetContent(m.renderConversation(true)) m.viewport.GotoBottom() return m, cmd
case llm.ToolResultMsg: cmd = m.agent.HandleToolResult(msg.ToolCallID, msg.Result) m.viewport.SetContent(m.renderConversation(true)) m.viewport.GotoBottom() return m, cmd
case tea.KeyMsg: viewState := m.agent.GetViewState() if viewState.IsConfirming { switch msg.String() { case"y", "Y": cmd = m.agent.HandleConfirmation(true) return m, cmd case"n", "N": cmd = m.agent.HandleConfirmation(false) return m, cmd } }
switch msg.Type { case tea.KeyCtrlC, tea.KeyEsc: return m, tea.Quit case tea.KeyEnter: prompt := strings.TrimSpace(m.textarea.Value()) if prompt != "" && !m.loading && !viewState.IsConfirming { cmd = m.agent.HandleUserInput(prompt) m.textarea.Reset() m.viewport.SetContent(m.renderConversation(true)) m.viewport.GotoBottom() return m, cmd } } }
在 View 中添加一个简单的确认 Box(UI 先糊弄着):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
var confirmationBox string if viewState.IsConfirming { confirmStyle := lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). BorderForeground(lipgloss.Color("205")). Padding(1, 2) question := fmt.Sprintf( "GhOst wants to run the tool: %s\n\nArguments:\n%s\n\nDo you want to allow this?", viewState.ConfirmingToolCall.Function.Name, viewState.ConfirmingToolCall.Function.Arguments, ) confirmationBox = confirmStyle.Render(question) }
至于 fs 工具中添加一个确认配置,注册时由工具自己声明控制:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// Tool represents a function that can be called by the agent. type Tool interface { // Name is the name of the tool, as it would be called by the model. Name() string // Description is a description of the tool's purpose, inputs, and outputs. Description() string // Parameters returns the JSON schema for the tool's arguments. Parameters() any // Execute runs the tool with the given arguments and returns the output. // The args are expected to be a JSON string. Execute(args string) (string, error) // RequiresConfirmation indicates whether the tool requires user confirmation before execution. RequiresConfirmation() bool }
var cmd *exec.Cmd if runtime.GOOS == "windows" { // Windows 系统 cmd = exec.Command("cmd", "/C", toolArgs.Command) } else { // Linux, macOS, and other Unix-like systems cmd = exec.Command("sh", "-c", toolArgs.Command) }
由于多了不同的工具类型,之前的工具定义接口也要单独提出来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// Tool represents a function that can be called by the agent. type Tool interface { // Name is the name of the tool, as it would be called by the model. Name() string // Description is a description of the tool's purpose, inputs, and outputs. Description() string // Parameters returns the JSON schema for the tool's arguments. Parameters() any // Execute runs the tool with the given arguments and returns the output. // The args are expected to be a JSON string. Execute(args string) (string, error) // RequiresConfirmation indicates whether the tool requires user confirmation before execution. RequiresConfirmation() bool }