Compare commits

...

6 Commits

Author SHA1 Message Date
pzhang_zywl efb5ed481e fix: step3 _normalize_rule 处理 section 为 list 的 LLM 格式问题 - Closes #69
CI / test (pull_request) Successful in 9s
LLM 输出 section 字段有时为 list 而非 string,导致 .strip() 崩溃。
添加 _clean_section() 将 list→首元素 string,空 list 回退到 rule path。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 17:44:56 +08:00
pzhang_zywl e54a221f34 Merge pull request 'fix: [test] conftest ir_data fixture 防御 LLM 产出的 list-type section - Closes #70' (#71) from test/issue-70 into main
CI / test (push) Successful in 8s
2026-06-02 17:38:31 +08:00
pzhang_zywl 473a3c8d4f test: conftest ir_data 防御 list-type section + normalize 异常回退 - Closes #70
CI / test (pull_request) Successful in 7s
2026-06-02 17:37:47 +08:00
pzhang_zywl 5f094a9a48 Merge pull request 'fix: [product] Dev-Agent PR 前必须跑完整 e2e pipeline 验收 - 防止修复回归 - Closes #67' (#68) from dev/issue-67-pr-e2e-gate into main
CI / test (push) Successful in 14s
2026-06-02 17:35:16 +08:00
pzhang_zywl 7c02db907b feat: Dev-Agent PR 前加入 e2e pipeline 验收步骤 - Closes #67
CI / test (pull_request) Successful in 7s
开发流程新增步骤 5-6:运行完整 pipeline + e2e 验收 (Layer A+B+C),
防止修复引入回归。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-02 17:34:39 +08:00
pzhang_zywl d682f64c01 Merge pull request 'fix: [bug] IR Layer A 仍失败: rules[56] 空 sources + Layer C QE Audit 100% 不合格 - 来自 #18 - Closes #64' (#65) from dev/issue-64-fix-empty-sources into main
CI / test (push) Successful in 13s
2026-06-02 17:25:59 +08:00
4 changed files with 60 additions and 5 deletions
+6 -3
View File
@@ -126,9 +126,11 @@ python scripts/agent_poller.py --action get --issue N
1. git pull origin main 1. git pull origin main
2. git checkout -b dev/issue-N-<slug> 2. git checkout -b dev/issue-N-<slug>
3. 修改功能代码 + 更新/补充 UT 和接口集成测试 3. 修改功能代码 + 更新/补充 UT 和接口集成测试
4. python -m pytest -v # 本地全量测试 4. python -m pytest -v # 本地全量 UT/集成测试
5. git commit -m "fix: <描述> - Closes #N" 5. python scripts/run_pipeline.py --input "input/<文档>.docx" # 运行完整 pipeline
6. git push origin dev/issue-N-<slug> 6. python -m pytest tests/acceptance/ -v --run-acceptance # e2e 验收 (Layer A+B+C)
7. git commit -m "fix: <描述> - Closes #N"
8. git push origin dev/issue-N-<slug>
``` ```
**开发原则:** **开发原则:**
@@ -137,6 +139,7 @@ python scripts/agent_poller.py --action get --issue N
- 关注 IR 一致性:对同一输入的多次运行结果应尽量稳定 - 关注 IR 一致性:对同一输入的多次运行结果应尽量稳定
- 关注功能覆盖率:确保 IR 覆盖了输入文档中的功能点 - 关注功能覆盖率:确保 IR 覆盖了输入文档中的功能点
- **验证是实际功能验证,不是 dry-run**:`pytest` 通过只是门槛,必须用真实输入文档实际运行 pipeline 确认功能生效 - **验证是实际功能验证,不是 dry-run**:`pytest` 通过只是门槛,必须用真实输入文档实际运行 pipeline 确认功能生效
- **PR 前必须通过 e2e 验收 (Layer A+B+C)**:防止修复引入回归。若无法运行完整 pipeline(API 不可用等),至少在 PR 描述中注明
### 4. 提交 PR ### 4. 提交 PR
@@ -174,11 +174,25 @@ def _normalize_rule(rule: dict) -> dict:
sources = rule.get("sources", []) sources = rule.get("sources", [])
valid_types = {"table", "text", "logic_tree"} valid_types = {"table", "text", "logic_tree"}
def _clean_section(val):
"""Normalize section value: list→first element, ensure string."""
if isinstance(val, list):
return str(val[0]).strip() if val else ""
if isinstance(val, str):
return val.strip()
return str(val).strip() if val else ""
# Normalize section fields that might be lists (LLM format instability)
for s in sources:
sec = s.get("section")
if sec is not None:
s["section"] = _clean_section(sec)
# try to infer a default section from the rule path # try to infer a default section from the rule path
default_section = "" default_section = ""
for s in sources: for s in sources:
sec = s.get("section", "") sec = s.get("section", "")
if sec and sec.strip(): if sec and isinstance(sec, str) and sec.strip():
default_section = sec.strip() default_section = sec.strip()
break break
if not default_section: if not default_section:
@@ -538,3 +538,28 @@ class TestNormalizeRule:
assert len(normalized["sources"]) == 1 assert len(normalized["sources"]) == 1
assert normalized["sources"][0]["type"] == "text" assert normalized["sources"][0]["type"] == "text"
assert normalized["sources"][0]["section"] == "3.1 策略" assert normalized["sources"][0]["section"] == "3.1 策略"
def test_normalize_section_is_list(self):
"""Section field that is a list (LLM format bug) is normalized to string."""
rule = {
"trigger": {"conditions": [{"signal": "x", "operator": "==", "value": "1"}]},
"sources": [
{"type": "table", "section": ["状态", "系统设置"], "row": 1},
{"type": "text", "section": ["后台限制"], "text_snippet": "x"},
],
}
normalized = _normalize_rule(rule)
assert normalized["sources"][0]["section"] == "状态"
assert normalized["sources"][1]["section"] == "后台限制"
def test_normalize_section_is_empty_list(self):
"""Empty list section falls back to rule path."""
rule = {
"trigger": {"conditions": [{"signal": "x", "operator": "==", "value": "1"}]},
"path": "4.2 关闭流程 > decision",
"sources": [
{"type": "table", "section": [], "row": 1},
],
}
normalized = _normalize_rule(rule)
assert normalized["sources"][0]["section"] == "4.2 关闭流程"
+14 -1
View File
@@ -150,7 +150,20 @@ def ir_data(ir_path: str) -> dict:
from step3_merge_and_audit import _normalize_rule from step3_merge_and_audit import _normalize_rule
rules = data.get("rules", []) rules = data.get("rules", [])
if rules: if rules:
data["rules"] = [_normalize_rule(r) for r in rules] normalized = []
for i, r in enumerate(rules):
if not isinstance(r, dict):
continue # Skip non-dict entries defensively
# Defensive: flatten list-type section fields (LLM produces these sometimes)
for src in r.get("sources", []):
sec = src.get("section")
if isinstance(sec, list):
src["section"] = sec[0] if sec else ""
try:
normalized.append(_normalize_rule(r))
except Exception:
normalized.append(r) # Fallback: use raw rule if normalize crashes
data["rules"] = normalized
return data return data