@hupan56/wlkj 3.3.14 → 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 (94) hide show
  1. package/bin/cli.js +16 -1
  2. package/package.json +1 -1
  3. package/templates/qoder/commands/optional/wl-spec.md +4 -2
  4. package/templates/qoder/commands/wl-commit.md +3 -1
  5. package/templates/qoder/commands/wl-init.md +2 -2
  6. package/templates/qoder/commands/wl-prd.md +24 -0
  7. package/templates/qoder/commands/wl-search.md +18 -11
  8. package/templates/qoder/commands/wl-task.md +3 -1
  9. package/templates/qoder/config.yaml +0 -6
  10. package/templates/qoder/contracts/insight.md +55 -0
  11. package/templates/qoder/hooks/pre-tool-use-commit.py +124 -0
  12. package/templates/qoder/hooks/session-start.py +20 -1
  13. package/templates/qoder/hooks/stop-eval.py +129 -0
  14. package/templates/qoder/scripts/capability/adapters/mcp.py +9 -1
  15. package/templates/qoder/scripts/capability/registry.py +0 -3
  16. package/templates/qoder/scripts/capability/registry_mcp.py +10 -4
  17. package/templates/qoder/scripts/deployment/setup/init_doctor.py +6 -2
  18. package/templates/qoder/scripts/deployment/setup/install_qoderwork.py +7 -7
  19. package/templates/qoder/scripts/deployment/setup/setup.py +5 -2
  20. package/templates/qoder/scripts/domain/integration/return_to_platform.py +4 -2
  21. package/templates/qoder/scripts/domain/integration/spec_upload.py +4 -4
  22. package/templates/qoder/scripts/domain/kg/build/build_entity_registry.py +12 -12
  23. package/templates/qoder/scripts/domain/kg/build/build_relations.py +12 -12
  24. package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +41 -5
  25. package/templates/qoder/scripts/domain/kg/kg.py +14 -0
  26. package/templates/qoder/scripts/domain/kg/switch_project.py +2 -2
  27. package/templates/qoder/scripts/domain/task/wlkj_panel.py +169 -14
  28. package/templates/qoder/scripts/foundation/io/context_cache.py +94 -0
  29. package/templates/qoder/scripts/orchestration/wlkj.py +106 -0
  30. package/templates/qoder/scripts/tool_guide.md +70 -0
  31. package/templates/qoder/scripts/validation/eval/alignment_matrix.py +176 -0
  32. package/templates/qoder/scripts/validation/eval/bf2_content_fidelity.py +110 -0
  33. package/templates/qoder/scripts/validation/eval/bf2_llmjudge.py +104 -0
  34. package/templates/qoder/scripts/validation/eval/bf_score.py +218 -0
  35. package/templates/qoder/scripts/validation/eval/code_flywheel.py +150 -0
  36. package/templates/qoder/scripts/validation/eval/dispatcher_ab.py +156 -0
  37. package/templates/qoder/scripts/validation/eval/dispatcher_ab_2026-07-21.json +23 -0
  38. package/templates/qoder/scripts/validation/eval/feature_fidelity_flywheel.py +143 -0
  39. package/templates/qoder/scripts/validation/eval/gradient_matrix.py +261 -0
  40. package/templates/qoder/scripts/validation/eval/gradient_matrix_baseline_2026-07-21.json +33 -0
  41. package/templates/qoder/scripts/validation/eval/metrics_dashboard.py +105 -0
  42. package/templates/qoder/scripts/validation/eval/multi_turn_flywheel.py +118 -0
  43. package/templates/qoder/scripts/validation/eval/prd_fidelity_flywheel.py +128 -0
  44. package/templates/qoder/scripts/validation/eval/prd_flywheel.py +148 -0
  45. package/templates/qoder/scripts/validation/eval/prototype_fidelity_flywheel.py +166 -0
  46. package/templates/qoder/scripts/validation/eval/recall_flywheel.py +148 -0
  47. package/templates/qoder/scripts/validation/eval/robustness_flywheel.py +139 -0
  48. package/templates/qoder/scripts/validation/eval/speed_accuracy_flywheel.py +188 -0
  49. package/templates/qoder/scripts/validation/eval/task_flywheel.py +124 -0
  50. package/templates/qoder/scripts/validation/eval/token_flywheel.py +88 -0
  51. package/templates/qoder/scripts/validation/metrics/eval_code_ac.py +177 -0
  52. package/templates/qoder/scripts/validation/metrics/lint_cases.py +170 -0
  53. package/templates/qoder/scripts/validation/test/test_context_cache.py +78 -0
  54. package/templates/qoder/scripts/validation/test/test_lint_cases.py +60 -0
  55. package/templates/qoder/scripts/validation/test/test_pre_tool_use_commit.py +70 -0
  56. package/templates/qoder/settings.json +8 -0
  57. package/templates/qoder/skills/wl-spec/SKILL.md +1 -1
  58. package/templates/qoder/agents/design-agent.md +0 -20
  59. package/templates/qoder/agents/spec-generator.md +0 -21
  60. package/templates/qoder/scripts/capability/smoke_test_report.json.new +0 -94
  61. package/templates/qoder/scripts/domain/kg/extract/asset/__init__.py +0 -10
  62. package/templates/qoder/scripts/domain/kg/extract/asset/asset_tree.py +0 -57
  63. package/templates/qoder/scripts/domain/kg/extract/asset/discussion_importer.py +0 -62
  64. package/templates/qoder/scripts/domain/kg/extract/asset/prd_importer.py +0 -146
  65. package/templates/qoder/scripts/domain/kg/extract/asset/prototype_importer.py +0 -64
  66. package/templates/qoder/scripts/domain/kg/extract/asset/returns_importer.py +0 -52
  67. package/templates/qoder/scripts/domain/kg/extract/build_goal3.py +0 -104
  68. package/templates/qoder/scripts/domain/kg/extract/build_goal4.py +0 -55
  69. package/templates/qoder/scripts/domain/kg/extract/build_goal5.py +0 -95
  70. package/templates/qoder/scripts/domain/kg/extract/db/__init__.py +0 -8
  71. package/templates/qoder/scripts/domain/kg/extract/db/data_profile.py +0 -22
  72. package/templates/qoder/scripts/domain/kg/extract/db/fk_extractor.py +0 -55
  73. package/templates/qoder/scripts/domain/kg/extract/db/schema_extractor.py +0 -90
  74. package/templates/qoder/scripts/domain/kg/extract/inference/__init__.py +0 -9
  75. package/templates/qoder/scripts/domain/kg/extract/inference/community_summarizer.py +0 -206
  76. package/templates/qoder/scripts/domain/kg/extract/inference/embed_builder.py +0 -132
  77. package/templates/qoder/scripts/domain/kg/extract/inference/naming_matcher.py +0 -80
  78. package/templates/qoder/scripts/domain/kg/extract/inference/promote.py +0 -59
  79. package/templates/qoder/scripts/domain/kg/extract/inference/recompute.py +0 -93
  80. package/templates/qoder/scripts/domain/kg/extract/inference/weak_link.py +0 -421
  81. package/templates/qoder/scripts/domain/kg/extract/mybatis/__init__.py +0 -9
  82. package/templates/qoder/scripts/domain/kg/extract/mybatis/all.py +0 -79
  83. package/templates/qoder/scripts/domain/kg/extract/mybatis/mapper_parser.py +0 -99
  84. package/templates/qoder/scripts/domain/kg/extract/mybatis/relation_builder.py +0 -69
  85. package/templates/qoder/scripts/domain/kg/extract/mybatis/sql_extractor.py +0 -78
  86. package/templates/qoder/scripts/domain/kg/extract/prd/__init__.py +0 -8
  87. package/templates/qoder/scripts/domain/kg/extract/prd/prd_chunk_embed.py +0 -105
  88. package/templates/qoder/scripts/domain/kg/extract/prd/prd_llm_extract.py +0 -153
  89. package/templates/qoder/scripts/domain/kg/extract/prd/req_anchor.py +0 -120
  90. package/templates/qoder/scripts/domain/kg/graph/kg_link_db.py +0 -235
  91. package/templates/qoder/scripts/domain/kg/search/enrich_prompt.py +0 -238
  92. package/templates/qoder/scripts/domain/kg/server/perf_bench.py +0 -197
  93. package/templates/qoder/scripts/domain/kg/sync_repowiki.py +0 -109
  94. package/templates/qoder/scripts/engine/poller.py +0 -219
