diff --git a/API.en.md b/API.en.md index 7d1ef36..2150ded 100644 --- a/API.en.md +++ b/API.en.md @@ -37,7 +37,7 @@ Docs: [Overview](README.en.md) / [Architecture](docs/ARCHITECTURE.en.md) / [Depl - OpenAI / Claude / Gemini protocols are now mounted on one shared `chi` router tree assembled in `internal/server/router.go`. - Adapter responsibilities are streamlined to: **request normalization → DeepSeek invocation → protocol-shaped rendering**, reducing legacy split-logic paths. -- Tool-calling semantics are aligned between Go and Node runtime: structured parsing first (JSON/XML/invoke/markup), plus stream-time anti-leak filtering. +- Tool-calling semantics are aligned between Go and Node runtime: parsing is now centered on XML/Markup-family tool syntax (`` / `` / `` / `tool_use` / antml variants), plus stream-time anti-leak filtering. - `Admin API` separates static config from runtime policy: `/admin/config*` for configuration state, `/admin/settings*` for runtime behavior. --- @@ -319,7 +319,12 @@ When `tools` is present, DS2API performs anti-leak handling: } ``` -**Stream**: Once high-confidence toolcall features are matched, DS2API emits `delta.tool_calls` immediately (without waiting for full JSON closure), then keeps sending argument deltas; confirmed raw tool JSON is never forwarded as `delta.content`. +**Stream**: Once high-confidence toolcall features are matched, DS2API emits `delta.tool_calls` immediately (without waiting for full argument closure), then keeps sending argument deltas; confirmed tool-call fragments are not forwarded as `delta.content`. + +Additional notes: + +- The parser currently follows XML/Markup-family tool payloads (``, ``, ``, `tool_use`, antml variants). Standalone JSON `tool_calls` payloads are not treated as executable tool calls by default. +- `tool_calls` shown inside fenced markdown code blocks (for example, ```json ... ```) are treated as examples, not executable calls. --- diff --git a/API.md b/API.md index b87332d..bb06170 100644 --- a/API.md +++ b/API.md @@ -37,7 +37,7 @@ - OpenAI / Claude / Gemini 三套协议已统一挂在同一 `chi` 路由树上,由 `internal/server/router.go` 负责装配。 - 适配器层职责收敛为:**请求归一化 → DeepSeek 调用 → 协议形态渲染**,减少历史版本中“同能力多处实现”的分叉。 -- Tool Calling 的解析策略在 Go 与 Node Runtime 间保持一致:优先结构化解析(JSON/XML/invoke/markup),并在流式场景执行防泄漏筛分。 +- Tool Calling 的解析策略在 Go 与 Node Runtime 间保持一致:当前以 XML/Markup 家族解析为主(含 `` / `` / `` / `tool_use` / antml 变体),并在流式场景执行防泄漏筛分。 - `Admin API` 将配置与运行时策略分开:`/admin/config*` 管静态配置,`/admin/settings*` 管运行时行为。 --- @@ -319,12 +319,12 @@ data: [DONE] } ``` -**流式**:命中高置信特征后立即输出 `delta.tool_calls`(不等待完整 JSON 闭合),并持续发送 arguments 增量;已确认的 toolcall 原始 JSON 不会回流到 `delta.content`。 +**流式**:命中高置信特征后立即输出 `delta.tool_calls`(不等待完整工具参数闭合),并持续发送 arguments 增量;已确认的工具调用片段不会回流到 `delta.content`。 补充说明: - **非代码块上下文**下,工具负载即使与普通文本混合,也会按特征识别并产出可执行 tool call(前后普通文本仍可透传)。 -- 解析器以 XML/Markup 为最高优先级,并兼容 JSON、ANTML、text-kv 等格式输入;最终按客户端协议转译为对应 tool call 结构(OpenAI/Claude/Gemini)。 +- 解析器当前走 XML/Markup 家族(包含 ``、``、``、`tool_use`、antml 风格);纯 JSON `tool_calls` 片段默认不会直接作为可执行调用解析。 - Markdown fenced code block(例如 ```json ... ```)中的 `tool_calls` 仅视为示例文本,不会被执行。 --- diff --git a/README.MD b/README.MD index 31ba76c..105b979 100644 --- a/README.MD +++ b/README.MD @@ -87,7 +87,7 @@ flowchart LR - **统一路由内核**:所有协议入口统一汇聚到 `internal/server/router.go`,并在同一路由树中注册 OpenAI / Claude / Gemini / Admin / WebUI 路由,避免多入口行为漂移。 - **统一执行链路**:Claude / Gemini 入口先经 `internal/translatorcliproxy` 做协议转换,再进入 `openai.ChatCompletions` 统一处理工具调用与流式语义,最后再转换回原协议响应。 - **适配器分层更清晰**:`internal/adapter/{claude,gemini}` 负责入口/出口协议封装,`internal/adapter/openai` 负责核心执行,DeepSeek 侧调用只保留在 OpenAI 内核中。 -- **Tool Calling 双运行时对齐**:Go 侧(`internal/toolcall`)与 Vercel Node 侧(`internal/js/helpers/stream-tool-sieve`)保持一致的解析/防泄漏语义,覆盖 JSON / XML / invoke / text-kv 多风格输入。 +- **Tool Calling 双运行时对齐**:Go 侧(`internal/toolcall`)与 Vercel Node 侧(`internal/js/helpers/stream-tool-sieve`)保持一致的解析/防泄漏语义;当前以 XML/Markup 家族为主(`` / `` / `` / `tool_use` / antml 变体)。 - **配置与运行时设置解耦**:静态配置(`config`)与运行时策略(`settings`)通过 Admin API 分离管理,支持热更新和密码轮换失效旧 JWT。 - **流式能力升级**:`/v1/responses` 与 `/v1/chat/completions` 共享更一致的工具调用增量输出策略,降低不同 SDK 下的行为差异。 - **可观测与可运维增强**:`/healthz`、`/readyz`、`/admin/version`、`/admin/dev/captures` 形成排障闭环,便于发布后验证。 @@ -155,7 +155,7 @@ flowchart LR - `ANTHROPIC_BASE_URL` 推荐直接指向 DS2API 根地址(例如 `http://127.0.0.1:5001`),Claude Code 会请求 `/v1/messages?beta=true`。 - `ANTHROPIC_API_KEY` 需要与 `config.json` 中 `keys` 一致;建议同时保留常规 key 与 `sk-ant-*` 形态 key,兼容不同客户端校验习惯。 - 若系统设置了代理,建议对 DS2API 地址配置 `NO_PROXY=127.0.0.1,localhost,<你的主机IP>`,避免本地回环请求被代理拦截。 -- 如遇“工具调用输出成文本、未执行”问题,请升级到包含 Claude 工具调用多格式解析(JSON/XML/ANTML/invoke)的版本。 +- 如遇“工具调用输出成文本、未执行”问题,请优先检查模型输出是否为受支持的 XML/Markup 工具块(例如 `` / `` / `` / `tool_use`),而不是纯 JSON `tool_calls` 片段。 ### Gemini 接口 @@ -398,7 +398,7 @@ Gemini 路由还可以使用 `x-goog-api-key`,或在没有认证头时使用 ` 当请求中带 `tools` 时,DS2API 会做防泄漏处理与结构化转译: 1. 只在**非代码块上下文**启用执行型 toolcall 识别(代码块示例默认不触发) -2. 解析层以 XML/Markup 为最高优先级,同时兼容 JSON / ANTML / invoke / text-kv,并统一归一到内部工具调用结构 +2. 解析层当前以 XML/Markup 家族为准(`` / `` / `` / `tool_use` / antml 变体);纯 JSON `tool_calls` 片段默认不作为可执行调用解析 3. `responses` 流式严格使用官方 item 生命周期事件(`response.output_item.*`、`response.content_part.*`、`response.function_call_arguments.*`) 4. `responses` 支持并执行 `tool_choice`(`auto`/`none`/`required`/强制函数);`required` 违规时非流式返回 `422`,流式返回 `response.failed` 5. 客户端请求哪种协议,就按该协议返回工具调用(OpenAI/Claude/Gemini 各自原生结构);模型侧优先约束输出规范 XML,再由兼容层转译 diff --git a/README.en.md b/README.en.md index 23c2a94..653ba6d 100644 --- a/README.en.md +++ b/README.en.md @@ -85,7 +85,7 @@ For the full module-by-module architecture and directory responsibilities, see [ - **Unified routing core**: all protocol entries are now centralized through `internal/server/router.go`, with OpenAI / Claude / Gemini / Admin / WebUI routes registered in one tree to avoid multi-entry drift. - **Unified execution chain**: Claude/Gemini entries are translated by `internal/translatorcliproxy`, then executed through `openai.ChatCompletions` for shared tool-calling and stream semantics, then translated back to the client protocol. - **Cleaner adapter boundaries**: `internal/adapter/{claude,gemini}` handles protocol wrappers, while `internal/adapter/openai` remains the execution core; upstream DeepSeek calls are retained only in the OpenAI core. -- **Tool-calling parity across runtimes**: Go (`internal/toolcall`) and Vercel Node (`internal/js/helpers/stream-tool-sieve`) follow aligned parsing/anti-leak semantics across JSON / XML / invoke / text-kv inputs. +- **Tool-calling parity across runtimes**: Go (`internal/toolcall`) and Vercel Node (`internal/js/helpers/stream-tool-sieve`) share aligned parsing/anti-leak semantics, now centered on XML/Markup-family payloads (`` / `` / `` / `tool_use` / antml variants). - **Config/runtime separation**: static config (`config`) and runtime policy (`settings`) are managed independently via Admin APIs, enabling hot updates and password rotation with JWT invalidation. - **Streaming behavior upgrade**: `/v1/responses` and `/v1/chat/completions` now share a more consistent incremental tool-call emission strategy across SDK ecosystems. - **Improved operability**: `/healthz`, `/readyz`, `/admin/version`, and `/admin/dev/captures` form a tighter post-deploy diagnostics loop. @@ -153,7 +153,7 @@ Besides the current primary aliases above, `/anthropic/v1/models` also returns C - Set `ANTHROPIC_BASE_URL` to the DS2API root URL (for example `http://127.0.0.1:5001`). Claude Code sends requests to `/v1/messages?beta=true`. - `ANTHROPIC_API_KEY` must match an entry in `keys` from `config.json`. Keeping both a regular key and an `sk-ant-*` style key improves client compatibility. - If your environment has proxy variables, set `NO_PROXY=127.0.0.1,localhost,` for DS2API to avoid proxy interception of local traffic. -- If tool calls are rendered as plain text and not executed, upgrade to a build that includes multi-format Claude tool-call parsing (JSON/XML/ANTML/invoke). +- If tool calls are rendered as plain text and not executed, first verify the model output uses supported XML/Markup tool blocks (`` / `` / `` / `tool_use`) rather than standalone JSON `tool_calls`. ### Gemini Endpoint @@ -396,7 +396,7 @@ Queue limit = DS2API_ACCOUNT_MAX_QUEUE (default = recommended concurrency) When `tools` is present in the request, DS2API performs anti-leak handling: 1. Toolcall feature matching is enabled only in **non-code-block context** (fenced examples are ignored) -2. The parser prioritizes XML/Markup, while also accepting JSON / ANTML / invoke / text-kv, and normalizes everything into the internal tool-call structure +2. The parser currently targets XML/Markup-family tool syntax (`` / `` / `` / `tool_use` / antml variants); standalone JSON `tool_calls` payloads are not treated as executable calls by default 3. `responses` streaming strictly uses official item lifecycle events (`response.output_item.*`, `response.content_part.*`, `response.function_call_arguments.*`) 4. `responses` supports and enforces `tool_choice` (`auto`/`none`/`required`/forced function); `required` violations return `422` for non-stream and `response.failed` for stream 5. The output protocol follows the client request (OpenAI / Claude / Gemini native shapes); model-side prompting can prefer XML, and the compatibility layer handles the protocol-specific translation diff --git a/docs/ARCHITECTURE.en.md b/docs/ARCHITECTURE.en.md index 5235183..81bb928 100644 --- a/docs/ARCHITECTURE.en.md +++ b/docs/ARCHITECTURE.en.md @@ -116,7 +116,7 @@ flowchart LR - `internal/translatorcliproxy`: structure translation between Claude/Gemini and OpenAI. - `internal/deepseek`: upstream request/session/PoW/SSE handling. - `internal/stream` + `internal/sse`: stream parsing and incremental assembly. -- `internal/toolcall`: JSON/XML/invoke/text-kv tool-call parsing + anti-leak sieve. +- `internal/toolcall`: XML/Markup-family tool-call parsing + anti-leak sieve (`` / `` / `` / `tool_use` / antml variants). - `internal/admin`: config/accounts/vercel sync/version/dev-capture endpoints. - `internal/config`: config loading/validation + runtime settings hot-reload. - `internal/account`: managed account pool, inflight slots, waiting queue. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 619c2e4..b439127 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -116,7 +116,7 @@ flowchart LR - `internal/translatorcliproxy`:Claude/Gemini 与 OpenAI 结构互转。 - `internal/deepseek`:上游请求、会话、PoW、SSE 消费。 - `internal/stream` + `internal/sse`:流式解析与增量处理。 -- `internal/toolcall`:JSON/XML/invoke/text-kv 工具调用解析及防泄漏筛分。 +- `internal/toolcall`:以 XML/Markup 家族为核心的工具调用解析与防泄漏筛分(`` / `` / `` / `tool_use` / antml 变体)。 - `internal/admin`:配置管理、账号管理、Vercel 同步、版本检查、开发抓包。 - `internal/config`:配置加载、校验、运行时 settings 热更新。 - `internal/account`:托管账号池、并发槽位、等待队列。 diff --git a/docs/toolcall-semantics.md b/docs/toolcall-semantics.md index 0c6b99d..889e3ca 100644 --- a/docs/toolcall-semantics.md +++ b/docs/toolcall-semantics.md @@ -1,74 +1,74 @@ # Tool call parsing semantics(Go/Node 统一语义) -本文档描述当前代码中 `ParseToolCallsDetailed` / `parseToolCallsDetailed` 的**实际行为**,用于对齐 Go 与 Node Runtime。 +本文档描述当前代码中工具调用解析链路的**实际行为**(以 `internal/toolcall` 与 `internal/js/helpers/stream-tool-sieve` 为准)。 文档导航:[总览](../README.MD) / [架构说明](./ARCHITECTURE.md) / [测试指南](./TESTING.md) -## 1) 输出结构(当前实现) +## 1) 当前输出结构 -- `calls`:解析得到的工具调用列表(`name` + `input`)。 -- `sawToolCallSyntax`:检测到工具调用语法特征时为 `true`(例如 `tool_calls`、``、``、``、`function.name:`)。 -- `rejectedByPolicy`:当前实现固定为 `false`(预留字段,尚未启用 allow-list 拒绝)。 +`ParseToolCallsDetailed` / `parseToolCallsDetailed` 返回: + +- `calls`:解析出的工具调用列表(`name` + `input`)。 +- `sawToolCallSyntax`:检测到工具调用语法特征时为 `true`。 +- `rejectedByPolicy`:当前实现固定为 `false`(预留字段)。 - `rejectedToolNames`:当前实现固定为空数组(预留字段)。 -> 说明:`filterToolCallsDetailed` 当前仅做结构清洗,不做工具名策略拒绝。 +> 当前 `filterToolCallsDetailed` 仅做结构清洗,不做 allow-list 工具名硬拒绝。 -## 2) 解析管线 +## 2) 解析范围(重点) -1. **示例保护**:若判定为 fenced code block 示例上下文,则跳过执行型解析。 -2. **候选片段构建**:从完整文本中构建候选(原文、围绕 `tool_calls` 的 JSON 片段、首尾大括号切片等)。 -3. **按序尝试解析(命中即停)**: - - 对“明显 JSON 工具载荷候选”(以 `{`/`[` 开头且包含 `tool_calls`/`\"function\"`)先走 JSON 解析,避免 JSON 字符串内偶发 XML 片段误命中; - - 其余候选优先 XML 解析(`` / `` / `` / `tool_use` / `antml:function_call` 等); - - JSON 解析(`{"tool_calls": [...]}`、列表、单对象); - - Markup 解析; - - Text-KV 回退(如 `function.name:` + `function.arguments:`)。 -4. **兜底**:候选全部失败后,再对全文做 XML / Text-KV 回退。 +当前版本的可执行解析以 **XML/Markup 家族**为主: -## 3) XML 能力边界(当前) +- `...` +- `...` +- `...`(含自闭合) +- `...` +- antml 变体(如 `antml:function_call` / `antml:argument`) -当前已支持输入端的“多 XML/标记风格”解析,包括但不限于: +并支持在这些标记块内部解析: -- `......` -- `tool...` -- `...` -- `antml:function_call` / `antml:argument` / `antml:parameters` -- `tool_use` 家族标签 +- JSON 参数字符串 +- 标签参数(`...`) +- key/value 风格子标签 -但**输出端仍统一转换为 OpenAI 兼容 JSON 事件/对象**(`message.tool_calls`、`delta.tool_calls`、`response.function_call_arguments.*`)。 +## 3) 不应再假设的行为 -## 4) 关于“是否可以封装成 XML 再喂给模型” +以下说法在当前实现中已不成立: -结论:**可以做,而且当前解析器已经能兼容 XML 作为输入格式之一**,但代码里并没有 `toolcall.prefer_xml_output` 这个开关。现有可调配置只有: +1. “纯 JSON `tool_calls` 片段会被直接当作可执行工具调用解析”。 +2. “存在 `toolcall.mode` / `toolcall.early_emit_confidence` 等可配置开关可以改变解析策略”。 -- `toolcall.mode`:`feature_match` / `off` -- `toolcall.early_emit_confidence`:`high` / `low` / `off` +当前策略在代码中固定为: -推荐思路仍然是“输入兼容层 + 输出按客户端协议渲染”: +- 特征匹配开启(feature-match on) +- 高置信度早发开启(early emit on) +- policy 拒绝字段保留但未启用 -1. **Prompt 约束层**:如果你要尝试 XML-first,可以在系统提示词里约束模型输出规范 XML tool block(例如 `...`)。 -2. **解析兼容层**:继续在 parser 中同时接受 JSON / XML / ANTML / invoke / text-kv。 -3. **协议归一层**:无论模型输出什么格式,统一落到内部 `ParsedToolCall`。 -4. **对外渲染层**:根据客户端请求协议渲染(OpenAI / Claude / Gemini 各自格式)。 +## 4) 流式与防泄漏语义 -这样可以同时获得: +在流式链路中(OpenAI / Claude / Gemini 统一内核): -- 减少模型端 JSON 转义/引号错误; -- 不破坏现有 SDK / 客户端生态; -- 逐步灰度(按模型、按租户、按请求开关)。 +- 工具调用片段会被优先提取为结构化增量输出; +- 已识别的工具调用原始片段不会作为普通文本再次回流; +- fenced code block 中的示例内容按文本处理,不作为可执行工具调用。 -## 5) 落地建议(低风险迭代) +## 5) 落地建议(按当前实现) -- 继续使用现有的 `toolcall.mode=feature_match` 和 `toolcall.early_emit_confidence=high` 作为默认策略。 -- 如果要试 XML-first,把它放在 prompt 层或上游模板层,不要假设代码里已有专门的 XML 输出开关。 -- 增加观测指标: - - `toolcall_parse_source`(json/xml/markup/textkv); - - `toolcall_parse_success_rate`; - - `toolcall_malformed_rate`; - - `toolcall_repair_rate`。 -- 先在 `responses` 链路灰度,再扩展 `chat.completions`。 +1. Prompt 里优先约束模型输出 XML/Markup 工具块。 +2. 执行器侧继续做工具名白名单与参数 schema 校验(不要依赖 parser 代替安全策略)。 +3. 需要兼容历史“纯 JSON tool_calls”模型输出时,请在上游模板层把输出规范化为 XML/Markup 风格再进入 DS2API。 -## 6) 兼容性提醒 +## 6) 回归验证建议 -- 上游模型若输出混合文本 + XML,仍可能出现“半结构化”噪声,需要依赖现有 sieve 增量消费策略。 -- XML 不等于安全:仍需做 tool 名、参数 schema、执行权限的服务端校验。 +可直接运行: + +```bash +go test -v -run 'TestParseToolCalls|TestRepair' ./internal/toolcall/ +node --test tests/node/stream-tool-sieve.test.js +``` + +重点覆盖: + +- `` / `` / `` / `tool_use` / antml 变体 +- 参数 JSON 修复与解析 +- 流式增量下的工具调用提取与文本防泄漏