资讯详情

【Python智能体开发实战:RAG、工具调用与多智能体协作】用Python定义第一个Agent工具:把库存查询函数变成明确的接口契约

📅 2026/10/4 8:14:25 | 华诺云谱 👁 阅读
【Python智能体开发实战:RAG、工具调用与多智能体协作】用Python定义第一个Agent工具:把库存查询函数变成明确的接口契约
用Python定义第一个Agent工具把库存查询函数变成明确的接口契约具体问题与完成目标你正在为一个内部工具编写 Agent 能力。假设场景是这样的运营团队希望 AI 助手能回答“商品 A 还有多少库存”这类问题。你手里已经有一个 Python 函数query_stock(sku)它在本地运行良好。但当你把它注册给一个 LLM Agent 时问题出现了。模型要么不用这个工具要么在调用时传入了你根本没有定义的参数比如product_name、warehouse_id要么把返回值当成了自然语言来“理解”而不是“引用”。根本原因不在于模型而在于你的函数缺少一份明确的接口契约——它告诉模型叫什么名字、用来做什么、需要什么参数、每个参数是什么含义、返回什么、什么情况下不应该调用。完成本文后你将能够用 Python 标准库typing.Protocol定义一个 Agent 工具的最小接口契约实现一个可被 LLM 正确调用的库存查询工具用三个可复现的测试场景验证工具契约是否生效。前置条件与适用环境读者假定掌握 Python 基础语法了解函数定义和类型注解的基本写法。适用环境Python 3.9typing.Protocol在 3.8 引入3.9 起稳定用于生产代码仅依赖标准库。本文示例在 Python 3.11 下完成静态语法检查未连接任何外部模型服务。案例数据虚构的库存数据包含 4 个 SKU覆盖正常、边界和失败三种情况。文件清单文件用途stock_tool.py库存工具的核心实现包含契约定义和查询函数test_stock_tool.py验证脚本覆盖三个验收场景必要原理为什么需要“接口契约”LLM Agent 的工具调用机制决定了模型不读你的源码只读你提供的工具描述。OpenAI 的函数调用文档明确指出函数名称、参数描述和指令应该“清晰且详细”目的是让模型理解“何时使用以及何时不使用”每个函数。换句话说你的 Python 函数本身对模型是不可见的。模型看到的是你提供的 JSON Schema函数名、描述、参数列表及其类型和说明。如果你只给出一个光秃秃的query_stock模型只能靠函数名猜测——然后猜错。typing.Protocol在本文中的作用是让你用 Python 类型系统从源头定义这份契约。它不是你直接递给模型的 JSON Schema而是你生成那份 Schema 的“单一事实来源”。当你用 Protocol 写清楚参数和返回类型后可以用代码提取这些信息生成模型能读懂的描述。这与 OpenAI 的“实习生测试”原则一致如果你把函数定义给一个实习生他能否仅凭这些信息正确调用如果不能缺失的信息就应该补进契约里。完整实现第一步定义协议契约# stock_tool.py库存查询 Agent 工具的接口契约与实现。fromtypingimportProtocol,runtime_checkablefromdataclassesimportdataclassdataclass(frozenTrue)classStockResult:库存查询结果。sku:strquantity:intunit:strwarehouse:strfound:boolruntime_checkableclassStockQueryTool(Protocol):库存查询工具的接口契约。 用途根据 SKU 查询当前可用库存数量。 何时使用当用户询问某个具体商品SKU的库存数量时调用。 何时不使用当用户询问商品价格、订单状态或模糊名称时不调用本工具。 defquery_stock(self,sku:str)-StockResult:查询指定 SKU 的库存。 Args: sku: 商品唯一标识格式为 XXX-数字例如 A-1001。 Returns: StockResult: 包含 sku、quantity、unit、warehouse、found 字段。 foundFalse 表示该 SKU 不存在于库存系统中。 Raises: ValueError: 当 sku 格式不合法不匹配 XXX-NNNN 模式时抛出。 ...关键设计决策runtime_checkable允许用isinstance()在运行时检查某个对象是否满足这个契约。这在我们后续验证实现是否正确时有用。契约文本本身包含“何时使用”和“何时不使用”OpenAI 的指南建议在系统提示或函数描述中明确说明使用条件。我把这部分直接写进了 Protocol 的文档字符串里因为它就是模型需要知道的全部信息。StockResult用 dataclass 而不是裸 dict模型需要理解返回值结构。一个明确定义的数据类比{qty: 100}更不容易被误解。而且 dataclass 的字段名和类型可以自动生成返回值的描述。第二步实现工具# stock_tool.py续# 虚构的库存数据库_STOCK_DB:dict[str,tuple[int,str,str]]{A-1001:(50,件,华东仓),B-2002:(0,件,华南仓),C-3003:(120,箱,华北仓),# D-4004 故意不添加用于测试“不存在”的情况}importre _SKU_PATTERNre.compile(r^[A-Z]-\d{4}$)classInventoryTool:StockQueryTool 契约的具体实现。defquery_stock(self,sku:str)-StockResult:# 参数校验格式不合法时明确拒绝而不是返回模糊结果ifnot_SKU_PATTERN.match(sku):raiseValueError(fSKU 格式不合法:{sku!r}应为 A-1001 格式大写字母-四位数字)entry_STOCK_DB.get(sku)ifentryisNone:returnStockResult(skusku,quantity0,unit,warehouse,foundFalse)quantity,unit,warehouseentryreturnStockResult(skusku,quantityquantity,unitunit,warehousewarehouse,foundTrue)defget_tool_contract()-dict:生成给 LLM 看的工具描述模拟 OpenAI function 格式。return{type:function,name:query_stock,description:(根据 SKU 查询当前可用库存数量。当用户询问某个具体商品SKU的库存时使用。当用户询问价格、订单或模糊名称时不要使用。),parameters:{type:object,properties:{sku:{type:string,description:(商品唯一标识格式为 XXX-数字例如 A-1001。大小写敏感。),}},required:[sku],},}要点说明格式校验前置如果 SKU 格式不对直接抛ValueError而不是返回foundFalse。这给了模型一个清晰的信号它不是“没找到”而是“用错了工具”。OpenAI 的文档建议用枚举和结构化类型来防止无效调用。虽然这里没法用枚举但正则校验达到了类似效果。“不存在”和“错误”区分开foundFalse表示 SKU 格式合法但系统里没有这个商品ValueError表示 SKU 格式本身就不合法。这两种情况对 Agent 的后续行为有不同含义。get_tool_contract()函数这是“契约”的对外呈现。它把 Protocol 中的信息翻译成模型能读的 JSON Schema。在真实 Agent 中这个函数就是你的工具注册逻辑。第三步验证脚本# test_stock_tool.py验证库存工具契约是否生效。fromstock_toolimportInventoryTool,StockQueryTooldeftest_contract_satisfied():静态契约检查实现类是否满足 Protocol。toolInventoryTool()assertisinstance(tool,StockQueryTool),(InventoryTool 未满足 StockQueryTool 契约)print(PASS: 契约满足检查通过)deftest_normal_query():正常场景查询存在的 SKU。toolInventoryTool()resulttool.query_stock(A-1001)assertresult.foundisTrueassertresult.quantity50assertresult.unit件assertresult.warehouse华东仓print(fPASS: 正常查询 A-1001 →{result.quantity}{result.unit})deftest_boundary_zero_stock():边界场景库存为 0 但 SKU 存在。toolInventoryTool()resulttool.query_stock(B-2002)assertresult.foundisTrueassertresult.quantity0print(PASS: 边界查询 B-2002 → 库存 0foundTrue)deftest_not_found():边界场景SKU 格式合法但不存在。toolInventoryTool()resulttool.query_stock(Z-9999)assertresult.foundisFalseassertresult.quantity0print(PASS: 不存在 SKU Z-9999 → foundFalse)deftest_invalid_format_failure():失败场景SKU 格式不合法应抛 ValueError。toolInventoryTool()try:tool.query_stock(invalid-sku)raiseAssertionError(应该抛出 ValueError 但没有)exceptValueErrorase:assert格式不合法instr(e)print(fPASS: 非法格式被拒绝 →{e})if__name____main__:test_contract_satisfied()test_normal_query()test_boundary_zero_stock()test_not_found()test_invalid_format_failure()print(\n全部验证通过。)运行方式与中间结果在隔离目录中执行# 将上述两个文件放在同一目录下python test_stock_tool.py预期输出基于给定数据可复现PASS: 契约满足检查通过 PASS: 正常查询 A-1001 → 50件 PASS: 边界查询 B-2002 → 库存 0foundTrue PASS: 不存在 SKU Z-9999 → foundFalse PASS: 非法格式被拒绝 → SKU 格式不合法: invalid-sku应为 A-1001 格式大写字母-四位数字 全部验证通过。可操作的验收与测试测试目的输入/操作预期结果判定方法契约实现正确性isinstance(InventoryTool(), StockQueryTool)True断言通过正常库存查询query_stock(A-1001)quantity50, foundTrue检查返回字段值零库存边界query_stock(B-2002)quantity0, foundTrue区分“存在但为零”与“不存在”SKU 不存在query_stock(Z-9999)foundFalse, quantity0found 标志为 False格式非法失败query_stock(invalid-sku)抛出 ValueError捕获异常并检查消息验收的核心原则模型不需要“理解”库存逻辑它只需要能正确调用。如果你的契约能让一个不了解业务的人或模型正确调用验收就通过了。常见故障的定位方法问题 1模型调用了工具但参数名不对如传了product_id而不是sku定位检查get_tool_contract()返回的 JSON Schema 中parameters.properties的键名是否与 Protocol 方法签名一致。模型只会按你给的 Schema 传参。如果 Schema 写的是sku模型传product_id那是模型没有遵循 Schema——此时需要在 description 中更明确地说明参数名称或者考虑是否需要别名机制。问题 2模型应该调用工具时没有调用定位在真实 Agent 中这是系统提示词的问题。OpenAI 建议“通常明确告诉模型该做什么”。你需要用自然语言告诉模型“当用户询问库存时调用query_stock传入 SKU 参数”。工具契约本身只定义了“怎么调用”没有定义“什么时候调用”——后者是系统提示词的职责。问题 3返回值被模型“解释”而不是“引用”这是 Agent 工作流的常见问题不是契约问题。模型看到{quantity: 50}后可能说“大约有五十件左右”而不是“精确为 50 件”。解决方式是在系统提示中要求“直接引用工具返回的数值不要估算”。适用边界本文聚焦的是工具接口契约不是完整的 Agent 工作流。未覆盖的部分包括真实 LLM 集成get_tool_contract()返回的字典格式模拟了 OpenAI 的 function calling 格式但本文没有连接任何真实 API。将契约接入真实模型时需要按你使用的 SDK如openai、langchain等的文档调整格式。多工具场景当你有 20 个以上工具时契约管理会变得复杂。OpenAI 建议初始暴露的工具数量“少于 20 个”。如果工具很多需要考虑工具筛选或语义检索机制这超出了本文范围。持久化与并发示例中的_STOCK_DB是内存字典不涉及数据库连接、并发写入或事务。生产环境中的库存查询需要考虑这些。本文的技术依据来自 Pythontyping官方文档中关于Protocol和runtime_checkable的说明以及 OpenAI 官方函数调用指南中关于函数定义最佳实践的描述。验证状态已完成Python 3.11 环境下对stock_tool.py和test_stock_tool.py的语法检查python -m py_compile。五个测试场景的静态逻辑复核契约检查、正常查询、零库存边界、不存在 SKU、非法格式失败。Protocol 签名与实现类方法签名的一致性核对。未执行实际运行test_stock_tool.py本文撰写时未在本地环境执行预期输出基于代码逻辑推导。连接真实 LLM 服务验证模型是否能正确理解契约并调用工具。多工具并发注册或工具数量超过 20 个时的行为验证。参考资料Python Documentation,typing — Support for type hints, https://docs.python.org/zh-cn/3.9/library/typing.html 核验日期2026-10-03OpenAI Developers,Function calling, https://developers.openai.com/api/docs/guides/function-calling 核验日期2026-10-03OpenAI Developers,Using tools, https://developers.openai.com/api/docs/guides/tools 核验日期2026-10-03
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