Go AI Agent 实战 24|工具系统工程化收尾与阶段验收

本系列共 10 阶段,面向有 Go 后端基础的开发者,全程用 Go 手写实现。

当前进度:阶段三 Tool Calling · 第 7 课(24)

上一篇:多轮工具调用实战与异常恢复

前六课已经完成从原理到完整工具调用链的实现。本课不再增加新工具,而是检查这套系统距离可靠后端组件还缺什么,并给出一套可以反复执行的验收清单。

一、本课学习目标

学完本课,你应该能够:

  1. 复盘模型协议、工具执行和业务权限的完整调用链。
  2. 使用清单检查注册、校验、超时、预算和错误处理。
  3. 严格校验 JSON 参数并检查重复调用 ID。
  4. 区分单元测试、集成测试和真实模型实验。
  5. 用综合题验证自己是否真正掌握 Tool Calling。

二、完整调用链复盘

text 复制代码
用户任务
   ↓
模型客户端:发送 messages + tools
   ↓
模型:返回 assistant.tool_calls
   ↓
Go:检查协议、预算是非、工具白名单
   ↓
工具:参数 → 业务规则 → 权限 → 执行
   ↓
Go:生成 tool 结果,关联 tool_call_id
   ↓
模型:读取结果,继续调用或返回最终回答

三个层次必须分开:

  • Schema:告诉模型参数应该长什么样。
  • Go 校验:验证实际输入是否合法。
  • 业务权限:决定用户是否允许执行操作。

模型能看到工具,不代表用户能调用;参数符合 Schema,不代表资源可访问或操作可执行。

三、阶段验收清单

验收项 通过标准
工具定义 名称、描述、参数 Schema、执行器完整
工具注册 重复名称拒绝,未知工具不能执行
参数校验 拒绝非对象、无效 JSON、未知字段和不合法值
调用协议 校验角色、类型、ID,拒绝重复 ID
消息历史 保留 assistant 调用消息和逐条 tool 结果
多轮执行 工具结果可以进入下一轮模型请求
权限安全 用户身份来自可信上下文,工具调用 BIZ 层
超时预算 单次工具、单次模型、任务总时长有限制
调用预算 工具次数和结果大小有限制
错误处理 区分协议、参数、业务、临时和内部错误
副作用控制 写操作有幂等、确认、审计或状态查询
自动化测试 成功、失败、停止和协议历史均有覆盖

验收不是只看"能返回一句回答",而是看异常和边界是否可预测。

四、严格校验工具参数

静态工具参数最适合使用严格解码。下面这个辅助函数要求输入必须是单个 JSON 对象:

go 复制代码
func DecodeStrictJSON(
	raw json.RawMessage,
	dst any,
) error {
	trimmed := bytes.TrimSpace(raw)
	if len(trimmed) == 0 ||
		trimmed[0] != '{' ||
		!json.Valid(trimmed) {
		return ErrInvalidArguments
	}

	decoder := json.NewDecoder(bytes.NewReader(trimmed))
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(dst); err != nil {
		return ErrInvalidArguments
	}

	var extra any
	if err := decoder.Decode(&extra); !errors.Is(err, io.EOF) {
		return ErrInvalidArguments
	}

	return nil
}

使用:

go 复制代码
type GetArticleArgs struct {
	ArticleID int64 `json:"article_id"`
}

var args GetArticleArgs
if err := DecodeStrictJSON(raw, &args); err != nil {
	return nil, err
}
if args.ArticleID <= 0 {
	return nil, ErrInvalidArguments
}

// 继续执行文章存在性、可见性和用户权限检查。

顺序应当是:

text 复制代码
严格 JSON 解码
   ↓
字段类型和范围
   ↓
业务资源状态
   ↓
用户权限
   ↓
执行与审计

DisallowUnknownFields 不等于 Schema 校验器,也不验证资源是否存在。复杂 Schema 规则需要专门的校验库,且必须与 Go 端实际约束保持一致。

五、检查工具调用 ID

每条工具结果依靠 tool_call_id 对应原调用。同一条模型响应中应拒绝空 ID、重复 ID 和不支持的类型:

go 复制代码
func ValidateToolCallIDs(calls []ToolCall) error {
	seen := make(map[string]struct{}, len(calls))

	for _, call := range calls {
		if strings.TrimSpace(call.ID) == "" {
			return ErrInvalidToolCall
		}
		if call.Type != "function" {
			return ErrInvalidToolCall
		}
		if _, exists := seen[call.ID]; exists {
			return ErrDuplicateToolCallID
		}
		seen[call.ID] = struct{}{}
	}

	return nil
}

执行前还应该限制:

  • 单轮调用数量。
  • 累计工具调用次数。
  • 单个参数字节数。
  • 单个工具结果字节数。
  • 模型消息和 Token 预算。

参数或结果过大时,即使协议合法,也可能拖垮内存或超出上下文窗口。

六、测试分为三层

1. 单元测试

不访问网络,使用 FakeModel、假工具和内存数据:

  • 工具注册、重复注册和未知工具。
  • 参数缺失、未知字段、null 和非法范围。
  • 空 ID、重复 ID 和错误调用类型。
  • 工具超时与父 Context 取消。
  • 最大轮数、最大工具次数。
  • 错误映射和消息历史。

2. 集成测试

验证组件组合,可以使用:

  • httptest.Server 模拟模型 API。
  • 测试数据库或事务回滚。
  • 假的外部搜索服务。

重点检查 HTTP 请求、响应解析、消息序列、工具结果回传和错误状态。

