2026年6月9日 2 分钟阅读

如何用 150 行代码构建自己的 AI Agent CLI

tinyash 0 条评论

Go Micro 团队最近发布了一篇精彩的教程——只需约 150 行代码,就能构建一个让 AI Agent 调用微服务的命令行工具。这听起来像是天方夜谭,但读完你会发现——工具调用(Tool Calling)的核心逻辑确实就这么简单。本文带你逐行拆解,让你理解每个环节并在自己的框架中复现。

问题:如何让 AI 调用你的服务?

假设你有一组微服务——创建用户、发送邮件、查询订单。你想用自然语言说”给 Alice 发一封欢迎邮件”,然后 AI 自动调用对应的服务。

LLM 能完成推理(”用户想发邮件,所以调用 email 服务”),但它需要你提供三样东西:

  1. 可用工具的列表——包括名称、描述和参数
  2. 工具执行的能力——在 LLM 选择某个工具后,实际调用它
  3. 对话记忆——让后续提问能理解上下文

这三个需求,就是整个 Agent CLI 的核心。下面我们逐个解决。

第一部分:发现工具

LLM 需要知道当前有哪些工具可用。在 Go Micro 中,每个服务都在注册中心注册了自己的端点,包括请求类型和字段元数据:

tools := ai.NewTools(reg, ai.ToolClient(client))
discovered, err := tools.Discover()

discovered 是一个 []ai.Tool 切片——每个服务端点对应一个工具。每个工具有名称(如 users_Users_Create)、描述(来自 handler 的 doc comment)和参数 schema(来自请求结构体字段)。

关键洞见:如果你不使用 Go Micro,这部分需要自己实现——枚举你的函数/端点,构建一个 {name, description, parameters} 列表。注册中心只是让这个过程自动化了。

// 文档注释自动成为工具描述
// @example 标签给 LLM 提供调用示例
// CreateUser creates a new user account.
// @example {"name": "Alice", "email": "alice@example.com"}
func (h *Users) CreateUser(ctx context.Context, 
    req *pb.CreateRequest, rsp *pb.CreateResponse) error {
    // ...
}

第二部分:创建模型

有了工具列表后,创建一个模型实例:

m := ai.New("anthropic", 
    ai.WithAPIKey(apiKey),
    ai.WithTools(tools),
)

这一步发生了两件事:

  1. ai.New 选择提供商(Anthropic、OpenAI、Gemini 等——统一接口)
  2. ai.WithTools(tools) 连接了执行端——当模型说”调用 users_Users_Create 并传入这些参数”,handler 会路由到正确的 RPC 并返回结果

Go Micro 的抽象层让切换提供商只需改一个字符串参数,这是其设计的精妙之处。

第三部分:跟踪对话

hist := ai.NewHistory(50)

History 是一个带大小限制的消息累加器。它本质上就是 []Message,外加 AddMessagesReset 三个方法。

每次交互后,你把用户的 prompt 和模型的回复加入 history,在下一次调用时传入已累积的消息。这就是后续提问能理解上下文的原因。

核心原则:对话记忆不需要复杂——一个简单的消息数组加上 max size 限制就够用了。你不需要向量数据库或 RAG 来实现多轮对话。

第四部分:核心循环

将以上三部分组合起来,核心函数只有约 40 行:

func ask(ctx context.Context, m ai.Model, hist *ai.History, 
    tools []ai.Tool, prompt string) error {
    // 1. 记录 prompt 到历史
    hist.Add("user", prompt)
    
    // 2. 调用模型(传入 prompt、系统指令、工具列表和对话历史)
    resp, err := m.Generate(ctx, &ai.Request{
        Prompt: prompt,
        SystemPrompt: systemPrompt,
        Tools: tools,
        Messages: hist.Messages(),
    })
    if err != nil { return err }
    
    // 3. 打印并记录回复
    if resp.Reply != "" {
        hist.Add("assistant", resp.Reply)
        fmt.Println(resp.Reply)
    }
    
    // 4. 展示哪些工具被调用了
    for _, tc := range resp.ToolCalls {
        args, _ := json.Marshal(tc.Input)
        fmt.Printf("  → called %s(%s)\n", tc.Name, args)
    }
    
    // 5. 打印最终答案
    if resp.Answer != "" {
        hist.Add("assistant", resp.Answer)
        fmt.Println(resp.Answer)
    }
    return nil
}

逐行解读:

  1. 记录 prompt——加入对话历史,后续轮次可用
  2. 调用模型——传入 prompt、系统指令、工具列表和历史消息
  3. 输出回复——模型的思考过程
  4. 报告工具调用——模型决定调用哪些工具,handler 自动执行
  5. 输出最终答案——工具执行完成后模型生成的摘要

模型的 Generate 方法承担了所有重活:它决定是否调用工具,handler(来自设置的第二步)执行它们,然后模型生成最终答案。你从未写过任何”如果用户要发邮件就调用 email 服务”的逻辑——LLM 从工具描述中自行完成这个推理。

第五部分:REPL 包装

ask 包在一个读取循环里,就是一个完整的聊天 CLI:

scanner := bufio.NewScanner(os.Stdin)
for {
    fmt.Print("> ")
    if !scanner.Scan() { return nil }
    line := strings.TrimSpace(scanner.Text())
    
    switch line {
    case "":   continue
    case "exit", "quit": return nil
    case "reset": hist.Reset(); continue
    default:
        if err := ask(ctx, m, hist, discovered, line); err != nil {
            fmt.Printf("error: %v\n", err)
        }
    }
}

支持 exitquitreset 三条内置命令,其余输入都当作 prompt 处理。

为什么只有 150 行?

Go Micro 框架做了三件让代码大幅精简的事情:

  1. 服务自描述:文档注释自动成为工具描述,@example 标签给 LLM 提供调用示例。你不必手写工具 schema
  2. 提供商统一:Anthropic、OpenAI、Gemini、Groq、Mistral、Together、Atlas Cloud——全部通过同一套 ai.Model 接口。切换提供商只需改一个字符串
  3. 执行自动化ai.WithTools(tools) 自动将工具调用路由到 RPC 分发,无需任何胶水代码

如果你不使用 Go Micro,而是对接原生 HTTP 服务,可能只需额外 50 行:一个枚举端点的函数和一个按名称调用端点的函数。其余逻辑完全不变。

扩展方向

这 150 行是一个起点,你可以按需扩展:

  • 加确认步骤:在破坏性工具调用前要求确认(”这将删除 3 条记录,继续吗?”)
  • 记录工具调用:输出到审计日志或观察性平台
  • 过滤工具列表:让 Agent 只看到特定服务
  • 替换输入源:将 REPL 改成 Slack 机器人——同一个 ask,不同的输入源
  • 预加载系统提示:注入领域知识,让 Agent 更了解你的服务

核心启示

Go Micro 团队的这个教程最值得学习的地方,不在于用了什么高级技术,而在于它揭示了 Agent 工具调用的本质——四个模块,150 行代码。你不需要 LangChain、不需要复杂的 Agent 框架,只需要理解工具发现、模型调用、记忆管理和交互循环这四个模块。

如果你正在考虑为自己的系统接入 AI Agent 能力,不妨从这篇文章的思路开始:先实现 150 行的核心,再按需扩展。很多时候,你需要的不是另一个框架,而是一个好的模式。

发表评论

你的邮箱地址不会被公开,带 * 的为必填项。