Agent、Runner 与真实工具注册:练习答案
参考实现
在同一环境单独运行:
python
import asyncio,json
from pydantic import BaseModel,StrictInt,ValidationError,ConfigDict
from agents import FunctionTool,RunContextWrapper,ModelBehaviorError
class QuantityArgs(BaseModel):
model_config=ConfigDict(extra='forbid')
quantity: StrictInt
entered=[]
async def invoke_twice(ctx,raw):
try:args=QuantityArgs.model_validate_json(raw)
except ValidationError as error:
# 显式 FunctionTool 自己负责参数与错误策略;不返回伪业务成功。
raise ModelBehaviorError('quantity must be a strict integer') from error
entered.append(args.quantity)
return args.quantity*2
twice=FunctionTool(name='twice',description='Double a strict integer quantity.',
params_json_schema=QuantityArgs.model_json_schema(),on_invoke_tool=invoke_twice)
async def main():
assert twice.params_json_schema['properties']['quantity']['type']=='integer'
assert await twice.on_invoke_tool(RunContextWrapper(None),'{"quantity":3}')==6
for value in [True,'3',1.5,3.0]:
before=len(entered)
try:await twice.on_invoke_tool(RunContextWrapper(None),json.dumps({'quantity':value}))
except ModelBehaviorError:pass
else:raise AssertionError('non-integer accepted')
assert len(entered)==before
for raw in ['not JSON','{}','{"quantity":3,"admin":true}']:
try:await twice.on_invoke_tool(RunContextWrapper(None),raw)
except ModelBehaviorError:pass
else:raise AssertionError('invalid object accepted')
assert entered==[3]
print('strict integer arguments rejected before effectful function body')
asyncio.run(main())StrictInt 在显式 QuantityArgs 中控制本地 Pydantic 解析,拒绝 bool、字符串和浮点;JSON schema 的 integer 声明本身不等于禁用自动转换。本课固定 SDK 的 function_tool 签名生成路径不能仅靠 StrictInt 注解保证这一行为,因此本答案选择真实 FunctionTool 并在 on_invoke_tool 入口明确校验。
显式 FunctionTool 自己负责错误处理,本例把参数错误传播为 ModelBehaviorError,解析失败不会到达 entered/计算逻辑。使用装饰器时默认 failure_error_function 会把错误转换成供模型读取的文本,设为 None 则传播;这与本例自定义入口是两种不同的错误策略。不能把错误文本当成功业务结果。本题不覆盖真实模型是否正确选择工具。