LLM 不会直接触及你的代码——它们只是向你提交请求,然后由你来执行函数。 这一简单事实打破了“模型会神奇地调用我的 Python 例程”这一迷思,并迫使开发者重新思考调试和安全性。

分发循环(Dispatch Loop)分步详解

当语言模型(LLM)需要使用工具时,它会遵循一个确定性的序列:

  1. 规划 (Planning) – 模型决定需要执行某个操作(例如,“退款”)。
  2. 生成请求 (Generating a request) – 它输出结构化文本(通常是 JSON),其中包含工具名称和参数。
  3. 解析 (Parsing) – 你的应用程序或支持框架读取该文本。
  4. 匹配 (Matching) – 框架在你暴露的真实函数注册表中查找该名称。
  5. 验证 (Validating) – 检查参数是否符合函数的 schema,并确认调用者是否已获得授权。
  6. 执行 (Executing) – 匹配到的函数在你的环境中运行,执行具体工作。
  7. 返回 (Returning) – 结果被封装并发送回模型,以便进行进一步推理。

你可以将 LLM 视为规划者,将框架视为分发器,而将函数视为实际处理数据或资金的工作人员。

为什么“魔法”迷思会持续存在

大多数开发者看到一行看起来像函数调用的模型输出,就会假设模型亲自执行了该操作。供应商文档中的“tool calling”一词听起来像是模型在直接调用代码。

实际上,模型只是生成文本描述一次调用。真正的重活是由你的流程完成的——包括查找、类型检查、权限强制执行和错误处理。

隐藏底层细节的框架

PydanticAILangChain 这样的库对循环进行了抽象,让你能够专注于业务逻辑。它们会自动执行以下操作:

  • 根据 schema 验证参数(例如,使用 Pydantic model)。
  • 强制执行权限,确保用户有权触发该工具。
  • 失败时重试,当工具返回错误时,将流程循环回模型。
  • 防止失控循环,限制连续的工具调用次数。
  • 维护对话状态,将工具结果缝合进对话中。

即便有了这些辅助工具,模式依然保持不变:模型从未执行过代码。

供应商提供的原生工具调用支持

一些供应商提供了“原生”工具调用接口,标准化了工具定义和请求格式。这简化了集成过程,但并没有消除分发步骤。你仍然需要编写(或导入)实际运行所请求操作的代码。

重新定义问题,让调试变得更容易

不要责怪“困惑的 Agent”,而要说问题是“模型响应中不包含任何工具调用”。这种区别至关重要:

  • 无工具调用 (No tool call) – 模型直接回答了问题,或者未能生成格式正确的请求。
  • 请求格式错误 (Malformed request) – JSON 语法错误或缺少必要字段,导致分发器拒绝执行。
  • 验证失败 (Validation failure) – 参数与 schema 不匹配,在执行前触发错误。

对失败进行分类,可以让你记录循环的每个阶段,并精准定位问题出在哪里。

构建可靠流水线的实用技巧

  • 将模型输出视为不可信输入。 在调用任何具有副作用的代码之前,通过确定性的验证来处理每个请求。
  • 记录原始请求以及每个验证步骤的结果。这在出现问题时可以创建一个可回溯的记录。
  • 为连续工具调用设置明确限制; 失控的循环可能会耗尽资源或触发速率限制。
  • 将每个函数封装在 try/except 块中,并返回模型可以理解的结构化错误对象,从而提示重试或优雅降级。
  • 将权限检查与业务逻辑分离。 在函数运行前验证调用者的权限,特别是对于像“删除用户”这样的高权限操作。
  • 使用 schema 驱动的定义(例如 Pydantic models),以便框架可以自动生成模型必须遵循的 JSON schema。

后续关注点

随着供应商不断完善原生工具调用 API,可以预见请求格式的契约会更加严密,错误代码也会更加丰富。这些变化将使验证变得更容易,并允许开发者构建更严格的安全屏障。请密切关注库的更新——许多库正在为最新的供应商功能添加内置支持。

核心总结

LLM 是一个高级的文本生成器,而非执行器。你的代码始终是执行操作的唯一权威,而你构建(或导入)的分发器则是负责验证、授权并运行这些操作的守门人。重新定义工作流可以消除“魔法”迷思,使调试更加精准,并强化每个生产系统所需的安全规范。