@hupan56/wlkj 3.3.12 → 3.3.14

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 (103) hide show
  1. package/bin/cli.js +1 -23
  2. package/package.json +29 -29
  3. package/templates/qoder/agents/design-agent.md +20 -0
  4. package/templates/qoder/agents/spec-generator.md +21 -0
  5. package/templates/qoder/commands/optional/wl-spec.md +2 -12
  6. package/templates/qoder/commands/optional/wl-status.md +0 -8
  7. package/templates/qoder/commands/wl-code.md +0 -8
  8. package/templates/qoder/commands/wl-commit.md +1 -11
  9. package/templates/qoder/commands/wl-init.md +129 -129
  10. package/templates/qoder/commands/wl-prd.md +0 -24
  11. package/templates/qoder/commands/wl-search.md +11 -26
  12. package/templates/qoder/commands/wl-task.md +1 -11
  13. package/templates/qoder/commands/wl-test.md +0 -8
  14. package/templates/qoder/config.yaml +8 -3
  15. package/templates/qoder/hooks/session-start.py +365 -384
  16. package/templates/qoder/hooks/stop-eval.py +254 -383
  17. package/templates/qoder/scripts/capability/adapters/mcp.py +314 -322
  18. package/templates/qoder/scripts/capability/registry.py +248 -245
  19. package/templates/qoder/scripts/capability/registry_mcp.py +4 -10
  20. package/templates/qoder/scripts/capability/smoke_test_report.json.new +94 -0
  21. package/templates/qoder/scripts/deployment/setup/init_doctor.py +2 -6
  22. package/templates/qoder/scripts/deployment/setup/install_qoderwork.py +14 -0
  23. package/templates/qoder/scripts/deployment/setup/setup.py +2 -16
  24. package/templates/qoder/scripts/domain/integration/return_to_platform.py +2 -4
  25. package/templates/qoder/scripts/domain/integration/spec_upload.py +4 -4
  26. package/templates/qoder/scripts/domain/kg/build/build_entity_registry.py +12 -12
  27. package/templates/qoder/scripts/domain/kg/build/build_relations.py +12 -12
  28. package/templates/qoder/scripts/domain/kg/extract/asset/__init__.py +10 -0
  29. package/templates/qoder/scripts/domain/kg/extract/asset/asset_tree.py +57 -0
  30. package/templates/qoder/scripts/domain/kg/extract/asset/discussion_importer.py +62 -0
  31. package/templates/qoder/scripts/domain/kg/extract/asset/prd_importer.py +146 -0
  32. package/templates/qoder/scripts/domain/kg/extract/asset/prototype_importer.py +64 -0
  33. package/templates/qoder/scripts/domain/kg/extract/asset/returns_importer.py +52 -0
  34. package/templates/qoder/scripts/domain/kg/extract/build_goal3.py +104 -0
  35. package/templates/qoder/scripts/domain/kg/extract/build_goal4.py +55 -0
  36. package/templates/qoder/scripts/domain/kg/extract/build_goal5.py +95 -0
  37. package/templates/qoder/scripts/domain/kg/extract/db/__init__.py +8 -0
  38. package/templates/qoder/scripts/domain/kg/extract/db/data_profile.py +22 -0
  39. package/templates/qoder/scripts/domain/kg/extract/db/fk_extractor.py +55 -0
  40. package/templates/qoder/scripts/domain/kg/extract/db/schema_extractor.py +90 -0
  41. package/templates/qoder/scripts/domain/kg/extract/inference/__init__.py +9 -0
  42. package/templates/qoder/scripts/domain/kg/extract/inference/community_summarizer.py +206 -0
  43. package/templates/qoder/scripts/domain/kg/extract/inference/embed_builder.py +132 -0
  44. package/templates/qoder/scripts/domain/kg/extract/inference/naming_matcher.py +80 -0
  45. package/templates/qoder/scripts/domain/kg/extract/inference/promote.py +59 -0
  46. package/templates/qoder/scripts/domain/kg/extract/inference/recompute.py +93 -0
  47. package/templates/qoder/scripts/domain/kg/extract/inference/weak_link.py +421 -0
  48. package/templates/qoder/scripts/domain/kg/extract/mybatis/__init__.py +9 -0
  49. package/templates/qoder/scripts/domain/kg/extract/mybatis/all.py +79 -0
  50. package/templates/qoder/scripts/domain/kg/extract/mybatis/mapper_parser.py +99 -0
  51. package/templates/qoder/scripts/domain/kg/extract/mybatis/relation_builder.py +69 -0
  52. package/templates/qoder/scripts/domain/kg/extract/mybatis/sql_extractor.py +78 -0
  53. package/templates/qoder/scripts/domain/kg/extract/prd/__init__.py +8 -0
  54. package/templates/qoder/scripts/domain/kg/extract/prd/prd_chunk_embed.py +105 -0
  55. package/templates/qoder/scripts/domain/kg/extract/prd/prd_llm_extract.py +153 -0
  56. package/templates/qoder/scripts/domain/kg/extract/prd/req_anchor.py +120 -0
  57. package/templates/qoder/scripts/domain/kg/graph/kg_link_db.py +235 -0
  58. package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +5 -41
  59. package/templates/qoder/scripts/domain/kg/kg.py +0 -14
  60. package/templates/qoder/scripts/domain/kg/search/enrich_prompt.py +238 -0
  61. package/templates/qoder/scripts/domain/kg/server/perf_bench.py +197 -0
  62. package/templates/qoder/scripts/domain/kg/switch_project.py +2 -2
  63. package/templates/qoder/scripts/domain/kg/sync_repowiki.py +109 -0
  64. package/templates/qoder/scripts/domain/task/wlkj_panel.py +1348 -1503
  65. package/templates/qoder/scripts/engine/poller.py +219 -0
  66. package/templates/qoder/scripts/foundation/bootstrap.py +145 -145
  67. package/templates/qoder/scripts/orchestration/wlkj.py +189 -306
  68. package/templates/qoder/settings.json +0 -8
  69. package/templates/qoder/skills/wl-spec/SKILL.md +1 -1
  70. package/templates/qoder/commands/wl-data.md +0 -46
  71. package/templates/qoder/commands/wl-fix.md +0 -47
  72. package/templates/qoder/commands/wl-knowledge.md +0 -41
  73. package/templates/qoder/commands/wl-review.md +0 -49
  74. package/templates/qoder/contracts/insight.md +0 -55
  75. package/templates/qoder/hooks/pre-tool-use-commit.py +0 -124
  76. package/templates/qoder/scripts/foundation/io/context_cache.py +0 -94
  77. package/templates/qoder/scripts/tool_guide.md +0 -70
  78. package/templates/qoder/scripts/validation/eval/alignment_matrix.py +0 -176
  79. package/templates/qoder/scripts/validation/eval/bf2_content_fidelity.py +0 -110
  80. package/templates/qoder/scripts/validation/eval/bf2_llmjudge.py +0 -104
  81. package/templates/qoder/scripts/validation/eval/bf_score.py +0 -218
  82. package/templates/qoder/scripts/validation/eval/code_flywheel.py +0 -150
  83. package/templates/qoder/scripts/validation/eval/dispatcher_ab.py +0 -156
  84. package/templates/qoder/scripts/validation/eval/dispatcher_ab_2026-07-21.json +0 -23
  85. package/templates/qoder/scripts/validation/eval/feature_fidelity_flywheel.py +0 -143
  86. package/templates/qoder/scripts/validation/eval/gradient_matrix.py +0 -261
  87. package/templates/qoder/scripts/validation/eval/gradient_matrix_baseline_2026-07-21.json +0 -33
  88. package/templates/qoder/scripts/validation/eval/metrics_dashboard.py +0 -105
  89. package/templates/qoder/scripts/validation/eval/multi_turn_flywheel.py +0 -118
  90. package/templates/qoder/scripts/validation/eval/prd_fidelity_flywheel.py +0 -128
  91. package/templates/qoder/scripts/validation/eval/prd_flywheel.py +0 -148
  92. package/templates/qoder/scripts/validation/eval/prototype_fidelity_flywheel.py +0 -166
  93. package/templates/qoder/scripts/validation/eval/recall_flywheel.py +0 -148
  94. package/templates/qoder/scripts/validation/eval/robustness_flywheel.py +0 -139
  95. package/templates/qoder/scripts/validation/eval/speed_accuracy_flywheel.py +0 -188
  96. package/templates/qoder/scripts/validation/eval/task_flywheel.py +0 -124
  97. package/templates/qoder/scripts/validation/eval/token_flywheel.py +0 -88
  98. package/templates/qoder/scripts/validation/metrics/eval_code_ac.py +0 -177
  99. package/templates/qoder/scripts/validation/metrics/lint_cases.py +0 -170
  100. package/templates/qoder/scripts/validation/test/test_context_cache.py +0 -78
  101. package/templates/qoder/scripts/validation/test/test_lint_cases.py +0 -60
  102. package/templates/qoder/scripts/validation/test/test_pre_tool_use_commit.py +0 -70
  103. package/templates/workspace/specs/REQ-TEST-CTX/.context-cache.json +0 -1
