@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,261 +1,261 @@
1
- """gradient_matrix.py — T1.0 低模型梯度评测尺子(飞轮 ruler)。
2
-
3
- 证卖点:工作流的 KG grounding 让【弱模型】不编造、召回真实体,追上强模型裸跑。
4
-
5
- 矩阵 = {弱/中/强模型} × {裸, +grounding},每格跨 10 个业务主题算:
6
- - grounding 精度(P) : 模型列出的"具体名称"里有多少是真 KG 实体(反幻觉)
7
- - 召回(R) : gold 实体被答出多少
8
- - 幻觉率(H=1-P) : 编造的占比
9
- - token : 单次总 token
10
-
11
- 跑法(容器内):
12
- docker exec -i wlinkj-workspace-backend-1 python /app/scripts/validation/eval/gradient_matrix.py
13
- ... --themes 考勤,排班 --json # 子集/机器可读
14
- """
15
- import os
16
- import re
17
- import sys
18
- import json
19
- import time
20
- import urllib.request
21
- import urllib.error
22
-
23
- # 容器内 /app 在 sys.path(from app.db import)
24
- for _p in ("/app", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))):
25
- if os.path.isdir(_p) and _p not in sys.path:
26
- sys.path.insert(0, _p)
27
-
28
- from sqlalchemy import text # noqa: E402
29
- from app.db import SessionLocal # noqa: E402
30
-
31
- # ── 配置 ─────────────────────────────────────────────────────────────
32
- CHAT_URL = os.environ.get("ALI_CHAT_BASE", "").rstrip("/") + "/chat/completions"
33
- CHAT_KEY = os.environ.get("DASHSCOPE_API_KEY", "")
34
- MODELS = {"weak": "qwen-turbo", "mid": "qwen-plus", "strong": "qwen-max"}
35
- DEFAULT_THEMES = ["考勤", "排班", "加班", "车辆", "报警", "保险", "能耗", "预算", "导出", "审批"]
36
- RETRIEVE_K = 45 # +grounding 喂的检索条数(FW1: 30→45 覆盖 gold-40,解 R 封顶 30/40=0.75)
37
- SYS = "你是车辆物联网系统(ICS)的代码/业务知识助手。只回答你确信真实存在的系统对象,绝不编造名称。"
38
-
39
-
40
- def chat(model, prompt, max_tokens=600, timeout=90):
41
- """同步 chat,返 (text, total_tokens)。失败重试 3 次。"""
42
- body = json.dumps({
43
- "model": model,
44
- "messages": [{"role": "system", "content": SYS},
45
- {"role": "user", "content": prompt}],
46
- "temperature": 0.0,
47
- "max_tokens": max_tokens,
48
- }).encode()
49
- for attempt in range(3):
50
- try:
51
- req = urllib.request.Request(
52
- CHAT_URL, data=body,
53
- headers={"Authorization": f"Bearer {CHAT_KEY}", "Content-Type": "application/json"})
54
- resp = urllib.request.urlopen(req, timeout=timeout)
55
- d = json.loads(resp.read())
56
- return d["choices"][0]["message"]["content"], int(d.get("usage", {}).get("total_tokens", 0))
57
- except (urllib.error.URLError, TimeoutError, OSError, KeyError) as e:
58
- if attempt < 2:
59
- time.sleep(1.0 * (attempt + 1))
60
- else:
61
- return f"[ERR {repr(e)[:80]}]", 0
62
-
63
-
64
- def load_universe_and_gold(themes):
65
- """载入实体名称全集(精度校验) + 每主题 gold 实体(召回校验)。"""
66
- s = SessionLocal()
67
- rows = _q(s, "SELECT canonical, cn FROM entities WHERE canonical IS NOT NULL")
68
- UNI = set()
69
- for canon, cn in rows:
70
- if canon:
71
- UNI.add(canon.strip())
72
- if cn:
73
- UNI.add(cn.strip())
74
- GOLD = {}
75
- for th in themes:
76
- rs = _q(s, "SELECT canonical, cn, type FROM entities "
77
- "WHERE (cn ILIKE :p OR canonical ILIKE :p) LIMIT 40", {"p": f"%{th}%"})
78
- GOLD[th] = [{"canonical": c, "cn": n, "type": t} for c, n, t in rs]
79
- s.close()
80
- return UNI, GOLD
81
-
82
-
83
- def retrieve_entities(theme, k=RETRIEVE_K):
84
- """模拟工作流 KG 检索(wl-search 工具会做的事):返回真实体 dict 列表。"""
85
- s = SessionLocal()
86
- rs = _q(s, "SELECT canonical, cn, type FROM entities "
87
- "WHERE (cn ILIKE :p OR canonical ILIKE :p) LIMIT :k",
88
- {"p": f"%{theme}%", "k": k})
89
- s.close()
90
- return [{"canonical": c, "cn": n, "type": t} for c, n, t in rs]
91
-
92
-
93
- def ctx_from_entities(ents):
94
- """把检索实体格式化成喂给 LLM 的上下文文本。"""
95
- out = []
96
- for e in ents:
97
- c, n, t = e["canonical"], e["cn"], e["type"]
98
- out.append(f"{t}: {c}" + (f" ({n})" if n else ""))
99
- return "\n".join(out)
100
-
101
-
102
- def _q(s, sql, params=None):
103
- try:
104
- return s.execute(text(sql), params or {}).fetchall()
105
- except Exception:
106
- try:
107
- s.rollback()
108
- except Exception:
109
- pass
110
- return []
111
-
112
-
113
- # ── prompt ───────────────────────────────────────────────────────────
114
- def prompt_bare(theme):
115
- return (f"在我们的车辆物联网系统(ICS)里,找出和「{theme}」相关的具体对象"
116
- f"(数据库表/Java类/接口/按钮/页面等)。\n"
117
- f"请逐行列出你【确信真实存在】的具体名称,每行一个,不要解释,不要编造。"
118
- f"不确定就少列。")
119
-
120
-
121
- def prompt_scaffold(theme, ctx):
122
- return (f"在我们的车辆物联网系统(ICS)里,找出和「{theme}」相关的具体对象"
123
- f"(数据库表/Java类/接口/按钮/页面等)。\n"
124
- f"知识图谱已检索到以下相关实体(均为真实存在):\n{ctx}\n\n"
125
- f"请把以上检索结果里的实体【尽量全部逐行列出】,每行一个,"
126
- f"直接用检索结果里的名称,不要自行筛选/合并/省略,不要解释。检索到几个就列几个。")
127
-
128
-
129
- # ── 评分 ─────────────────────────────────────────────────────────────
130
- _LINE_CLEAN = re.compile(r"^[\-\*\d\.\)、\s]+")
131
- _CODE_TOK = re.compile(r"[A-Za-z_][\w./\-:]{2,}")
132
-
133
-
134
- def _clean_lines(ans):
135
- """'每行一个' 答案 → 清洗后的短行列表(模型声称存在的对象,每行一个)。"""
136
- out = []
137
- for l in ans.splitlines():
138
- l = _LINE_CLEAN.sub("", l).strip()
139
- if l and len(l) <= 60:
140
- out.append(l)
141
- return out
142
-
143
-
144
- def score(ans, gold, UNI, ground_cn):
145
- """grounding 精度:每行算'真实'当其含某真代码标识符(∈UNI) 或 命中本格 ground_cn(中文)。
146
- ground_cn = 该格可作'真实'依据的中文名集合(bare=gold, scaffold=retrieved),bare/scaffold 同口径公平。
147
- """
148
- lines = _clean_lines(ans)
149
- real_n = 0
150
- for l in lines:
151
- if set(_CODE_TOK.findall(l)) & UNI:
152
- real_n += 1
153
- elif any(g and g in l for g in ground_cn):
154
- real_n += 1
155
- precision = (real_n / len(lines)) if lines else 0.0
156
- hit = 0
157
- for g in gold:
158
- c = g.get("canonical") or ""
159
- n = g.get("cn") or ""
160
- if (c and c in ans) or (n and n in ans):
161
- hit += 1
162
- recall = (hit / len(gold)) if gold else 0.0
163
- return {
164
- "precision": round(precision, 3),
165
- "recall": round(recall, 3),
166
- "halluc": round(1 - precision, 3),
167
- "n_lines": len(lines),
168
- "n_real": real_n,
169
- "gold_hit": hit,
170
- "gold_n": len(gold),
171
- }
172
-
173
-
174
- def aggregate(rows):
175
- n = len(rows) or 1
176
- return {
177
- "P": round(sum(r["precision"] for r in rows) / n, 3),
178
- "R": round(sum(r["recall"] for r in rows) / n, 3),
179
- "H": round(sum(r["halluc"] for r in rows) / n, 3),
180
- "tokens": round(sum(r["tokens"] for r in rows) / n, 0),
181
- "n": len(rows),
182
- }
183
-
184
-
185
- def run(themes, verbose=True):
186
- def log(*a):
187
- if verbose:
188
- print(*a)
189
-
190
- if not CHAT_KEY:
191
- raise RuntimeError("缺 DASHSCOPE_API_KEY")
192
- log("[ruler] 载入 KG 全集 + gold…")
193
- UNI, GOLD = load_universe_and_gold(themes)
194
- log(f"[ruler] 实体名称全集 {len(UNI)} 条")
195
- for th in themes:
196
- log(f" theme {th}: gold={len(GOLD[th])}")
197
-
198
- matrix = {}
199
- t0 = time.time()
200
- for tier, model in MODELS.items():
201
- for mode in ("bare", "scaffold"):
202
- rows = []
203
- for th in themes:
204
- gold = GOLD[th]
205
- if mode == "bare":
206
- ground_cn = {g["canonical"] for g in gold} | {g["cn"] for g in gold if g["cn"]}
207
- p = prompt_bare(th)
208
- else:
209
- ents = retrieve_entities(th)
210
- ground_cn = {e["canonical"] for e in ents} | {e["cn"] for e in ents if e["cn"]}
211
- p = prompt_scaffold(th, ctx_from_entities(ents))
212
- ans, tok = chat(model, p)
213
- sc = score(ans, gold, UNI, ground_cn)
214
- sc["tokens"] = tok
215
- sc["theme"] = th
216
- rows.append(sc)
217
- log(f" [{tier}/{mode}] {th}: P={sc['precision']:.2f} R={sc['recall']:.2f} "
218
- f"H={sc['halluc']:.2f} tok={tok} lines={sc['n_lines']}")
219
- matrix[f"{tier}/{mode}"] = aggregate(rows)
220
- log(f"[{tier}/{mode}] agg P={matrix[f'{tier}/{mode}']['P']:.3f} "
221
- f"R={matrix[f'{tier}/{mode}']['R']:.3f} H={matrix[f'{tier}/{mode}']['H']:.3f} "
222
- f"tok={matrix[f'{tier}/{mode}']['tokens']:.0f}")
223
- log(f"[ruler] 总耗时 {int(time.time()-t0)}s")
224
- return {"matrix": matrix, "themes": themes, "models": MODELS}
225
-
226
-
227
- def format_table(res):
228
- m = res["matrix"]
229
- lines = ["## 低模型梯度矩阵(grounding 精度P / 召回R / 幻觉H / token)",
230
- "", "| 模型档 | 裸 P | 裸 R | 裸 H | 裸tok | +grounding P | +grounding R | +grounding H | +ground tok |",
231
- "|---|---|---|---|---|---|---|---|---|"]
232
- for tier in MODELS:
233
- b = m[f"{tier}/bare"]
234
- f = m[f"{tier}/scaffold"]
235
- lines.append(f"| {tier}({MODELS[tier]}) | {b['P']:.2f} | {b['R']:.2f} | {b['H']:.2f} | {b['tokens']:.0f} | "
236
- f"{f['P']:.2f} | {f['R']:.2f} | {f['H']:.2f} | {f['tokens']:.0f} |")
237
- # 卖点提炼
238
- lines.append("")
239
- weak_gain_p = m["weak/scaffold"]["P"] - m["weak/bare"]["P"]
240
- weak_catchup = m["weak/scaffold"]["P"] - m["strong/bare"]["P"]
241
- lines.append(f"- 弱模型 +grounding 精度提升: **{weak_gain_p*100:+.1f}pp**(反幻觉增益)")
242
- lines.append(f"- 弱模型+grounding vs 强模型裸 精度差: **{weak_catchup*100:+.1f}pp**(>0=追上/反超=卖点成立)")
243
- return "\n".join(lines)
244
-
245
-
246
- def main():
247
- import argparse
248
- ap = argparse.ArgumentParser()
249
- ap.add_argument("--themes", default=",".join(DEFAULT_THEMES))
250
- ap.add_argument("--json", action="store_true")
251
- args = ap.parse_args()
252
- themes = [t.strip() for t in args.themes.split(",") if t.strip()]
253
- res = run(themes)
254
- if args.json:
255
- print(json.dumps(res, ensure_ascii=False, indent=2))
256
- else:
257
- print("\n" + format_table(res))
258
-
259
-
260
- if __name__ == "__main__":
261
- main()
1
+ """gradient_matrix.py — T1.0 低模型梯度评测尺子(飞轮 ruler)。
2
+
3
+ 证卖点:工作流的 KG grounding 让【弱模型】不编造、召回真实体,追上强模型裸跑。
4
+
5
+ 矩阵 = {弱/中/强模型} × {裸, +grounding},每格跨 10 个业务主题算:
6
+ - grounding 精度(P) : 模型列出的"具体名称"里有多少是真 KG 实体(反幻觉)
7
+ - 召回(R) : gold 实体被答出多少
8
+ - 幻觉率(H=1-P) : 编造的占比
9
+ - token : 单次总 token
10
+
11
+ 跑法(容器内):
12
+ docker exec -i wlinkj-workspace-backend-1 python /app/scripts/validation/eval/gradient_matrix.py
13
+ ... --themes 考勤,排班 --json # 子集/机器可读
14
+ """
15
+ import os
16
+ import re
17
+ import sys
18
+ import json
19
+ import time
20
+ import urllib.request
21
+ import urllib.error
22
+
23
+ # 容器内 /app 在 sys.path(from app.db import)
24
+ for _p in ("/app", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))):
25
+ if os.path.isdir(_p) and _p not in sys.path:
26
+ sys.path.insert(0, _p)
27
+
28
+ from sqlalchemy import text # noqa: E402
29
+ from app.db import SessionLocal # noqa: E402
30
+
31
+ # ── 配置 ─────────────────────────────────────────────────────────────
32
+ CHAT_URL = os.environ.get("ALI_CHAT_BASE", "").rstrip("/") + "/chat/completions"
33
+ CHAT_KEY = os.environ.get("DASHSCOPE_API_KEY", "")
34
+ MODELS = {"weak": "qwen-turbo", "mid": "qwen-plus", "strong": "qwen-max"}
35
+ DEFAULT_THEMES = ["考勤", "排班", "加班", "车辆", "报警", "保险", "能耗", "预算", "导出", "审批"]
36
+ RETRIEVE_K = 45 # +grounding 喂的检索条数(FW1: 30→45 覆盖 gold-40,解 R 封顶 30/40=0.75)
37
+ SYS = "你是车辆物联网系统(ICS)的代码/业务知识助手。只回答你确信真实存在的系统对象,绝不编造名称。"
38
+
39
+
40
+ def chat(model, prompt, max_tokens=600, timeout=90):
41
+ """同步 chat,返 (text, total_tokens)。失败重试 3 次。"""
42
+ body = json.dumps({
43
+ "model": model,
44
+ "messages": [{"role": "system", "content": SYS},
45
+ {"role": "user", "content": prompt}],
46
+ "temperature": 0.0,
47
+ "max_tokens": max_tokens,
48
+ }).encode()
49
+ for attempt in range(3):
50
+ try:
51
+ req = urllib.request.Request(
52
+ CHAT_URL, data=body,
53
+ headers={"Authorization": f"Bearer {CHAT_KEY}", "Content-Type": "application/json"})
54
+ resp = urllib.request.urlopen(req, timeout=timeout)
55
+ d = json.loads(resp.read())
56
+ return d["choices"][0]["message"]["content"], int(d.get("usage", {}).get("total_tokens", 0))
57
+ except (urllib.error.URLError, TimeoutError, OSError, KeyError) as e:
58
+ if attempt < 2:
59
+ time.sleep(1.0 * (attempt + 1))
60
+ else:
61
+ return f"[ERR {repr(e)[:80]}]", 0
62
+
63
+
64
+ def load_universe_and_gold(themes):
65
+ """载入实体名称全集(精度校验) + 每主题 gold 实体(召回校验)。"""
66
+ s = SessionLocal()
67
+ rows = _q(s, "SELECT canonical, cn FROM entities WHERE canonical IS NOT NULL")
68
+ UNI = set()
69
+ for canon, cn in rows:
70
+ if canon:
71
+ UNI.add(canon.strip())
72
+ if cn:
73
+ UNI.add(cn.strip())
74
+ GOLD = {}
75
+ for th in themes:
76
+ rs = _q(s, "SELECT canonical, cn, type FROM entities "
77
+ "WHERE (cn ILIKE :p OR canonical ILIKE :p) LIMIT 40", {"p": f"%{th}%"})
78
+ GOLD[th] = [{"canonical": c, "cn": n, "type": t} for c, n, t in rs]
79
+ s.close()
80
+ return UNI, GOLD
81
+
82
+
83
+ def retrieve_entities(theme, k=RETRIEVE_K):
84
+ """模拟工作流 KG 检索(wl-search 工具会做的事):返回真实体 dict 列表。"""
85
+ s = SessionLocal()
86
+ rs = _q(s, "SELECT canonical, cn, type FROM entities "
87
+ "WHERE (cn ILIKE :p OR canonical ILIKE :p) LIMIT :k",
88
+ {"p": f"%{theme}%", "k": k})
89
+ s.close()
90
+ return [{"canonical": c, "cn": n, "type": t} for c, n, t in rs]
91
+
92
+
93
+ def ctx_from_entities(ents):
94
+ """把检索实体格式化成喂给 LLM 的上下文文本。"""
95
+ out = []
96
+ for e in ents:
97
+ c, n, t = e["canonical"], e["cn"], e["type"]
98
+ out.append(f"{t}: {c}" + (f" ({n})" if n else ""))
99
+ return "\n".join(out)
100
+
101
+
102
+ def _q(s, sql, params=None):
103
+ try:
104
+ return s.execute(text(sql), params or {}).fetchall()
105
+ except Exception:
106
+ try:
107
+ s.rollback()
108
+ except Exception:
109
+ pass
110
+ return []
111
+
112
+
113
+ # ── prompt ───────────────────────────────────────────────────────────
114
+ def prompt_bare(theme):
115
+ return (f"在我们的车辆物联网系统(ICS)里,找出和「{theme}」相关的具体对象"
116
+ f"(数据库表/Java类/接口/按钮/页面等)。\n"
117
+ f"请逐行列出你【确信真实存在】的具体名称,每行一个,不要解释,不要编造。"
118
+ f"不确定就少列。")
119
+
120
+
121
+ def prompt_scaffold(theme, ctx):
122
+ return (f"在我们的车辆物联网系统(ICS)里,找出和「{theme}」相关的具体对象"
123
+ f"(数据库表/Java类/接口/按钮/页面等)。\n"
124
+ f"知识图谱已检索到以下相关实体(均为真实存在):\n{ctx}\n\n"
125
+ f"请把以上检索结果里的实体【尽量全部逐行列出】,每行一个,"
126
+ f"直接用检索结果里的名称,不要自行筛选/合并/省略,不要解释。检索到几个就列几个。")
127
+
128
+
129
+ # ── 评分 ─────────────────────────────────────────────────────────────
130
+ _LINE_CLEAN = re.compile(r"^[\-\*\d\.\)、\s]+")
131
+ _CODE_TOK = re.compile(r"[A-Za-z_][\w./\-:]{2,}")
132
+
133
+
134
+ def _clean_lines(ans):
135
+ """'每行一个' 答案 → 清洗后的短行列表(模型声称存在的对象,每行一个)。"""
136
+ out = []
137
+ for l in ans.splitlines():
138
+ l = _LINE_CLEAN.sub("", l).strip()
139
+ if l and len(l) <= 60:
140
+ out.append(l)
141
+ return out
142
+
143
+
144
+ def score(ans, gold, UNI, ground_cn):
145
+ """grounding 精度:每行算'真实'当其含某真代码标识符(∈UNI) 或 命中本格 ground_cn(中文)。
146
+ ground_cn = 该格可作'真实'依据的中文名集合(bare=gold, scaffold=retrieved),bare/scaffold 同口径公平。
147
+ """
148
+ lines = _clean_lines(ans)
149
+ real_n = 0
150
+ for l in lines:
151
+ if set(_CODE_TOK.findall(l)) & UNI:
152
+ real_n += 1
153
+ elif any(g and g in l for g in ground_cn):
154
+ real_n += 1
155
+ precision = (real_n / len(lines)) if lines else 0.0
156
+ hit = 0
157
+ for g in gold:
158
+ c = g.get("canonical") or ""
159
+ n = g.get("cn") or ""
160
+ if (c and c in ans) or (n and n in ans):
161
+ hit += 1
162
+ recall = (hit / len(gold)) if gold else 0.0
163
+ return {
164
+ "precision": round(precision, 3),
165
+ "recall": round(recall, 3),
166
+ "halluc": round(1 - precision, 3),
167
+ "n_lines": len(lines),
168
+ "n_real": real_n,
169
+ "gold_hit": hit,
170
+ "gold_n": len(gold),
171
+ }
172
+
173
+
174
+ def aggregate(rows):
175
+ n = len(rows) or 1
176
+ return {
177
+ "P": round(sum(r["precision"] for r in rows) / n, 3),
178
+ "R": round(sum(r["recall"] for r in rows) / n, 3),
179
+ "H": round(sum(r["halluc"] for r in rows) / n, 3),
180
+ "tokens": round(sum(r["tokens"] for r in rows) / n, 0),
181
+ "n": len(rows),
182
+ }
183
+
184
+
185
+ def run(themes, verbose=True):
186
+ def log(*a):
187
+ if verbose:
188
+ print(*a)
189
+
190
+ if not CHAT_KEY:
191
+ raise RuntimeError("缺 DASHSCOPE_API_KEY")
192
+ log("[ruler] 载入 KG 全集 + gold…")
193
+ UNI, GOLD = load_universe_and_gold(themes)
194
+ log(f"[ruler] 实体名称全集 {len(UNI)} 条")
195
+ for th in themes:
196
+ log(f" theme {th}: gold={len(GOLD[th])}")
197
+
198
+ matrix = {}
199
+ t0 = time.time()
200
+ for tier, model in MODELS.items():
201
+ for mode in ("bare", "scaffold"):
202
+ rows = []
203
+ for th in themes:
204
+ gold = GOLD[th]
205
+ if mode == "bare":
206
+ ground_cn = {g["canonical"] for g in gold} | {g["cn"] for g in gold if g["cn"]}
207
+ p = prompt_bare(th)
208
+ else:
209
+ ents = retrieve_entities(th)
210
+ ground_cn = {e["canonical"] for e in ents} | {e["cn"] for e in ents if e["cn"]}
211
+ p = prompt_scaffold(th, ctx_from_entities(ents))
212
+ ans, tok = chat(model, p)
213
+ sc = score(ans, gold, UNI, ground_cn)
214
+ sc["tokens"] = tok
215
+ sc["theme"] = th
216
+ rows.append(sc)
217
+ log(f" [{tier}/{mode}] {th}: P={sc['precision']:.2f} R={sc['recall']:.2f} "
218
+ f"H={sc['halluc']:.2f} tok={tok} lines={sc['n_lines']}")
219
+ matrix[f"{tier}/{mode}"] = aggregate(rows)
220
+ log(f"[{tier}/{mode}] agg P={matrix[f'{tier}/{mode}']['P']:.3f} "
221
+ f"R={matrix[f'{tier}/{mode}']['R']:.3f} H={matrix[f'{tier}/{mode}']['H']:.3f} "
222
+ f"tok={matrix[f'{tier}/{mode}']['tokens']:.0f}")
223
+ log(f"[ruler] 总耗时 {int(time.time()-t0)}s")
224
+ return {"matrix": matrix, "themes": themes, "models": MODELS}
225
+
226
+
227
+ def format_table(res):
228
+ m = res["matrix"]
229
+ lines = ["## 低模型梯度矩阵(grounding 精度P / 召回R / 幻觉H / token)",
230
+ "", "| 模型档 | 裸 P | 裸 R | 裸 H | 裸tok | +grounding P | +grounding R | +grounding H | +ground tok |",
231
+ "|---|---|---|---|---|---|---|---|---|"]
232
+ for tier in MODELS:
233
+ b = m[f"{tier}/bare"]
234
+ f = m[f"{tier}/scaffold"]
235
+ lines.append(f"| {tier}({MODELS[tier]}) | {b['P']:.2f} | {b['R']:.2f} | {b['H']:.2f} | {b['tokens']:.0f} | "
236
+ f"{f['P']:.2f} | {f['R']:.2f} | {f['H']:.2f} | {f['tokens']:.0f} |")
237
+ # 卖点提炼
238
+ lines.append("")
239
+ weak_gain_p = m["weak/scaffold"]["P"] - m["weak/bare"]["P"]
240
+ weak_catchup = m["weak/scaffold"]["P"] - m["strong/bare"]["P"]
241
+ lines.append(f"- 弱模型 +grounding 精度提升: **{weak_gain_p*100:+.1f}pp**(反幻觉增益)")
242
+ lines.append(f"- 弱模型+grounding vs 强模型裸 精度差: **{weak_catchup*100:+.1f}pp**(>0=追上/反超=卖点成立)")
243
+ return "\n".join(lines)
244
+
245
+
246
+ def main():
247
+ import argparse
248
+ ap = argparse.ArgumentParser()
249
+ ap.add_argument("--themes", default=",".join(DEFAULT_THEMES))
250
+ ap.add_argument("--json", action="store_true")
251
+ args = ap.parse_args()
252
+ themes = [t.strip() for t in args.themes.split(",") if t.strip()]
253
+ res = run(themes)
254
+ if args.json:
255
+ print(json.dumps(res, ensure_ascii=False, indent=2))
256
+ else:
257
+ print("\n" + format_table(res))
258
+
259
+
260
+ if __name__ == "__main__":
261
+ main()
@@ -1,33 +1,33 @@
1
- {
2
- "task": "T1.0 低模型梯度评测基线",
3
- "date": "2026-07-21",
4
- "ruler": "scripts/validation/eval/gradient_matrix.py",
5
- "env": "云 10.89.7.120 容器 wlinkj-workspace-backend-1, DashScope compatible-mode, KG entities=81255 edges=96965",
6
- "models": {"weak": "qwen-turbo", "mid": "qwen-plus", "strong": "qwen-max"},
7
- "themes": ["考勤", "排班", "加班", "车辆", "报警", "保险", "能耗", "预算", "导出", "审批"],
8
- "wall_seconds": 138,
9
- "chat_calls": 60,
10
- "matrix": {
11
- "weak/bare": {"P": 0.02, "R": 0.00, "H": 0.98, "tokens": 210},
12
- "weak/scaffold": {"P": 0.961, "R": 0.815, "H": 0.039, "tokens": 929},
13
- "mid/bare": {"P": 0.00, "R": 0.00, "H": 1.00, "tokens": 134},
14
- "mid/scaffold": {"P": 0.986, "R": 0.823, "H": 0.014, "tokens": 859},
15
- "strong/bare": {"P": 0.032, "R": 0.00, "H": 0.968, "tokens": 136},
16
- "strong/scaffold":{"P": 1.000,"R": 0.796, "H": 0.000, "tokens": 844}
17
- },
18
- "selling_point_proof": {
19
- "all_bare_hallucinate": "三档裸跑 P 0-0.032 / R 0 / H 0.97-1.0 — 连 qwen-max 无 grounding 召回 0",
20
- "weak_plus_workflow_vs_strong_bare_pp": 92.9,
21
- "tier_converges_with_grounding": "+grounding 后三档 P 0.961/0.986/1.000, R 0.815/0.823/0.796 — 模型档位几乎不影响质量"
22
- },
23
- "flywheel_round1_gap": {
24
- "shortboard": "召回天花板 R≈0.80 (ILIKE top-30 未覆盖全 gold + 模型偶过滤)",
25
- "lever": "检索升级: 真实 rag_search 语义 + 1跳图扩展 (映射 T1.4)",
26
- "target_R": 0.90
27
- },
28
- "honest_caveats": [
29
- "token 维度本尺子不适用: +grounding 必然加 token(喂上下文换 P 0->0.96); §9.1 的 token<=裸×0.8 指命令层去重/缓存(T1.2/T1.3/T1.7 真命令transcript量)",
30
- "🖥️ 宿主真梯度(QoderWork Haiku/Sonnet/Opus × 真命令) 待用户跑 (SPEC §12 V1.1); 本代理用 DashScope turbo/plus/max 同构证卖点方向"
31
- ],
32
- "status": "尺子就绪 + API 代理基线完成; 🖥️ 宿主梯度列入 §12"
33
- }
1
+ {
2
+ "task": "T1.0 低模型梯度评测基线",
3
+ "date": "2026-07-21",
4
+ "ruler": "scripts/validation/eval/gradient_matrix.py",
5
+ "env": "云 10.89.7.120 容器 wlinkj-workspace-backend-1, DashScope compatible-mode, KG entities=81255 edges=96965",
6
+ "models": {"weak": "qwen-turbo", "mid": "qwen-plus", "strong": "qwen-max"},
7
+ "themes": ["考勤", "排班", "加班", "车辆", "报警", "保险", "能耗", "预算", "导出", "审批"],
8
+ "wall_seconds": 138,
9
+ "chat_calls": 60,
10
+ "matrix": {
11
+ "weak/bare": {"P": 0.02, "R": 0.00, "H": 0.98, "tokens": 210},
12
+ "weak/scaffold": {"P": 0.961, "R": 0.815, "H": 0.039, "tokens": 929},
13
+ "mid/bare": {"P": 0.00, "R": 0.00, "H": 1.00, "tokens": 134},
14
+ "mid/scaffold": {"P": 0.986, "R": 0.823, "H": 0.014, "tokens": 859},
15
+ "strong/bare": {"P": 0.032, "R": 0.00, "H": 0.968, "tokens": 136},
16
+ "strong/scaffold":{"P": 1.000,"R": 0.796, "H": 0.000, "tokens": 844}
17
+ },
18
+ "selling_point_proof": {
19
+ "all_bare_hallucinate": "三档裸跑 P 0-0.032 / R 0 / H 0.97-1.0 — 连 qwen-max 无 grounding 召回 0",
20
+ "weak_plus_workflow_vs_strong_bare_pp": 92.9,
21
+ "tier_converges_with_grounding": "+grounding 后三档 P 0.961/0.986/1.000, R 0.815/0.823/0.796 — 模型档位几乎不影响质量"
22
+ },
23
+ "flywheel_round1_gap": {
24
+ "shortboard": "召回天花板 R≈0.80 (ILIKE top-30 未覆盖全 gold + 模型偶过滤)",
25
+ "lever": "检索升级: 真实 rag_search 语义 + 1跳图扩展 (映射 T1.4)",
26
+ "target_R": 0.90
27
+ },
28
+ "honest_caveats": [
29
+ "token 维度本尺子不适用: +grounding 必然加 token(喂上下文换 P 0->0.96); §9.1 的 token<=裸×0.8 指命令层去重/缓存(T1.2/T1.3/T1.7 真命令transcript量)",
30
+ "🖥️ 宿主真梯度(QoderWork Haiku/Sonnet/Opus × 真命令) 待用户跑 (SPEC §12 V1.1); 本代理用 DashScope turbo/plus/max 同构证卖点方向"
31
+ ],
32
+ "status": "尺子就绪 + API 代理基线完成; 🖥️ 宿主梯度列入 §12"
33
+ }