Files
2026Technology-Competition/docs/milestone3-inference-review.md
T
lhl 8239a37a99 feat(inference): LLM 客户端全异步化(T8 架构审查整改)
- Issue9: client.py 由同步 httpx.Client 全异步化
  - LLMClient Protocol / HttpLLMClient.chat → async;httpx.AsyncClient + asyncio.sleep 退避
  - __enter__/__exit__ → __aenter__/__aexit__(async with 生命周期闭环)
- engine.py chat/chat_structured/_call 全部 async + await
- FakeLLMClient.chat → async;测试用 anyio pytest 插件转换(engine 32 + client 10 用例)
- 同步 inference-engine spec 与 milestone3 review 的 httpx 描述
- 全量 182 passed / 100.00%(987 stmts/252 br)
2026-08-12 09:50:37 +08:00

70 lines
5.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 里程碑 3.1 InferenceEngine 交付报告
## 1. 概述
完成 `agent-runtime-design.md` §2 定义的推理引擎(InferenceEngine),为多 Agent 协作提供统一 LLM 调用入口:模型选择/降级、重试、超时、Token 管理、Prompt 注册表与结构化输出。全程 TDD、零真实网络、覆盖率红线 100% 达成。
- **评审对象**`docs` 阶段(spec + plan)经 plan-design-review 批准后,实施提交范围 `2fbff07..HEAD`(共 6 个 commit
- **验收结论**With fixes → 2 项 Important 已修复 → 重新验证 **Ready**117 passed / 100% 覆盖)
## 2. 交付内容
| 模块 | 文件 | 说明 |
|------|------|------|
| 数据模型 | `src/genesis/inference/types.py` | TokenUsage / ChatMessage / ChatResult / StructuredResult / Prompt |
| 异常体系 | `src/genesis/inference/exceptions.py` | LLMError 五类:Network / Timeout / NotConfigured / ResponseError |
| Token 估算 | `src/genesis/inference/token.py` | approximate4 字符≈1 token+ tiktoken 可选懒加载回落 |
| Prompt 注册表 | `src/genesis/inference/prompt_registry.py` | register / get / list_versions / renderjinja2 |
| LLM 客户端 | `src/genesis/inference/client.py` | LLMClient Protocol + HttpLLMClientOpenAI 兼容) |
| 编排引擎 | `src/genesis/inference/engine.py` | InferenceEnginechat / chat_structured |
| 测试 | `tests/test_inference_{types,errors,token,prompt_registry,client,engine}.py` + `tests/inference_helpers.py` | 全部离线用例 |
## 3. 关键设计决策
1. **httpx 选型**`httpx.AsyncClient(timeout=..., transport=...)` 支持 transport 注入(MockTransport 离线测试);5xx/网络错误指数退避重试(1s/3s/7s),超时→LLMTimeoutError4xx 不重试→LLMNetworkError2xx 结构损坏→LLMResponseError3xx 不误判成功。(T8 整改后为异步 AsyncClient + asyncio.sleep 退避 + async with 生命周期)
2. **结构化重试语义**`chat_structured` 对 JSON 解析失败带错误信息重试(retry_count+1),重试成功仍为 `status="ok"`(fallback 语义保留给模型降级);最终失败返回 `parse_error` + raw_text,不抛异常(spec §3.4 验收 3)。
3. **异常折叠边界**`chat` 失败降级备用模型(status=fallback),双模型全败 status=failed;仅折叠 LLMError,非 LLM 异常照常冒出。
4. **显式延后(非目标)**
- `chat_structured` 不做模型降级——plan 明确「生产建议显式传主模型,实现已足够 v1」(plan L1142),结构化重试固定主模型。
- Prompt 目录加载(`prompts/{name}/{version}.txt`)为 v2v1 仅代码内注册。
- LLMResponseError 在 v1.0 spec 已定义但无触发点,最终评审后补齐(见 §5)。
## 4. 测试与验证
- 聚焦测试:各模块 RED→GREEN 全绿(types 8 / token 7 / registry 6 +1 / client 9 / engine 16
- 全量回归:`python -m pytest -q`**117 passed / 覆盖率 100.00%785 stmts / 186 br**`fail_under=99` 达成
- 零真实网络:单测全部注入 FakeLLMClient / httpx.MockTransport`DEEPSEEK_API_KEY` 不落代码
- 基线不回退:既有 parsers / data_models / config 全绿保持
## 5. 最终评审(base 2fbff07 → 8bc5e5e)与修正
评审判定 **With fixes**(无 Critical2 项 Important;若干 Minor):
1. **[已修复] 响应结构异常逃逸**:2xx 畸形响应(非 JSON / 缺 choices 字段)抛裸 `KeyError/JSONDecodeError`,逃出 engine 的 `except LLMError`,与「不把异常抛给调用方」承诺冲突。修复:client 内捕获 `(json.JSONDecodeError, KeyError, IndexError, TypeError)``raise LLMResponseError`(spec §3.8 既有类型首次被触发,闭环 api-design §7 LLM_PARSE_ERROR 来源列)。TDD:新增 `test_chat_3xx_no_retry``test_chat_malformed_response_raises_llm_response_error`
2. **[已修复] 3xx 误判成功**:httpx 默认不跟随重定向,301/302 落入成功路径。修复:显式 `200 <= resp.status_code < 300` 判定,非 2xx 一律 LLMNetworkError(不重试)。
3. **[确认为有意] chat_structured 无模型降级**plan 已声明 v1 单模型链,生产显式传主模型,非回归缺陷。
4. **[Minor 挂账 backlog]**`chat` 失败仅保留最后一次模型错误信息;`__init__.py` 缺尾换行;prompt_registry 模块级 jinja2 导入冗余;chat_structured 未做 token 超限检测;HttpLLMClient 无 `__enter__/__exit__`(调用方负责关闭生命周期)。
修复提交 `14062d1`(fix: 客户端响应结构损坏归入 LLMResponseError3xx 不再误判成功)。
## 6. 提交清单
```
c2fad77 feat: 推理引擎数据模型与异常层(types/exceptions)及 httpx/jinja2 依赖
71d38b9 feat: Token 估算(approximate 内置 / tiktoken 可选回落)
59c1ea7 feat: PromptRegistry 注册/版本/渲染(jinja2
add7b15 feat: HttpLLMClienthttpx + 重试退避/超时/鉴权)
8bc5e5e feat: InferenceEngine chat/chat_structured 全流程 + 文档补丁(补丁1/2/3)
14062d1 fix: 客户端响应结构损坏归入 LLMResponseError3xx 不再误判成功
```
## 7. 涉及文件
- 新建:`src/genesis/inference/{types,exceptions,token,prompt_registry,client,engine}.py``tests/inference_helpers.py``tests/test_inference_{types,errors,token,prompt_registry,client,engine}.py`
- 修改:`src/genesis/inference/__init__.py``pyproject.toml`httpx/jinja2 依赖)、`docs/agent-runtime-design.md``docs/api-design.md``docs/config-design.md`
- 文档:spec `docs/superpowers/specs/2026-08-09-inference-engine-design.md`、plan `docs/superpowers/plans/2026-08-09-inference-engine.md`
## 8. 后续工作
- 接入 Parser Agent 自由记述型结构化(待后续里程碑)
- LLMResponseError 已闭环,可为 engine 增加按错误分类的重试选项(api §7 retry/skip/abort UX