上一课模型可以选择工具,但模型生成的参数不一定合法。
例如:
{"book_name": "三体", "quantity": -2}
不能只靠提示词说"数量必须正确",还要让程序检查。
这节增加一个"计算报价"工具:数量必须是 1~10之间的整数。只计算报价,不下单、不扣库存。
1. 复制上一课文件
另存为:
C:\Users\Administrator\langchain-study\unit5_lesson4_tool_validation.py
添加导入:
from pydantic import BaseModel, Field, ValidationError
2. 定义参数规则和报价工具
放在原来的两个工具函数后面、tools 列表前面:
class QuoteInput(BaseModel):
book_name: str = Field(
min_length=1,
description="图书名称,例如三体,不包含书名号",
)
quantity: int = Field(
strict=True,
ge=1,
le=10,
description="购买数量,必须是1到10之间的整数",
)
@tool(args_schema=QuoteInput)
def calculate_quote(book_name: str, quantity: int) -> str:
"""查询指定图书的库存,并计算指定数量的现货报价。
只提供报价,不创建订单,也不扣减库存。
"""
print("[报价工具] 参数校验通过,开始执行")
name = book_name.strip().replace("《", "").replace("》", "")
if name not in BOOK_PRICES or name not in BOOK_STOCK:
return f"《{name}》的价格或库存记录不完整,无法报价。"
stock = BOOK_STOCK[name]
if quantity > stock:
return (
f"《{name}》只有 {stock} 本库存,"
f"不足 {quantity} 本,无法满足本次现货需求。"
)
# 沿用上一课的演示价格:整数,单位为元
unit_price = BOOK_PRICES[name]
total_price = unit_price * quantity
return (
f"《{name}》单价 {unit_price} 元,"
f"{quantity} 本合计 {total_price} 元。"
"这只是报价,尚未下单或扣减库存。"
)
args_schema=QuoteInput 指定工具输入的校验规则。通过工具的 .invoke() 调用时,参数会先校验,再进入函数主体。LangChain 参数模型说明
3. 看懂这段语法
quantity: int = Field(
strict=True,
ge=1,
le=10,
)
| 写法 | 含义 |
|---|---|
quantity: int |
数量字段是整数 |
strict=True |
严格检查类型,不把字符串 "2" 自动转换成整数 |
ge=1 |
大于或等于1 |
le=10 |
小于或等于10 |
因此:
2 # 通过
0 # 不通过
-2 # 不通过
11 # 不通过
2.5 # 不通过
"2" # 不通过:这是字符串
这里没有提供 default,所以数量是必填项。Field(...) 是字段规则,不是给数量赋了一个普通数值。Pydantic 字段说明
4. 注册工具并更新提示词
将工具列表改成:
tools = [
get_book_stock,
get_book_price,
calculate_quote,
]
原来的这两处继续使用同一个列表,不用修改:
model_with_tools = model.bind_tools(tools)
builder.add_node("tools", ToolNode(tools))
在系统提示词中补充:
"用户要求计算指定数量的总价或报价时,调用 calculate_quote。"
"报价工具已经检查库存并计算总价,不必重复查询相同信息。"
"报价需要明确的书名和数量,缺少时先询问。"
"数量必须是1到10之间的整数。"
"不要擅自修改用户数量,也不要拆分请求来绕过数量限制。"
"报价不代表订单已经创建。"
5. 先直接测试校验,再测试模型
仅向模型问"买负2本",它可能直接拒绝,根本不调用工具。这样不能证明程序校验真的生效。
为了确定校验有效,可在工具定义后、模型初始化前,临时添加:
for quantity in [2, 0, -2, 11, 2.5, "2"]:
print(f"\n测试数量:{quantity!r}")
try:
result = calculate_quote.invoke({
"book_name": "三体",
"quantity": quantity,
})
print("工具结果:", result)
except ValidationError:
print("校验失败:没有进入报价函数。")
# 临时结束程序,这次测试不调用模型
raise SystemExit
运行:
python unit5_lesson4_tool_validation.py
预期只有整数 2 会出现:
[报价工具] 参数校验通过,开始执行
其他测试值都应该被拦截。
测试完成后,删除这段临时测试代码,包括 raise SystemExit。
再运行并提问:
买两本《三体》多少钱?请给我报价。
按原来的演示数据,应该得到90元,并说明没有下单。
6. 参数合法,不代表业务可行
再问:
请给我9本《三体》的现货报价。
假设库存仍为8本:
9是1~10之间的整数,参数校验通过。- 但9本超过库存,业务检查不通过。
这两层检查职责不同:
参数校验检查"输入格式和范围是否正确";业务检查判断"当前条件下能不能办"。
另外,参数校验不能证明模型忠实提取了用户意思:如果用户说12本,模型擅自传入10,格式仍然合法。所以测试时也要检查打印出的工具参数是否与用户要求一致。