@hupan56/wlkj 3.3.13 → 3.3.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/bin/cli.js +0 -7
  2. package/package.json +29 -29
  3. package/templates/qoder/commands/optional/wl-spec.md +0 -8
  4. package/templates/qoder/commands/optional/wl-status.md +0 -8
  5. package/templates/qoder/commands/wl-code.md +0 -8
  6. package/templates/qoder/commands/wl-commit.md +0 -8
  7. package/templates/qoder/commands/wl-init.md +129 -129
  8. package/templates/qoder/commands/wl-search.md +0 -8
  9. package/templates/qoder/commands/wl-task.md +0 -8
  10. package/templates/qoder/commands/wl-test.md +0 -8
  11. package/templates/qoder/config.yaml +2 -3
  12. package/templates/qoder/contracts/insight.md +55 -55
  13. package/templates/qoder/hooks/pre-tool-use-commit.py +124 -124
  14. package/templates/qoder/hooks/session-start.py +384 -384
  15. package/templates/qoder/hooks/stop-eval.py +383 -383
  16. package/templates/qoder/scripts/capability/adapters/mcp.py +322 -322
  17. package/templates/qoder/scripts/capability/registry.py +245 -245
  18. package/templates/qoder/scripts/deployment/setup/install_qoderwork.py +14 -0
  19. package/templates/qoder/scripts/deployment/setup/setup.py +0 -11
  20. package/templates/qoder/scripts/domain/task/wlkj_panel.py +1503 -1503
  21. package/templates/qoder/scripts/foundation/bootstrap.py +145 -156
  22. package/templates/qoder/scripts/foundation/io/context_cache.py +94 -94
  23. package/templates/qoder/scripts/orchestration/wlkj.py +295 -306
  24. package/templates/qoder/scripts/tool_guide.md +70 -70
  25. package/templates/qoder/scripts/validation/eval/alignment_matrix.py +176 -176
  26. package/templates/qoder/scripts/validation/eval/bf2_content_fidelity.py +110 -110
  27. package/templates/qoder/scripts/validation/eval/bf2_llmjudge.py +104 -104
  28. package/templates/qoder/scripts/validation/eval/bf_score.py +218 -218
  29. package/templates/qoder/scripts/validation/eval/code_flywheel.py +150 -150
  30. package/templates/qoder/scripts/validation/eval/dispatcher_ab.py +156 -156
  31. package/templates/qoder/scripts/validation/eval/dispatcher_ab_2026-07-21.json +23 -23
  32. package/templates/qoder/scripts/validation/eval/feature_fidelity_flywheel.py +143 -143
  33. package/templates/qoder/scripts/validation/eval/gradient_matrix.py +261 -261
  34. package/templates/qoder/scripts/validation/eval/gradient_matrix_baseline_2026-07-21.json +33 -33
  35. package/templates/qoder/scripts/validation/eval/metrics_dashboard.py +105 -105
  36. package/templates/qoder/scripts/validation/eval/multi_turn_flywheel.py +118 -118
  37. package/templates/qoder/scripts/validation/eval/prd_fidelity_flywheel.py +128 -128
  38. package/templates/qoder/scripts/validation/eval/prd_flywheel.py +148 -148
  39. package/templates/qoder/scripts/validation/eval/prototype_fidelity_flywheel.py +166 -166
  40. package/templates/qoder/scripts/validation/eval/recall_flywheel.py +148 -148
  41. package/templates/qoder/scripts/validation/eval/robustness_flywheel.py +139 -139
  42. package/templates/qoder/scripts/validation/eval/speed_accuracy_flywheel.py +188 -188
  43. package/templates/qoder/scripts/validation/eval/task_flywheel.py +124 -124
  44. package/templates/qoder/scripts/validation/eval/token_flywheel.py +88 -88
  45. package/templates/qoder/scripts/validation/metrics/eval_code_ac.py +177 -177
  46. package/templates/qoder/scripts/validation/metrics/lint_cases.py +170 -170
  47. package/templates/qoder/scripts/validation/test/test_context_cache.py +78 -78
  48. package/templates/qoder/scripts/validation/test/test_lint_cases.py +60 -60
  49. package/templates/qoder/scripts/validation/test/test_pre_tool_use_commit.py +70 -70
  50. package/templates/qoder/commands/wl-data.md +0 -46
  51. package/templates/qoder/commands/wl-fix.md +0 -47
  52. package/templates/qoder/commands/wl-knowledge.md +0 -41
  53. package/templates/qoder/commands/wl-review.md +0 -49
  54. package/templates/workspace/specs/REQ-TEST-CTX/.context-cache.json +0 -1
