150 行代码搭建 AI Agent CLI:拆解工具调用的四个核心组件
背景
Go Micro 团队最近发布了一篇技术博客,展示如何用约 150 行代码构建一个能与微服务通过自然语言交互的 AI Agent CLI。项目的核心承诺听起来像魔法:你只需说「创建用户 Alice」,Agent 就能自动找到正确的服务端点并完成调用,全程不需要手写任何「if 用户请求 email 则调用 email 服务」这样的逻辑判断。
但这背后没有魔法。整个系统的核心只有四个组件——工具发现、模型创建、对话记忆、主循环——每个组件的代码量都小得令人惊讶。本文将这些组件逐一拆解,用 Go 代码(原文语言)展示每个部分,并提供框架无关的分析,让你能在任意技术栈中复现这个模式。
组件一:工具发现(Discover the Tools)
LLM 在做工具调用之前,首先需要知道有哪些工具可用。这是整个系统的基础。
原文中,Go Micro 利用了自己的服务注册表(Registry)来实现自动发现:每个部署的服务都会注册它的端点、请求类型和字段元数据。注册表将这些信息转为一个工具列表——每个服务端点对应一个工具,工具名就是端点名,描述来自 handler 的 doc comment,参数 schema 来自请求结构体的字段定义。
// 从注册表自动发现工具列表(Go Micro 专用) tools := ai.NewTools(reg, ai.ToolClient(client)) discovered, err := tools.Discover()
这段代码之所以简洁,完全得益于 Go Micro 的自描述服务机制:每个服务都公开自己的元数据。doc comment 自动成为工具描述,@example tag 为 LLM 提供参数格式示例。
框架无关分析:如果你不用 Go Micro,你需要手动枚举你的函数/端点并构建一个 {name, description, parameters} 列表。可以是一个 JSON 配置文件,也可以是一个中心化的函数注册表。关键是——没有一个额外的「智能调度层」去解析用户意图——你只是把本地的 API 清单格式化成了 LLM 能理解的 Schema。
组件二:模型创建(Create the Model)
有了工具列表,Agent 需要与 LLM 交互。Go Micro 的 ai.New() 接受提供商名(如 anthropic)和配置选项。
// 一行代码创建模型 + 绑定工具集
m := ai.New("anthropic",
ai.WithAPIKey(os.Getenv("ANTHROPIC_API_KEY")),
ai.WithTools(tools),
)
这里的核心设计是 m 对象做了两件事:推理面和执行面。推理面构建工具列表并发送给模型;执行面在模型说「调用 X 工具的 Y 参数」时,将调用路由到正确的 RPC 端点并返回结果。
框架无关分析:ai.New() 对多个提供商(Anthropic、OpenAI、Gemini、Groq、Mistral、Together、Atlas Cloud)使用了统一接口,切换提供商只需要修改第一个参数(如 "openai"、"gemini")。如果你自己实现,你需要选择一个 LLM SDK,并为每个提供商实现一个 Model 接口——但核心逻辑是一样的:构建 messages + tools 的请求,解析 tool_use 响应,路由到 handler 执行,将结果返回。
组件三:对话记忆(Track the Conversation)
AI Agent 能理解上下文的关键在于「记住前面说了什么」。原文的实现出人意料的简洁——用 ai.NewHistory() 创建一个带大小限制的消息累加器:
// 一个带滑动窗口的消息历史
hist := ai.NewHistory(50)
// 使用时只需追加消息
hist.Add("user", prompt)
// 获取当前所有消息(自动限制在窗口大小内)
msgs := hist.Messages()
它本质上就是一个消息列表 + 滑动窗口。用户的消息和模型的回复在每次交互后被追加进来,下次调用时一起传入 LLM。这个简单结构支持的却是多轮对话和自然语言追问——你可以说「创建用户 Alice」→ 系统执行 →「再给她发一封欢迎邮件」,而不需要手动拼接上下文。需要重置对话时,调用 hist.Reset() 清空即可。
框架无关分析:对话记忆是所有 AI Agent 中最容易被低估的组件。很多实现倾向于加入「记忆压缩」「语义摘要」「向量检索」等复杂机制,但 90% 的场景下,一个简单的消息累加器就足够了。滑动窗口大小(传递给 NewHistory 的参数)可以根据模型的最大上下文长度设定——例如 128K 上下文的模型可以设置更大的窗口,64K 的模型则需要更紧凑。关键原则:先做对的,再做快的。
组件四:主循环(The Core Loop)
有了前面三个组件,主循环就是将一切串联起来的胶水。原文的核心代码只有约 40 行:
func ask(ctx context.Context, m ai.Model, hist *ai.History, tools []ai.Tool, prompt string) error {
hist.Add("user", prompt)
resp, err := m.Generate(ctx, &ai.Request{
Prompt: prompt,
SystemPrompt: systemPrompt,
Tools: tools,
Messages: hist.Messages(),
})
if err != nil {
return err
}
if resp.Reply != "" {
hist.Add("assistant", resp.Reply)
fmt.Println(resp.Reply)
}
// 显示调用了哪些工具
for _, tc := range resp.ToolCalls {
args, _ := json.Marshal(tc.Input)
fmt.Printf(" → called %s(%s)\n", tc.Name, args)
}
if resp.Answer != "" {
hist.Add("assistant", resp.Answer)
fmt.Println(resp.Answer)
}
return nil
}
func main() {
scanner := bufio.NewScanner(os.Stdin)
for {
fmt.Print("> ")
if !scanner.Scan() { return nil }
line := strings.TrimSpace(scanner.Text())
switch line {
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)
}
}
}
}
m.Generate() 是核心——它内部实现了「模型决定 → 调用工具 → 获取结果 → 再次询问模型是否需要继续」的循环。我们从来没有写过一句「if 用户想要 email,调用 email 服务」的逻辑。LLM 根据工具描述自己做了这个推理。
框架无关分析:主循环模式是一个生成 → 执行 → 反馈的迭代过程。即使你不用 Go Micro,在任何语言中都可以实现同样的循环:读取 prompt → 构造请求(含 tools)→ 调用 LLM API → 解析响应(检查是否有 tool_use)→ 如果有工具调用则执行并反馈结果 → 继续循环直到模型返回纯文本答案。这个模式的通用性使其成为所有 tool-calling Agent 的基座。
扩展方向
原文提出了六个从原型走向生产级的方向:
- 确认步骤:对危险操作(删除记录、修改权限)加入人工确认
- 审计日志:所有工具调用记录到审计追踪或可观测性栈
- 作用域限制:基于角色的工具可见性,让 Agent 只能看到权限范围内的服务
- 切换到 Slack Bot:将 stdin/stdout 替换为 Slack 消息循环
- 预加载系统提示:注入领域知识(如业务规则、命名约定)
- 事件驱动:从 stdin 循环改为 cron 或 Webhook 触发
其中审计日志和确认步骤两个方向对生产级部署尤其关键——前者解决合规需求,后者防止意外操作。
核心启示
Go Micro 这篇博客展示了一个重要的工程认知:构建工具调用的 AI Agent 不是需要学习复杂框架的任务——它是一个可复制的、约 150 行的设计模式。四个组件的职责清晰、边界明确,每个组件都可以独立替换或升级。工具发现可以用配置文件代替注册表;模型创建可以换 SDK;对话记忆可以加持久化;主循环可以接不同的输入源。
对于正在搭建 Agent 系统的开发者来说,这个架构的启示是:不要急于引入框架,先用 150 行理解系统的本质,再根据实际需要逐步添加复杂性。
原文:Build Your Own AI Agent CLI in 150 Lines(Go Micro Blog, May 30, 2026)