把 Python 函数变成工具
设计工具名称、参数、返回值与执行边界。
学完这一课,你将能够
- 实现一个带参数校验的工具
- 通过白名单分发工具调用
- 把执行错误转成结构化结果
学习目标
- 将一个普通函数包装成具有稳定契约的工具。
- 用工具白名单和参数校验控制执行范围。
- 把工具错误返回给决策层,而不是让整个任务崩溃。
前置要求:Python 函数、JSON 校验和模型适配层。 本节只用本地数据,不调用模型或外部服务。
工具调用并不是模型执行了代码
模型通常生成一个工具调用提议,包括工具名、参数以及调用标识。 应用收到提议后,检查是否允许,再调用实际函数。 执行结果通过对应调用标识交还给模型,模型才能依据结果继续回答。
这条边界让程序保留控制权。
即使模型提出一个不存在的工具,或把数量写成负数,执行层也必须能拒绝。
不能把模型输出直接交给 eval、任意 shell 或任意网络地址。
好工具的输入和输出都具体
lookup_stock(product_id) 比 do_everything(query) 更容易理解和测试。
名称表达动作,参数表达必要输入,描述说明适用范围与限制。
返回值保持稳定结构,让成功、空结果和失败可以明确区分。
{
"name": "lookup_stock",
"description": "按商品 ID 查询当前库存,只读,不下单。",
"inputSchema": {
"type": "object",
"properties": { "product_id": { "type": "string", "minLength": 1 } },
"required": ["product_id"],
"additionalProperties": false
}
}这里用 inputSchema 表达通用概念。
真实模型服务的工具定义字段可能不同,应由适配层转换。
写一个可运行的分发器
保存为 tools.py,运行 python tools.py。
CATALOG = {"python-book": 3, "agent-book": 0}
def lookup_stock(args):
if not isinstance(args, dict) or set(args) != {"product_id"}:
return {"ok": False, "error": "invalid_fields"}
product_id = args["product_id"]
if not isinstance(product_id, str) or not product_id:
return {"ok": False, "error": "invalid_product_id"}
if product_id not in CATALOG:
return {"ok": False, "error": "product_not_found"}
return {"ok": True, "product_id": product_id, "stock": CATALOG[product_id]}
TOOLS = {"lookup_stock": lookup_stock}
def dispatch(call):
if not isinstance(call, dict):
return {"ok": False, "error": "invalid_call"}
name = call.get("name")
if not isinstance(name, str) or name not in TOOLS:
return {"ok": False, "error": "unknown_tool"}
return TOOLS[name](call.get("arguments"))
calls = [
{"name": "lookup_stock", "arguments": {"product_id": "python-book"}},
{"name": "lookup_stock", "arguments": {"product_id": "missing"}},
{"name": "delete_database", "arguments": {}},
{"name": "lookup_stock", "arguments": {"product_id": ["python-book"]}},
]
for call in calls:
print(dispatch(call))预期只有第一条返回库存,其余返回不同的结构化错误。 第四条特别检查了类型,避免把列表直接用作字典键导致异常。
参数合法之后,还要判断权限
Schema 检查数据形状,业务系统检查是否有权执行。
例如 customer_id 是合法字符串,不代表当前用户能读取该客户信息。
账户作用域应由可信会话注入,而不是让模型任意提供。
只读查询和写入操作也需要不同策略。 发邮件、提交订单或修改文件前,应明确操作对象、影响范围和批准条件。 第五模块会实现一个绑定具体参数的确认流程。
工具返回什么最有用
优先返回完成任务需要的字段,避免把整个数据库对象塞回上下文。 涉及事实时附带来源或更新时间,方便后续回答标注依据。 错误最好附稳定错误码与简短说明;是否可重试应由明确策略决定。
外部工具返回的文本可能包含误导指令,仍应被视为数据。 工具有权限,不意味着工具返回的内容有权改写任务规则。
练习与验收
增加一个只读工具 list_products,返回商品 ID 列表。
它不接收业务参数,传入多余字段时应拒绝。
- 两个工具都只能通过白名单调用。
- 未知工具不会触发动态代码执行。
- 参数错误、商品不存在和零库存有不同结果。
- 每条工具结果都可以序列化为 JSON。
常见误区
工具越多不一定越好。重叠的名称和模糊描述会让选择更加困难。 先用两三个职责明确的工具完成小任务,再增加能力。 重试未知工具名也通常没有意义,需要修正决策或工具清单。
延伸阅读
下一课将把分发器放进一个会读取观察结果的循环。
让这一课,真正成为你的收获
完成练习后标记完成,也可以随时回来复习。
笔记与进度保存在当前浏览器,无需登录