- 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)
5.8 KiB
5.8 KiB
里程碑 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 |
approximate(4 字符≈1 token)+ tiktoken 可选懒加载回落 |
| Prompt 注册表 | src/genesis/inference/prompt_registry.py |
register / get / list_versions / render(jinja2) |
| LLM 客户端 | src/genesis/inference/client.py |
LLMClient Protocol + HttpLLMClient(OpenAI 兼容) |
| 编排引擎 | src/genesis/inference/engine.py |
InferenceEngine:chat / chat_structured |
| 测试 | tests/test_inference_{types,errors,token,prompt_registry,client,engine}.py + tests/inference_helpers.py |
全部离线用例 |
3. 关键设计决策
- httpx 选型:
httpx.AsyncClient(timeout=..., transport=...)支持 transport 注入(MockTransport 离线测试);5xx/网络错误指数退避重试(1s/3s/7s),超时→LLMTimeoutError,4xx 不重试→LLMNetworkError,2xx 结构损坏→LLMResponseError,3xx 不误判成功。(T8 整改后为异步 AsyncClient + asyncio.sleep 退避 + async with 生命周期) - 结构化重试语义:
chat_structured对 JSON 解析失败带错误信息重试(retry_count+1),重试成功仍为status="ok"(fallback 语义保留给模型降级);最终失败返回parse_error+ raw_text,不抛异常(spec §3.4 验收 3)。 - 异常折叠边界:
chat失败降级备用模型(status=fallback),双模型全败 status=failed;仅折叠 LLMError,非 LLM 异常照常冒出。 - 显式延后(非目标):
chat_structured不做模型降级——plan 明确「生产建议显式传主模型,实现已足够 v1」(plan L1142),结构化重试固定主模型。- Prompt 目录加载(
prompts/{name}/{version}.txt)为 v2,v1 仅代码内注册。 - 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(无 Critical;2 项 Important;若干 Minor):
- [已修复] 响应结构异常逃逸: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。 - [已修复] 3xx 误判成功:httpx 默认不跟随重定向,301/302 落入成功路径。修复:显式
200 <= resp.status_code < 300判定,非 2xx 一律 LLMNetworkError(不重试)。 - [确认为有意] chat_structured 无模型降级:plan 已声明 v1 单模型链,生产显式传主模型,非回归缺陷。
- [Minor 挂账 backlog]:
chat失败仅保留最后一次模型错误信息;__init__.py缺尾换行;prompt_registry 模块级 jinja2 导入冗余;chat_structured 未做 token 超限检测;HttpLLMClient 无__enter__/__exit__(调用方负责关闭生命周期)。
修复提交 14062d1(fix: 客户端响应结构损坏归入 LLMResponseError;3xx 不再误判成功)。
6. 提交清单
c2fad77 feat: 推理引擎数据模型与异常层(types/exceptions)及 httpx/jinja2 依赖
71d38b9 feat: Token 估算(approximate 内置 / tiktoken 可选回落)
59c1ea7 feat: PromptRegistry 注册/版本/渲染(jinja2)
add7b15 feat: HttpLLMClient(httpx + 重试退避/超时/鉴权)
8bc5e5e feat: InferenceEngine chat/chat_structured 全流程 + 文档补丁(补丁1/2/3)
14062d1 fix: 客户端响应结构损坏归入 LLMResponseError;3xx 不再误判成功
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、plandocs/superpowers/plans/2026-08-09-inference-engine.md
8. 后续工作
- 接入 Parser Agent 自由记述型结构化(待后续里程碑)
- LLMResponseError 已闭环,可为 engine 增加按错误分类的重试选项(api §7 retry/skip/abort UX)