feat(inference): chat_structured 接入 jsonschema 真校验(T1 架构审查整改)

- pyproject.toml 新增 jsonschema>=4.23 依赖
- engine.py: json.loads 后 jsonschema.validate,校验失败按解析失败重试(带错误信息)
- 新增 3 用例:违规重试成功/耗尽 parse_error 含详情/合法一次通过
- 全量 163 passed / 100.00% 覆盖(936 stmts/238 br),fail_under=99 达标
This commit is contained in:
lhl
2026-08-12 09:32:02 +08:00
parent 0a79b07356
commit 25fc472d9b
5 changed files with 241 additions and 0 deletions
+2
View File
@@ -69,3 +69,5 @@
| 2026-08-10 | 测试验证 | Phase3 Task5 实现:真实样本 Word 解析集成测试。tests/test_real_samples.py 追加 4 用例(保留现有 4 个 Excel 用例与 _x 辅助):模板→7 H1(はじめに…バッチ一覧)+section:introduction/doc_title 占位符+template_start 书签;记入規則→category=write/file_type=word/# 1. 機能一覧の書き方/- 機能ID は F001 から連番で付与する。(・前缀双通道检测);做成説明書→category=write/# 2. 機能一覧;全量组装→tables 非空+template 非空+rule_docs==2 且全 write;聚焦测试 8 passed 无 skippytest 全量 160 passed 覆盖 100.00%929 stmts/236 br),fail_under=99 达标 | tests/test_real_samples.py, _AI_USAGE_LOG.md | deepseek-v4-flash-free |
| 2026-08-10 | 测试验证 | Phase3 Word 解析最终评审收尾:3 文件补尾随换行、test_real_samples import 排序、SourceParser docstring 契约优先级注释 | src/genesis/parsers/source_aggregator.py, tests/test_source_aggregator.py, tests/test_real_samples.py, _AI_USAGE_LOG.md | deepseek-v4-flash-free |
| 2026-08-10 | 测试验证 | Phase3 Word 解析里程碑评审报告:docs/milestone3-word-parser-review.md5 任务 task review 全 Approved + 最终 whole-branch review With fixes 闭环;160 passed / 100.00% 分支覆盖 929 stmts/236 brPre-Flight 裁决 3 项 + 覆盖补齐 4 项记录;deferred 遗留 6 项移交后续) | docs/milestone3-word-parser-review.md, _AI_USAGE_LOG.md | deepseek-v4-flash-free |
| 2026-08-11 | 架构设计 | 整体架构工程审查(plan-eng-reviewSCOPE_REDUCED 模式):Step0 裁定移除 Qdrant 抽象+Redis 双实现(单一 Chroma+InMemory);Section1 架构 7 issuejsonschema 真校验/解析降级链/cancelled+resume 状态/引擎层注入防护/删死配置/v1 rerank/记忆完整实现);Section2 代码质量 3 issueDRY helper/全异步化/局部变量);Section4 性能 2 issueCJK token 估算/串行约束文档化);Outside Voice 8 项全接受(文档同步/bge-m3/resolver/黄金集/DataGate 机制化/QA 护栏+fallback/任务持久化/docx 原型提前);产出 17 项 Implementation Tasks JSONL;报告 docs/architecture-eng-review.md | docs/architecture-eng-review.md, _AI_USAGE_LOG.md | deepseek-v4-flash-free |
| 2026-08-11 | Agent 实现 | T1(架构审查整改):chat_structured 接入 jsonschema 真校验。pyproject.toml 新增 jsonschema>=4.23 依赖;engine.py 在 json.loads 后调用 jsonschema.validateschema 非空时),校验失败按解析失败重试(新增 except jsonschema.ValidationError 分支,带校验错误信息重试);新增 3 用例(违规重试成功/违规耗尽 parse_error 含校验详情/合法一次通过);TDD 验证 RED{'a':'not_a_number'} 未被拒直接返回)→ GREEN(聚焦 4 passed)→ 全量 163 passed 覆盖 100.00%936 stmts/238 br),fail_under=99 达标 | pyproject.toml, src/genesis/inference/engine.py, tests/test_inference_engine.py, _AI_USAGE_LOG.md | deepseek-v4-flash-free |
+185
View File
@@ -0,0 +1,185 @@
# 整体架构工程审查报告(plan-eng-review
> 版本: v1.0 | 日期: 2026-08-11 | 状态: 已裁定
>
> 审查技能: gstack `plan-eng-review`version 1.0.0| 审查对象: 整体架构全景(docs/design.md + agent-runtime-design.md + rag-layer-design.md + api-design.md + implementation-plan.md| 当前进度: Phase3 Word 解析收尾(160 tests / 100%
---
## 1. 审查范围与裁定摘要
本次审查锁定**整体架构全景**,目标为后续 Phase4-6RAG / Impact / Writer / Web UI)实施提供架构定论。
| 审查阶段 | 结果 |
|---------|------|
| Step 0 Scope Challenge | **范围缩减**:移除 Qdrant 切换抽象 + Redis/Valkey 双实现(单一 Chroma + InMemoryQueue|
| Section 1 Architecture | 7 个 issue,全部裁定 |
| Section 2 Code Quality | 3 个 issue,全部裁定 |
| Section 3 Test Review | 覆盖图完成,无独立缺口(随架构裁定覆盖)|
| Section 4 Performance | 2 个 issue,全部裁定 |
| Outside Voice | 独立挑战完成,8 项全部接受 |
---
## 2. Step 0: Scope Challenge 裁定
**复杂度检查触发**(全架构远超 8 文件/2 服务阈值),发现 2 处过度设计:
| # | 过度设计点 | 裁定 |
|---|-----------|------|
| S1 | Qdrant 可切换抽象(rag-layer §9 StorageAdapter| **移除** — v1 单机竞赛场景 Chroma 嵌入式足够(搜索验证:<10 万向量 Chroma 正确默认)|
| S2 | Redis/Valkey TaskQueue 双实现(api-design §5| **移除** — v1 InMemoryQueue 满足演示需求,抽象接口保留但双实现不建 |
> Search Check 结论:手写 RAGchromadb+rank_bm25+RRF)与 2026 主流实践一致;Chroma 嵌入式对小规模规则库正确。
---
## 3. Section 1: Architecture Review7 issue 全裁定)
| # | 严重度 | 置信度 | 发现 | 裁定 |
|---|--------|--------|------|------|
| I1 | P1 | 9/10 | `chat_structured` 无真正 schema 校验(engine.py:160 直接 json.loads 返回)| **加 jsonschema 真校验**(失败→带错误重试→parse_error+校验详情)|
| I2 | P2 | 8/10 | 解析重试路径硬编码首选模型,从不降级(engine.py:155| **解析重试也走降级链**primary→fallback|
| I3 | P1 | 7/10 | 状态机 8 状态无 cancelled/aborted 定义,但 api 有 cancel 端点 | **加 cancelled+resume 状态**cancelled 终态 + resume 回到中断前)|
| I4 | P2 | 7/10 | design §8.1 宣称「引擎统一注入防护」但 engine.py 无实现 | **引擎层统一防护**(边界包裹+系统指令恒定性,全 Agent 生效)|
| I5 | P2 | 8/10 | config.py 残留 QdrantStoreConfigL95-103/ redis_urlL38-41)死配置 | **删死配置+同步文档** |
| I6 | P2 | 7/10 | RAG 设计缺 rerank 精排(2026 主流实践为 向量→rerank→精排)| **v1 加 rerank**bge-reranker-v2-m3|
| I7 | P3 | 5/10 | 三层记忆 + DataGate + AgentState 对单进程场景可能过度 | **完整实现**(用户认为记忆系统是核心价值)|
## 4. Section 2: Code Quality Review3 issue 全裁定)
| # | 严重度 | 置信度 | 发现 | 裁定 |
|---|--------|--------|------|------|
| I8 | P2 | 9/10 | SourceParser 三重复制校验(source_aggregator.py:39-66 DRY 违规)| **提取 _validate_path helper** |
| I9 | P2 | 8/10 | client.py:82 time.sleep 阻塞 asyncio 任务池 + client 生命周期未闭环 | **全异步化**AsyncClient + asyncio.sleep + async 上下文)|
| I10 | P3 | 6/10 | chat_structured 重复调用 _model_names(None)[0]4 处)| **随 I2 一并修** |
## 5. Section 3: Test Review
已实现部分 **160 tests / 100% 覆盖**fail_under=99 门禁生效),FakeLLMClient 注入模式成熟。覆盖图确认关键测试缺口集中在**已裁定架构变更**(jsonschema/降级链/注入防护/异步化需测试先行)与后续阶段(RAG chunker/检索 eval、状态机、渲染链路 E2E)。
## 6. Section 4: Performance Review2 issue 全裁定)
| # | 严重度 | 置信度 | 发现 | 裁定 |
|---|--------|--------|------|------|
| I11 | P2 | 7/10 | token.py:18 cl100k_base 对 CJK 严重低估 token 数 → 裁剪策略失效 | **CJK 保守估算**(模型 tokenizer 优先,退化 CJK 字符×1.5|
| I14 | P2 | 7/10 | Writer 逐章串行生成 3-5 分钟 | **保持串行+文档约束**(章间引用需前章摘要,并行复杂;UI 预估时长提示)|
## 7. Outside Voice(独立挑战,8 项全接受)
| # | 发现 | 裁定 |
|---|------|------|
| OV1 | 审查决策未写回执行文档(rag-layer/api-design/runtime/plan 仍含双实现描述,Phase4 会重建放弃的抽象)| 接受:T5 同步修订四处文档 |
| OV2 | bge-small-zh-v1.5 面向中文,实际语料为日文 | 接受:T11 切换 bge-m3+日文样本验证 |
| OV3 | 可追溯性标准未操作性定义(URI 格式不一致、无 resolver、QA#8 可作弊)| 接受:T12 统一 URI+resolver+强验证 |
| OV4 | 成功标准无量度(无黄金集/评分器)| 接受:T13 建立黄金集+评分器 |
| OV5 | DataGate 是原则非机制,1000 行 Excel 上下文爆炸 | 接受:T14 机制化+规模测试前置 |
| OV6 | QA 循环无边界 + 自校验盲区(DeepSeek 检查 DeepSeek| 接受:T15 护栏+QA 走 fallback_model |
| OV7 | 崩溃恢复只到会话级,任务层丢数据 | 接受:T16 任务级持久化 |
| OV8 | 最难成功标准(格式精度)排关键路径末尾 | 接受:T17 docx 注入原型提前 |
---
## 8. Required Outputs
### 8.1 NOT in scope(本次审查明确延后)
| 项 | 延后理由 |
|----|---------|
| Qdrant/QdrantAdapter | 与 S1 裁定一致,移除(v2 触发项)|
| Redis/Valkey Queue 实现 | 与 S2 裁定一致,接口保留(v2 触发项)|
| LLM 调用缓存 | design §9.2 已列 v2,识别为有意延期 |
| 成本/限流监控 | design §9.1 v2 预留 |
| Type C 参考设计文档 RAG | rag-layer §1.1 已声明可选增强,初期聚焦 Type A+B |
| 多用户并发隔离 | v1 单用户演示,多用户隔离基线保留(§8.2)|
### 8.2 What already exists(设计复用核查)
| 已实现 | 复用情况 |
|--------|---------|
| InferenceEngineM3.1| ✅ 设计直接复用;需按 I1/I2/I4/I9 加固 |
| Excel/Word/混合 ParserPhase2/2.5/3| ✅ 设计直接复用;I8 微重构 |
| PromptRegistry / token 估算 | ✅ 复用;I11 修正估算 |
| 7 个脱敏真实样本(samples/| ✅ T13 黄金集基础 |
| config 三 yaml + env 覆盖 | ✅ 复用;I5 清理死配置 |
### 8.3 失败模式(关键路径)
| 路径 | 失败场景 | 测试覆盖 | 错误处理 | 用户可见性 |
|------|---------|---------|----------|-----------|
| chat_structured 输出不合 schema | LLM 返回缺字段 JSON → 消费方 KeyError | I1 修复后新增 | I1 修复后 parse_error | ✅ 重试后显示失败 |
| 解析重试主模型弱 | 重试 2 次全败 → 无降级 | I2 修复后新增 | I2 修复后 fallback | ✅ 降级事件可见 |
| 用户取消生成 | 状态悬置无定义 | I3 修复后新增 | I3 修复后 cancelled | ✅ cancel 端点语义明确 |
| 恶意规则文档注入 | 规则含「忽略以上指令」→ 污染输出 | I4 修复后新增 | I4 修复后引擎级阻断 | ⚠️ 需测试验证 |
| 日文长文本超上下文 | 裁剪低估 → API 超限拒绝 → 重试循环 | I11 修复后新增 | I11 修复后裁剪准确 | ✅ 重试机制 |
| 服务重启丢进行中任务 | 中断后重复章节或卡死 | OV7(T16) 后新增 | 需任务落盘 | ⚠️ 当前**静默失败** |
### 8.4 Worktree 并行化策略
```
Lane A(前端+编排):T3 状态机 → T14 DataGate → T16 任务持久化(共享 orchestrator/
Lane BRAG 独立) T6 rerank → T11 bge-m3(共享 rag/,不与 A 冲突)
Lane C(推理加固) T1 jsonschema → T2 降级链 → T4 注入防护 → T8 异步 → T9 token(共享 inference/,串行)
Lane D(基础设施) T7 helper / T5 死配置 / T10 文档 / T12 resolver / T13 黄金集 / T15 QA / T17 原型
```
执行序:**C(加固核心)先于一切** → A + B 并行 → D 穿插。Lane C 与 A 共享引擎层,建议 C 完成后 A 再启动。
---
## 9. Implementation Tasks17 项)
全部任务已输出至 `~/.gstack/projects/unknown/tasks-eng-review-20260811-160000.jsonl`/autoplan 可聚合)。
- [ ] **T1 (P1, human: ~2h / CC: ~20min)** — inference — chat_structured 接入 jsonschema 真校验
- [ ] **T2 (P1, human: ~1.5h / CC: ~15min)** — inference — 解析重试降级链 + 模型名局部变量
- [ ] **T3 (P1, human: ~3h / CC: ~40min)** — orchestrator — 状态机 cancelled+resume
- [ ] **T4 (P1, human: ~3h / CC: ~30min)** — inference — 引擎层统一注入防护
- [ ] **T5 (P2, human: ~1h / CC: ~10min)** — config — 删死配置+同步文档(含 OV1)
- [ ] **T6 (P2, human: ~4h / CC: ~45min)** — rag — v1 rerank 精排
- [ ] **T7 (P2, human: ~30min / CC: ~5min)** — parsers — 提取 _validate_path helper
- [ ] **T8 (P1, human: ~5h / CC: ~1h)** — inference — LLM 客户端全异步化
- [ ] **T9 (P2, human: ~1h / CC: ~15min)** — inference — CJK 保守 token 估算
- [ ] **T10 (P3, human: ~1h / CC: ~10min)** — documentation — Writer 串行约束写回
- [ ] **T11 (P2, human: ~3h / CC: ~30min)** — rag — bge-m3 + 日文样本验证(OV2
- [ ] **T12 (P1, human: ~6h / CC: ~1.5h)** — provenance — URI 统一+resolverOV3
- [ ] **T13 (P1, human: ~8h / CC: ~2h)** — eval — 黄金集+评分器(OV4)
- [ ] **T14 (P2, human: ~5h / CC: ~1h)** — orchestrator — DataGate 机制化(OV5
- [ ] **T15 (P2, human: ~3h / CC: ~40min)** — qa — QA 护栏+fallback 路由(OV6
- [ ] **T16 (P2, human: ~6h / CC: ~1.5h)** — orchestrator — 任务级持久化(OV7
- [ ] **T17 (P1, human: ~6h / CC: ~1.5h)** — writer — docx 注入原型提前(OV8
---
## 10. 完成摘要(Completion Summary
- Step 0 Scope Challenge: **范围缩减**(移除 2 处双实现抽象)
- Architecture Review: 7 issues found(全部裁定)
- Code Quality Review: 3 issues found(全部裁定)
- Test Review: 覆盖图已产出,0 独立缺口(随架构裁定覆盖)
- Performance Review: 2 issues found(全部裁定)
- NOT in scope: 已写入(§8.1
- What already exists: 已写入(§8.2
- TODOS.md: N/A(项目无此文件;17 项任务已入 JSONL)
- Failure modes: 6 条关键路径记录,1 处**临界缺口**(重启丢任务静默失败 → T16 修复)
- Outside voice: 已运行(Claude 子代理),8 项全部接受
- Parallelization: 4 lanesC→(A+B) 并行,D 穿插
- Lake Score: 17/17 推荐选择完整方案
---
## GSTACK REVIEW REPORT
| Review | Trigger | Why | Runs | Status | Findings |
|--------|---------|-----|------|--------|----------|
| CEO Review | `/plan-ceo-review` | Scope & strategy | 0 | — | — |
| Codex Review | `/codex review` | Independent 2nd opinion | 0 | — | — |
| Eng Review | `/plan-eng-review` | Architecture & tests (required) | 1 | ISSUES_OPEN | 11 issues + 8 outside voice |
| Design Review | `/plan-design-review` | UI/UX gaps | 0 | — | — |
| DX Review | `/plan-devex-review` | Developer experience gaps | 0 | — | — |
- **CROSS-MODEL:** Outside voiceClaude 子代理)与主审查交叉验证——OV1 为已裁定项的执行监督,OV2/3/4/5/6/7/8 为互补新发现,无实质分歧。
- **VERDICT:** ENG REVIEW 完成(11 issues + 8 findings 全部裁定,待实施后复核)。**eng review not yet CLEAR — 需任务实施后重跑确认。**
**UNRESOLVED DECISIONS:**
- 无(全部 19 项裁定完成)
+1
View File
@@ -15,6 +15,7 @@ dependencies = [
"python-docx>=1.0",
"httpx>=0.28",
"jinja2>=3.1",
"jsonschema>=4.23",
]
[project.optional-dependencies]
+10
View File
@@ -4,6 +4,8 @@ import json
import time
from typing import Any, Callable
import jsonschema
from .client import LLMClient
from .exceptions import LLMError
from .prompt_registry import PromptRegistry
@@ -158,6 +160,9 @@ class InferenceEngine:
)
last_raw = text
data = json.loads(text)
if schema:
# 真 schema 校验:不合 schema 时按解析失败重试(T1)
jsonschema.validate(instance=data, schema=schema)
return StructuredResult(
data=data, raw_text=text, parse_attempts=attempts,
model=self._model_names(None)[0],
@@ -172,6 +177,11 @@ class InferenceEngine:
last_error_code = "LLM_PARSE_ERROR"
# 带错误信息重试
base_rendered = base_rendered + f"\n\n上次解析失败:{exc}。请重新输出合法 JSON。"
except jsonschema.ValidationError as exc:
last_error = f"校验失败: {exc.message}"
last_error_code = "LLM_PARSE_ERROR"
# 带校验错误信息重试
base_rendered = base_rendered + f"\n\n上次校验失败:{exc.message}。请重新输出符合 JSON Schema 的 JSON。"
except LLMError as exc:
return StructuredResult(
data={}, raw_text="", parse_attempts=attempts,
+43
View File
@@ -318,6 +318,49 @@ def test_chat_structured_empty_schema_no_hint():
assert r.status == "ok" and r.data == {"v": True}
# ---------- T1: chat_structured 真 schema 校验(jsonschema ----------
def test_chat_structured_schema_violation_retries():
"""返回不合 schema 的 JSON 时带错误信息重试;第二次合法 → ok。"""
client = FakeLLMClient([("ok", '{"a": "not_a_number"}'), ("ok", '{"a": 2}')])
eng = make_engine(client)
r = eng.chat_structured(
session_id="s1", prompt=Prompt(name="p", version="v1", template="提取"),
variables={},
schema={"type": "object", "properties": {"a": {"type": "number"}}, "required": ["a"]},
)
assert r.status == "ok" and r.data == {"a": 2} and r.parse_attempts == 2
# 第二次调用带上次校验错误信息(重试提示)
assert "校验失败" in client.calls[1]["messages"][0]
def test_chat_structured_schema_violation_parse_error():
"""全部返回不合 schema 的 JSON → parse_errorerror 含校验详情。"""
client = FakeLLMClient([("ok", '{"a": "bad"}'), ("ok", '{"a": "bad"}')])
eng = make_engine(client)
r = eng.chat_structured(
session_id="s1", prompt=Prompt(name="p", version="v1", template="提取"),
variables={},
schema={"type": "object", "properties": {"a": {"type": "number"}}, "required": ["a"]},
retry_count=1,
)
assert r.status == "parse_error"
assert "校验失败" in (r.error or "")
assert r.parse_attempts == 2
def test_chat_structured_schema_valid_passes_without_retry():
"""返回合法 JSON 时一次通过,不触发重试。"""
client = FakeLLMClient([("ok", '{"a": 1}')])
eng = make_engine(client)
r = eng.chat_structured(
session_id="s1", prompt=Prompt(name="p", version="v1", template="提取"),
variables={},
schema={"type": "object", "properties": {"a": {"type": "number"}}, "required": ["a"]},
)
assert r.status == "ok" and r.data == {"a": 1} and r.parse_attempts == 1
def test_chat_truncation_callback_returns_none_keeps_variables():
"""truncate_cb 返回 None 时回退原 variables(覆盖 new_vars is None 分支)。"""
def truncate_cb(prompt_text, variables):