猪头少年 - 云南AI专家 - 云南独立开发者

Engineering note

工具调用协议:让模型能可靠地使用你的能力

这是「Agent 工程实战」的第 5 篇。专题从一个能聊天、能调工具的 .NET Agent 出发,逐步补齐可靠性、安全、测试与发布能力。

本篇要解决的问题: 理解工具定义、参数结构、调用标识和结果回传,避免“看起来会调用”的假闭环。

返回专题路线


本章定位:本章把工具调用当作一份 API 契约来研究。你会学习工具 schema、调用 ID、结构化结果和描述质量如何共同影响 Agent 的行为。

建议阅读方式:先通读原理,再对照当前项目源码,最后完成本章实践。本章适合在你准备增加第二、第三个工具前阅读;工具一多,命名和契约问题会立即暴露。

本章导读

本章把工具调用当作一份 API 契约来研究。你会学习工具 schema、调用 ID、结构化结果和描述质量如何共同影响 Agent 的行为。

本章采用“源码观察 → 概念拆解 → 工程改造 → 实践验证”的顺序。示例中的接口和代码骨架用于说明设计方向,真正提交代码时应结合项目当前状态逐步落地。

5.1 工具描述的形状

当前 Tool 类会序列化成类似这样的结构:

{
  "type": "function",
  "function": {
    "name": "ReadFile",
    "description": "读取指定文件中的内容",
    "parameters": {
      "type": "object",
      "properties": {
        "filePath": {
          "type": "string",
          "description": "读取的文件路径"
        }
      },
      "required": ["filePath"]
    }
  }
}

工具描述是给模型看的“可调用 API 文档”。描述越准确,模型越容易选择正确工具和生成正确参数。

5.2 工具调用不是函数调用

模型不会直接运行 C# 方法。它只能输出结构化意图:工具名、调用 ID 和 JSON 参数。真正执行工具的是你的程序。

这意味着程序必须承担四项责任:

  • 工具是否存在;
  • 参数是否符合 schema;
  • 当前用户是否有权限;
  • 执行结果如何安全地反馈给模型。

模型说“请执行某命令”不等于程序必须执行。模型是决策建议者,程序是最终的策略执行者。

5.3 工具结果应该稳定

当前 AgentToolResult 只有 MessageResult。建议逐步演进为:

public sealed record ToolResult(
    bool Success,
    string Message,
    object? Data = null,
    string? ErrorCode = null);

稳定的字段可以让模型区分“文件为空”“文件不存在”“没有权限”和“程序内部错误”。不要只把异常文本原样交给模型。

5.4 工具描述的写作原则

  • 说明工具能做什么,不要只写方法名;
  • 明确路径是文件还是目录、是否允许相对路径;
  • 说明危险操作的限制;
  • 说明返回值格式和失败情况;
  • 参数名要表达业务含义,不要使用 arg1data 这类模糊名称。

5.5 本章小结

工具 schema 是 Agent 的 API 契约。它既影响模型决策,也决定程序能否校验、审计和限制调用。

5.6 工具名称是公共 API

工具名一旦被模型使用,就不再是普通方法名。改名会影响提示词、评测集、日志查询和旧会话恢复。因此建议使用稳定的命名规则:

资源.动作
filesystem.read_file
filesystem.write_file
process.run_allowed
knowledge.search

内部 C# 方法可以叫 ReadFileAsync,外部工具名仍然保持 filesystem.read_file。这样既符合 C# 命名习惯,也能让模型更容易理解工具类别。

5.7 schema 的描述决定调用质量

下面两个描述看起来都能工作,但质量不同:

{"description":"读文件"}
{"description":"读取 workspace 内的 UTF-8 文本文件。参数必须是相对路径,不允许使用绝对路径或 .. 穿越。文件不存在时返回结构化错误。"}

第二种描述把边界告诉了模型,也把程序的校验规则提前暴露给调用方。描述应该与实际实现保持一致,否则模型会根据错误契约生成请求。

5.8 工具结果不是给人看的日志

模型需要结构化结果,而不是一段难以区分的控制台输出。推荐:

{
  "success": false,
  "errorCode": "FILE_NOT_FOUND",
  "message": "目标文件不存在",
  "data": {"path":"notes/today.md"}
}

其中 message 应适合模型理解,errorCode 适合程序判断,data 适合后续逻辑使用。完整异常和堆栈放在日志里,不放进工具结果。

5.9 练习:给工具做 API 评审

选择当前的 GetFiles,回答:它是否说明了搜索深度?返回的是绝对路径还是相对路径?目录不存在和目录为空能否区分?一次最多允许返回多少文件?

如果这些问题没有答案,就先修改工具描述和结果模型,再让模型调用。工具契约比工具实现更值得提前设计。

5.10 本章产出

为现有工具建立一份工具目录表,至少记录名称、用途、参数、返回值、风险等级和失败码。后续的权限、评测和帮助命令都可以从这份定义生成。

单篇实战作业

实践:挑一个现有工具,写出它的名称、参数、成功结果、失败结果和风险等级,作为工具目录的第一条记录。

建议把作业拆成一个独立提交,并在提交说明中写清楚:改动前的行为、改动后的行为、验证命令、尚未解决的风险。本章适合在你准备增加第二、第三个工具前阅读;工具一多,命名和契约问题会立即暴露。

章节复盘

复盘问题:模型看到的描述是否与程序实际允许的行为一致?不一致时,应该优先改 schema 还是改实现?

本章的完成标准不是把所有设计一次性做完,而是能把它变成项目中的一个明确边界,并为下一章留下可验证的接口。


下一篇:第 6 篇《用反射把 C# 方法注册成工具:便利与边界》

如果你正在把 Agent 放进真实工作流,建议完成本篇的实战作业后再继续:每一步都应留下可验证的代码、测试或运行记录。

Next step

不要停在单篇文章。

沿主专题继续阅读,查看同一问题从概念到实战的完整路径。

返回「Agent 工程实战」路线