1 · 你在哪里会用到它
ChatGPT 可以连接应用,OpenAI API 文档也把 function calling 描述为多步交互:应用执行工具,再返回结果。本例接收预写调用列表,运行本地订单查询,演示执行控制,而非自主规划或 ChatGPT 连接器内部实现。
官方产品/API 文档 ↗来源核对日期 2026-10-25。产品行为来自官方公开说明,下方代码是独立教学实现。
2 · 关键工程决策
模型输出工具名和参数只是提议,不是权限。分派前验证允许列表和参数契约,限制实际执行次数,按调用 ID 缓存结果,并拒绝同 ID 搭配不同参数。轨迹应区分阻止、无效、重放与执行。只读样例让你无需发消息或改真实系统即可测试边界。
- call-1 使用 lookup_order 查询 A17,执行一次。
- 重复 call-1 返回缓存结果,不额外消耗执行预算。
- delete_order 不在允许列表内,被阻止。
- 最终 executed 为 1。将 budget 改成 0,查询在执行前被阻止。
3 · 运行并修改输入
完全在浏览器运行,无模型调用、网络请求、API Key 或上传。输入为 JSON,这是确定性组件演示,不是生成式助手。
{
"executed": 1,
"trace": [
{
"id": "call-1",
"status": "executed",
"result": {
"status": "shipped",
"tracking": "DEMO-123"
}
},
{
"id": "call-1",
"status": "replayed",
"result": {
"status": "shipped",
"tracking": "DEMO-123"
}
},
{
"id": "call-2",
"status": "blocked_tool"
}
]
}初始输出是构建时实际计算的示例结果。修改后点击运行以计算你的结果。
4 · 完整源代码
需要现代浏览器或 Node.js 18+。下载模块,将下方 runner 存为同目录 demo.mjs,执行 node demo.mjs,无需 npm 依赖。
下载源代码 ↓// Execute a supplied tool plan against a local read-only fixture. No agent/LLM.
export const example = {budget: 2, calls: [
{id: 'call-1', name: 'lookup_order', arguments: {order_id: 'A17'}},
{id: 'call-1', name: 'lookup_order', arguments: {order_id: 'A17'}},
{id: 'call-2', name: 'delete_order', arguments: {order_id: 'A17'}}
]};
export function run({budget, calls}) {
if (!Number.isInteger(budget) || budget < 0 || budget > 10) throw Error('budget must be 0–10');
if (!Array.isArray(calls) || calls.length > 20) throw Error('At most 20 calls');
const orders = new Map([['A17', {status: 'shipped', tracking: 'DEMO-123'}]]);
const cache = new Map(); let executed = 0;
const trace = calls.map(call => {
if (!call || typeof call.id !== 'string' || typeof call.name !== 'string') return {status: 'invalid_call'};
if (call.name !== 'lookup_order') return {id: call.id, status: 'blocked_tool'};
const args = call.arguments;
if (!args || typeof args !== 'object' || Array.isArray(args) ||
Object.keys(args).length !== 1 || typeof args.order_id !== 'string') return {id: call.id, status: 'invalid_arguments'};
const fingerprint = JSON.stringify([call.name, args.order_id]);
if (cache.has(call.id)) {
const prior = cache.get(call.id);
return {id: call.id, status: prior.fingerprint === fingerprint ? 'replayed' : 'id_conflict',
...(prior.fingerprint === fingerprint ? {result: prior.result} : {})};
}
if (executed >= budget) return {id: call.id, status: 'budget_exhausted'};
executed++;
const result = orders.get(args.order_id) || {status: 'not_found'};
cache.set(call.id, {fingerprint, result});
return {id: call.id, status: 'executed', result};
});
return {executed, trace};
}
import {run, example} from './bounded-tools.mjs';
console.log(JSON.stringify(run(example), null, 2));5 · 失败案例与生产差距
缓存仅存在于一次运行中。真实有副作用工具需要持久幂等、授权、超时、取消和审计轨迹。重放相同 ID 与使用新 ID 重复请求不同。只读状态返回不能被描述成订单已修改的证据。
6 · 你的实现练习
测试未知工具、多余参数键、相同 ID 查询不同订单及预算耗尽。在加入任何写操作前,先设计超时与审批状态机。
交付修改后的代码、至少一个成功与一个失败案例,并解释测试不能证明什么。
继续学习关联知识点 →