如何用 150 行代码构建自己的 AI Agent CLI
Go Micro 团队最近发布了一篇精彩的教程——只需约 150 行代码,就能构建一个让 AI Agent 调用微服务的命令行工具。这听起来像是天方夜谭,但读完你会发现——工具调用(Tool Calling)的核心逻辑确实就这么简单。本文带你逐行拆解,让你理解每个环节并在自己的框架中复现。
问题:如何让 AI 调用你的服务?
假设你有一组微服务——创建用户、发送邮件、查询订单。你想用自然语言说”给 Alice 发一封欢迎邮件”,然后 AI 自动调用对应的服务。
LLM 能完成推理(”用户想发邮件,所以调用 email 服务”),但它需要你提供三样东西:
- 可用工具的列表——包括名称、描述和参数
- 工具执行的能力——在 LLM 选择某个工具后,实际调用它
- 对话记忆——让后续提问能理解上下文
这三个需求,就是整个 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),
)
这一步发生了两件事:
ai.New选择提供商(Anthropic、OpenAI、Gemini 等——统一接口)ai.WithTools(tools)连接了执行端——当模型说”调用users_Users_Create并传入这些参数”,handler 会路由到正确的 RPC 并返回结果
Go Micro 的抽象层让切换提供商只需改一个字符串参数,这是其设计的精妙之处。
第三部分:跟踪对话
hist := ai.NewHistory(50)
History 是一个带大小限制的消息累加器。它本质上就是 []Message,外加 Add、Messages、Reset 三个方法。
每次交互后,你把用户的 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
}
逐行解读:
- 记录 prompt——加入对话历史,后续轮次可用
- 调用模型——传入 prompt、系统指令、工具列表和历史消息
- 输出回复——模型的思考过程
- 报告工具调用——模型决定调用哪些工具,handler 自动执行
- 输出最终答案——工具执行完成后模型生成的摘要
模型的 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)
}
}
}
支持 exit、quit、reset 三条内置命令,其余输入都当作 prompt 处理。
为什么只有 150 行?
Go Micro 框架做了三件让代码大幅精简的事情:
- 服务自描述:文档注释自动成为工具描述,
@example标签给 LLM 提供调用示例。你不必手写工具 schema - 提供商统一:Anthropic、OpenAI、Gemini、Groq、Mistral、Together、Atlas Cloud——全部通过同一套
ai.Model接口。切换提供商只需改一个字符串 - 执行自动化:
ai.WithTools(tools)自动将工具调用路由到 RPC 分发,无需任何胶水代码
如果你不使用 Go Micro,而是对接原生 HTTP 服务,可能只需额外 50 行:一个枚举端点的函数和一个按名称调用端点的函数。其余逻辑完全不变。
扩展方向
这 150 行是一个起点,你可以按需扩展:
- 加确认步骤:在破坏性工具调用前要求确认(”这将删除 3 条记录,继续吗?”)
- 记录工具调用:输出到审计日志或观察性平台
- 过滤工具列表:让 Agent 只看到特定服务
- 替换输入源:将 REPL 改成 Slack 机器人——同一个
ask,不同的输入源 - 预加载系统提示:注入领域知识,让 Agent 更了解你的服务
核心启示
Go Micro 团队的这个教程最值得学习的地方,不在于用了什么高级技术,而在于它揭示了 Agent 工具调用的本质——四个模块,150 行代码。你不需要 LangChain、不需要复杂的 Agent 框架,只需要理解工具发现、模型调用、记忆管理和交互循环这四个模块。
如果你正在考虑为自己的系统接入 AI Agent 能力,不妨从这篇文章的思路开始:先实现 150 行的核心,再按需扩展。很多时候,你需要的不是另一个框架,而是一个好的模式。