从服务到工具:用 Go Micro 搭建可控 AI Agent 的实现路线
很多「从零写 Agent」教程把重点放在模型调用:准备一段 prompt,向模型发送消息,等它返回一段文本。但一旦 Agent 要读取业务数据、创建订单、触发部署或调用内部系统,真正困难的部分会立刻出现:工具如何被发现?哪些调用可以执行?失败后是否会重复执行?会话状态存在哪里?
Go Micro 是一个 Go 的 agent harness 与服务框架。它把 Agent 看作分布式系统的一部分:服务的方法可以成为可调用工具,Agent 自身也可以作为服务注册;模型、记忆、工具执行、保护机制与工作流则围绕同一个运行时组织。这个思路很适合需要把 AI 接入既有 Go 服务、但又不希望把所有权限一次性交给模型的团队。
本文不把「能跑起来」等同于「能安全上线」,而是用官方仓库当前的最小示例为起点,梳理一条从服务工具到受控执行的实现路线。Go Micro 仓库采用 Apache-2.0 许可证,模块路径为 go-micro.dev/v6。
先划清边界:模型负责选择,服务负责执行
一个可维护的 Agent 至少可以拆成四层:
- 服务层:业务能力以 RPC 服务方法实现,例如查询库存、列出笔记或创建工单;
- 工具层:把服务方法的元数据转换成模型可理解的工具描述;
- Agent 层:维护一次对话的上下文,向模型请求下一步,并把工具调用分发出去;
- 控制层:在真正执行前做审批、步数限制、超时、循环检测、日志与持久化。
关键的边界是:LLM 不直接访问数据库,也不直接拥有云端凭证。它只提出「调用哪个工具、带什么输入」;服务端仍以普通 Go 代码执行鉴权、参数校验和事务处理。换模型不应改变业务规则,替换服务实现也不应改变模型的工具协议。
这比把一串 shell 命令直接暴露给模型更容易收敛权限:服务 API 本来就可以定义细粒度请求结构、身份校验和错误返回。工具描述来自端点元数据时,接口注释与 @example 也会成为模型理解能力边界的一部分,因此要像维护公开 API 一样维护它们。
用官方最小示例验证工具发现链路
官方 examples/first-agent 使用内存 Registry、Broker 和 Store,不需要任何模型提供商密钥。它定义了一个 NotesService.List 方法,随后让 Agent 发现名为 notes 的服务。下面是从该示例抽出的关键形状;它刻意只提供读取能力:
type ListNotesRequest struct{}
type ListNotesResponse struct {
Notes []string `json:"notes" description:"Notes the assistant can summarize"`
}
// List returns the starter notes the first agent can read.
// @example {}
func (s *NotesService) List(
ctx context.Context,
req *ListNotesRequest,
rsp *ListNotesResponse,
) error {
rsp.Notes = []string{"Install the micro CLI", "Run a service", "Chat with an agent"}
return nil
}
服务启动后,创建 Agent 时把允许发现的服务名写进 agent.Services。示例还显式传入 Registry、Client、Broker 与内存 Store,使测试环境不依赖外部基础设施:
assistant := agent.New(
agent.Name("assistant"),
agent.Services("notes"),
agent.Prompt("Use the notes service before answering."),
agent.Provider("first-agent-mock"),
agent.WithRegistry(reg),
agent.WithClient(cl),
agent.WithBroker(br),
agent.WithStore(store.NewMemoryStore()),
)
接着调用 assistant.Ask(ctx, "Summarize my next steps")。在官方示例中,mock model 会从请求中的工具列表找到 List 工具,触发一次调用,再返回最终回答。这个闭环验证的不是模型能力,而是更基础也更容易出错的链路:服务注册、工具发现、调用分发和结果回填是否一致。
如果先想运行完整示例,可在已克隆的仓库根目录执行:
go run ./examples/first-agent
它使用确定性的 mock model,因此适合作为 CI 冒烟测试。不要在这一步就接入有写权限的生产 API;先让只读服务跑通,才能把「模型误选工具」与「业务副作用」分开排查。
接入真实模型时,先固定工具契约
Go Micro 的 ai 包提供统一模型接口。官方 README 展示的最小调用形状如下:
m := ai.New("anthropic", ai.WithAPIKey(key))
resp, err := m.Generate(ctx, &ai.Request{Prompt: "hello"})
if err != nil {
return err
}
_ = resp
提供商抽象解决的是模型接入,而不是权限问题。生产代码中,工具请求仍必须经过服务侧的结构化验证。例如,创建工单的请求应声明允许字段、长度和枚举值;服务根据当前用户身份决定可操作的项目范围;返回给模型的错误信息只解释可恢复问题,不能泄露密钥、内部堆栈或其他租户的数据。
工具描述同样值得版本化。给模型的名称应稳定、动词明确,例如 ticket_create;输入字段要写清单位、时区和必填性。不要把「删除资源」与「预览删除结果」设计成同一工具的可选布尔参数——拆成 resource_preview_delete 与受审批保护的 resource_delete,能让策略与审计记录更清楚。
把保护放在执行点,而不是 prompt 里
Prompt 中写「不要删除数据」只能影响模型倾向,不能构成权限边界。Go Micro 的 Agent 选项提供了 MaxSteps、LoopLimit、工具超时、重试配置和 Approve 回调等控制点。尤其是审批回调,适合把高风险工具从自动路径中拿出来:
agent.ApproveTool(func(tool string, input map[string]any) (bool, string) {
if tool == "billing_Invoice_Refund" {
return false, "退款必须经人工审批"
}
return true, ""
})
这里的工具名必须以实际注册后的名称为准;不要根据服务或方法名猜测。更重要的是,拒绝审批后模型会收到原因,因此它可以改为请求补充信息、执行只读查询或结束任务,而不是盲目重试。
MaxSteps 用来限制单次 Ask 最多执行多少次工具调用;LoopLimit 则针对「相同工具加相同参数反复调用」的无进展循环。两者都要设置:前者限制总成本与总副作用,后者更早暴露设计不良的重试路径。对于会产生副作用的服务,工具重试不应默认开启;网络超时并不意味着服务端没有成功写入,自动重放可能创建重复工单或重复扣款。
对真实接口,还应把调用者身份、请求范围和变更前后的资源版本写入审计记录。这样即使模型产生了不合理的下一步建议,服务也可以依据领域规则拒绝请求;事后排查时也能区分模型计划、获批的工具调用与实际落库结果。
状态、记忆与可恢复性要分开设计
对话历史、业务事实和任务执行记录不是同一种数据。短期对话历史用于让模型理解当前任务;业务事实应落在领域数据库;一次长任务的 checkpoint 则用于在进程重启后继续运行,而不是重新执行已经完成的工具调用。
Go Micro 的 Agent 配置包含 Store、Memory 与 Checkpoint 相关能力,也支持对历史进行限制、检索或压缩。实际落地时应先回答三个问题:
- 哪些信息可进入模型上下文,保留多久?
- 哪些工具结果必须作为业务审计证据长期保存?
- 恢复任务时,如何区分「尚未执行」和「已执行但客户端没收到响应」?
把最后一个问题交给模型猜测很危险。更稳妥的做法是在写操作服务中使用业务幂等键,并记录请求 ID、操作者、输入摘要和结果 ID。Agent 重启后先查询该记录,再决定是否需要继续。这是服务工程里的幂等性原则,不会因为调用者换成 LLM 而失效。
一条可逐步上线的路线
建议按风险递增推进,而不是直接做全权限助手:
- 从官方 mock 示例开始,验证服务发现与工具调用闭环;
- 接入一个只读内部服务,补齐身份、审计和脱敏错误返回;
- 为每个写操作增加领域幂等键,并用
Approve或独立审批服务拦截; - 设置
MaxSteps、LoopLimit、工具超时和预算上限,记录每次工具调用; - 为长任务启用 checkpoint,并演练「工具已成功、响应丢失」的恢复流程;
- 最后才给 Agent 更广的服务发现范围,且持续以服务 API 的授权规则作为最终防线。
Go Micro 的价值不在于把 Agent 循环写得多短,而在于让模型调用进入已有的服务治理体系:明确接口、受控执行、可追踪状态和可恢复失败。对于需要落在真实业务系统里的 Go Agent,这比一个只会生成文本的聊天循环更接近可运维的软件。