2026年8月7日 2 分钟阅读

从服务到工具:用 Go Micro 搭建可控 AI Agent 的实现路线

tinyash 0 条评论

很多「从零写 Agent」教程把重点放在模型调用:准备一段 prompt,向模型发送消息,等它返回一段文本。但一旦 Agent 要读取业务数据、创建订单、触发部署或调用内部系统,真正困难的部分会立刻出现:工具如何被发现?哪些调用可以执行?失败后是否会重复执行?会话状态存在哪里?

Go Micro 是一个 Go 的 agent harness 与服务框架。它把 Agent 看作分布式系统的一部分:服务的方法可以成为可调用工具,Agent 自身也可以作为服务注册;模型、记忆、工具执行、保护机制与工作流则围绕同一个运行时组织。这个思路很适合需要把 AI 接入既有 Go 服务、但又不希望把所有权限一次性交给模型的团队。

本文不把「能跑起来」等同于「能安全上线」,而是用官方仓库当前的最小示例为起点,梳理一条从服务工具到受控执行的实现路线。Go Micro 仓库采用 Apache-2.0 许可证,模块路径为 go-micro.dev/v6

先划清边界:模型负责选择,服务负责执行

一个可维护的 Agent 至少可以拆成四层:

  1. 服务层:业务能力以 RPC 服务方法实现,例如查询库存、列出笔记或创建工单;
  2. 工具层:把服务方法的元数据转换成模型可理解的工具描述;
  3. Agent 层:维护一次对话的上下文,向模型请求下一步,并把工具调用分发出去;
  4. 控制层:在真正执行前做审批、步数限制、超时、循环检测、日志与持久化。

关键的边界是: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 选项提供了 MaxStepsLoopLimit、工具超时、重试配置和 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 而失效。

一条可逐步上线的路线

建议按风险递增推进,而不是直接做全权限助手:

  1. 从官方 mock 示例开始,验证服务发现与工具调用闭环;
  2. 接入一个只读内部服务,补齐身份、审计和脱敏错误返回;
  3. 为每个写操作增加领域幂等键,并用 Approve 或独立审批服务拦截;
  4. 设置 MaxStepsLoopLimit、工具超时和预算上限,记录每次工具调用;
  5. 为长任务启用 checkpoint,并演练「工具已成功、响应丢失」的恢复流程;
  6. 最后才给 Agent 更广的服务发现范围,且持续以服务 API 的授权规则作为最终防线。

Go Micro 的价值不在于把 Agent 循环写得多短,而在于让模型调用进入已有的服务治理体系:明确接口、受控执行、可追踪状态和可恢复失败。对于需要落在真实业务系统里的 Go Agent,这比一个只会生成文本的聊天循环更接近可运维的软件。

相关链接

发表评论

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