前面已经把工具契约、循环、审批和 MCP 分开解释。现在让它们使用同一套订单函数,观察代码从本地调用走到跨进程调用时,究竟哪些地方改变。
本篇包含固定轨迹、真实 SDK 调用与可选真实模型接入。教学订单和退款始终是模拟数据;本次修订没有执行这些程序、安装依赖或调用模型,文中结果均是代码预期。
先准备最小文件集合
本地工具部分使用 tool_contracts.py下载 和 tool_loop.py下载,只需要 Python 标准库。文件放在同一目录,入口才能导入同目录模块。
# 供读者运行的命令;固定轨迹不产生真实模型请求。
python tool_loop.py
轨迹先调用 get_order,再调用 get_refund_policy,最后返回作者预置的条件性回答。你可以观察每条工具结果对应哪个调用 ID,以及参数错误怎样转换成错误数据。它没有根据结果自主规划,不能用它评估模型能力。
让一个函数承担业务规则
dispatch() 只接受明确注册的两个只读工具,并调用 validate_order_args() 拒绝未知字段和非法订单格式。get_order() 再检查固定教学主体是否拥有该订单,返回必要业务字段。
格式校验与归属校验都在普通业务模块里,因此通过本地运行时或 MCP 调用,规则仍然存在。Server 装饰器负责暴露能力,不应该让业务函数只在某一种入口下才检查权限。
固定 DEMO_USER 是单用户教学配置,不是认证实现。生产换成认证中间件提供的主体,并在数据源执行授权范围;不能直接把这个 Server 开到公网就宣称多租户安全。
再观察写操作的审批与重试
approval_runtime.py下载 用 Draft、Approval、Ledger 分别保存意图、批准和模拟回执。金额以分表达,审批严格接受布尔值,并绑定主体、操作、参数与版本。
from approval_runtime import Draft, Ledger, approve
# 这是一个新的教学操作;重复传输同一操作时保留 operation_id。
draft = Draft("shop-a", "user-417", "refund-op-9", "A1042", 10000)
approval = approve(draft, {"approved": True}, now=0)
ledger = Ledger()
# 两个 True 明确模拟业务已经核验且当前主体获准,不来自模型自行判断。
receipt = ledger.submit(draft, approval, now=1, authorized=True, eligible=True)
print(receipt)
同一操作再次提交返回已有模拟结果;金额改变、审批过期或权限拒绝会失败。failure_scenarios.py下载 进一步模拟成功回执丢失,再通过操作 ID 查询回来。
这里没有数据库事务,也没有真实支付。它证明的是代码表达了哪些条件,不是已经建立端到端 exactly-once。原 LangGraph 示例保留为 safe_tool_graph.py下载,让读者看到暂停和恢复如何调用同一审批模块。
把只读能力放进 MCP Server
mcp_server.py下载 注册两个工具、一个政策资源与一个解释模板。工具内部转到相同业务函数,已知失败转为 ToolError,Server 启动时使用 stdio。
from mcp.server import MCPServer
# 这个独立小片段展示资源注册;完整订单 Server 见配套文件。
mcp = MCPServer("policy-demo")
@mcp.resource("policy://refund")
def refund_policy() -> str:
"""教学政策资源;不是对任何现实订单的承诺。"""
return "三十天内,经核实的质量问题退货由商家承担运费。"
需要安装的 MCP 直接依赖固定为 2.1.1,放在单独的 mcp-requirements.txt下载。它不是全部传递依赖和哈希的完整锁文件;原包含 LangGraph 的 requirements.txt 继续用于原框架入口。
用两个 Client 模式观察不同边界
mcp_client_demo.py下载 先列出工具,再调用订单工具、读取政策资源并获取模板。工具列表会处理分页,避免把第一页当成全部能力。
# 在独立虚拟环境中安装 MCP 依赖;本次没有执行这些命令。
python -m pip install -r mcp-requirements.txt
# 内存模式直接连接对象,观察业务分发和结果形状。
python mcp_client_demo.py --mode memory
# stdio 模式用当前解释器启动同目录 Server,发生真实进程间协议通信。
python mcp_client_demo.py --mode stdio
如果内存模式正常而 stdio 失败,应看解释器路径、进程启动、依赖和 stdout 污染,而不是立刻修改工具描述。stdio 连接本身不需要模型 API Key。
旧 test_mcp_server.py 保留下载入口,但转到内存演示并明确它不是完整测试套件。没有把没有断言的打印脚本继续描述为已验证全部行为。
接入真实模型之后发生什么
claude_tool_loop.py下载 使用标准库发送 Messages API 请求,型号来自 TOOL_MODEL,凭据来自 ANTHROPIC_API_KEY。工具仍然只查询教学数据。
它读取 tool_use 内容块,将名称和 input 交给执行器,保留原 assistant 内容,再用匹配 tool_use_id 的 tool_result 回填。它检查停止原因、轮数、调用次数和任务截止时间,不把截断回答当成成功终止。
模型是否会按预期选择工具、输出怎样的答案,需要真实运行才知道。该文件不包含虚构模型运行日志,也不保证使用任意模型名称都支持同样工具能力。消息格式应结合所选服务文档维护。
这个客户端选择非流式路径以便看清完整循环。流式参数的缓冲机制另在 streamed_arguments.py下载 中用人工事件解释,不能将其教学事件当成供应商原始流格式。
把执行器换成 MCP,模型循环保持什么
mcp_model_bridge.py下载 复用同一模型循环,只将本地执行器替换为 MCP Client 调用。它启动 stdio Server,发现工具,再筛选两个预先允许的只读能力。
工具定义从 SDK 的 input_schema 转成模型所需字段。本例的 Schema 简单且兼容;任意第三方复杂 Schema 不能照抄后宣称所有模型都支持。
执行时,Host 再检查参数,Server 再检查业务范围。MCP 工具错误保留错误标志,成功结果要求是预期结构化对象。模型调用 ID 仍用于回填,MCP 请求关联由 Client 管理,两者没有混成业务幂等键。
运行这个桥接入口会发生真实模型网络请求和 stdio 通信,因此与前面的标准库模拟分开提供。它没有暴露退款提交工具,写操作机制在独立审批示例中观察。
沿固定版本源码理解注册
源码导读使用 Python SDK v2.1.1,不把未来 latest 文档和固定源码混成同一个版本。
从 server.py 的 tool 装饰器进入,工具注册交给管理器。list_tools() 再把内部记录转换成对外工具定义,而不是把 Python 函数对象发送给模型。
在 tools/base.py 中,Tool.from_function() 收集函数名、说明和参数元数据,构造参数 Schema,识别异步函数以及需要从上下文注入的参数。
因此函数类型标注确实影响模型看到的契约,但没有让 SDK 自动理解“当前用户只能查自己的订单”。那部分仍然在业务代码里。
再沿源码理解调用与返回
tool_manager.py 根据名称寻找已注册工具,再交给工具对象执行。不存在的名称会失败,不是动态执行一段模型输出代码。
工具执行处理参数与结果转换,再由 Server 形成协议结果。Client 则负责连接方式、请求与结果对象;该版本对内存 Server 可以直接分发,对 stdio 则建立传输。Client 源码
配套 sdk_inspect.py下载 打印当前安装包中相关函数的文件位置、工具 Schema 和一次直接 Server 结果,方便确认正在阅读哪个环境。它没有遍历或验证所有 SDK 内部路径。
哪些能力还需要生产接入
模型客户端没有完整的流式、多模态和所有停止状态恢复;内存账本没有持久化与跨进程原子性;Server 没有远程 OAuth;固定教学身份不是用户认证。生产设计篇解释这些接口应由谁补齐。
运行记录也需要分层:固定轨迹是否按规则分发、MCP 是否连接、模型是否正确选择、业务动作是否符合预期,不能用一个“示例成功”覆盖全部问题。
通过同一套业务函数观察这几条路径,读者就能理解:工具契约描述能力,运行时执行能力,MCP 连接能力,模型使用返回信息继续任务。每一层都可替换,但对应的责任不能消失。
继续阅读
上一篇:生产中怎样设计工具平台:注册、策略、执行与连接管理。
下载文件、依赖与运行边界见配套指南下载。