NATIVE_FUNCTION_CALLING_DESIGN.md 5.5 KB

原生 function-calling 改造设计(评估建议 ①)

状态:设计 → 分阶段实现 日期:2026-06-17 · 基线 v1.4.2 目标:用各 provider 的结构化 tool_calls 取代 react-over-text 的"发文本求 JSON、正则抠", 从根上消灭"0 执行 / input 裸串 / path vs file_path / 相对路径落错"这一整类 bug (run_541390a1ea49 实锤)。

1. 现状(为什么脆)

react loop: 构造文本 prompt("请输出一个 JSON 工具调用")
        → think.apply(text) → provider.chat(messages) → 返回 **纯 text**
        → _extract_tool_call(text) 正则/JSON 扫描抠 {"tool":...,"input":...}
        → 执行 → 把结果拼成文本 "[工具执行结果]..." 再喂回

provider.chat 只发 {model, messages, temperature, max_tokens}不发 tools,只返回 content。 模型把工具调用当散文手写 → 格式飘 → 解析失败/参数错。

codex 修正(2026-06-17)

  • FC 只治"格式漂移 / JSON 抠错 / 裸串 input / 工具名解析"这一类;"相对路径落错目录" 不在其中——那是 workspace contract + cwd 绑定 + sandbox + 参数 canonicalization 的 工具层硬约束(file_tools._resolve/_sandbox,已部分解决)。别把后者算进 FC 收益。
  • 战略上 react-over-text 降级为 legacy/fallback,不再作为主车道(不是与 FC 平级)。 FC-capable provider 一律走 FC;native harness(claude-code/codex)负责 workspace agentic。

2. 目标架构

FC react: provider.chat_with_tools(messages, tools_schema)
        → 模型走原生 tool_use → 返回 **结构化** {content, tool_calls:[{name,arguments}]}
        → 直接执行 tool_calls(无需解析文本)
        → 结果作 role=tool 消息回灌(标准 function-calling 协议)
  • OpenAI 兼容(qwen/deepseek/openai/moonshot/zhipu):请求加 tools + tool_choice, 响应读 choices[0].message.tool_calls
  • claude-code:已有 native 车道,不动。
  • 不支持 FC 的模型(部分 ollama 旧模型):回退现有 react-over-text 文本解析。

3. 关键决策:新增"FC 增强"而非重写旧 loop

旧 react loop 深耦合文本(state 累积、enforceLoop、give-up、session 重注入都基于文本)。 全量替换风险极高。借鉴 native claude-code 的成功经验(新增干净一路 > 改旧封装),采用 provider 能力 + 配置开关双门控的 FC 路径,旧文本路径保留作回退。

判定:react.nativeToolCalls: true(opt-in,默认 off) provider 支持 FC → 走 FC;否则走现有文本 react。

4. 分阶段实现(降风险、每阶段可独测)

阶段 1(本次):provider 基础层 + 工具 JSON schema(低风险、纯增量)

  • providers/openai_compat_provider.py
    • 新增 chat_with_tools(messages, tools, tool_choice="auto") -> dict,返回 {"content": str|None, "tool_calls": [{"id","name","arguments"(dict)}], "usage": {...}}。 复用现有 HTTP/错误处理(含 _is_transient_net_error 重试、HTTPError)。
    • supports_function_calling() → True(OpenAI 兼容默认支持;ollama 按模型)。
  • fromconfig/compiler.py:新增 _tools_json_schema(cfg) -> list,复用 _generate_tool_schema_docs 的签名内省,产出 OpenAI tools 规格 [{"type":"function","function":{"name","description","parameters":{JSON schema}}}]。 参数类型从 schema 类 __init__ 注解推断(str/int/bool…),缺省→optional。
  • 单测 test_function_calling
    • _tools_json_schema 对 ReadFile/WriteFile/Bash 产出正确 name/required;
    • chat_with_tools 用 mock HTTP 返回带 tool_calls 的响应 → 正确解析成 {name, arguments(dict)};返回 content-only 时 tool_calls=[];arguments 是字符串 JSON 时能 parse。
  • 不动 react loop → 零回归风险。交付一个被测过的可靠积木。

阶段 2(已完成,live 验证):FC react 执行 + 路由

  • _compile_react 内分叉:FC 模式下,think 步用 chat_with_tools(tools=_tools_json_schema(cfg)), 读 tool_calls 直接执行(跳过 _extract_tool_call);结果作 role=tool 消息回灌; 复用现有 Tool 执行 / Guard / on_step / 取消 / enforceLoop。
  • ConversationLam:FC 模式消息序列含 assistant(tool_calls) + tool(result)。
  • 路由门控:react.nativeToolCalls + provider.supports_function_calling()
  • 先在 qwen-plus 上 live 验证:同批任务工具执行成功率/落盘率对比文本路径。

阶段 3(评估②联动):用 eval 量化收益

  • 复用 docs/AGENT_QUALITY_ASSESSMENT.md ② 的 eval 闭环,对比 FC vs 文本路径的 落盘率/达标率/步数/成本,数据驱动决定是否把 FC 设为默认。

5. 风险与缓解

  • 旧 loop 不动(阶段 1)→ 无回归;阶段 2 的 FC 路径 opt-in、可一键退回文本路径。
  • provider 兼容差异:dashscope 的 tools 字段与 OpenAI 基本一致;ollama 视模型。tool_choice 部分端点不认 → 缺省不传、仅传 tools。
  • arguments 可能是字符串(某些端点把 arguments 序列化成 JSON 字符串)→ 解析层兼容 dict/str。
  • 参数 schema 不全:内省拿不到类型时退化为 string,不阻断。

6. 验收

  • 阶段 1:test_function_calling 全绿;chat_with_tools 解析鲁棒。
  • 阶段 2:qwen-plus FC 路径 live 跑通一条多步任务,工具真执行、产物落盘、0 个"格式飘"错。
  • 整体:FC 路径下 run_541390a1ea49 类(0 执行)不再出现。