# 原生 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 执行）不再出现。
