主题
02 - 用 Go 从零实现一个 Agent
目标
我们要实现的 Agent 架构:
┌─────────────────────────────────────────────────────────┐
│ SimpleAgent │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Agent Loop │ │
│ │ │ │
│ │ User Input │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌────────────────┐ │ │
│ │ │ Build Messages │ │ │
│ │ │ System + Tools │ │ │
│ │ │ + History │ │ │
│ │ └───────┬────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌────────────────┐ ┌────────────────────┐ │ │
│ │ │ LLM API Call │────▶│ Tool Call? │ │ │
│ │ │ (Claude API) │ │ │ │ │
│ │ └────────────────┘ │ Yes: Execute Tool │ │ │
│ │ ▲ │ → Loop │ │ │
│ │ │ │ │ │ │
│ │ └──────────────│ No: Return Text │ │ │
│ │ │ → Done │ │ │
│ │ └────────────────────┘ │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ Tools: │
│ ┌───────────┐ ┌─────────────┐ ┌───────────────────┐ │
│ │ calculate │ │ get_time │ │ read_file │ │
│ │ 计算表达式 │ │ 获取当前时间 │ │ 读取文件内容 │ │
│ └───────────┘ └─────────────┘ └───────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘完整代码
go
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"math"
"net/http"
"os"
"strconv"
"strings"
"time"
)
// ==================== 数据结构 ====================
// Claude API 的消息格式
type Message struct {
Role string `json:"role"`
Content []Content `json:"content"`
}
type Content struct {
Type string `json:"type"`
Text string `json:"text,omitempty"`
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Input any `json:"input,omitempty"`
ToolUseID string `json:"tool_use_id,omitempty"`
Content string `json:"content,omitempty"` // tool_result 的内容
}
type Tool struct {
Name string `json:"name"`
Description string `json:"description"`
InputSchema InputSchema `json:"input_schema"`
}
type InputSchema struct {
Type string `json:"type"`
Properties map[string]Property `json:"properties"`
Required []string `json:"required"`
}
type Property struct {
Type string `json:"type"`
Description string `json:"description"`
}
type APIRequest struct {
Model string `json:"model"`
MaxTokens int `json:"max_tokens"`
System string `json:"system"`
Tools []Tool `json:"tools"`
Messages []Message `json:"messages"`
}
type APIResponse struct {
Content []Content `json:"content"`
StopReason string `json:"stop_reason"`
}
// ==================== 工具定义 ====================
// 定义 Agent 可以使用的工具
func getTools() []Tool {
return []Tool{
{
Name: "calculate",
Description: "计算一个数学表达式,支持加减乘除和常见函数",
InputSchema: InputSchema{
Type: "object",
Properties: map[string]Property{
"expression": {
Type: "string",
Description: "要计算的数学表达式,如 '2+3*4' 或 'sqrt(16)'",
},
},
Required: []string{"expression"},
},
},
{
Name: "get_current_time",
Description: "获取当前的日期和时间",
InputSchema: InputSchema{
Type: "object",
Properties: map[string]Property{},
},
},
{
Name: "read_file",
Description: "读取指定路径的文件内容",
InputSchema: InputSchema{
Type: "object",
Properties: map[string]Property{
"path": {
Type: "string",
Description: "文件路径",
},
},
Required: []string{"path"},
},
},
}
}
// ==================== 工具执行 ====================
// 根据工具名执行对应的函数
func executeTool(name string, input map[string]any) string {
switch name {
case "calculate":
expr, _ := input["expression"].(string)
return calculate(expr)
case "get_current_time":
return time.Now().Format("2006-01-02 15:04:05 (Monday)")
case "read_file":
path, _ := input["path"].(string)
data, err := os.ReadFile(path)
if err != nil {
return fmt.Sprintf("Error: %v", err)
}
return string(data)
default:
return fmt.Sprintf("Unknown tool: %s", name)
}
}
// 简单的计算器
func calculate(expr string) string {
expr = strings.TrimSpace(expr)
// 处理 sqrt
if strings.HasPrefix(expr, "sqrt(") {
numStr := expr[5 : len(expr)-1]
num, err := strconv.ParseFloat(numStr, 64)
if err != nil {
return "Error: invalid number"
}
return fmt.Sprintf("%g", math.Sqrt(num))
}
// 简单四则运算(生产环境请用表达式解析库)
for _, op := range []string{"+", "-", "*", "/"} {
if idx := strings.LastIndex(expr, op); idx > 0 {
left, err1 := strconv.ParseFloat(strings.TrimSpace(expr[:idx]), 64)
right, err2 := strconv.ParseFloat(strings.TrimSpace(expr[idx+1:]), 64)
if err1 != nil || err2 != nil {
continue
}
switch op {
case "+": return fmt.Sprintf("%g", left+right)
case "-": return fmt.Sprintf("%g", left-right)
case "*": return fmt.Sprintf("%g", left*right)
case "/":
if right == 0 { return "Error: division by zero" }
return fmt.Sprintf("%g", left/right)
}
}
}
return "Error: cannot parse expression"
}
// ==================== Agent 核心 ====================
type Agent struct {
apiKey string
model string
system string
tools []Tool
messages []Message
}
func NewAgent(apiKey string) *Agent {
return &Agent{
apiKey: apiKey,
model: "claude-sonnet-4-6-20250514",
system: `你是一个有用的助手。你可以使用提供的工具来帮助用户。
当需要计算、查询时间或读取文件时,请使用对应的工具。
回答要简洁明了。`,
tools: getTools(),
messages: []Message{},
}
}
// 调用 Claude API
func (a *Agent) callLLM() (*APIResponse, error) {
reqBody := APIRequest{
Model: a.model,
MaxTokens: 1024,
System: a.system,
Tools: a.tools,
Messages: a.messages,
}
jsonData, _ := json.Marshal(reqBody)
req, _ := http.NewRequest("POST", "https://api.anthropic.com/v1/messages", bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("x-api-key", a.apiKey)
req.Header.Set("anthropic-version", "2023-06-01")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("API call failed: %w", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != 200 {
return nil, fmt.Errorf("API error %d: %s", resp.StatusCode, string(body))
}
var apiResp APIResponse
if err := json.Unmarshal(body, &apiResp); err != nil {
return nil, fmt.Errorf("parse response failed: %w", err)
}
return &apiResp, nil
}
// Agent 主循环 — 这是最核心的部分!
func (a *Agent) Run(userInput string) (string, error) {
// 添加用户消息
a.messages = append(a.messages, Message{
Role: "user",
Content: []Content{{Type: "text", Text: userInput}},
})
maxIterations := 10 // 防止无限循环
for i := 0; i < maxIterations; i++ {
fmt.Printf("\n--- Agent Loop 第 %d 轮 ---\n", i+1)
// 调用 LLM
resp, err := a.callLLM()
if err != nil {
return "", err
}
// 把 LLM 的响应加入历史
a.messages = append(a.messages, Message{
Role: "assistant",
Content: resp.Content,
})
// 检查是否有工具调用
var toolResults []Content
hasToolUse := false
for _, content := range resp.Content {
switch content.Type {
case "text":
fmt.Printf("[Thought] %s\n", content.Text)
case "tool_use":
hasToolUse = true
inputMap, _ := content.Input.(map[string]any)
fmt.Printf("[Action] %s(%v)\n", content.Name, inputMap)
// 执行工具
result := executeTool(content.Name, inputMap)
fmt.Printf("[Observation] %s\n", result)
toolResults = append(toolResults, Content{
Type: "tool_result",
ToolUseID: content.ID,
Content: result,
})
}
}
// 如果没有工具调用,说明 Agent 决定直接回答
if !hasToolUse {
for _, content := range resp.Content {
if content.Type == "text" {
return content.Text, nil
}
}
}
// 有工具调用 → 把结果喂回给 LLM → 继续循环
a.messages = append(a.messages, Message{
Role: "user",
Content: toolResults,
})
}
return "", fmt.Errorf("agent exceeded max iterations")
}
// ==================== 主函数 ====================
func main() {
apiKey := os.Getenv("ANTHROPIC_API_KEY")
if apiKey == "" {
fmt.Println("请设置 ANTHROPIC_API_KEY 环境变量")
fmt.Println("export ANTHROPIC_API_KEY=your-key-here")
return
}
agent := NewAgent(apiKey)
// 测试: 需要多步工具调用的任务
query := "现在几点了?另外帮我算一下 1024 * 768 等于多少"
fmt.Println("用户:", query)
answer, err := agent.Run(query)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("\n最终回答:", answer)
}Agent Loop 执行过程可视化
用户: "现在几点了?另外帮我算一下 1024 * 768 等于多少"
┌─── Loop 第 1 轮 ──────────────────────────────────────┐
│ │
│ → 发送给 LLM (带工具定义) │
│ │
│ ← LLM 返回: │
│ [text] "让我查一下时间并计算。" │
│ [tool_use] get_current_time() │
│ [tool_use] calculate({expression: "1024*768"}) │
│ │
│ → 执行工具: │
│ get_current_time() → "2026-04-22 14:30:00" │
│ calculate("1024*768") → "786432" │
│ │
│ → 工具结果加入消息历史 │
│ │
└───────────────────────────────────────────────────────┘
┌─── Loop 第 2 轮 ──────────────────────────────────────┐
│ │
│ → 发送给 LLM (包含工具结果) │
│ │
│ ← LLM 返回: │
│ [text] "现在是 14:30,1024×768=786432" │
│ (没有工具调用 → 循环结束!) │
│ │
└───────────────────────────────────────────────────────┘
消息历史演变:
第1轮前: [system, user:"几点了..."]
│
第1轮后: [system, user, assistant:[thought+tool_use], user:[tool_results]]
│
第2轮后: [system, user, assistant, user, assistant:[final_answer]]核心知识点
┌──────────────────────────────────────────────────────────┐
│ 从这个实现中学到的 Agent 关键设计 │
├──────────────────────────────────────────────────────────┤
│ │
│ 1. Agent Loop 是一个 while 循环 │
│ → 不断调用 LLM 直到它不再请求工具 │
│ │
│ 2. 工具定义 = JSON Schema │
│ → LLM 需要知道工具的名称、描述和参数格式 │
│ │
│ 3. LLM 不执行工具,只决定调什么 │
│ → Agent 框架负责实际执行和结果回传 │
│ │
│ 4. 消息历史 = Agent 的记忆 │
│ → 每轮的推理和工具结果都追加到历史中 │
│ │
│ 5. 需要防护措施 │
│ → 最大迭代次数(防止无限循环) │
│ → 工具执行的安全沙箱 │
│ → 上下文长度管理 │
│ │
│ Claude Code 本质上就是这个模式的超级增强版: │
│ → 更多工具 (Bash, Read, Edit, Write, Agent...) │
│ → 更复杂的 System Prompt │
│ → 上下文压缩 (Compaction) │
│ → 权限控制 │
│ → 流式输出 │
│ │
└──────────────────────────────────────────────────────────┘模块四完成!
下一个模块: 模块五:RAG 全链路