id: tool-native-fc
module: A
week: 2
title: "工具调用范式②:原生 function-calling(结构化 tool_calls)"

concept:
  textbook: |
    现代 LLM API(OpenAI / Anthropic / 兼容协议)原生支持 function calling:
    把可用工具以 JSON Schema 形式声明给模型,模型在需要时返回一个**结构化**的
    tool_calls 对象(工具名 + 已按 schema 校验的参数),而不是一段自由文本。

    平台执行完工具后,以 role=tool 的消息把结果回灌,继续下一轮。整个过程里
    "调用"是 API 协议的一等公民,参数有结构、可校验,不存在"解析模型散文"这一步。
  key_point: >
    原生 FC 把工具调用从"文本 + 正则"提升为"结构化 + schema 校验",从根上消除了
    格式飘移与 path/file_path 不一致。它应当作主车道,react-over-text 退为 fallback。

code:
  - file: lambdagent/src/lambdagent/fromconfig/compiler.py
    symbol: _run_fc_loop
    lines: "778-878"
    note: >
      FC 版 ReAct 循环:chat_with_tools(messages, tools) → 直接执行返回的 tool_calls
      → 以 role=tool 回灌。复用 _timeout_call 与 StepEvent,带 enforceLoop 计数。
  - file: lambdagent/src/lambdagent/fromconfig/compiler.py
    symbol: _tools_json_schema
    lines: "736-778"
    note: >
      从工具 schema 类的 __init__ 注解生成 OpenAI tools 规格。坑:from __future__
      import annotations 让注解变字符串,需 _json_type_of 按名兜底(int→integer)。

formal:
  term: "Loop(chat_with_tools ▷ exec_tool_calls, cond, max_steps)"
  effect: "IO[tool]"
  note: >
    相比范式①,这里**没有 Partial**:tool_calls 由 API 按 schema 产出并校验,
    参数类型在调用边界即被约束,解析失败这一失效模式在类型层被消除。
    这是"形式化提前拒绝"的具体兑现:坏调用编译/校验期即挡下,而非运行期才暴露。

live_demo:
  run: ""
  highlight: >
    对照实验在「双车道对比台」(机制③,evals/bench_providers.py)录制:同一 golden
    任务并排跑范式①②③,出"执行次数 / 落盘次数 / 格式飘移率 / token / cost"对照表。
    已知 live 结果:qwen-plus 走 FC 真返回结构化 tool_call,file_path 不飘。

related:
  - tool-react-over-text
  - tool-claude-code-native

takeaway: |
  与范式①并排看,核心差异是一个 effect 标注:Partial 的有无。
  学生应能指出:范式①的失效来自"模型写错文本",范式②把这条路堵死在 API 协议层;
  但要强调 codex 的修正——FC 只治格式/解析类问题,"路径落错(写到别处)"是工具层
  (_resolve / _sandbox)的事,见范式相关的沙箱卡(week 8)。
