ToolFunction定义规范
ToolFunction定义规范
🎯 面试现场回答:ToolFunction 定义规范
面试官您好,关于 ToolFunction 的定义规范,我结合行业通用的 Function Call 标准和 Java 项目的落地经验,从核心本质、标准规范、调用全流程、Java 落地准则、避坑要点五个维度来阐述。
核心本质 🔧
ToolFunction(工具函数)是大模型与外部系统交互的接口契约,本质是把 Java 中的「方法签名 + 功能说明」按照 LLM 可理解的格式标准化,让大模型能够自主判断:什么时候调用、调用哪个函数、传什么参数、怎么使用返回结果,是 AI Agent 能力的核心基础。
标准定义规范 📐
行业通用遵循类 OpenAI 的 JSON Schema 规范,核心分为三大模块,结构拆解如下:
核心规则拆解:
1.函数元信息
name:全局唯一、见名知意,是服务端路由调用的唯一标识description:模型调用决策的核心依据,必须写清「适用场景 + 能力边界」
2.入参定义
- 必须用 JSON Schema 声明字段类型、业务含义、取值约束
- 必填参数必须在
required数组中明确,避免模型漏传关键参数 - 有固定选项的参数必须加
enum约束,大幅降低模型幻觉
3.返回约定
- 强制返回结构化 JSON,禁止返回无格式纯文本
- 只保留模型回答需要的核心字段,冗余信息会浪费上下文窗口
完整调用生命周期 🚀
工具调用是「模型 - 解析层 - 业务系统」的三方协作,完整流程如下:
整个流程的稳定性和准确率,完全依赖 ToolFunction 定义的规范性。
Java 生态落地准则 ✅
在 Java 项目中,通常通过注解驱动实现函数定义的自动生成与管理,核心规范如下:
- 注解映射:自定义
@ToolFunc标注方法(含名称、功能描述),@ToolParam标注入参(含含义、是否必填、约束) - 自动注册:项目启动时扫描注解,自动生成符合规范的 JSON Schema,注册到大模型客户端
- 类型对齐:严格做好 Java 类型与 JSON Schema 的映射(
int→integer、枚举类→enum、BigDecimal→number) - 统一返回:所有工具方法统一返回
ToolResult<T>结构,包含success、data、errorMsg字段,降低模型解析成本
高频踩坑与避坑 ⚠️
这也是实际项目里最容易出问题的地方:
- 描述模糊导致乱调用:description 只写 “查询订单”,未限定场景 → 优化:补充 “当用户需要查询订单详情、物流状态时调用”
- 无约束导致参数幻觉:状态类字符串参数不加范围限制 → 优化:枚举类参数强制加enum约束,数值类加
min/max - 返回冗余浪费上下文:把数据库整张表的字段全返回 → 优化:只保留模型回答需要的 3-5 个核心字段
- 命名冲突导致路由错误:多个业务模块都定义
queryInfo→ 优化:加业务前缀,如order_queryDetail、user_queryInfo
真实面试模拟
真实面试模拟
面试官:
我看到你简历里写了做过 AI Agent,用了大模型的工具调用。那先聊个基础但很要命的问题吧——你是怎么定义 Tool Function 的?随便挑一个你项目里的工具,说说它的规范是什么样的?
候选人:
好的,就拿我做的“查物流”工具来说。我定义的规范就是按照 OpenAI 的 Function Calling 格式,本质是一个 JSON 对象,包含这几个核心字段:
| 字段 | 我的例子 | 为什么这么定 |
|---|---|---|
name | get_order_status | 小写+下划线,模型也容易识别,千万别用驼峰 |
description | "根据订单号查询物流状态...不要用于取消/退款场景" | 不但说能干啥,还要说不能干啥,不然模型老乱调 |
parameters | JSON Schema,规定 order_id 是必填 string,正则 ^DD\d{17}$ | 给出精确约束,避免模型脑补出一个错的订单号 |
strict | 开启 true | 强制模型输出完全符合 Schema,不然我们后端反序列化就炸了 ✅ |
🌰 举个例子,实际发送给大模型的工具定义就是这样的:
{
"type": "function",
"function": {
"name": "get_order_status",
"description": "输入订单号,返回物流状态、承运商、预计送达时间。仅用于已支付且未完成订单,不适用于退款/取消场景。",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "19位订单号,以DD开头",
"pattern": "^DD\\d{17}$"
}
},
"required": ["order_id"],
"additionalProperties": false
}
}
}面试官:
描述写得不错,我看出来你加了反向约束。那追问一下,这个 description 到底有多重要?你遇过没写清楚导致模型死也不调用的情况吗?
候选人:
太有了 😅 早期我把描述写成“查询订单”,结果用户问“我订单到哪了”,模型居然去调了“订单详情”工具,因为名字里都有“订单”。后来改成“只查物流轨迹,不返回金额、商品明细”,马上准了。
描述就是给模型的唯一说明书,要同时满足两点:
- 正向:什么场景下该用我
- 反向:什么场景下不准用我
很多团队在这上面踩坑,以为写个名字就够了。
面试官:
好,那调用链路里,你们 Java 侧怎么接住这个 function_call 的?能画个图说说吗?
候选人:
可以,整体流程是这样的:
重点是参数校验这一步。我们用了 network.nt 的 JSON Schema 校验库,如果不通过就直接返回 error,让模型重新思考,防止脏数据把服务打挂 🛡️
面试官:
很清晰。那除了输入校验,工具返回结果有什么讲究?直接返回一个 Java 对象的 JSON 就行吗?
候选人:
不够。工具返回其实也是 Prompt 的一部分,我们规范了三段式:
- 精简数据:别把数据库一整行都扔过去,只给模型需要的关键字段
- 固定结构:
{"status":"success","data":{...},"message":"...(给人看)"} - 错误友好:即使失败也返回
{"error":"订单号不存在"},而不是抛 500 让流程断掉
这样模型无论是继续对话还是报错都游刃有余 👍
面试官:
那在工程上,这么多工具你怎么优雅注册?总不能每次写一大串 JSON 吧?
候选人:
我们做了注解驱动。定义一个 @ToolFunction 注解,标记在方法上,参数用 @ToolParam 描述。启动时 Spring 扫描所有带注解的 Bean,反射读方法签名和注解,自动生成 OpenAI 格式的 JSON Schema 注册到大模型。
这样代码即文档,描述跟着方法走,不会出现实现和 Schema 脱节。代码大概是这样:
@ToolFunction(name = "get_weather",
description = "获取指定城市实时天气,包含温度、湿度")
public WeatherResult getWeather(
@ToolParam(description = "城市英文名,如 Beijing", required = true) String city,
@ToolParam(description = "单位 celsius/fahrenheit") String unit) {
// ...
}面试官:
不错,规范工具定义的同时也统一了工程范式。那如果让你给团队定一个工具开发规范清单,你会列哪几条?
候选人:
我总结五个必查点 🖐️:
- name 统一小写下划线,和模型对齐
- description 必须有正反向约束,防止误调用
- parameters 开启 strict + 提供 example,让校验严格
- 工具返回结构标准化,包含 status 和 message
- 注解驱动生成 Schema,保持代码与定义同源
面试官:
很好,条理很清楚。这个问题我们就聊到这儿,你理解得挺扎实的
