Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,12 @@
- **实验原始数据入库**:`data/experiments/` 19 个文件(34 组实测逐轮 jsonl + 汇总 csv)+ `data/README.md` 溯源表——实验文档每个数字可回查到具体文件具体行;`.gitignore` 改为只忽略运行时产物、放行精选数据目录
- LIMITATIONS #12 补标已闭环(property-based + lossy-fields 已于 PR #8 落地,原标记滞后)
- README:目录补 data/、已知限制行标注 3 项已闭环、单测数 40 → 46;REVIEW.md 测试口径统一为 46 项

## 2026-09-09 · v1.6 SSE 逐块流式(LIMITATIONS #1 部分闭环,本提交)

- **`src/gateway/sse.py`**:`openai_chat` 客户端 ← `anthropic` 上游方向的逐块流式转换。text_delta 即时翻成 Chat chunk 下发(首 token 延迟与直连一致);`input_json_delta` 按既定折损缓冲到 `content_block_stop` 一次性发(非完整 JSON 片段无法流式透传);usage 在 message_start/message_delta 分次到达、流尾合并照常进 5 项埋点;`synthetic_response()` 复用 `assistant_from_upstream`——流式与非流式落库/埋点同一条代码路径,不另造语义
- 网关:抽 `_backend_headers` / `_backend_opener` / `open_stream`,do_POST 尾部逻辑收敛为 `finalize_turn()`(非流式与流式共用);流式请求同样占 `_GATE` 并发名额
- 设计验证:urllib 响应对象按行增量吐数据(实测逐行到达),无需引入第三方 SSE 库,保持零依赖
- mock:Anthropic 路径发完整官方事件序列(message_start 带 usage 与缓存字段 → content_block_* → message_delta → message_stop)
- 测试:`TestSseConversion` 7 项单测(分帧容错 / 即时下发 / 参数缓冲 / stop_reason 映射 / usage 合并 / 错误帧);冒烟第 6 组 7 项(**实测首尾帧间隔 30.7ms,证明逐块到达非整读补吐** + 流式轮照常进埋点)。53 项单测 + 18 项冒烟全过
- 文档:LIMITATIONS #1 标「部分闭环」(其余方向与 dropped 可见性写明);README SSE 行与测试口径同步;REVIEW 网关文件清单补 sse.py
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ OpenAI Chat / OpenAI Response ↔ Anthropic 的协议转换层(网关/代理 +
| 状态层(SQLite 持久化)+ 可切换 Session 配置 | ✅ v1.1 | `src/state/` + `config/session.json`,重启不丢(`TestStateLayer`) |
| 三对 adapter(字段映射) | ✅ 离线 | `src/adapters/`,录制样例可测(`TestAdapterRoundTrip`) |
| 网关转发 | ✅ 离线(mock)/ Groq 真实链路已验证 | `src/gateway/`;E29 真实端点对账 |
| SSE 流式 | ◐ 整读透传 | 逐块转换未实现 → `docs/LIMITATIONS.md` #1;E21 证明流式无损命中率 |
| SSE 流式 | ◐ **v1.6 部分逐块**(chat 客户端 ← anthropic 上游) | `src/gateway/sse.py` 逐块转换(冒烟实测 30.7ms 逐块到达);其余方向整读透传 → `docs/LIMITATIONS.md` #1;E21 证明流式无损命中率 |
| 记忆注入(幂等去重 + memory_cap) | ✅ v1.1 新增 | `inject_memories` + `TestGatewayPolicies`;管理口 `/v1/admin/session/meta` |
| 工具调用全链路(含 ID 双向映射) | ✅ **v1.4** | 跨协议参数/签名不丢(`TestCrossProtocolToolArgs`/`TestThinkingSignature`);`src/state/idmap.py` 会话级映射(`TestToolIdMap`) |
| 状态层重放(结构化块) | ✅ **v1.4** | thinking(signature/redacted)与 tool_use 入历史(`TestAssistantFromUpstream`) |
Expand All @@ -39,8 +39,8 @@ OpenAI Chat / OpenAI Response ↔ Anthropic 的协议转换层(网关/代理 +

```bash
# 零外部依赖(仅标准库),Python 3.11+
python -m unittest tests.test_offline -v # 46 项离线单测:IR 往返 / adapter / 状态层 / 预热 / 三轮自查回归 / 多轮链 E2E / ID 映射 / property-based / 惰性淘汰
python tools/smoke_e2e.py # 11 项端到端冒烟:真起 mock+网关子进程,5 组链路断言
python -m unittest tests.test_offline -v # 53 项离线单测:IR 往返 / adapter / 状态层 / 预热 / 三轮自查回归 / 多轮链 E2E / ID 映射 / property-based / 惰性淘汰 / SSE 转换
python tools/smoke_e2e.py # 18 项端到端冒烟:真起 mock+网关子进程,6 组链路断言(含 SSE 逐块流式)
python tools/mock_backend.py # 起 mock backend(127.0.0.1:9100,按端点返回三种协议形状)
python -m src.gateway.server # 起网关(转发到 mock)
python experiments/run_experiments.py --dry-run # 实验框架 dry-run(不真实调 API)
Expand Down Expand Up @@ -95,7 +95,7 @@ src/
ir/ IR 数据模型(L0/L1/L2)
adapters/ chat / response / anthropic 三对 adapter
state/ 会话表 + TTL + session 配置 + build_prefix
gateway/ HTTP 转发(SSE 为整读透传,逐块转换见 docs/LIMITATIONS.md #1)
gateway/ HTTP 转发 + SSE 逐块转换(chat←anthropic 已实现 sse.py,其余方向整读透传见 docs/LIMITATIONS.md #1)
observability/ 命中率埋点(5 项暴露)
warmup/ max_tokens:0 预热
webui/ 弹网页本地仪表盘(安全骨架 + 记忆编排界面)
Expand Down
4 changes: 3 additions & 1 deletion docs/LIMITATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

## 一、协议转换链路

### #1 SSE 逐块流式转换未实现(整读透传)
### #1 SSE 逐块流式转换(v1.6 部分闭环:chat 客户端 ← anthropic 上游)

> ◐ **v1.6 部分闭环**(2026-09-09):`openai_chat` 客户端 ← `anthropic` 上游方向已实现**逐块流式**——`src/gateway/sse.py` 把 Anthropic 事件逐块翻成 Chat chunk 即时下发(冒烟实测首尾帧间隔 30.7ms,证明非整读补吐);text_delta 即时下发、工具参数按既定折损缓冲到 `content_block_stop`、usage 流尾合并后照常进 5 项埋点、历史落库复用 `assistant_from_upstream`(流式与非流式同一路径)。回归测试 `TestSseConversion` 7 项 + 冒烟第 6 组 7 项。**仍为整读透传的方向**:chat←chat、response 源、anthropic 客户端;流式轮的 dropped 清单对客户端不可见(SSE 帧无其位置,埋点中 degradation 仍如实记录)。以下原文留档,未闭环范围以四要素为准。

- **影响半径**:流式体验与首 token 延迟(TTFB)。功能正确性不受影响——响应内容完整、usage 归一与埋点照常工作。
- **触发条件**:客户端请求 `stream:true` 经过网关。当前网关 `post_json` 整读响应后一次性返回;跨协议的事件模型转换(Chat `delta` / Responses 三维寻址 / Anthropic 块生命周期)未实现。
Expand Down
12 changes: 7 additions & 5 deletions docs/REVIEW.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# 代码评审材料(Code Review Pack)

> 版本 v1.2(2026-09-02)| src 1493 行 + tools/experiments/tests 1065 行(纯标准库,零第三方依赖,Python 3.11+)
> 测试:**46 项单测全过 + 11 项端到端冒烟全过**(2026-09-09,含第三轮自查回归 + property-based 性质测试 + 惰性淘汰回归)+ Groq 真实链路验证通过(2026-09-01)
> 测试:**53 项单测全过 + 18 项端到端冒烟全过**(2026-09-09,含第三轮自查回归 + property-based 性质测试 + 惰性淘汰回归 + SSE 逐块转换)+ Groq 真实链路验证通过(2026-09-01)
> 本版差异:**两轮代码自查发现 9 个真实 bug,全部修复并各配回归测试**(见第六节)

## 一、整体架构(IR 星型)
Expand Down Expand Up @@ -66,6 +66,7 @@ do_POST(路由 /v1/{source}/to/{target},未知协议 → 400)
| 文件 | 行数 | 功能 |
| ---- | --- | ---- |
| `server.py` | 363 | 见下方「网关关键函数」 |
| `sse.py` | 150 | **v1.6 新增**:SSE 逐块流式转换(chat 客户端 ← anthropic 上游)。`parse_sse_lines` 行流分帧;`AnthropicToChatStream` 事件→Chat chunk:text_delta 即时下发、工具参数缓冲到 content_block_stop(既定折损)、usage 流尾合并、`synthetic_response()` 复用 `assistant_from_upstream` 落库 |

**网关关键函数**:

Expand Down Expand Up @@ -110,9 +111,9 @@ do_POST(路由 /v1/{source}/to/{target},未知协议 → 400)
| ---- | --- | ---- |
| `mock_backend.py` | 194 | 离线顶替真实端点。**v1.2 按端点路径返回三种协议各自形状**(/v1/messages、/responses、/chat/completions),mock 与真实后端走同一条转发路径;有状态缓存模拟(前缀哈希记账);支持 `max_tokens:0` 预热形态与 SSE |
| `verify_cache.py` | 152 | **验站脚本**:唯一随机 run_id 前缀两步验证(预热看 creation → 复发看 read);PASS/PARTIAL/FAIL;`max_tokens:0` 被拒自动降级 1 |
| `smoke_e2e.py` | 164 | **v1.2 新增**:端到端冒烟——真起 mock + 网关两个子进程,跑 5 组 11 项断言(直通/跨协议/多轮链/预热/非法路由),退出码 0/1 |
| `smoke_e2e.py` | 220 | 端到端冒烟——真起 mock + 网关两个子进程,跑 6 组 18 项断言(直通/跨协议/多轮链/预热/非法路由/**SSE 逐块流式**),退出码 0/1 |

### tests/ —— 测试(46 项全过)
### tests/ —— 测试(53 项全过)

| 测试类 | 覆盖 |
| ------ | ---- |
Expand All @@ -128,13 +129,14 @@ do_POST(路由 /v1/{source}/to/{target},未知协议 → 400)
| `TestToolIdMap` | **v1.4 新增**:工具 ID 双向映射(含 Anthropic→Chat→Anthropic 还原、并发分叉不串号) |
| `TestAssistantFromUpstream` | **v1.4 新增**:状态层重放保留 thinking / tool_use 结构化块 |
| `TestPropertyRoundTrip` | **v1.4 新增**:property-based 往返(440 随机用例、固定种子可复现):断点恒在 [3,4] / 未知字段必降级 / 工具参数守恒 / signature 守恒 |
| `TestSseConversion` | **v1.6 新增**(7 项):SSE 分帧(注释行/半帧容错)、text_delta 即时下发、工具参数缓冲到块结束、stop_reason 映射、usage 流尾合并 + 落库复用、上游错误帧 |

## 四、已验证记录

| 验证 | 结果 | 日期 |
| ---- | ---- | ---- |
| 46 项单测(`-W error::ResourceWarning` 下零警告,含第三轮自查回归 + property-based + 惰性淘汰) | ✅ 全过 | 2026-09-09 |
| 11 项端到端冒烟(子进程级,`tools/smoke_e2e.py`) | ✅ 全过 | 2026-09-02 |
| 53 项单测(`-W error::ResourceWarning` 下零警告,含第三轮自查回归 + property-based + 惰性淘汰 + SSE 转换) | ✅ 全过 | 2026-09-09 |
| 18 项端到端冒烟(子进程级,`tools/smoke_e2e.py`,含 SSE 逐块到达实测 30.7ms) | ✅ 全过 | 2026-09-09 |
| 多轮 previous_response_id 链路(0→2→4 条重放) | ✅ 修复后通过 | 2026-09-02 |
| Groq 真实链路(Chat 直通 + Responses→Chat + usage 归一 + 埋点) | ✅ | 2026-09-01 |
| 实验框架 dry-run | ✅ | 2026-09-02 |
Expand Down
2 changes: 1 addition & 1 deletion docs/capability-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,6 @@
| usage 归一 | ✅ | ✅ | ✅ | 离线已测 + Groq 真实链路验证 |
| previous_response_id | — | ✅(状态层重放) | — | **已实现**:SQLite 会话表 + TTL + 多轮链 E2E 测试(0→2→4 重放累积) |
| 缓存断点布局 | ✅ | ✅ | — | **已落地**:3 固定 + 1 滚动,exp7/E24 实测 |
| SSE 流式 | ◐ | ◐ | ◐ | **整读透传**:网关整读响应再返回,逐块流式转换未实现 → LIMITATIONS #1(E21 直连实验证明流式无损缓存命中率) |
| SSE 流式 | ◐ | ◐ | ◐ | **v1.6 部分逐块**:chat 客户端 ← anthropic 上游已逐块转换(`src/gateway/sse.py`,冒烟实测 30.7ms 逐块到达);其余方向整读透传 → LIMITATIONS #1(E21 直连实验证明流式无损缓存命中率) |
| max_tokens:0 预热 | — | — | ✅ | 字段处理已测 + 实测预热后 100% 命中 |
| 多模态 / Realtime / Batches | ❌ | ❌ | ❌ | 方案 3.8 明确不做 |
Loading
Loading