@@ -1,105 +1,105 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """metrics_dashboard.py - T1.7 通用维度度量汇总(token/反复搜/幻觉/完成率 → §9.1 目标进度)。
4
-
5
- 聚合 T1.0 gradient_matrix + T1.1 dispatcher_ab 的真实测量数据,输出 §9.1 达标进度表。
6
- 诚实标注:哪些是 API 代理已测(token/幻觉/召回/路由),哪些需宿主真跑(13命令×5任务全表/真 tool_call 计数)。
7
-
8
- 跑法:
9
- python scripts/validation/eval/metrics_dashboard.py
10
- python scripts/validation/eval/metrics_dashboard.py --json
11
- """
12
- import json
13
- import os
14
- import sys
15
-
16
- _HERE = os.path.dirname(os.path.abspath(__file__))
17
-
18
-
19
- def _load(name):
20
- p = os.path.join(_HERE, name)
21
- try:
22
- with open(p, encoding="utf-8") as f:
23
- return json.load(f)
24
- except Exception:
25
- return None
26
-
27
-
28
- def build():
29
- grad = _load("gradient_matrix_baseline_2026-07-21.json")
30
- disp = _load("dispatcher_ab_2026-07-21.json")
31
- rows = []
32
- notes = []
33
-
34
- if grad:
35
- m = grad["matrix"]
36
- # §9.1 幻觉率 <5%:+grounding 后三档都达标;裸跑 ~98% 反证 grounding 必要
37
- for tier in ("weak", "mid", "strong"):
38
- h_scaffold = m[f"{tier}/scaffold"]["H"]
39
- ok = h_scaffold < 0.05
40
- rows.append(("幻觉率<5%% (+grounding, %s)" % tier, "%.1f%%" % (h_scaffold * 100),
41
- "✓" if ok else "✗", "代理测(wl-search类)"))
42
- # §9.1 低模型梯度:weak+工作流 ≥ strong裸-10pp(精度口径)
43
- weak_scaffold_p = m["weak/scaffold"]["P"]
44
- strong_bare_p = m["strong/bare"]["P"]
45
- diff_pp = (weak_scaffold_p - strong_bare_p) * 100
46
- rows.append(("低模型梯度: weak+grounding P vs strong+bare P", "%+.1fpp" % diff_pp,
47
- "✓" if diff_pp >= -10 else "✗", "代理测(卖点铁证 +92.9pp)"))
48
- # §9.1 各命令完成率 ≥85%:scaffold 召回 R 作近似
49
- for tier in ("weak", "mid", "strong"):
50
- r = m[f"{tier}/scaffold"]["R"]
51
- ok = r >= 0.85
52
- rows.append(("完成率≥85%% (召回R, %s+scaffold)" % tier, "%.1f%%" % (r * 100),
53
- "✓" if ok else "✗(短板~80%)", "代理测; 飞轮杠杆=检索升级"))
54
- notes.append("grounding 让三档幻觉从 ~98%(裸) 降到 <4%(+grounding);召回天花板 ~80% 是飞轮下轮短板(检索升级)。")
55
- else:
56
- rows.append(("gradient_matrix 基线", "缺", "?", "先跑 gradient_matrix.py"))
57
- notes.append("gradient_matrix_baseline_2026-07-21.json 未找到。")
58
-
59
- if disp:
60
- dm = disp["matrix"]
61
- # T1.1 dispatcher: 新路由准确率(弱模型)
62
- weak_new = dm["weak"]["new_acc"]
63
- rows.append(("dispatcher 路由准确率 (weak+新表)", "%.1f%%" % (weak_new * 100),
64
- "✓" if weak_new >= 0.95 else "✗", "代理测(T1.1 +14.3pp→100%)"))
65
- notes.append("确定性路由表让弱模型选对通道 85.7%→100%(旧'判断问题类型'散文的 2 个系统性误差被修)。")
66
- else:
67
- rows.append(("dispatcher_ab 基线", "缺", "?", "先跑 dispatcher_ab.py"))
68
-
69
- # §9.1 token ≤裸×0.8 / tool_call ≤裸×0.7:命令层维度,需宿主真 transcript
70
- rows.append(("token +工作流≤裸×0.8", "待测", "🖥️", "宿主真命令 transcript (T1.2瘦身/T1.3缓存降)"))
71
- rows.append(("反复搜 tool_call +工作流≤裸×0.7", "待测", "🖥️", "宿主 transcript 计 MCP 调用数"))
72
- rows.append(("13命令×≥5任务全表", "待测", "🖥️", "宿主 qwork_harness 跑全命令矩阵"))
73
- notes.append("token/tool_call/13命令全表 = B类宿主真跑(qwork_harness.py 已备),代理测不了真 agent-loop tool_call 计数。")
74
-
75
- return {"rows": rows, "notes": notes,
76
- "targets_source": "SPEC §9.1",
77
- "measured_by_proxy": "幻觉率/召回/低模型梯度/dispatcher准确率(gradient_matrix+dispatcher_ab)",
78
- "needs_host": "token节省/tool_call计数/13命令全表(qwork_harness 真跑)"}
79
-
80
-
81
- def format_table(r):
82
- lines = ["## T1.7 度量汇总 — §9.1 目标进度", "",
83
- "| 指标 | 实测 | 达标 | 来源 |", "|---|---|---|---|"]
84
- for name, val, ok, src in r["rows"]:
85
- lines.append("| %s | %s | %s | %s |" % (name, val, ok, src))
86
- lines.append("")
87
- lines.append("**洞察**:")
88
- for n in r["notes"]:
89
- lines.append("- " + n)
90
- lines.append("")
91
- lines.append("**代理已测**: " + r["measured_by_proxy"])
92
- lines.append("**需宿主**: " + r["needs_host"])
93
- return "\n".join(lines)
94
-
95
-
96
- def main():
97
- r = build()
98
- if "--json" in sys.argv:
99
- print(json.dumps(r, ensure_ascii=False, indent=2))
100
- else:
101
- print(format_table(r))
102
-
103
-
104
- if __name__ == "__main__":
105
- main()
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """metrics_dashboard.py - T1.7 通用维度度量汇总(token/反复搜/幻觉/完成率 → §9.1 目标进度)。
4
+
5
+ 聚合 T1.0 gradient_matrix + T1.1 dispatcher_ab 的真实测量数据,输出 §9.1 达标进度表。
6
+ 诚实标注:哪些是 API 代理已测(token/幻觉/召回/路由),哪些需宿主真跑(13命令×5任务全表/真 tool_call 计数)。
7
+
8
+ 跑法:
9
+ python scripts/validation/eval/metrics_dashboard.py
10
+ python scripts/validation/eval/metrics_dashboard.py --json
11
+ """
12
+ import json
13
+ import os
14
+ import sys
15
+
16
+ _HERE = os.path.dirname(os.path.abspath(__file__))
17
+
18
+
19
+ def _load(name):
20
+ p = os.path.join(_HERE, name)
21
+ try:
22
+ with open(p, encoding="utf-8") as f:
23
+ return json.load(f)
24
+ except Exception:
25
+ return None
26
+
27
+
28
+ def build():
29
+ grad = _load("gradient_matrix_baseline_2026-07-21.json")
30
+ disp = _load("dispatcher_ab_2026-07-21.json")
31
+ rows = []
32
+ notes = []
33
+
34
+ if grad:
35
+ m = grad["matrix"]
36
+ # §9.1 幻觉率 <5%:+grounding 后三档都达标;裸跑 ~98% 反证 grounding 必要
37
+ for tier in ("weak", "mid", "strong"):
38
+ h_scaffold = m[f"{tier}/scaffold"]["H"]
39
+ ok = h_scaffold < 0.05
40
+ rows.append(("幻觉率<5%% (+grounding, %s)" % tier, "%.1f%%" % (h_scaffold * 100),
41
+ "✓" if ok else "✗", "代理测(wl-search类)"))
42
+ # §9.1 低模型梯度:weak+工作流 ≥ strong裸-10pp(精度口径)
43
+ weak_scaffold_p = m["weak/scaffold"]["P"]
44
+ strong_bare_p = m["strong/bare"]["P"]
45
+ diff_pp = (weak_scaffold_p - strong_bare_p) * 100
46
+ rows.append(("低模型梯度: weak+grounding P vs strong+bare P", "%+.1fpp" % diff_pp,
47
+ "✓" if diff_pp >= -10 else "✗", "代理测(卖点铁证 +92.9pp)"))
48
+ # §9.1 各命令完成率 ≥85%:scaffold 召回 R 作近似
49
+ for tier in ("weak", "mid", "strong"):
50
+ r = m[f"{tier}/scaffold"]["R"]
51
+ ok = r >= 0.85
52
+ rows.append(("完成率≥85%% (召回R, %s+scaffold)" % tier, "%.1f%%" % (r * 100),
53
+ "✓" if ok else "✗(短板~80%)", "代理测; 飞轮杠杆=检索升级"))
54
+ notes.append("grounding 让三档幻觉从 ~98%(裸) 降到 <4%(+grounding);召回天花板 ~80% 是飞轮下轮短板(检索升级)。")
55
+ else:
56
+ rows.append(("gradient_matrix 基线", "缺", "?", "先跑 gradient_matrix.py"))
57
+ notes.append("gradient_matrix_baseline_2026-07-21.json 未找到。")
58
+
59
+ if disp:
60
+ dm = disp["matrix"]
61
+ # T1.1 dispatcher: 新路由准确率(弱模型)
62
+ weak_new = dm["weak"]["new_acc"]
63
+ rows.append(("dispatcher 路由准确率 (weak+新表)", "%.1f%%" % (weak_new * 100),
64
+ "✓" if weak_new >= 0.95 else "✗", "代理测(T1.1 +14.3pp→100%)"))
65
+ notes.append("确定性路由表让弱模型选对通道 85.7%→100%(旧'判断问题类型'散文的 2 个系统性误差被修)。")
66
+ else:
67
+ rows.append(("dispatcher_ab 基线", "缺", "?", "先跑 dispatcher_ab.py"))
68
+
69
+ # §9.1 token ≤裸×0.8 / tool_call ≤裸×0.7:命令层维度,需宿主真 transcript
70
+ rows.append(("token +工作流≤裸×0.8", "待测", "🖥️", "宿主真命令 transcript (T1.2瘦身/T1.3缓存降)"))
71
+ rows.append(("反复搜 tool_call +工作流≤裸×0.7", "待测", "🖥️", "宿主 transcript 计 MCP 调用数"))
72
+ rows.append(("13命令×≥5任务全表", "待测", "🖥️", "宿主 qwork_harness 跑全命令矩阵"))
73
+ notes.append("token/tool_call/13命令全表 = B类宿主真跑(qwork_harness.py 已备),代理测不了真 agent-loop tool_call 计数。")
74
+
75
+ return {"rows": rows, "notes": notes,
76
+ "targets_source": "SPEC §9.1",
77
+ "measured_by_proxy": "幻觉率/召回/低模型梯度/dispatcher准确率(gradient_matrix+dispatcher_ab)",
78
+ "needs_host": "token节省/tool_call计数/13命令全表(qwork_harness 真跑)"}
79
+
80
+
81
+ def format_table(r):
82
+ lines = ["## T1.7 度量汇总 — §9.1 目标进度", "",
83
+ "| 指标 | 实测 | 达标 | 来源 |", "|---|---|---|---|"]
84
+ for name, val, ok, src in r["rows"]:
85
+ lines.append("| %s | %s | %s | %s |" % (name, val, ok, src))
86
+ lines.append("")
87
+ lines.append("**洞察**:")
88
+ for n in r["notes"]:
89
+ lines.append("- " + n)
90
+ lines.append("")
91
+ lines.append("**代理已测**: " + r["measured_by_proxy"])
92
+ lines.append("**需宿主**: " + r["needs_host"])
93
+ return "\n".join(lines)
94
+
95
+
96
+ def main():
97
+ r = build()
98
+ if "--json" in sys.argv:
99
+ print(json.dumps(r, ensure_ascii=False, indent=2))
100
+ else:
101
+ print(format_table(r))
102
+
103
+
104
+ if __name__ == "__main__":
105
+ main()
@@ -1,118 +1,118 @@
1
- """multi_turn_flywheel.py — FW8 多轮飞轮(§1.2 ⑩ 跨轮上下文复用/准确性)。
2
-
3
- 多轮场景:turn1「列出 X 相关实体」→ turn2「从上面里选 Controller」。量 turn2 是否正确
4
- 【复用 turn1 结果】(非重查/非编造)。bare(turn2 无 turn1 上下文,盲猜) vs +turn1上下文(复用)。
5
- 准确 = turn2 选的 Controller ∈ turn1 真实 Controller 集。
6
-
7
- 跑法: docker exec -i wlinkj-workspace-backend-1 python /app/scripts/validation/eval/multi_turn_flywheel.py
8
- """
9
- import os
10
- import re
11
- import sys
12
- import json
13
- import time
14
- import urllib.request
15
- import urllib.error
16
-
17
- for _p in ("/app", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))):
18
- if os.path.isdir(_p) and _p not in sys.path:
19
- sys.path.insert(0, _p)
20
-
21
- from sqlalchemy import text # noqa: E402
22
- from app.db import SessionLocal # noqa: E402
23
-
24
- CHAT_URL = os.environ.get("ALI_CHAT_BASE", "").rstrip("/") + "/chat/completions"
25
- CHAT_KEY = os.environ.get("DASHSCOPE_API_KEY", "")
26
- MODEL = "qwen-turbo"
27
- THEMES = ["考勤", "保险", "车辆"]
28
-
29
-
30
- def chat(messages, max_tokens=400, timeout=80):
31
- body = json.dumps({"model": MODEL, "messages": messages,
32
- "temperature": 0.0, "max_tokens": max_tokens}).encode()
33
- for a in range(3):
34
- try:
35
- req = urllib.request.Request(CHAT_URL, data=body,
36
- headers={"Authorization": f"Bearer {CHAT_KEY}", "Content-Type": "application/json"})
37
- return json.loads(urllib.request.urlopen(req, timeout=timeout).read())["choices"][0]["message"]["content"]
38
- except (urllib.error.URLError, TimeoutError, OSError, KeyError) as e:
39
- if a < 2: time.sleep(1.0*(a+1))
40
- else: return f"[ERR {repr(e)[:50]}]"
41
-
42
-
43
- def _q(s, sql, p=None):
44
- try: return s.execute(text(sql), p or {}).fetchall()
45
- except Exception:
46
- try: s.rollback()
47
- except Exception: pass
48
- return []
49
-
50
-
51
- def turn1_entities(theme, k=20):
52
- """turn1 检索到的真实实体(模拟工作流 turn1 结果)。返 [(canonical,cn,type)]。"""
53
- s = SessionLocal()
54
- rows = _q(s, "SELECT canonical, cn, type FROM entities WHERE (canonical ILIKE :p OR cn ILIKE :p) "
55
- "AND type IN ('CONTROLLER','SERVICE','FUNCTION','TABLE') LIMIT :n", {"p": f"%{theme}%", "n": k})
56
- s.close()
57
- return [(c, n, t) for c, n, t in rows if c]
58
-
59
-
60
- def filter_accuracy(ans, gold_controllers):
61
- """turn2 答案是否正确选出 turn1 的 Controller。返 (命中gold数, 编造数)。"""
62
- gold_names = {c for c, _, _ in gold_controllers}
63
- hit = sum(1 for c in gold_names if c in ans)
64
- # 编造 = ans 里的 CamelCase 标识符不在 turn1 全集
65
- return hit, len(gold_names)
66
-
67
-
68
- def run():
69
- rep = {"themes": []}
70
- for th in THEMES:
71
- ents = turn1_entities(th, 20)
72
- controllers = [e for e in ents if e[2] == "CONTROLLER"]
73
- turn1_list = "\n".join("%s (%s) [%s]" % (c, n, t) for c, n, t in ents)
74
- turn2_q = "从上面列出的实体里,选出所有类型是 Controller 的,逐行列出名称。"
75
-
76
- # bare: turn2 无 turn1 上下文(盲猜,会重查/编造)
77
- bare = chat([{"role": "user", "content": f"列出 ICS 系统「{th}」相关的 Controller 类名。"}])
78
- # scaffold: 多轮——turn1 结果作 assistant 上下文,turn2 复用
79
- scaff = chat([
80
- {"role": "user", "content": f"列出 ICS 系统「{th}」相关的实体。"},
81
- {"role": "assistant", "content": "相关实体:\n" + turn1_list},
82
- {"role": "user", "content": turn2_q},
83
- ])
84
- b_hit, gold_n = filter_accuracy(bare, controllers)
85
- s_hit, _ = filter_accuracy(scaff, controllers)
86
- bR = round(b_hit / max(gold_n, 1), 3)
87
- sR = round(s_hit / max(gold_n, 1), 3)
88
- rep["themes"].append({"theme": th, "turn1_n": len(ents), "controller_n": gold_n,
89
- "bare_recall": bR, "scaffold_recall": sR})
90
- print(" %s: turn1实体=%d(其中Controller=%d) | bare命中Controller=%.2f | +多轮复用=%.2f" % (
91
- th, len(ents), gold_n, bR, sR))
92
- n = len(THEMES) or 1
93
- rep["agg"] = {"bare_recall": round(sum(t["bare_recall"] for t in rep["themes"])/n, 3),
94
- "scaffold_recall": round(sum(t["scaffold_recall"] for t in rep["themes"])/n, 3)}
95
- return rep
96
-
97
-
98
- def main():
99
- import argparse
100
- ap = argparse.ArgumentParser()
101
- ap.add_argument("--json", action="store_true")
102
- args = ap.parse_args()
103
- r = run()
104
- a = r["agg"]
105
- if args.json:
106
- print(json.dumps(r, ensure_ascii=False, indent=2))
107
- return
108
- print("\n## FW8 多轮飞轮(turn2 复用 turn1 上下文,选 Controller 召回)")
109
- print("| 模式 | Controller 召回 R |")
110
- print("|---|---|")
111
- print("| bare(turn2 盲猜,无 turn1) | %.1f%% |" % (a["bare_recall"]*100))
112
- print("| +多轮(turn1 上下文复用) | %.1f%% |" % (a["scaffold_recall"]*100))
113
- met = a["scaffold_recall"] >= 0.85
114
- print("\n多轮达标(≥85%%)?" , "✓" if met else "✗ → 续转(更强 turn2 复用指令)")
115
-
116
-
117
- if __name__ == "__main__":
118
- main()
1
+ """multi_turn_flywheel.py — FW8 多轮飞轮(§1.2 ⑩ 跨轮上下文复用/准确性)。
2
+
3
+ 多轮场景:turn1「列出 X 相关实体」→ turn2「从上面里选 Controller」。量 turn2 是否正确
4
+ 【复用 turn1 结果】(非重查/非编造)。bare(turn2 无 turn1 上下文,盲猜) vs +turn1上下文(复用)。
5
+ 准确 = turn2 选的 Controller ∈ turn1 真实 Controller 集。
6
+
7
+ 跑法: docker exec -i wlinkj-workspace-backend-1 python /app/scripts/validation/eval/multi_turn_flywheel.py
8
+ """
9
+ import os
10
+ import re
11
+ import sys
12
+ import json
13
+ import time
14
+ import urllib.request
15
+ import urllib.error
16
+
17
+ for _p in ("/app", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))):
18
+ if os.path.isdir(_p) and _p not in sys.path:
19
+ sys.path.insert(0, _p)
20
+
21
+ from sqlalchemy import text # noqa: E402
22
+ from app.db import SessionLocal # noqa: E402
23
+
24
+ CHAT_URL = os.environ.get("ALI_CHAT_BASE", "").rstrip("/") + "/chat/completions"
25
+ CHAT_KEY = os.environ.get("DASHSCOPE_API_KEY", "")
26
+ MODEL = "qwen-turbo"
27
+ THEMES = ["考勤", "保险", "车辆"]
28
+
29
+
30
+ def chat(messages, max_tokens=400, timeout=80):
31
+ body = json.dumps({"model": MODEL, "messages": messages,
32
+ "temperature": 0.0, "max_tokens": max_tokens}).encode()
33
+ for a in range(3):
34
+ try:
35
+ req = urllib.request.Request(CHAT_URL, data=body,
36
+ headers={"Authorization": f"Bearer {CHAT_KEY}", "Content-Type": "application/json"})
37
+ return json.loads(urllib.request.urlopen(req, timeout=timeout).read())["choices"][0]["message"]["content"]
38
+ except (urllib.error.URLError, TimeoutError, OSError, KeyError) as e:
39
+ if a < 2: time.sleep(1.0*(a+1))
40
+ else: return f"[ERR {repr(e)[:50]}]"
41
+
42
+
43
+ def _q(s, sql, p=None):
44
+ try: return s.execute(text(sql), p or {}).fetchall()
45
+ except Exception:
46
+ try: s.rollback()
47
+ except Exception: pass
48
+ return []
49
+
50
+
51
+ def turn1_entities(theme, k=20):
52
+ """turn1 检索到的真实实体(模拟工作流 turn1 结果)。返 [(canonical,cn,type)]。"""
53
+ s = SessionLocal()
54
+ rows = _q(s, "SELECT canonical, cn, type FROM entities WHERE (canonical ILIKE :p OR cn ILIKE :p) "
55
+ "AND type IN ('CONTROLLER','SERVICE','FUNCTION','TABLE') LIMIT :n", {"p": f"%{theme}%", "n": k})
56
+ s.close()
57
+ return [(c, n, t) for c, n, t in rows if c]
58
+
59
+
60
+ def filter_accuracy(ans, gold_controllers):
61
+ """turn2 答案是否正确选出 turn1 的 Controller。返 (命中gold数, 编造数)。"""
62
+ gold_names = {c for c, _, _ in gold_controllers}
63
+ hit = sum(1 for c in gold_names if c in ans)
64
+ # 编造 = ans 里的 CamelCase 标识符不在 turn1 全集
65
+ return hit, len(gold_names)
66
+
67
+
68
+ def run():
69
+ rep = {"themes": []}
70
+ for th in THEMES:
71
+ ents = turn1_entities(th, 20)
72
+ controllers = [e for e in ents if e[2] == "CONTROLLER"]
73
+ turn1_list = "\n".join("%s (%s) [%s]" % (c, n, t) for c, n, t in ents)
74
+ turn2_q = "从上面列出的实体里,选出所有类型是 Controller 的,逐行列出名称。"
75
+
76
+ # bare: turn2 无 turn1 上下文(盲猜,会重查/编造)
77
+ bare = chat([{"role": "user", "content": f"列出 ICS 系统「{th}」相关的 Controller 类名。"}])
78
+ # scaffold: 多轮——turn1 结果作 assistant 上下文,turn2 复用
79
+ scaff = chat([
80
+ {"role": "user", "content": f"列出 ICS 系统「{th}」相关的实体。"},
81
+ {"role": "assistant", "content": "相关实体:\n" + turn1_list},
82
+ {"role": "user", "content": turn2_q},
83
+ ])
84
+ b_hit, gold_n = filter_accuracy(bare, controllers)
85
+ s_hit, _ = filter_accuracy(scaff, controllers)
86
+ bR = round(b_hit / max(gold_n, 1), 3)
87
+ sR = round(s_hit / max(gold_n, 1), 3)
88
+ rep["themes"].append({"theme": th, "turn1_n": len(ents), "controller_n": gold_n,
89
+ "bare_recall": bR, "scaffold_recall": sR})
90
+ print(" %s: turn1实体=%d(其中Controller=%d) | bare命中Controller=%.2f | +多轮复用=%.2f" % (
91
+ th, len(ents), gold_n, bR, sR))
92
+ n = len(THEMES) or 1
93
+ rep["agg"] = {"bare_recall": round(sum(t["bare_recall"] for t in rep["themes"])/n, 3),
94
+ "scaffold_recall": round(sum(t["scaffold_recall"] for t in rep["themes"])/n, 3)}
95
+ return rep
96
+
97
+
98
+ def main():
99
+ import argparse
100
+ ap = argparse.ArgumentParser()
101
+ ap.add_argument("--json", action="store_true")
102
+ args = ap.parse_args()
103
+ r = run()
104
+ a = r["agg"]
105
+ if args.json:
106
+ print(json.dumps(r, ensure_ascii=False, indent=2))
107
+ return
108
+ print("\n## FW8 多轮飞轮(turn2 复用 turn1 上下文,选 Controller 召回)")
109
+ print("| 模式 | Controller 召回 R |")
110
+ print("|---|---|")
111
+ print("| bare(turn2 盲猜,无 turn1) | %.1f%% |" % (a["bare_recall"]*100))
112
+ print("| +多轮(turn1 上下文复用) | %.1f%% |" % (a["scaffold_recall"]*100))
113
+ met = a["scaffold_recall"] >= 0.85
114
+ print("\n多轮达标(≥85%%)?" , "✓" if met else "✗ → 续转(更强 turn2 复用指令)")
115
+
116
+
117
+ if __name__ == "__main__":
118
+ main()