🐎 第8章 器以载道:Agent SDK 智能体开发套件
📄 一句话概括

自定义工具是「让模型能调你自己写的函数」。它要解决两件事:告诉模型有什么工具、参数是什么; 挡住传错的参数。 实测三个调用两个被拒。 被拒时的错误信息必须能直接告诉模型「改哪儿」——只说「参数错误」,它只能瞎猜。

📝 本章和前面几章的关系

上一节讲会话,这一节讲扩展:怎么让它调你的函数。 这一节真跑了三件事:从签名生成 schema、三种调用逐个校验、错误信息怎么写才有用。

一、工具的三件套

名字:模型用它来调。要短、动词开头,别用内部代号。 说明:模型决定要不要用它。要写「什么时候用」,不是「它是什么」。 参数 schema:模型照着填。类型、必填、取值范围。

三条里最容易写错的是第二条:写成「这个函数统计词频」,模型不知道什么时候该用; 写成「当需要知道某个文件里最常出现的词时用」,它就知道该在什么场景调。

二、真跑:从函数签名生成 schema

演示里的装饰器读函数签名:有默认值的算可选,没默认值的算必填;注解决定类型。 生成出来的 schema 就是两行:path 是字符串必填,top 是数字可选。 从签名生成的好处是「只有一处定义」:改函数签名,schema 跟着变,不会对不上。

手写 schema 的坏处正是这个:改了函数忘了改 schema,模型就会按旧的填。

三、真跑:三个调用逐个校验

三个调用:一个完全对、一个缺必填、一个类型不对。结果两个被拒。 被拒的时候,错误信息要能直接告诉模型「改哪儿」。 「缺必填参数:path」这句话模型看得懂,它下一轮会补上。

如果只说「参数错误」,它只能瞎猜——这就是校验信息要写清楚的原因。

四、为什么要 Pydantic 这类库

手写校验能覆盖必填和类型,但覆盖不了嵌套结构、取值范围、格式(邮箱、路径)。 而且手写的错误信息往往不如库里的清楚:库会把「哪个字段、期望什么、给了什么」一次说全。 代价是多一个依赖,以及「模型生成的参数」和「模型类的字段」要保持同步。

判据:工具参数超过五个、或者有嵌套结构,就上库;两三个简单参数,手写够了。

五、注册与启用

定义完还要注册:把工具放进选项里,它才会出现在模型的工具清单里。 这一步和 6.7 节那个「常驻 token」是同一件事:每多一个工具,每次会话就多一段 schema。 所以工具不是越多越好——十个工具的描述,可能比你的任务还长。

实用做法:把工具按场景分组,这个场景只注册这一组。

六、动手 Demo:一个装饰器生成 schema,三种调用两种被拒

自定义工具就是「让模型能调你自己写的函数」。 这里真跑三件事:从签名生成 schema、三种调用逐个校验、看错误信息够不够清楚。

@tool
def word_count(path: str, top: int = 3):
    return {u"path": path, u"top": top}

# schema 由签名生成:path 必填、top 可选

实跑输出:

--------------------------------------------------------------------
一个装饰器生成 schema,三种调用两种被拒
--------------------------------------------------------------------

  自定义工具就是「让模型能调你自己写的函数」。
  它要解决两件事:告诉模型「有什么工具、参数是什么」,以及「传进来的参数对不对」。

  ① 工具的三件套
  --------------------------------------------------------------------
| 部分 | 谁在用 | 怎么写 |
|---|---|---|
| 名字 | 模型用它来调 | 短、动词开头 |
| 说明 | 模型决定要不要用它 | 写清「什么时候用」,不是「它是什么」 |
| 参数 schema | 模型照着填 | 类型、必填、取值范围 |


  ② 真跑:从函数签名生成 schema
  --------------------------------------------------------------------
  word_count 的签名里,path 没有默认值、top 默认为三,生成的 schema 就是这个样子的:
