2026年10月6日 2 分钟阅读

用 Go 构建可审计的 AI Agent:Golem 的类型安全实战

tinyash 0 条评论

很多 Agent 原型的问题不在于模型不会调用工具,而在于工程边界太松:依赖藏在全局变量里,工具参数落到 map[string]any,模型换一次就要重写一遍适配层,测试还必须真的请求远端 API。Golem 是一个 Go 优先的 AI Agent 框架,试图用泛型、显式依赖和可观察运行记录,把这些问题变成编译期和接口层的问题。

Golem 解决的是什么问题?

Golem 的核心类型是 Agent[Deps, Output]。Deps 表示一次运行需要注入的依赖,例如数据库或 HTTP 客户端;Output 表示 Agent 最终要产出的类型。这样的设计比让工具自行读取全局配置更容易测试,也更容易审查一次运行究竟访问了哪些资源。

项目 README 给出的定位是生产级 Go Agent 框架,仓库采用 MIT 许可证,当前要求 Go 1.26.5 或更高版本,并声明不依赖外部库。它还提供 OpenAI、Anthropic、Gemini、AWS Bedrock、Azure OpenAI,以及 Ollama/LM Studio 等适配器。这里要注意:适配器可用不等于所有模型能力完全相同,具体的流式输出、思考、视觉或 token 统计能力仍应按适配器文档确认。

安装只需要:

go get github.com/abubakarsiddik31/golem

先跑通一个最小 Agent

下面的例子来自项目 Quick Start 的基本结构:从环境变量读取密钥,创建 OpenAI 适配器,再把模型响应解码为字符串。示例没有把密钥写进源码,适合先验证调用链。

package main

import (
    "context"
    "fmt"
    "os"

    "github.com/abubakarsiddik31/golem"
    "github.com/abubakarsiddik31/golem/model"
    "github.com/abubakarsiddik31/golem/providers/openai"
)

func main() {
    client, err := openai.New(openai.Config{
        APIKey: os.Getenv("OPENAI_API_KEY"),
        Model:  "gpt-4o-mini",
    })
    if err != nil {
        panic(err)
    }

    agent, err := golem.New[struct{}, string](client,
        golem.DecodeFunc[string](func(_ context.Context, r model.Response) (string, error) {
            return r.Message.Content, nil
        }),
    )
    if err != nil {
        panic(err)
    }

    result, err := agent.Run(context.Background(), golem.RunContext[struct{}]{}, "解释 Go 为什么适合构建 AI Agent")
    if err != nil {
        panic(err)
    }
    fmt.Println(result.Output)
}

运行前设置 OPENAI_API_KEY。生产代码不建议用 panic 处理所有错误,但在最小示例中,它能把配置错误和运行错误直接暴露出来。更重要的是,result 不只有输出文本,README 示例还展示了输入和输出 token 的使用量;这为后续接入成本统计和预算限制留下了明确入口。

用泛型注入工具依赖

真正有价值的部分,是把工具依赖放进 RunContext[Deps]。例如查询用户的工具可以声明自己需要 Database,Agent 运行时显式传入数据库对象,而不是在工具内部偷偷读取单例:

type Database struct {
    Users map[int]string
}

getUser := tool.MustNew(tool.Tool[Database]{
    Name:        "get_user",
    Description: "Look up a user name by their ID.",
    Schema: json.RawMessage(`{
        "type": "object",
        "properties": {"id": {"type": "integer"}},
        "required": ["id"]
    }`),
    Exec: func(ctx context.Context, db Database, args json.RawMessage) (tool.Result, error) {
        var input struct {
            ID int `json:"id"`
        }
        if err := json.Unmarshal(args, &input); err != nil {
            return tool.Result{}, err
        }
        name, ok := db.Users[input.ID]
        if !ok {
            return tool.Text("User not found"), nil
        }
        return tool.Text(name), nil
    },
})

agent, err := golem.New[Database, string](client,
    golem.DecodeFunc[string](func(_ context.Context, r model.Response) (string, error) {
        return r.Message.Content, nil
    }),
    golem.WithTools[Database, string](getUser),
)

db := Database{Users: map[int]string{42: "Alice"}}
result, err := agent.Run(ctx, golem.RunContext[Database]{Deps: db}, "Who is user 42?")

这段代码是依赖注入的示意片段,完整程序还需要补齐 context、encoding/json、tool 等 import。它体现了两个实践原则:工具的 Schema 描述交给模型,真正的数据库访问由 Go 函数控制;测试时则可以传入内存数据库,不必连接真实服务。

结构化输出比“请返回 JSON”可靠

如果 Agent 要生成工单、审计结果或 API 请求,不应只在提示词里要求“返回 JSON”。Golem 提供 golem.DecodeJSON[T]() 和输出 Schema,能够把响应解码为具体的 Go 结构,并用 additionalProperties: false 约束额外字段:

type WeatherReport struct {
    City        string  `json:"city"`
    Temperature float64 `json:"temperature_celsius"`
    Condition   string  `json:"condition"`
}

agent, err := golem.New[struct{}, WeatherReport](client,
    golem.DecodeJSON[WeatherReport](),
    golem.WithOutputSchema[struct{}, WeatherReport](schema),
)

实际项目中,schema 应该是与结构体字段同步维护的 JSON Schema,而不是为了文章示例随意省略。对于金融、权限或部署类 Agent,结构化输出还应在业务层再次校验,不能把模型返回的合法 JSON 直接视为合法业务请求。

MCP、文件和测试:把能力放在边界内

Golem 内置的工具包覆盖 MCP 客户端、PDF 和 Office 文档提取、网页文本读取、工作区文件读取、命令执行,以及按需加载 SKILL.md 的 skills 工具。MCP 支持 stdio 和流式 HTTP;文件和 shell 能力则更适合放在受限工作区、超时和权限策略之后,而不是直接暴露给任意 Agent。

测试方面,项目提供 deterministic 的 testmodel,可以在不配置 Provider、不消耗 token 的情况下返回预设响应。一个合理的测试分层是:先用 fake model 验证工具参数、错误处理和输出 Schema,再用少量集成测试验证真实 Provider 的适配器行为。这样能避免把网络波动误判成 Agent 逻辑错误。

适合哪些项目?

如果团队已经使用 Go,并且关心并发、可部署二进制、依赖可控和运行审计,Golem 值得做一个小型 PoC。它尤其适合“模型负责决策、Go 负责边界”的服务:模型选择工具和参数,Go 控制依赖、超时、Schema、重试与审计记录。

不过它不是把任意 Python Agent 一键迁移到 Go 的兼容层。你仍然需要确认 Provider 的能力差异、补齐业务级授权,并为 shell、文件、MCP 连接设置最小权限。类型安全可以减少一类运行时错误,却不能替代提示注入防护和工具调用审批。

小结

Golem 的价值不只是“用 Go 调模型”,而是把 Agent 拆成几个可检查的合同:依赖是什么、工具接收什么、输出是什么、运行留下什么证据。对于准备从 Demo 走向服务化的团队,这种边界比再增加一个提示词模板更重要。建议从一个只读工具和 testmodel 开始,先验证依赖注入、结构化输出与失败恢复,再逐步开放 MCP、文件或命令执行能力。

相关链接

发表评论

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