@@ -0,0 +1,143 @@
1
+ """feature_fidelity_flywheel.py — FW10 BF5/BF6 feature-scoped 业务保真飞轮。
2
+
3
+ 关键:precision 改成 vs【该 feature 真业务实体集】(非任意真实体)。
4
+ 之前 code/search 量"列出的是不是真实体"(可列真但不相关的实体也得分)→ 业务保真虚高。
5
+ BF5/BF6 量"列出的是不是【这个 feature 的】真实体"——真业务相关,非泛实体。
6
+ - BF6 search feature相关率 = 返回实体 ∈ feature_gold / 返回总数
7
+ - BF5 code feature精准 = 改实体 ∈ feature_gold / 改总数
8
+ bare(无接地→泛/编造) vs +该feature gold接地。
9
+
10
+ 跑法: docker exec -i wlinkj-workspace-backend-1 python /app/scripts/validation/eval/feature_fidelity_flywheel.py
11
+ """
12
+ import os
13
+ import re
14
+ import sys
15
+ import json
16
+ import time
17
+ import urllib.request
18
+ import urllib.error
19
+
20
+ for _p in ("/app", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))):
21
+ if os.path.isdir(_p) and _p not in sys.path:
22
+ sys.path.insert(0, _p)
23
+
24
+ from sqlalchemy import text # noqa: E402
25
+ from app.db import SessionLocal # noqa: E402
26
+
27
+ CHAT_URL = os.environ.get("ALI_CHAT_BASE", "").rstrip("/") + "/chat/completions"
28
+ CHAT_KEY = os.environ.get("DASHSCOPE_API_KEY", "")
29
+ MODEL = "qwen-turbo"
30
+ FEATURES = [("车辆保险管理", "保险"), ("异常记录管理", "异常"), ("合同维护", "合同")]
31
+ _LINE_CLEAN = re.compile(r"^[\-\*\d\.\)、\s]+")
32
+ _TOK = re.compile(r"[A-Za-z_][\w./\-:]{2,}")
33
+
34
+
35
+ def chat(prompt, max_tokens=400, timeout=80):
36
+ body = json.dumps({"model": MODEL,
37
+ "messages": [{"role": "system", "content": "你是车辆物联网系统(ICS)助手,只答真实存在的对象。"},
38
+ {"role": "user", "content": prompt}], "temperature": 0.0, "max_tokens": max_tokens}).encode()
39
+ for a in range(3):
40
+ try:
41
+ req = urllib.request.Request(CHAT_URL, data=body,
42
+ headers={"Authorization": f"Bearer {CHAT_KEY}", "Content-Type": "application/json"})
43
+ return json.loads(urllib.request.urlopen(req, timeout=timeout).read())["choices"][0]["message"]["content"]
44
+ except (urllib.error.URLError, TimeoutError, OSError, KeyError) as e:
45
+ if a < 2: time.sleep(1.0*(a+1))
46
+ else: return ""
47
+
48
+
49
+ def _q(s, sql, p=None):
50
+ try: return s.execute(text(sql), p or {}).fetchall()
51
+ except Exception:
52
+ try: s.rollback()
53
+ except Exception: pass
54
+ return []
55
+
56
+
57
+ def feature_gold(theme, k=20):
58
+ """该 feature 的真业务实体集(BF5/BF6 的 gold)。"""
59
+ s = SessionLocal()
60
+ rows = _q(s, "SELECT canonical, cn FROM entities WHERE (canonical ILIKE :p OR cn ILIKE :p) "
61
+ "AND type IN ('CONTROLLER','SERVICE','TABLE','COLUMN','FUNCTION') LIMIT :n",
62
+ {"p": f"%{theme}%", "n": k})
63
+ s.close()
64
+ return [(c, n) for c, n in rows if c]
65
+
66
+
67
+ def extract_names(ans):
68
+ out = set()
69
+ for line in ans.splitlines():
70
+ line = _LINE_CLEAN.sub("", line).strip()
71
+ if line and len(line) <= 60:
72
+ m = _TOK.findall(line)
73
+ if m: out.add(m[0])
74
+ return out
75
+
76
+
77
+ def feature_precision(ans, gold):
78
+ """feature 保真:列出的 ∈ 该feature gold 比例(非任意真实体)。"""
79
+ cands = extract_names(ans)
80
+ if not cands: return 0.0, 0
81
+ gold_canon = {c for c, _ in gold}
82
+ hit = sum(1 for c in cands if c in gold_canon)
83
+ # 也算 cn 命中(ans 文本含 gold cn)
84
+ # precision = 命中gold的候选 / 总候选
85
+ feat_hit = 0
86
+ for c in cands:
87
+ if c in gold_canon:
88
+ feat_hit += 1
89
+ return round(feat_hit / len(cands), 3), len(cands)
90
+
91
+
92
+ def run():
93
+ rep = {"features": []}
94
+ for feat, theme in FEATURES:
95
+ gold = feature_gold(theme, 20)
96
+ gold_ctx = "\n".join("%s (%s)" % (c, n) for c, n in gold) or "(无)"
97
+ # BF6 search
98
+ b_search = chat(f"找 ICS 系统「{feat}」相关的具体对象(类/表/字段/接口),逐行列出,勿编造。")
99
+ s_search = chat(f"找 ICS 系统「{feat}」相关对象。知识图谱检索到(均真实):\n{gold_ctx}\n请把检索结果实体全部逐行列出,直接用名称,勿编造。")
100
+ bP_s, bN_s = feature_precision(b_search, gold)
101
+ sP_s, sN_s = feature_precision(s_search, gold)
102
+ # BF5 code
103
+ b_code = chat(f"给「{feat}」加功能,逐行列出需修改的代码类/方法名,勿编造。")
104
+ s_code = chat(f"给「{feat}」加功能。get_impact 检索到真实实体:\n{gold_ctx}\n请把检索结果实体全部逐行列出,直接用名称,勿编造。")
105
+ bP_c, bN_c = feature_precision(b_code, gold)
106
+ sP_c, sN_c = feature_precision(s_code, gold)
107
+ rep["features"].append({"feature": feat, "gold_n": len(gold),
108
+ "BF6_bare": bP_s, "BF6_scaffold": sP_s,
109
+ "BF5_bare": bP_c, "BF5_scaffold": sP_c})
110
+ print(" %s(gold=%d): BF6 search 相关率 bare=%.0f%%→scaff=%.0f%% | BF5 code 精准 bare=%.0f%%→scaff=%.0f%%" % (
111
+ feat, len(gold), bP_s*100, sP_s*100, bP_c*100, sP_c*100))
112
+ n = len(FEATURES) or 1
113
+ rep["agg"] = {
114
+ "BF6_bare": round(sum(f["BF6_bare"] for f in rep["features"])/n, 3),
115
+ "BF6_scaffold": round(sum(f["BF6_scaffold"] for f in rep["features"])/n, 3),
116
+ "BF5_bare": round(sum(f["BF5_bare"] for f in rep["features"])/n, 3),
117
+ "BF5_scaffold": round(sum(f["BF5_scaffold"] for f in rep["features"])/n, 3),
118
+ }
119
+ return rep
120
+
121
+
122
+ def main():
123
+ import argparse
124
+ ap = argparse.ArgumentParser()
125
+ ap.add_argument("--json", action="store_true")
126
+ args = ap.parse_args()
127
+ r = run()
128
+ a = r["agg"]
129
+ if args.json:
130
+ print(json.dumps(r, ensure_ascii=False, indent=2))
131
+ return
132
+ print("\n## FW10 BF5/BF6 feature-scoped 业务保真飞轮(列的是【该feature】真实体,非任意真实体)")
133
+ print("| 指标 | bare | +该feature gold接地 | 目标 |")
134
+ print("|---|---|---|---|")
135
+ print("| BF6 search feature相关率 | %.0f%% | %.0f%% | ≥85%% |" % (a["BF6_bare"]*100, a["BF6_scaffold"]*100))
136
+ print("| BF5 code feature精准 | %.0f%% | %.0f%% | ≥85%% |" % (a["BF5_bare"]*100, a["BF5_scaffold"]*100))
137
+ b6 = a["BF6_scaffold"] >= 0.85; b5 = a["BF5_scaffold"] >= 0.85
138
+ print("\nBF6达标?" , "✓" if b6 else "✗", "| BF5达标?", "✓" if b5 else "✗",
139
+ "| 不达标续转(更强feature接地/拒非feature实体)")
140
+
141
+
142
+ if __name__ == "__main__":
143
+ main()
@@ -0,0 +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()
@@ -0,0 +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
+ }
@@ -0,0 +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()
@@ -0,0 +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()