3. 真实模型实验

使用无副作用工具验证真实模型行为:

  • 模型是否会选择合适工具。
  • function.arguments 是否符合预期。
  • 工具结果能否正确回传。
  • 最终回答是否依据工具数据。

不要在真实模型实验中直接测试删除、支付等高风险工具。

常用命令:

powershell 复制代码
go test ./...
go test -v ./...
go test -race ./...

-race 只能发现被测试路径真正触发的数据竞争,不能证明所有并发路径都安全。

七、阶段综合题

练习 1:执行权属于谁

模型请求 delete_article(123),工具已注册且参数合法。是否应该直接删除?

参考答案:不能。注册只说明工具存在,参数只说明输入格式正确。还要检查当前用户身份、文章归属、业务状态、确认要求和幂等审计。

练习 2:工具失败后如何返回

search_articles 发生可恢复的临时错误,想让模型继续处理,应怎样返回?

参考答案 :通过脱敏的错误码和消息生成对应的 role: "tool" 结果,并使用原调用的 tool_call_id。如果是权限拒绝、预算耗尽或协议损坏,应按策略停止。

练习 3:Schema 与后端校验

JSON Schema、Go 参数校验、业务校验和权限校验分别解决什么?

参考答案:Schema 描述参数结构;Go 校验实际输入;业务校验检查资源和状态;权限校验检查当前用户是否允许执行。四层不能互相替代。

练习 4:两个预算为什么都需要

为什么同时需要 MaxRounds 和 MaxToolCalls?

参考答案:一轮模型响应可能包含多个工具调用。只限制轮数不能严格限制工具执行总量;只限制工具数也不能限制模型反复空转的交互次数。

练习 5:FakeModel 能否替代真实实验

单元测试全部通过,是否说明真实模型接入一定正确?

参考答案:不能。FakeModel验证 Agent 控制流,但不能证明真实 HTTP 请求、认证、响应结构和模型选择行为。仍需适配器集成测试和少量真实实验。

八、建议的小型综合实验

构建一个只读博客助手,包含:

  • search_articles:按关键词搜索公开文章。
  • get_article:查询用户可访问的文章详情。
  • get_current_time:获取服务器时间。

验收时检查:

  1. 模型是否根据问题选择正确工具。
  2. 多轮调用时是否保留完整消息历史。
  3. 每个结果是否关联正确 tool_call_id。
  4. 未知工具、非法参数和越权访问是否被拒绝。
  5. 超过轮数、工具次数或总时长时是否返回明确的未完成状态。
  6. 工具超时后是否只在安全范围内恢复。

综合实验应优先使用只读工具。涉及写操作时,先完成认证、权限、幂等和人工确认设计,再让模型接触真实副作用。

九、第三阶段总结

第三阶段完成了 Tool Calling 的核心工程链路:

  1. 模型输出结构化调用请求,Go 程序负责授权与执行。
  2. 工具由名称、描述、参数 Schema 和执行器组成。
  3. 注册表负责白名单、查找和统一执行入口。
  4. 参数必须经过 JSON、类型、范围和业务校验。
  5. assistant 工具调用消息与 tool 结果必须成对管理。
  6. 单轮多调用、连续多轮调用有不同执行与错误策略。
  7. 轮数、工具次数、超时、结果大小和 Token 都需要预算。
  8. 权限来自可信后端上下文,高风险副作用需要人工确认。

下一阶段将研究更完整的 Agent 核心原理:Chat、Workflow 和 Agent 的区别,以及 Observe、Think、Act、任务规划、停止条件和失败恢复。

十、结课检查

如果以下问题仍无法清楚回答,建议回到对应课程复习:

  1. 为什么模型返回 JSON 不等于工具已经执行?
  2. 为什么 function.arguments 是字符串后还要校验?
  3. 为什么 Schema 不能替代权限?
  4. 为什么必须保留 assistant 工具调用消息?
  5. 为什么轮数限制不等于工具次数限制?
  6. 为什么写操作超时后不能盲目重试?

能回答这些问题,并能用测试验证对应行为,才算真正完成 Tool Calling 从原理到工程的闭环。


下一篇:Go AI Agent 实战 25|Chat、Workflow 与 Agent 的区别

专栏:Go 开发者学 AI Agent:从大模型到工程落地

相关推荐
释厄6232 小时前
AI学习层更新与跨体转移律——人类社会同构·不缺理解缺 U
人工智能·学习
吠品2 小时前
智谱开源 ZCode 之后,真正的考试才开始
单片机·嵌入式硬件·1024程序员节
xzlAwin2 小时前
Win10 安装OpenClaw 龙虾
ai·openclaw·龙虾
老金带你玩AI2 小时前
集万家所长,一键复制的Agent.md!
人工智能
ZhuoAIStage2 小时前
舞台工艺多专业审图提速:用 AI 智能体编排 A+B+C+G 四段读图流水线
人工智能·自动化·工作流编排·舞台工艺·审图智能体
日日大王学编程2 小时前
#Feed 流项目复盘:Redis 时间线缓存与 Outbox 消息可靠性设计
golang·1024程序员节
小奶包他干奶奶3 小时前
AI学习——1、机器学习概念
人工智能·机器学习
展讯快报3 小时前
悬浮提词器免费App评估报告:说得AI的免费策略与可用性分析
人工智能
Yuzhiyuxia3 小时前
【AI Agent面试系列】AI Agent记忆机制知识点1
人工智能·面试·职场和发展