| 参数 | 类型 | 必填吗 |
|---|---|:---:|
| path | string | 必填 |
| top | number | 可选 |

  从签名生成的好处是「只有一处定义」:改函数签名,schema 跟着变,不会对不上。

  ③ 真跑:三个调用逐个校验
  --------------------------------------------------------------------
| 传进来的参数 | 结果 | 原因 |
|---|:---:|---|
| {"path": "src/app.py", "top": 5} | 接受 | 通过 |
| {"top": 5} | 拒绝 | 缺必填参数:path |
| {"path": 123} | 拒绝 | 参数 path 类型不对:要 string,给了 number |

  三个里两个被拒。被拒的时候,错误信息要能直接告诉模型「改哪儿」。
  「缺必填参数:path」这句话模型看得懂,它下一轮会补上。
  如果只说「参数错误」,它只能瞎猜——这就是校验信息要写清楚的原因。

  ④ 为什么要 Pydantic 这类库
  --------------------------------------------------------------------
  手写校验能覆盖必填和类型,但覆盖不了嵌套结构、取值范围、格式(邮箱、路径)。
  而且手写的错误信息往往不如库里的清楚。
  代价是多一个依赖,以及「模型生成的参数」和「模型类的字段」要保持同步。

  ⑤ 注册与启用
  --------------------------------------------------------------------
  定义完还要注册:把工具放进选项里,它才会出现在模型的工具清单里。
  这一步和 6.7 节那个「常驻 token」是同一件事:每多一个工具,每次会话就多一段 schema。
  所以工具不是越多越好——十个工具的描述,可能比你的任务还长。
  ! 这里的装饰器是最小实现,真实库会做更多(异步、超时、权限、类型转换)。

①工具的三件套:名字(短、动词开头)、说明(写「什么时候用」不是「它是什么」)、参数 schema(类型、 必填、范围)。 ②从签名生成 schema 的好处是「只有一处定义」:改签名 schema 跟着变,不会对不上。 手写最容易犯的错正是改了函数忘了改 schema。 ③三个调用两个被拒:一个缺必填、一个类型不对。 被拒时的错误信息要能直接告诉模型「改哪儿」——「缺必填参数:path」它看得懂,「参数错误」它只能瞎猜。 ④定义完还要注册:工具放进选项才会出现在清单里。 每多一个工具,每次会话就多一段 schema,所以工具不是越多越好。

30 秒实验

  1. 把 word_count 的 top 默认值删掉,重跑一遍。schema 里它变成必填了吗?这会改变模型的行为吗?
  2. 给参数加一个取值范围(top 最多一百),重跑一遍。手写校验能覆盖这条吗?要覆盖它得改哪儿?
  3. 把说明从「统计一个文件里出现最多的词」改成「当需要知道某个文件里最常出现的词时用」,再想一下: 模型的选择会变吗?为什么?

常见误区

  1. 说明写成「这个函数干什么」,而不是「什么时候该用它」。模型不知道何时该调。
  2. 改了函数签名忘了改 schema。模型按旧的填,然后一直被拒。
  3. 校验失败只说「参数错误」。模型只能瞎猜,重试几次还是错。
  4. 把十几个工具全注册上。每次会话都多付一大段 schema。
  5. 两三个简单参数也上重依赖。手写校验够用的时候,别加依赖。
自测三问(点开对答案) 1. 工具的三件套是什么? —— 名字(短、动词开头)、说明(写什么时候用)、参数 schema(类型、必填、范围)。 2. 校验失败时的错误信息该怎么写? —— 要能直接告诉模型改哪儿,比如「缺必填参数:path」。只说「参数错误」它只能瞎猜。 3. 什么时候该用 Pydantic 这类库? —— 参数超过五个、或者有嵌套结构、格式要求的时候。两三个简单参数手写就够。

自己的工具能挂了,下一节挂拦截:在工具执行前后插自己的判断。

相关笔记