因为截至2024年底缺乏成熟、活跃、接口清晰的Go原生AI Agent开源框架,现有项目多为玩具级或仅封装HTTP客户端,故需自建轻量可控的Agent骨架;其核心由Planner、Executor、Memory三组件构成,通过struct+interface解耦,配合JSON防御解析、step限制、timeout及状态去重等机制保障稳定。

为什么不用现成的 Go AI Agent 框架
因为目前(截至 2024 年底)没有成熟、活跃、接口清晰的开源 Go 原生 AI Agent 框架。你搜到的 go-agent、gollm 或某些 GitHub 仓库,要么是玩具级 demo,要么长期未维护,要么本质只是个 HTTP client 封装(比如只调 OpenAI API),不提供 memory、tool calling、loop control 等核心能力。
这意味着:你要自己搭骨架。但好处是——轻量、可控、无隐藏依赖,适合嵌入 CLI 工具、边缘服务或已有 Go 后端。
Agent 核心三组件怎么用 struct + interface 组装
别一上来就写调度器。先定义三个不可绕开的角色:
-
Executor:负责执行单步动作,比如调 LLM、运行本地函数、查数据库。它接收context.Context和map[string]any输入,返回string或error -
Planner:决定下一步做什么。典型实现是把当前history+tools描述喂给 LLM,让它输出 JSON 格式的{"action": "search", "input": "golang generics"}。注意:必须做 schema 强校验,否则 LLM 胡说会导致 panic -
Memory:不是指内存,而是对话历史管理。推荐用 slice ofmap[string]string,字段固定为"role"("user"/"assistant"/"tool")和"content";避免用嵌套结构,方便序列化和 prompt 拼接
它们之间靠接口解耦,例如:
立即学习“go语言免费学习笔记(深入)”;
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
type Planner interface {
Plan(ctx context.Context, history []map[string]string, tools []Tool) (Action, error)
}
type Action struct {
Name string `json:"action"`
Input map[string]interface{} `json:"input"`
}
Tool calling 怎么避免 JSON 解析崩溃
LLM 返回的 JSON 经常不合法:多逗号、单引号、字段名错位、嵌套过深。直接 json.Unmarshal 必 panic。
- 永远用
json.RawMessage先接住原始字节,再做防御性解析 - 对每个
Action.Name做白名单检查,不在[]string{"web_search", "read_file", "exec_cmd"}里的直接拒掉 -
Action.Input字段不要强绑定 struct;改用map[string]json.RawMessage,按需解析具体 key,比如只取input["query"]再转 string - 加一层重试:解析失败 → 用更严格的 system prompt 让 LLM 重生成 → 最多 2 次,否则 fallback 到默认响应
示例片段:
var raw map[string]json.RawMessage
if err := json.Unmarshal(b, &raw); err != nil {
return Action{}, fmt.Errorf("invalid JSON: %w", err)
}
name, _ := raw["action"].MarshalJSON() // 先确认字段存在
// ...后续校验逻辑
如何让 Agent 不死循环或卡住
没约束的 while 循环 + LLM 是定时炸弹。两个硬控制点必须设:
- 最大 step 数:全局设
maxSteps = 8,每次 loop 后step++,超限就break并返回截断结果。别信“LLM 自己会停” - 单步 timeout:每个
Planner.Plan()和Executor.Exec()都带ctx, cancel := context.WithTimeout(ctx, 15*time.Second),防止某次 API 卡住拖垮整个流程 - 状态去重:记录最近 3 轮的
Action.Name + hash(Input),连续两次相同就触发 early stop —— 这能拦住“搜索→搜索→搜索…”类震荡
真正难的是工具设计本身:一个 read_file tool 如果允许读任意路径,又没沙箱,那它就不是 agent,是后门。

















