Conversation
|
是后台自动的还是像CC switch 那样 |
|
Thank you so much for your attention and contribution! We will arrange an internal review for this PR shortly, and all feedback will be shared right here in the discussion. |
|
后台自动的。配置后,请求进来时代理会自动在 Anthropic / Chat / Responses 之间翻译,客户端和上游都不需要感知。
和 ccswitch 不冲突:ccswitch 负责选接哪个上游,转换层负责翻译协议。
|
- 三层组合转换(Responses↔Chat↔Anthropic),usage 保真 - thinking/reasoning 双向映射 + signature 保真(preserveSignature 开关) - tool_choice / stop / parallel_tool_calls / finish_reason 完整映射 - 多模态 tool_result、legacy functions/function_call、developer 角色 - 统一 SSE 解析器(LF/CRLF/紧凑格式/多 data 行/跨 chunk) - 流式错误透传对称;无对位参数 onDropped 显式观测 - 性能打点 + /metrics(Prometheus):转换 p50/p95/p99、上游缓存命中率 - 测试 64/64(协议 39、组合 10、解析器 8、模糊 4、统计 4)+ 映射矩阵文档
- Anthropic→Chat SSE:tool_calls 序号按 tool_use 出现顺序重映射为 0..n-1, 不再泄漏 Anthropic content block index(thinking/text 占位会导致跳号) - recordCacheUsage 按输入方字段语义读取(chat cached_tokens / anthropic cache_read_input_tokens),缓存命中率指标不再恒 0 - chat tool_choice:none → 移除全部 tools(Anthropic 无 none 语义, 保留 tools 会反转成 auto) - 回归:+7 用例(流式双场景、none 语义、cache 统计双向)
上游只发 finish_reason/[DONE] 或直接 EOF 时,finish() 现在先 ensureStarted() 再输出 message_delta/message_stop,避免缺头的非法 Anthropic SSE 流。 回归:+1 用例(空内容流仍以 message_start 开头)
- createChatSseToResponses 改为按 chat tool index 维护独立输出项,修复并行工具调用参数串流(前一把空参数、后一把收走全部碎片) - createChatSseToAnthropicSse 支持 thinking: map|strip(默认 strip,与 chatJsonToAnthropicJson 一致),signature 先于 thinking 输出,开 text/tool 前先关 thinking 块 - system/developer/assistant content 数组统一提取文本,不再 JSON.stringify 当正文 - protocol-stats 采样改环形缓冲,避免每次 shift O(n) - 新增并行工具、thinking 默认 strip、内容数组回归用例(85 -> 89)
… user 合并,并同步上游类型适配(tsc 0)
de64160 to
9e68053
Compare
- server.ts:会话决策指标改挂 /session/metrics,避免与协议 PR 的 /metrics 同路由遮蔽;server 新增 import 移到独立锚点并用别名引入 admin auth, 与 TencentCloud#1226 顺序合入时 server.ts 零冲突 - session/codebuddy/init.ts:resetEpoch 字段换位放置,resetFlow/resetEpoch 两边接口补充不再互相冲突 - config/types:autoConversationId 补 deterministic/deterministicBucketMinutes 的 yaml 解析与运行时字段(此前只有校验、配置不生效),桶宽校验保留 - docs/session-policy.md:测试数 108→76,删除对不存在 optimizations.test.ts 的引用,改列本 PR 实际用例文件
…+ 矩阵边界补全 - responsesBodyToChat / chatBodyToResponses 双向映射 json_object / json_schema(name 缺省补 response) - 矩阵:结构化输出行、reasoning 方向说明(Chat/Responses→Anthropic 默认 strip 的原因)、 多模态仅图片 / 辅助端点同协议透传 / Responses 会话状态端点边界 - 新增 4 个结构化输出回归用例(93 -> 97)
…反向映射锁定 - responsesBodyToChat / chatBodyToResponses:json_schema 的 description 原样透传 (官方 Responses/Chat 均支持),name 缺省补 response 不变 - chatBodyToResponses:json_object → text.format 保留(Responses text.format 官方 支持 legacy JSON mode),新增反向回归用例锁定语义 - 矩阵:Chat→Anthropic 行改为显式丢弃(无对位顶层字段),补 Anthropic 侧边界说明 - 回归 97 -> 98
4ca26c3 to
c44ad8a
Compare
Cross the two test suites that never met: the real InjectionPipeline output is now fed into the real conversion layer, locking four invariants per direction: injected text survives exactly once, lands in the cacheable prefix slot (Anthropic system / Chat messages[0] / Responses instructions), never leaks cache_control upstream, and is byte-deterministic across runs. Also mirrors the codex/workbuddy synthetic-body assembly (extract messages[0] -> re-attach to the real Responses request) and locks its known gap with it.fails: blocks injected at user.* are silently dropped there today.
codex / workbuddy handler 的合成体只抽 messages[0](system),任何 user.* 注入点(如 L1 召回的 point=user.before)都落在占位 user 消息上被静默丢弃 —— 请求照样 200,只是记忆没了。 - 新增 common/synthetic-injection.ts:splitSyntheticInjection(按 role 抽 system / user 增量)+ prependToLastUserMessage(前插到本轮最后一个 user message,形态不符时原样返回) - codexHandler / workbuddyHandler 的抽取与贴回改走这两个纯函数(handler 侧只多两行) - 回归用例由 it.fails 转正:user.before 现在断言注入恰好存活一次、落在 user 消息而非 developer 段;另加贴回位置的直接单测(多轮取最后一条 / 无 user message 原样返回)
与 TencentCloud#1253 同源的同一处修复(chat-anthropic-compat.ts 在两支逐字节一致):相邻同角色合并(assistant 侧 thinking 前移);tool_result 必须紧邻对应 tool_use,悬空结果降级为普通 user 文本并计入 /metrics,避免上游 400 连记忆注入一起失败。新增 11 例回归,并修正 protocol-conformance 中一条构造了悬空 tool 消息的多模态用例。
上游 Anthropic 对 messages 有四条硬要求:首条是 user、角色严格交替、 tool_use 与 tool_result 相邻成对、content 不为空。客户端裁剪历史、 只回传半边工具结果、发空 content 时都会破坏它们,上游 400 会把整轮请求 连同记忆注入一起打回(和上一轮修的悬空 tool_result 是同一类问题)。 - chatToAnthropic 收尾新增 normalizeAnthropicMessages:丢空消息 → 合并同角色 → 去掉开头的 assistant 残段 → 工具配对(配不上的 tool_use 摘掉、配不上的 tool_result 降级为文本),整段没有 user 时用最后一段 assistant 文本兜底; 丢弃项计入 /metrics 丢参计数(leading_assistant / orphan_tool_use / orphan_tool_result / empty_message / missing_user_message)。 - 只有 user 轮次插入才算打断工具配对,assistant 的纯文本片段会与前一条合并。 - chat-anthropic-role-rules 新增 8 例覆盖上述形状;protocol-conformance / responses-anthropic-compat 中 5 处「assistant 开头」的用例脚手架补上首条 user(断言对象不变,只把请求形状改成上游能收的)。 验证:tsc 0;转换层 vitest 129/129,协议接线分支 150/150。
文档里的用例数跟代码对不上(109→129、131→150、probe 12→17、接缝 7→8, 还漏了 chat-anthropic-role-rules 整节),而且改完没人会发现。这次一并做两件事: - docs/protocol-conversion-matrix.md:计数按分支改成实测值(转换层 10 文件 / 129 用例;协议接线分支 12 文件 / 150 用例;两支合并 13 文件 / 158 用例), 补齐 role-rules(19) 与接缝用例(8) 两节说明,去掉 126 这个已失效的合计; - scripts/qa/check-doc-claims.mjs:跑一次 vitest(json) 拿实测用例数,再扫 docs/**.md 里「测试文件名 + 数字」的声明逐条比对,不在本分支的测试文件跳过。 CI 增加一步执行它,文档再写错数字会直接把流水线打红(已用 61→62 实测能报错)。
上游 400 的三类形状缺口本轮复审又补了一处上游 400 缺口。 Anthropic 对
修法: 另外文档计数按实测更正为 129/129,并新增 验证: |
§10 参考里引用了两个不存在的东西:commit c9152ee 与 docs/design/2026-08-28-verification-handbook.md。改为指向可达的位置: 本 PR 分支与 PR TencentCloud#1251、本文 §9 与 docs/session-policy.md 的 §3/§5; 并把跨 PR 的 docs/protocol-conversion-matrix.md 明确标注为随 TencentCloud#1226 引入、 本分支不含它,避免被读成悬空引用。
同一份矩阵文档在两支都必须逐字节相同,否则合入时会在这几行上冲突。本支同步为:协议接线分支 13 个文件 / 156 用例(含新增的第一跳丢参计数用例),两支合并 14 个文件 / 164 用例。本支不含 responses-chat-compat.test.ts,文档校验会自动跳过该行。
同一份校验脚本在两支必须一致;改动内容见接线分支同名提交。
同一份矩阵文档在两支必须逐字节相同,否则合入时会在这几行上冲突。本次同步 probe.test.ts 25 例与接线分支合计 13 个文件 / 164 用例、两支合并 14 个文件 / 172 用例。
客户端裁剪历史时,Anthropic 客户端这条路(anthropicToChat)会把没有 tool_result 回应的 tool_use 原样转成 assistant.tool_calls,Chat 上游随即整轮 400: An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'. (insufficient tool messages following tool_calls message) (本机实测:claude-code 端点 + 悬空 tool_use → DeepSeek 400;空 content 那条本来就 200。) 这是与 chat_to_anthropic 方向同一条硬约束的镜像,早先只补了上游是 Anthropic 的那一侧。 本次补上反方向: - 预扫每条 assistant 消息的 tool_use 是否被紧随其后的 user 消息里的 tool_result 回应; 没有回应的摘掉并计 orphan_tool_use;调用被摘光且没有正文的 assistant 整条丢掉(empty_message); - 反过来找不到调用方的 tool_result 降级为普通 user 消息并计 orphan_tool_result, 降级时保留内容形态(文本仍文本、图片仍 image_url),只改角色不丢内容; - 新增 6 个用例(chat-anthropic-role-rules.test.ts 19→25)覆盖上述四种情形与回归护栏; - 多模态 tool_result 用例改为带配对的形态(原先用的是孤立 tool_result,属于现在会被 合法降级的输入);矩阵文档用例数同步 129→135。
|
##更新:Anthropic→Chat 方向的工具配对 客户端裁剪历史时, 这与早先修的 2)修复
3)验证
4)不在本 PR 范围的一处约束 任何「回放带 |
单独执行 check-doc-claims.mjs 时会读到工作区里残留的 .vitest-report.json(例如在 TencentCloud#1270/TencentCloud#1307/TencentCloud#1309/TencentCloud#1310 这类没有 posttest 的分支跑完 npm test 之后),结果是「核对 7 条、跳过 30 条」式假通过:故意把 role-rules 的 25 改成 26 仍然报通过。改为只有 posttest 阶段(同一轮 npm test 刚写出报告)才信任该文件,其余情况先删除再自己跑一次 vitest。 矩阵文档:补回 TencentCloud#1253 引入的 upstream-auth.test.ts 行,并把「协议接线分支额外测试」的聚合数由 13/164 更正为 14/175(两支合并 15/189 已实测无误)。
本支原带的是该脚本的旧版本(缺「读完即删」与 posttest 判定),既会在合并 TencentCloud#1226/TencentCloud#1253 时产生无意义冲突,也保留了「单独执行读到残留 .vitest-report.json 造成假通过」的缺陷。现已同步为 TencentCloud#1226/TencentCloud#1253 的同一版本:blob f4aa881。 同步后,TencentCloud#1326→TencentCloud#1226→TencentCloud#1253→TencentCloud#1334→TencentCloud#1270→TencentCloud#1328 这条顺序上 check-doc-claims.mjs 不再产生冲突。
与 TencentCloud#1226 同一处修复(该脚本由 TencentCloud#1226 引入,本支与 TencentCloud#1226 逐字节同步,避免合并时产生无意义冲突):只有 posttest 阶段才信任 .vitest-report.json,单独执行时先删残留再自己跑一次 vitest。 矩阵文档:本支聚合数按实测更正——「两支合并」183→189(TencentCloud#1226+TencentCloud#1253 实测 15 文件 / 189 用例)、「协议接线分支额外测试」171→175(本支实测 14 文件 / 175 用例)。文档顶部「转换层分支 129/19」保持原样:那是本支 checkout 里转换层文件的实测值(本支不含 TencentCloud#1226 之后补的 6 个配对用例),改成 135/25 会让本支的文档校验直接失败。
复核修复按复核发现修掉两处。 1)合并态会丢一行文档
2)文档数字护栏存在假通过窗口
验证:本支 |
capability-probe 改为「按协议选路 + 按上游地址去重」之后,probe.test.ts 由 25 例增加到 30 例(见 TencentCloud#1253 的 refactor(probe) 提交)。 本支的矩阵文档是合并态下的取胜方(冲突按「取 TencentCloud#1226 一侧」解决),所以数字必须 在这里同步:否则全批合入态下 probe.test.ts 的实测值(30)与文档写死的 25 不一致, `npm test` 的 posttest 文档用例数校验会直接失败。 同时把「协议接线分支额外测试」的聚合数与「两支合并」数一并更正为 180 / 194。 验证:tsc --noEmit 0 错误;npm test 10 文件 / 135 用例全过;文档用例数校验 20 条通过(probe.test.ts 不在本支,按「不在本分支的测试文件」跳过)。
TencentCloud#1253 的「转换决策改为请求期按协议判定」提交新增了 4 例 conversionEnabled 用例, probe.test.ts 由 30 例增至 34 例。 本支的矩阵文档是合并态下的取胜方(冲突按「取 TencentCloud#1226 一侧」解决),所以数字必须在这里 同步:否则全批合入态下 probe.test.ts 的实测值(34)与文档写死的 30 不一致, `npm test` 的 posttest 文档用例数校验会直接失败(本地实合已复现该失败)。 同时把「协议接线分支额外测试」聚合数与「两支合并」数一并更正为 184 / 198。 验证:tsc --noEmit 0 错误;npm test 10 文件 / 135 用例全过;文档用例数校验 20 条通过 (probe.test.ts 不在本支,按「不在本分支的测试文件」跳过)。
`docs/protocol-conversion-matrix.md` 在 TencentCloud#1226 / TencentCloud#1253 各有一份副本、合并时取 TencentCloud#1226 一侧,因此 TencentCloud#1253 侧新增的内容必须在 TencentCloud#1226 侧同样存在,否则: - 合并态丢整节 `chatToResponses` 字段映射; - `probe.test.ts` 仍是 34,而合并态实测 36,`npm test` 的 posttest(文档用例数校验)失败。 本提交只动文档:补「OpenAI Chat → Responses(chatToResponses)」一节与选路口径, 并把 probe.test.ts 34 → 36、协议接线分支聚合 184 → 186、两支合并 198 → 200。 本支自身用例数不变(135/135),聚合数只在合并态生效。
协议转换层:以 OpenAI Chat 为公共中间表示,实现 Chat ↔ Anthropic 与 Responses ↔ Anthropic(经 Chat)的双向转换。
纯函数 +
TransformStream,不依赖任何 handler,可单独评审、单独测试。转换的难点不在字段搬运,而在补齐两端不对等的前提:两侧对消息形状的硬要求不同(首条必须是 user、user/assistant 严格交替、tool_use与tool_result必须相邻成对、content 不能为空),客户端裁剪历史时会逐个破坏它们。逐字段映射与保真表见docs/protocol-conversion-matrix.md。改动
src/common/chat-anthropic-compat.tstool_use/tool_result相邻配对、首条 user、空 content 收敛;悬空项摘除或降级并计数src/common/responses-chat-compat.tsinstructions→ system、function_call→tool_calls、独有字段逐项计数src/common/responses-anthropic-compat.tscomposeTransforms):串联两跳,suppressUsageStat保证 usage 只计一次src/common/sse.ts、src/common/protocol-stats.ts/metrics丢参与转换耗时打点src/common/synthetic-injection.tsuser.*注入不再被静默丢弃src/server.ts/metrics路由docs/protocol-conversion-matrix.md、scripts/qa/check-doc-claims.mjsposttest)页面累计(
git diff origin/feat/server_team...<head>,2026-09-12 实测):30 个文件 / +7,020 / −25。其中 10 个文件是 #1326 基线修复的副本,本支自身的转换层约 +6.9k 行。本支不与 #1326 直接堆叠,故不写「净增量」——拿别的支当基准做差会把对方的回退算进来,无法复现。验证
npx tsc --noEmit→ 0 错误npm test→ 10 文件 / 135 用例全过cache_control、两次转换字节一致tool_use、空 content;真机对照见修订记录边界
n>1多候选、usage 细分)已在矩阵文档中逐条标注。tool_calls的 assistant 消息」时上游思考模式对reasoning_content的额外要求不在本支范围,已单独记录:与工具配对无关,完全配对的报文向该上游发送同样 400。合入顺序(本批 13 支)
#1326→ 协议#1226 → #1253→ 接入#1334 → #1325→ Opik#1270 → #1307 → #1309 → #1310 → #1328;#1251、#1346、#1347无冲突面,任意时间合。tsc --noEmit0 错误、37 文件 / 408 用例通过package.json取并集(本支带 json reporter +posttest的那行);docs/protocol-conversion-matrix.md与src/__tests__/chat-anthropic-role-rules.test.ts为 add/add,取 feat(protocol): OpenAI Chat/Responses ↔ Anthropic 转换层与一致性 #1226 一侧;scripts/qa/check-doc-claims.mjs已在 feat(protocol): OpenAI Chat/Responses ↔ Anthropic 转换层与一致性 #1226 / feat(protocol): 协议接线 + 上游能力自动探测 #1253 / feat(obs): memory-access 审计覆盖读路径(recall / search / query / read) #1328 三支逐字节同步(blobf4aa8815),不再产生冲突;src/agent-adapters/{index,types}.ts已随 feat(agent-adapters): Hermes 交互式 tools 接入 #1334 合并。核心逻辑文件零冲突。sessionInit.enabled/threadIsolation/upstream.autoDetect/opik/sessionInit.autoConversationId.enabled默认全部为false,合入不改变现有部署行为;需要「服务端为无会话头的客户端签发auto-*会话」时显式打开该开关cjl-ux),而 PR 的 base 只能是目标仓库的分支,所以做不到把堆叠 PR 的 base 指向前一层——页面 diff 只能是相对公共祖先的累计值。要看本层请用git diff <上一层 head> <本支 head>修订记录
(head
5dfdb6e)probe.test.ts30 → 34、「协议接线分支额外测试」聚合 180 → 184、「两支合并」194 → 198;全批合入态 399 → 403。矩阵文档在本支是合并态的取胜方,不同步会让合并态posttest直接失败(本地实合已复现)。(head
8a2c99e)probe.test.ts25 → 30、「协议接线分支额外测试」聚合 175 → 180、「两支合并」189 → 194。本支的矩阵文档是合并态的取胜方(冲突按「取 feat(protocol): OpenAI Chat/Responses ↔ Anthropic 转换层与一致性 #1226 一侧」解决),不同步会让全批合入态下posttest的文档用例数校验直接失败。(head
2fc8edc)upstream-auth.test.ts | 7表行,并把「协议接线分支额外测试」的聚合数从 13 个文件 / 164 用例更正为 14 个文件 / 175 用例;实合实测确认顶部「两支合并 15 个文件 / 189 用例」无误(feat(protocol): OpenAI Chat/Responses ↔ Anthropic 转换层与一致性 #1226 + feat(protocol): 协议接线 + 上游能力自动探测 #1253 实合 = 15 文件 / 189 用例)。该文件按「取 feat(protocol): OpenAI Chat/Responses ↔ Anthropic 转换层与一致性 #1226 一侧」解决冲突时会把这两处一起丢掉,导致合并态文档与代码不一致。scripts/qa/check-doc-claims.mjs:改为只有posttest阶段才信任.vitest-report.json。此前单独执行会读到工作区残留的报告造成假通过(实测把 role-rules 的 25 改成 26 仍打印「校验通过:核对 7 条,跳过 30 条」);修复后同场景退出码 1。tsc --noEmit0 错误、10 文件 / 135 用例通过、文档校验 20 条通过(新补的 upstream-auth 行在本支按「不在本分支的测试文件」跳过)。(head
bbf405b)messages的三类形状缺口(首条 assistant / 悬空tool_use/ 空 content):chatToAnthropic收尾统一修形(丢空消息 → 合并同角色 → 去掉开头 assistant 残段 → 工具配对),丢弃项计入/metrics丢参计数;protocol-conformance/responses-anthropic-compat里 5 处「assistant 开头」的旧用例脚手架补上首条 user(断言对象不变)。anthropicToChat会把没有tool_result回应的tool_use原样转成assistant.tool_calls,Chat 上游整轮 400。现改为预扫配对、没有回应的tool_use摘除并计orphan_tool_use,找不到调用方的tool_result降级为普通 user 消息并计orphan_tool_result(保留内容形态:文本仍是文本、图片仍是image_url);chat-anthropic-role-rules.test.ts19 → 25 例。bash check-message-shapes.sh三类形状全部 200;/metrics的tdai_conversion_dropped_total{kind="chat_to_anthropic|anthropic_to_chat",param="…"}分别出现orphan_tool_use/orphan_tool_result/empty_message三个 param 值。scripts/qa/check-doc-claims.mjs并挂在posttest:文档里的单文件用例数与实测不符即让测试失败。