假设一个个人资料助手既能搜索你保存的活动通知,也能查询报名系统。你已经在页面选中“九月读书会”,程序知道当前活动,然后你问它:
按我保存的通知,九月读书会报名截止日期是哪天?
随后又问:
查一下报名系统,九月读书会现在还接受报名吗?
两句话都涉及“读书会报名”,需要的依据却不同。第一句要找到通知中的日期,第二句要取得报名系统的当前状态。通知可能尚未过期,报名入口却已经因为名额用尽而关闭。
如果只给模型两个叫search和query的工具,再分别写上“搜索信息”“查询信息”,这些说明就没有提供区分两项任务的充分依据。模型也许能从其他上下文猜中,但我们还没有把程序真正知道的区别告诉它。
在这类函数调用接入方式中,模型通常收到的是工具名称、描述和参数约定,函数实现由程序运行。要让模型更容易用对工具,需要先把两件事说明白:这个工具适合完成什么动作,以及完成动作所需的值从哪里取得。
1. 工具之间的区别,要落在用户需要的依据上
可以先把两个工具写成下面这样:
| 工具 | 负责取得什么 | 什么时候使用 |
|---|---|---|
search_notices:搜索已保存通知 | 匹配通知的编号、标题、发布日期和正文 | 要核对通知写了什么、日期是否有调整 |
get_current_registration_status:查询当前活动的报名状态 | 报名系统此刻是否接受报名、名额情况和查询时间 | 要确认当前活动现在是否还能报名 |
名称先让模型辨认动作与对象,描述再补上名称装不下的条件。这里最有用的内容是:搜索范围是已保存通知;状态查询面向报名系统;两项操作都只读取信息,不会替用户报名。
这几句话为什么有用?因为“通知截止日期”和“当前是否接受报名”对应不同事实。说明信息来源和返回内容,模型才有依据判断哪项操作能够取得问题所需的事实。仅把名字改得更长,仍然没有解释数据来源,作用就很有限。
这也不意味着两项工具只能二选一。如果用户问“通知说还没截止,为什么系统不让我报名”,两种依据都有价值,可能需要分别查询。工具职责清楚,是为了让模型按问题选择,不是规定每项任务只能调用一个工具。
工具也不必拆得越细越好。假如保存的通知很少,搜索后直接返回完整正文就足够了,没有必要强制增加“先取编号,再读正文”的一次调用。只有通知很长、匹配项很多或读取成本不同等条件出现时,才需要重新考虑是否分开。
2. 参数要解释“这个值是什么意思”,也要解释“该从哪里取”
现在给通知搜索增加一个常见需求:用户可以只看某一天及之后发布的通知。
这个工具需要两个参数。query表示查什么,published_from表示通知发布日期的下限。后者尤其容易产生歧义:它是一段日期文字,但读书会还可能有活动日期、报名开始日期和报名截止日期。
只写“published_from的类型是字符串,格式为日期”,并没有说清该填哪一个日期。可以通过同一个活动的两种问法看出区别:
| 用户的问题 | published_from应填什么 | 原因 |
|---|---|---|
| 找出 2026 年 9 月 15 日及之后发布的报名通知 | "2026-09-15" | 用户明确限制了通知的发布时间,且包含 9 月 15 日当天 |
| 从保存的通知里查,报名截止日期是不是 2026 年 9 月 18 日 | null | 9 月 18 日是待核对的报名截止日期,没有限定通知的发布时间 |
第二种问法如果被转换成“只搜 9 月 18 日及之后发布的通知”,就可能漏掉 9 月 16 日发布、宣布 9 月 18 日截止的更新。日期的格式完全正确,搜索范围却已经变了。
因此,参数说明要把取值条件写出来:
published_from是通知发布日期下限,包含当天,按活动所在地日历日期填写,格式为YYYY-MM-DD。仅在用户明确限制通知发布时间时填写;报名截止日期不能作为这个筛选值。没有发布时间限制时使用null。
null在这里表示“不按发布日期筛选”,不是“把日期填成今天”,也不是“这个参数可以随意省略”。程序接收到它以后,应按这个约定执行。
query也需要说明:填写活动和事项的搜索关键词,多个关键词用空格分隔。用户正在询问截止日期时,可以搜索“九月读书会 报名 截止”,不应先把“已经截止”当成已知结论写进查询条件。具体该用哪些关键词,还要与搜索实现支持的方式一致;纯关键词搜索和能够理解完整问句的搜索,不必使用同一种参数说明。
这类字段约定通常用JSON Schema表达:它描述一个参数对象允许有哪些字段,各字段接受什么类型,哪些字段必须出现。下面是通知搜索的定义示例,采用 Responses API 的函数工具格式:
{"type":"function","name":"search_notices","description":"搜索当前用户获准读取的已保存活动通知,返回匹配通知的编号、标题、发布日期和正文。用于核对通知写了什么,不查询报名系统实时状态。","parameters":{"type":"object","properties":{"query":{"type":"string","description":"活动和事项的简短关键词,多个关键词用空格分隔;不要把待核对的结论写成已知事实。"},"published_from":{"type":["string","null"],"description":"通知发布日期下限,包含当天,按活动所在地日历日期填写 YYYY-MM-DD。仅当用户明确限制通知发布时间时填写;用户提到的报名截止日期不能当作此筛选值。不筛选发布时间时填写 null。"}},"required":["query","published_from"],"additionalProperties":false},"strict":true}其中,required要求两个字段都出现,additionalProperties: false不允许添加其他字段。published_from接受字符串或null,让“需要这个字段”和“不一定按日期筛选”能够同时成立。
OpenAI 的函数调用指南要求严格模式下的对象关闭额外属性,并把声明的属性列为必填;业务上可选的值可以用包含null的类型表达。其他接口是否支持相同写法,需要按其约定确认。
结构约束解决的是参数形状。上面的日期格式仍然写在描述里,字符串类型本身不会检验日历日期是否合法;即使生成了合法日期,也不代表模型理解对了它的含义。若某个参数只有几个允许值,例如排序方式只有“按发布日期升序”和“按发布日期降序”,可以用枚举列出固定值,让模型在有限选项中选择。自由关键词则不适合硬塞进枚举。
3. 程序已经确定的信息,不必让模型再填一遍
报名状态查询似乎需要一个activity_id,也就是报名系统用来识别活动的编号。是否应该把它交给模型填写,要看程序已经知道什么。
在这个例子里,用户先在页面选中了“九月读书会”,程序已经持有该活动的准确编号。因此,模型可见的工具可以没有参数:
{"type":"function","name":"get_current_registration_status","description":"只读查询用户在页面上已选中活动的报名系统当前状态,返回是否接受报名、名额情况和查询时间。不代用户报名,不查询历史通知。活动 ID 由程序根据页面选择确定,访问凭证由程序从登录会话或已授权的服务连接取得,均无需模型填写。","parameters":{"type":"object","properties":{},"required":[],"additionalProperties":false},"strict":true}模型只需要判断是否调用。实际执行时,程序用已选中的活动编号发起查询,不要求模型把“九月读书会”再转换成内部编号。这样省去了一次没有必要的推断,也避免了活动名称、编号和页面选择不一致的问题。
OpenAI 的工具设计建议也使用了类似例子:如果程序已经从菜单选择取得订单编号,就不必让模型再次填写该编号。这个例子说明的是参数从哪里取得;它本身不代表相关操作已经获准执行。
这里有一个不能省略的前提:程序确实知道用户选中了哪项活动。如果用户没有选择,或者问的是另一场活动,就需要先确定目标。此时可以要求用户选择,也可以另行设计活动查找能力,不能靠一个无参数工具悄悄沿用无关活动。
当前用户、访问凭证和允许读取的资料范围,同样应由程序管理。把参数拿掉只是减少模型需要填写的内容;能否访问某项资料,仍须由程序判断。
4. 用接近的问法,检查模型是否理解了工具边界
检查工具设计时,只问“帮我查通知”还不够。这样只能观察最明显的选择,容易漏掉日期含义、资料来源和当前对象的歧义。
为观察这些区别,对这两个工具做了四题、每题两次的真实调用。每次是独立请求,输入先说明:
页面已选中活动“九月读书会”,程序能够确定这个活动。 问题:后面追加具体问题。八次请求共用下面这段指令:
你是个人资料助手。根据用户问题所需的信息选择工具。模型发出的调用请求不等于查询已经完成;未收到对应工具结果时,不得声称已经查询或编造查询结论。如果已有信息不足以选择合适工具,先向用户补问。工具选择设置为tool_choice="auto",没有指定必须调用哪一个。请求通过第三方 Responses API 兼容服务发送,请求和返回的模型标识均为gpt-6;这个名称不构成对服务底层模型身份的独立验证。
- 请求设置了
reasoning.effort="high"、max_output_tokens=4096、parallel_tool_calls=false,未传入温度等采样参数。 - 响应中的输出上限字段为
null,因此不能据此认定服务执行了请求设置的上限。 - 通知搜索的定义与上文一致。状态查询工具在实验中使用的来源说明是“活动 ID 和访问凭证由程序从已选中活动取得,无需模型填写”,其余定义与上文一致。上文已将活动编号与凭证的来源分开说明;以下结果对应原始措辞,未验证这处表述调整对模型表现的影响。
调用前按题意固定判定标准,再按下表顺序各问一遍,随后按相同顺序重复一遍。实际结果是:
| 完整问题 | 应取得的依据与参数要求 | 两次实际调用 |
|---|---|---|
| 按我保存的通知,九月读书会报名截止日期是哪天? | 搜索通知,不限制通知发布时间 | 均选search_notices,published_from为null |
| 查一下报名系统,九月读书会现在还接受报名吗? | 查询已选中活动的实时状态,无需填活动编号 | 均选get_current_registration_status,参数为{} |
| 找出9月15日(2026年)及之后发布的九月读书会报名通知。 | 搜索通知,发布日期从 9 月 15 日起 | 均选search_notices,published_from为"2026-09-15" |
| 从我保存的通知里查一下,九月读书会报名截止日期是不是2026年9月18日? | 核对截止日期,不把它当作发布时间筛选条件 | 均选search_notices,published_from为null |
六次通知搜索的关键词均包含“读书会”和“报名”,采用了约定的空格分隔,也没有把待核对的结论写成已知事实。比如第四题第一次返回的调用,展开名称和解析后的参数是:
{"name":"search_notices","arguments":{"published_from":null,"query":"九月读书会 报名 截止日期"}}模型保留了截止日期这个查询主题,没有因为问题里出现 9 月 18 日,就把搜索范围缩到那天之后。两次实时状态查询则都返回空参数对象,由程序负责确定活动。
八次请求全部正常完成,每次只提出一个工具调用,没有提前生成查询结论,也没有重试。这个实验停在模型提出操作的步骤,没有执行通知搜索或接入报名服务,因此能观察的是“选了什么、填了什么”,不能说明查询结果正确或报名系统可用。
在这四题、每题两次的范围内,工具选择和参数均符合事先的要求,但这些明确的问法没有覆盖对象不清、多个需求混在一起等情况。要判断某次修改有没有改善结果,还需要在相同问题和标准下比较修改前后;这八次调用只检查了一套定义,不支持准确率提升的结论。
5. 说明写到哪里才够,取决于还剩下什么需要猜
可以把工具说明交给一个不了解实现的人,只让他根据当前问题选择工具、填写参数。如果他仍然要问“这个查询查的是本地资料还是实时系统”“日期指哪一天”“活动编号从哪里来”,这些就是说明中需要补齐的内容。
不必把所有背景都塞进去。对上面的通知工具,发布日期和报名截止日期的区别值得写清;重复几遍“请准确查询”,并没有增加可执行的信息。示例也应针对真实存在的歧义。一个能区分两种日期的例子,比十个只有关键词变化的正确调用更有解释价值;是否改善模型表现,还要通过实际测试判断。
最后试着处理三个变化:
- 用户问:“找出 2026 年 9 月 15 日当天发布的读书会通知。”只填发布日期下限是否足够?
- 用户问:“按保存的通知,这个活动今天是不是已经过了报名截止日?”是否也要把今天填成发布日期下限?
- 用户说:“帮我报名。”状态查询工具能否完成这项任务?
第一种情况确实涉及通知发布时间,但现有参数只有下限。填入"2026-09-15"还可能找到 9 月 16 日及之后的通知,不能精确表达“只要当天”。要满足这个要求,可以增加结束日期,或者由程序从结果中筛出当天发布的通知。已有参数能表达什么,也限制了这个工具能直接完成什么。
第二种情况中的今天是判断报名是否截止的时间,没有限制通知何时发布,所以published_from应为null。第三种情况需要写入操作,而当前两个工具都只能查询;名称或提示词写得再明确,也不能凭空产生报名能力。
好的工具设计把模型需要判断的事情留得清楚:根据问题选择能够取得依据的操作,再从明确的来源取得参数。能由程序确定的值由程序带入;有限选项用结构表达;容易混淆的含义用说明和针对性例子解释。做到这些以后,仍要检查真实调用,因为清楚的约定提供了选择依据,实际结果才能说明模型有没有按这些依据行动。