乐于分享
好东西不私藏

让 AI 自己决定调哪个工具:最小 Agent 长什么样(第一期)

让 AI 自己决定调哪个工具:最小 Agent 长什么样(第一期)

让 AI 自己决定调哪个工具:最小 Agent 长什么样(第一期)

Harness = 6 件套

任何 Agent 框架(Anthropic / OpenAI / LangChain / Hermes Agent)核心层都叫 Harness(驾驭层)——把 LLM 圈进"可控循环"。

Gartner 2026 预测:40% Agentic AI 项目到 2027 年底将被取消——死因是 Harness 没做对。

Harness = 6 件套

干啥的
Loop 控制循环(while + 工具调用 + 退出条件)
Tool 工具注册 + 执行
Context 上下文(system/messages/tools)
Memory 短期/中期/长期记忆
Permission 沙箱(文件/Shell/工具/成本)
Eval 评估(准确率/成本/延迟/安全/可解释)

本系列从 0 造——12 期走完你就有完整 Harness 生产版,不依赖任何框架

本期做 1 件:Loop

第一期只演示 6 件套中的 1 件:Loop——AI 决定"调哪个工具 + 啥时候停"。

缺什么 怎么补
多工具 (Tool Registry) 4 工具 → 5 工具 第三期+
错误处理 (retries/ctx) 分层骨架 第二期
持久记忆 SQLite + RAG 第八期
权限沙箱 os/exec 受限 第十期
评估体系 5 维度 runner 第十一期
多 Agent 协作 chan 消息队列 第十期

最小可跑 Agent 长这样

下面只截 3 段关键逻辑说明 Agent 怎么跑:

片段 1:工具声明(9 行)

var calculator = map[string]any{
    "type""function""function"map[string]any{
        "name""calculator""description""计算 2 个数的加减乘除",
        "parameters"map[string]any{"type""object""properties"map[string]any{
            "a"map[string]any{"type""number"},
            "op"map[string]any{"type""string"},
            "b"map[string]any{"type""number"},
        }, "required": []string{"a""op""b"}},
    },
}

告诉 LLM "你有 calculator 可用"。

片段 2:调 LLM(10 行)

func call(msgs []msg) (*msg, error) {
    body, _ := json.Marshal(map[string]any{
        "model""gpt-4o-mini""messages": msgs,
        "tools": []any{calculator},
    })
    req, _ := http.NewRequest("POST", baseURL+"/chat/completions", bytes.NewReader(body))
    req.Header.Set("Authorization""Bearer "+os.Getenv("OPENAI_API_KEY"))
    resp, _ := http.DefaultClient.Do(req)
    defer resp.Body.Close()
    var out struct{ Choices []struct{ Message msg } `json:"choices"` }
    json.NewDecoder(resp.Body).Decode(&out)
    return &out.Choices[0].Message, nil
}

HTTP POST + JSON 解析。没有任何花活

片段 3:循环 + 工具回灌(15 行)

hist := []msg{{Role: "user", Content: "2+3*4 等于多少?"}}
for i := 0; i < 5; i++ {
    m, _ := call(hist)
    hist = append(hist, *m)
    if len(m.ToolCalls) == 0 { fmt.Println(m.Content); break }
    for _, tc := range m.ToolCalls {
        var args struct{ A float64; Op string; B float64 }
        json.Unmarshal([]byte(tc["function"].(map[string]any)["arguments"].(string)), &args)
        var r float64
        switch args.Op { case "+": r = args.A+args.B; case "*": r = args.A*args.B }
        hist = append(hist, msg{Role: "tool", Content: strconv.FormatFloat(r, 'f'-164), ToolCallID: tc["id"].(string)})
    }
}

这段是 Agent 灵魂——while + 调 LLM + 看 tool_calls + 执行工具 + 回灌消息。

4 件事 = 任何 Agent 框架的核心

干啥的
for i := 0; i < 5 1 Loop: while 循环
body, _ := json.Marshal(...) 3 Loop: 调 LLM
if len(m.ToolCalls) == 0 1 Loop: 退出条件
var calculator = map[string]any{...} 9 Tool: 工具声明
for _, tc := range m.ToolCalls 7 Loop: 执行 + 回灌
json.Unmarshal(...) + switch args.Op 8 Tool: 工具执行

剩 27 行是 JSON 结构体定义(type msg struct, type chatResp struct)——不是核心逻辑。

跑起来

export OPENAI_API_KEY=sk-xxx
go run .

期望输出:

20

完整执行轨迹(用户看到的是):

[轮 1] LLM 决定调 calculator(2, "+", 3) = 5
[轮 2] LLM 决定调 calculator(5, "*", 4) = 20
[轮 3] LLM: 2+3*4 = 20
退出

任意 OpenAI 兼容 API 都行(Anthropic / MiniMax / DeepSeek),改 baseURL 即可。

Loop 的退出条件

条件 触发
LLM 不再调工具 len(m.ToolCalls) == 0 退出
达到 maxTurns=5 防止死循环
用户 Ctrl-C 本版不支持(第二期补)

关键字段(Context 视角)

字段 属于哪件 作用
tools Context 告诉 LLM "你有 calculator 可用"
tool_calls Loop 信号 LLM 决定 "我要调 calculator"
role: "tool" Memory + Loop 把 "5" 塞回对话
tool_call_id Memory 关联 关联到具体哪次 tool_call

4 个缺一不可

最小版 ≠ "能上生产"

缺什么
错误处理(4 层包装) 第二期
多文件分层(main/agent/llm/tools) 第二期
全链路 ctx(Ctrl-C 取消) 第二期
6 个 flag 配置 第二期
2 个单元测试 第二期
slog 结构化日志 第二期
多工具(5 个 + Registry) 第三期
持久记忆(SQLite + 轻 RAG) 第八期
权限沙箱(os/exec 受限) 第十期
评估体系(5 维度 runner) 第十一期

第二期把这 6 件 Loop 增强全补了——单文件 → 5 模块分层,但仍只覆盖 1 件 Harness:Loop

12 期路线图

类型 Harness 件 主题 行数
第一期 教学 Loop 本文:最小可跑 Agent
第二期 实战 Loop 分层骨架(入口 + 业务)
第三期 教学 Tool Tool Registry 模式
第四期 实战 Tool 5 工具完整实现
第五期 教学 Context 4 要素拆解
第六期 实战 Context 多轮对话 + 压缩
第七期 教学 Memory 3 层模型
第八期 实战 Memory SQLite + 轻 RAG
第九期 教学 Permission 4 维沙箱
第十期 实战 Permission os/exec 受限
第十一期 教学 Eval 5 维度评估
第十二期 实战+收官 Eval GopherAgent 1.0

进度█░░░░░░░░░░░░░░░░░░░░ 1/12 (8.3%)

下期

第二期 GopherAgent 0.1 骨架——把本期单文件扩到分层结构,加错误处理 + ctx + 测试 + 6 个 flag + slog。Loop 这一件从"能跑"升级到"能上测试环境"。