@@ -1,156 +0,0 @@
1
- """dispatcher_ab.py — T1.1 mode dispatcher A/B(飞轮:量→改→重测)。
2
-
3
- 证"确定性路由表"比"判断问题类型"散文更能让【弱模型】选对检索通道(零猜测)。
4
- 给 {弱/中/强} 模型喂【旧路由 / 新路由】+ 14 条 gold 查询,量选通道准确率。
5
-
6
- 跑法(容器内):
7
- docker exec -i wlinkj-workspace-backend-1 python /app/scripts/validation/eval/dispatcher_ab.py
8
- """
9
- import os
10
- import sys
11
- import json
12
- import time
13
- import urllib.request
14
- import urllib.error
15
-
16
- for _p in ("/app", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))):
17
- if os.path.isdir(_p) and _p not in sys.path:
18
- sys.path.insert(0, _p)
19
-
20
- CHAT_URL = os.environ.get("ALI_CHAT_BASE", "").rstrip("/") + "/chat/completions"
21
- CHAT_KEY = os.environ.get("DASHSCOPE_API_KEY", "")
22
- MODELS = {"weak": "qwen-turbo", "mid": "qwen-plus", "strong": "qwen-max"}
23
- CHANNELS = ["get_impact", "db", "coverage_matrix", "ask_corpus", "context_pack", "search_code", "rag_search"]
24
-
25
- # 14 条 gold 查询(7 通道 × 2),覆盖确定性路由表的每个信号
26
- GOLD = [
27
- ("改 handleExport 影响谁", "get_impact"),
28
- ("波及范围分析 vehicleNo 字段", "get_impact"),
29
- ("t_asset 表结构", "db"),
30
- ("status 字段有哪些枚举值", "db"),
31
- ("哪些功能没测试", "coverage_matrix"),
32
- ("测试覆盖盲区在哪", "coverage_matrix"),
33
- ("资产管理的业务流程是什么", "ask_corpus"),
34
- ("这个功能整体怎么运转", "ask_corpus"),
35
- ("盘点考勤有哪些代码和字段", "context_pack"),
36
- ("一次取全车辆管理的代码和API", "context_pack"),
37
- ("handleExport 在哪定义", "search_code"),
38
- ("AppWorkCalendarController 在哪", "search_code"),
39
- ("车辆保养", "rag_search"),
40
- ("保险异常筛选", "rag_search"),
41
- ]
42
-
43
- # 旧路由("判断问题类型" 散文,原 wl-search 第一步)
44
- SPEC_OLD = """本命令有 3 路检索,按问题类型选最合适的一路(不是无脑 search_code):
45
- - "车辆保养"相关的代码/字段/API(语义找相关)→ rag_search 语义召回
46
- - "业务流程是什么/整体怎么运转"(全局问答)→ ask_corpus GraphRAG 问答
47
- - "handleExport 这个符号在哪定义/调用"(精确符号)→ search_code 关键词
48
- - "XX怎么实现的"(一次取全代码+API+字段)→ context_pack
49
- - "改 XX 影响谁" → get_impact
50
- 顺序铁律:理解/盘点类先 rag_search;整体流程再补 ask_corpus;精确符号才 search_code。
51
- (注:数据库表结构/字段/枚举 → 先 list_envs 问环境;哪些没测/覆盖盲区 → coverage_matrix)"""
52
-
53
- # 新路由(确定性首匹配表,T1.1 改后)
54
- SPEC_NEW = """按顺序匹配第一个命中的信号,第一个命中决定通道,不要"判断问题类型",按表查:
55
- 1. 含"影响谁/改XX影响/波及" → get_impact
56
- 2. 含"表结构/字段/枚举/真实数据/查库" → db(先 list_envs 问环境)
57
- 3. 含"哪些没测/覆盖/盲区/测试矩阵" → coverage_matrix
58
- 4. 含"业务流程/整体怎么运转/是干嘛的" → ask_corpus(再叠 rag_search)
59
- 5. 含"盘点/有哪些代码/一次取全" → context_pack
60
- 6. 精确符号(camelCase/下划线/路径,如 handleExport、/asset) → search_code
61
- 7. 以上都没命中(默认) → rag_search(语义召回最全)"""
62
-
63
-
64
- def chat(model, prompt, max_tokens=40, timeout=60):
65
- body = json.dumps({
66
- "model": model,
67
- "messages": [{"role": "user", "content": prompt}],
68
- "temperature": 0.0,
69
- "max_tokens": max_tokens,
70
- }).encode()
71
- for attempt in range(3):
72
- try:
73
- req = urllib.request.Request(CHAT_URL, data=body,
74
- headers={"Authorization": f"Bearer {CHAT_KEY}", "Content-Type": "application/json"})
75
- resp = urllib.request.urlopen(req, timeout=timeout)
76
- return json.loads(resp.read())["choices"][0]["message"]["content"].strip()
77
- except (urllib.error.URLError, TimeoutError, OSError, KeyError) as e:
78
- if attempt < 2:
79
- time.sleep(1.0 * (attempt + 1))
80
- else:
81
- return f"[ERR {repr(e)[:60]}]"
82
-
83
-
84
- def _norm(ans):
85
- """从短答里抽出通道名(模型可能带解释/引号/中文)。"""
86
- a = ans.lower()
87
- for ch in CHANNELS:
88
- if ch in a:
89
- return ch
90
- # 容错:db 写成 list_envs / 数据库
91
- if "list_envs" in a or "数据库" in ans or "查库" in ans:
92
- return "db"
93
- return "__none__"
94
-
95
-
96
- def run_spec(model, spec, spec_name, verbose=True):
97
- correct = 0
98
- details = []
99
- for query, gold in GOLD:
100
- prompt = (f"你是搜索命令 /wl-search 的路由器。严格根据下面的【路由规则】判断这条查询该用哪个检索通道。\n\n"
101
- f"【路由规则】\n{spec}\n\n"
102
- f"【查询】{query}\n\n"
103
- f"只输出一个通道名,从这些里选: {', '.join(CHANNELS)}。不要解释。")
104
- ans = chat(model, prompt)
105
- got = _norm(ans)
106
- ok = (got == gold)
107
- correct += int(ok)
108
- details.append({"q": query, "gold": gold, "got": got, "ok": ok, "raw": ans[:30]})
109
- if verbose:
110
- print(f" [{spec_name}] {query[:18]:<20} gold={gold:<16} got={got:<16} {'✓' if ok else '✗ '+ans[:25]}")
111
- acc = correct / len(GOLD)
112
- return {"acc": round(acc, 3), "correct": correct, "n": len(GOLD), "details": details}
113
-
114
-
115
- def run(verbose=True):
116
- res = {"models": MODELS, "n": len(GOLD), "matrix": {}}
117
- for tier, model in MODELS.items():
118
- old = run_spec(model, SPEC_OLD, "old", verbose)
119
- new = run_spec(model, SPEC_NEW, "new", verbose)
120
- res["matrix"][tier] = {"old_acc": old["acc"], "new_acc": new["acc"],
121
- "gain_pp": round((new["acc"] - old["acc"]) * 100, 1)}
122
- print(f" >> {tier}({model}): old={old['acc']:.2f} new={new['acc']:.2f} "
123
- f"gain={res['matrix'][tier]['gain_pp']:+.1f}pp")
124
- return res
125
-
126
-
127
- def format_table(res):
128
- m = res["matrix"]
129
- lines = ["## T1.1 dispatcher A/B(选对通道准确率:旧'判断问题类型' vs 新'确定性表')", "",
130
- "| 模型档 | 旧路由 acc | 新路由 acc | 增益 |",
131
- "|---|---|---|---|"]
132
- for tier in MODELS:
133
- c = m[tier]
134
- lines.append(f"| {tier}({MODELS[tier]}) | {c['old_acc']:.1%} | {c['new_acc']:.1%} | {c['gain_pp']:+.1f}pp |")
135
- lines.append("")
136
- weak_gain = m["weak"]["gain_pp"]
137
- weak_new_vs_strong_old = (m["weak"]["new_acc"] - m["strong"]["old_acc"]) * 100
138
- lines.append(f"- 弱模型增益: **{weak_gain:+.1f}pp**(确定性表让弱模型选对更多)")
139
- lines.append(f"- 弱模型+新路由 vs 强模型+旧路由: **{weak_new_vs_strong_old:+.1f}pp**(>0=弱模型借确定性表追上/反超)")
140
- return "\n".join(lines)
141
-
142
-
143
- def main():
144
- import argparse
145
- ap = argparse.ArgumentParser()
146
- ap.add_argument("--json", action="store_true")
147
- args = ap.parse_args()
148
- res = run()
149
- if args.json:
150
- print(json.dumps(res, ensure_ascii=False, indent=2))
151
- else:
152
- print("\n" + format_table(res))
153
-
154
-
155
- if __name__ == "__main__":
156
- main()
@@ -1,23 +0,0 @@
1
- {
2
- "task": "T1.1 mode dispatcher 确定性路由 (wl-search)",
3
- "date": "2026-07-21",
4
- "ruler": "scripts/validation/eval/dispatcher_ab.py",
5
- "env": "云 10.89.7.120, DashScope turbo/plus/max, 14 gold 查询(7通道x2)",
6
- "change": "wl-search '第一步:先判断问题类型'(纯LLM猜测) -> '确定性路由(按顺序首匹配信号,零猜测)' 7行表+默认rag_search; sync packages->.qoder diff空",
7
- "matrix": {
8
- "weak": {"old_acc": 0.857, "new_acc": 1.0, "gain_pp": 14.3},
9
- "mid": {"old_acc": 0.857, "new_acc": 1.0, "gain_pp": 14.3},
10
- "strong": {"old_acc": 0.857, "new_acc": 1.0, "gain_pp": 14.3}
11
- },
12
- "old_failures": [
13
- "status 字段有哪些枚举值 -> gold=db, got=rag_search (旧表 db 只在括号注)",
14
- "盘点考勤有哪些代码和字段 -> gold=context_pack, got=rag_search (旧表'盘点'非明确信号)"
15
- ],
16
- "insight": "三档同错同对(2错全修)再次印证 T1.0: 结构好时弱模型=强模型; dispatcher 价值在表结构非模型档位",
17
- "honest_caveats": [
18
- "gold 仅14条 + 作者同时写 gold 和新表, 有小样本/作者重叠风险(新表可能贴 gold)",
19
- "2 修复项(枚举->db/盘点->context_pack)确为旧表缺新表补的显式信号, 改进真实",
20
- "🖥️ 宿主端到端(弱模型跑真/wl-search 是否真按表走) 待用户验"
21
- ],
22
- "status": "代码改+sync+自验A/B完成; 🖥️宿主端到端列入 §12 V1.2"
23
- }
@@ -1,143 +0,0 @@
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()
@@ -1,261 +0,0 @@
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 +0,0 @@
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
- }