@hupan56/wlkj 3.3.13 → 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 -156
  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,148 +0,0 @@
1
- """recall_flywheel.py — FW1 检索类召回飞轮(非循环 held-out gold)。
2
-
3
- T1.0 的召回 R~80% 用 ILIKE-gold 是循环的(改非ILIKE检索提不了R)。本飞轮用**非循环 gold**:
4
- held-out = 图邻居中【不含主题关键词】的实体(只有 graph/semantic 检索能找到,ILIKE 找不到)。
5
- 量各检索策略对此 gold 的召回 → 改检索(加图扩展→语义)→ 重量 → 直到 ≥0.85。
6
- 纯检索质量(无 LLM),快,可反复转飞轮。
7
-
8
- 跑法: docker exec -i wlinkj-workspace-backend-1 python /app/scripts/validation/eval/recall_flywheel.py
9
- """
10
- import os
11
- import sys
12
- import json
13
-
14
- for _p in ("/app", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))):
15
- if os.path.isdir(_p) and _p not in sys.path:
16
- sys.path.insert(0, _p)
17
-
18
- from sqlalchemy import text, bindparam # noqa: E402
19
- from app.db import SessionLocal # noqa: E402
20
-
21
- THEMES = ["考勤", "排班", "加班", "车辆", "报警", "保险", "能耗", "预算", "导出", "审批"]
22
- TARGET = 0.85
23
-
24
-
25
- def _q(s, sql, params=None):
26
- try:
27
- return s.execute(text(sql), params or {}).fetchall()
28
- except Exception:
29
- try:
30
- s.rollback()
31
- except Exception:
32
- pass
33
- return []
34
-
35
-
36
- def ilike_ids(s, theme, limit):
37
- """name/cn 关键词命中(T1.0 现状检索口径)。"""
38
- return [r[0] for r in _q(s,
39
- "SELECT id FROM entities WHERE (cn ILIKE :p OR canonical ILIKE :p) LIMIT :n",
40
- {"p": f"%{theme}%", "n": limit})]
41
-
42
-
43
- def heldout_gold(s, theme, max_n=25):
44
- """非循环 gold:cn_desc(描述)提及主题 但 name/cn 不含 = 只有描述/语义/图能找到,
45
- 关键词搜 name/cn 找不到。cn_desc 与 name/cn 是独立信号 → 公平。"""
46
- rows = _q(s,
47
- "SELECT id FROM entities WHERE cn_desc ILIKE :p "
48
- "AND NOT (canonical ILIKE :p OR cn ILIKE :p) LIMIT :n",
49
- {"p": f"%{theme}%", "n": max_n * 3})
50
- return [r[0] for r in rows][:max_n]
51
-
52
-
53
- def strat_ilike(s, theme, limit=80):
54
- """基线:name/cn 关键词(T1.0 现状)。"""
55
- return set(ilike_ids(s, theme, limit))
56
-
57
-
58
- def strat_ilike_desc(s, theme, limit=80):
59
- """杠杆1:name/cn + cn_desc(多搜描述列,廉价高杠杆)。"""
60
- return set(r[0] for r in _q(s,
61
- "SELECT id FROM entities WHERE (canonical ILIKE :p OR cn ILIKE :p OR cn_desc ILIKE :p) LIMIT :n",
62
- {"p": f"%{theme}%", "n": limit}))
63
-
64
-
65
- def strat_ilike_graph(s, theme, seed_limit=25):
66
- """杠杆2:name/cn 种子 + 1跳图扩展。"""
67
- seeds = ilike_ids(s, theme, seed_limit)
68
- res = set(seeds)
69
- if seeds:
70
- stmt = text(
71
- "SELECT DISTINCT e.to_id FROM edges e WHERE e.from_id IN :ids"
72
- ).bindparams(bindparam("ids", expanding=True))
73
- try:
74
- for r in s.execute(stmt, {"ids": seeds}).fetchall():
75
- if r[0]:
76
- res.add(r[0])
77
- except Exception:
78
- try:
79
- s.rollback()
80
- except Exception:
81
- pass
82
- return res
83
-
84
-
85
- def recall(strategy_ids, gold):
86
- if not gold:
87
- return None
88
- return len(set(gold) & strategy_ids) / len(gold)
89
-
90
-
91
- def run(verbose=True):
92
- s = SessionLocal()
93
- per_theme = []
94
- accA = accB = accC = 0.0
95
- cnt = 0
96
- for th in THEMES:
97
- gold = heldout_gold(s, th, 25)
98
- if not gold:
99
- continue
100
- A = strat_ilike(s, th, 80)
101
- B = strat_ilike_desc(s, th, 80)
102
- C = strat_ilike_graph(s, th, 25)
103
- rA, rB, rC = recall(A, gold), recall(B, gold), recall(C, gold)
104
- per_theme.append({"theme": th, "gold_n": len(gold),
105
- "ilike": round(rA, 3), "ilike_desc": round(rB, 3), "ilike_graph": round(rC, 3)})
106
- accA += rA
107
- accB += rB
108
- accC += rC
109
- cnt += 1
110
- if verbose:
111
- print(" %s: gold=%d | ILIKE=%.2f | +desc=%.2f | +图=%.2f" % (th, len(gold), rA, rB, rC))
112
- s.close()
113
- n = cnt or 1
114
- return {"per_theme": per_theme, "n_themes": cnt,
115
- "ilike": round(accA / n, 3), "ilike_desc": round(accB / n, 3), "ilike_graph": round(accC / n, 3),
116
- "desc_gain_pp": round((accB - accA) / n * 100, 1),
117
- "graph_gain_pp": round((accC - accA) / n * 100, 1),
118
- "target": TARGET}
119
-
120
-
121
- def main():
122
- import argparse
123
- ap = argparse.ArgumentParser()
124
- ap.add_argument("--json", action="store_true")
125
- args = ap.parse_args()
126
- r = run()
127
- if args.json:
128
- print(json.dumps(r, ensure_ascii=False, indent=2))
129
- return
130
- print("\n## FW1 召回飞轮(held-out = cn_desc 提及但 name/cn 不含,非循环独立 gold)")
131
- print("| 检索策略 | 召回 R | 达标(≥85%%) |")
132
- print("|---|---|---|")
133
- print("| ILIKE name/cn(T1.0 现状) | %.1f%% | %s |" % (r["ilike"] * 100, "✓" if r["ilike"] >= TARGET else "✗"))
134
- print("| ILIKE +cn_desc(杠杆1 廉价) | %.1f%% | %s |" % (r["ilike_desc"] * 100, "✓" if r["ilike_desc"] >= TARGET else "✗"))
135
- print("| ILIKE +图扩展(杠杆2) | %.1f%% | %s |" % (r["ilike_graph"] * 100, "✓" if r["ilike_graph"] >= TARGET else "✗"))
136
- print("| +cn_desc 增益 | %+.1fpp | — |" % r["desc_gain_pp"])
137
- print("| +图扩展 增益 | %+.1fpp | — |" % r["graph_gain_pp"])
138
- best = max(r["ilike_desc"], r["ilike_graph"])
139
- met = best >= TARGET
140
- winner = "+cn_desc" if r["ilike_desc"] >= r["ilike_graph"] else "+图扩展"
141
- print("\n飞轮状态: 最佳策略(%s)召回 %.1f%% %s 85%% 目标。%s" % (
142
- winner, best * 100, "达标" if met else "未达",
143
- "→ 胜出杠杆该接进 wl-search 检索;召回飞轮此轮达标,转 FW2" if met
144
- else "→ 下轮:组合杠杆(+desc+图) / 语义 rag_search / 提种子数,继续转"))
145
-
146
-
147
- if __name__ == "__main__":
148
- main()
@@ -1,139 +0,0 @@
1
- """robustness_flywheel.py — FW7 鲁棒性飞轮(§1.2 ⑨ 歧义/对抗输入下准确性)。
2
-
3
- 两维鲁棒性:
4
- - 歧义处理:「保险」在 ICS 既指车险(正确域)又指社保(养老/医疗/失业,KG 也有)。
5
- bare 字面取 → 车险/社保混;+工作流(歧义提炼→车辆保险) → 车险精确率升。
6
- - 对抗拒答:假功能「火星人登录」「时间穿越」该拒(说不存在),不该编造实体。
7
- bare 可能编造;+工作流(只列真实检索结果) → 拒答。
8
- 飞轮量→改(歧义提炼 prompt / 只信检索)→重量→达标。
9
-
10
- 跑法: docker exec -i wlinkj-workspace-backend-1 python /app/scripts/validation/eval/robustness_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
- # 域分类:社保词 vs 车险词
31
- _SHEBAO = ["养老", "医疗", "失业", "工伤", "生育", "社保", "公积金"]
32
- _CHEXIAN = ["车", "vehicle", "交强", "商业险", "承保", "理赔", "保单"]
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}],
39
- "temperature": 0.0, "max_tokens": max_tokens}).encode()
40
- for a in range(3):
41
- try:
42
- req = urllib.request.Request(CHAT_URL, data=body,
43
- headers={"Authorization": f"Bearer {CHAT_KEY}", "Content-Type": "application/json"})
44
- return json.loads(urllib.request.urlopen(req, timeout=timeout).read())["choices"][0]["message"]["content"]
45
- except (urllib.error.URLError, TimeoutError, OSError, KeyError) as e:
46
- if a < 2: time.sleep(1.0*(a+1))
47
- else: return f"[ERR {repr(e)[:50]}]"
48
-
49
-
50
- def _q(s, sql, p=None):
51
- try: return s.execute(text(sql), p or {}).fetchall()
52
- except Exception:
53
- try: s.rollback()
54
- except Exception: pass
55
- return []
56
-
57
-
58
- def classify_line(line):
59
- """行 → 车险 / 社保 / 其他。"""
60
- low = line
61
- if any(w in low for w in _SHEBAO): return "shebao"
62
- if any(w in low for w in _CHEXIAN): return "chexian"
63
- return "other"
64
-
65
-
66
- def ambiguity_run():
67
- """「保险」查询 → 车险精确率(正确域占比)。bare vs +歧义提炼 scaffold。"""
68
- # KG 里 保险 的真实体(车险+社保混)
69
- s = SessionLocal()
70
- rows = _q(s, "SELECT canonical, cn FROM entities WHERE (canonical ILIKE '%保险%' OR cn ILIKE '%保险%') LIMIT 30")
71
- s.close()
72
- kg_ctx = "\n".join("%s (%s)" % (c, n) for c, n in rows if c)
73
-
74
- bare_p = ("找出 ICS 系统里和「保险」相关的具体对象(表/类/字段/接口),"
75
- "逐行列出确信真实存在的名称,勿编造。")
76
- scaff_p = ("找出 ICS 系统(车辆物联网)里和「保险」相关的具体对象。\n"
77
- "⚠️ 歧义提炼:本系统是【车辆物联网】,'保险' 指【车辆保险/交强险/商业险/承保理赔】,"
78
- "**不是社保(养老/医疗/失业)**。请只列车辆保险相关,排除社保。\n"
79
- "知识图谱检索到的实体:\n%s\n请逐行列出【车辆保险】相关名称,勿列社保,勿编造。" % kg_ctx)
80
-
81
- def chexian_precision(ans):
82
- cls = [classify_line(l) for l in ans.splitlines() if l.strip() and len(l.strip()) <= 60]
83
- cx = cls.count("chexian"); sb = cls.count("shebao")
84
- tot = cx + sb
85
- return round(cx / tot, 3) if tot else 0.0, cx, sb
86
-
87
- b = chat(bare_p); f = chat(scaff_p)
88
- bp, bcx, bsb = chexian_precision(b)
89
- fp, fcx, fsb = chexian_precision(f)
90
- return {"bare_chexian_precision": bp, "bare_chexian": bcx, "bare_shebao": bsb,
91
- "scaffold_chexian_precision": fp, "scaffold_chexian": fcx, "scaffold_shebao": fsb}
92
-
93
-
94
- def adversarial_run():
95
- """假功能查询 → 拒答率(说不存在 vs 编造)。"""
96
- FAKES = ["火星人指纹登录", "时间穿越审批", "心灵感应派单"]
97
- out = []
98
- for fake in FAKES:
99
- bare_p = f"为「{fake}」功能,逐行列出 ICS 系统里需修改的代码文件/类/方法名,确信真实存在的。"
100
- scaff_p = (f"为「{fake}」功能,逐行列出需修改的代码实体。\n"
101
- f"⚠️ 若该功能在系统里不存在(知识图谱无相关实体),直接说'此功能不存在',"
102
- f"不要编造任何名称。")
103
- b = chat(bare_p); f = chat(scaff_p)
104
- # 拒答 = 含"不存在"/"无"/"没找到" 且 不含具体编造的代码名(CamelCase/下划线标识符)
105
- def refused(ans):
106
- has_no = any(w in ans for w in ["不存在", "没有", "未找到", "无此", "找不到", "不在系统"])
107
- has_fab = bool(re.search(r'[A-Za-z_][\w]{4,}', ans)) and not has_no
108
- return has_no and not has_fab
109
- out.append({"fake": fake, "bare_refused": refused(b), "scaffold_refused": refused(f),
110
- "bare_fab": bool(re.search(r'[A-Za-z_][\w]{4,}', b)), "scaffold_fab": bool(re.search(r'[A-Za-z_][\w]{4,}', f))})
111
- n = len(FAKES)
112
- return {"items": out,
113
- "bare_refuse_rate": round(sum(1 for x in out if x["bare_refused"]) / n, 3),
114
- "scaffold_refuse_rate": round(sum(1 for x in out if x["scaffold_refused"]) / n, 3)}
115
-
116
-
117
- def main():
118
- import argparse
119
- ap = argparse.ArgumentParser()
120
- ap.add_argument("--json", action="store_true")
121
- args = ap.parse_args()
122
- amb = ambiguity_run()
123
- adv = adversarial_run()
124
- r = {"ambiguity": amb, "adversarial": adv}
125
- if args.json:
126
- print(json.dumps(r, ensure_ascii=False, indent=2))
127
- return
128
- print("## FW7 鲁棒性飞轮(歧义/对抗)")
129
- print("【歧义】「保险」车险精确率(正确域占比,越高越好):")
130
- print(" bare(字面): %.0f%% (车险%d / 社保%d)" % (amb["bare_chexian_precision"]*100, amb["bare_chexian"], amb["bare_shebao"]))
131
- print(" +歧义提炼: %.0f%% (车险%d / 社保%d)" % (amb["scaffold_chexian_precision"]*100, amb["scaffold_chexian"], amb["scaffold_shebao"]))
132
- print("【对抗】假功能拒答率(说不存在不编造,越高越好):")
133
- print(" bare: %.0f%%" % (adv["bare_refuse_rate"]*100))
134
- print(" +拒答铁律: %.0f%%" % (adv["scaffold_refuse_rate"]*100))
135
- print("\n达标? 歧义车险精确率 +对抗拒答率 越高越好;不达标续转(更强歧义词表/硬拒答 prompt)。")
136
-
137
-
138
- if __name__ == "__main__":
139
- main()
@@ -1,188 +0,0 @@
1
- """speed_accuracy_flywheel.py — FW6 又快又准对齐飞轮(准确性 × 速度 × token Pareto)。
2
-
3
- GOAL=又快又准。之前只测"准"(P/R/完整性/路由),没测"快"(latency)。本尺子对齐测:
4
- - 准确性:grounding 精度 P(反幻觉) + 召回 R
5
- - 速度:latency P50/P95(API 代理真测 wall-clock)
6
- - token:prompt+completion total
7
- 跨 {weak/mid/strong} × {bare/scaffold}, 搜 wl-search 任务(端到端: 检索+LLM)。
8
- 找 Pareto 最优档(够准的最快最省 token 配置)→ 卖点"弱模型+工作流又快又准"的速度证据。
9
-
10
- 跑法: docker exec -i wlinkj-workspace-backend-1 python /app/scripts/validation/eval/speed_accuracy_flywheel.py
11
- """
12
- import os
13
- import re
14
- import sys
15
- import json
16
- import time
17
- import statistics
18
- import urllib.request
19
- import urllib.error
20
-
21
- for _p in ("/app", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))):
22
- if os.path.isdir(_p) and _p not in sys.path:
23
- sys.path.insert(0, _p)
24
-
25
- from sqlalchemy import text # noqa: E402
26
- from app.db import SessionLocal # noqa: E402
27
-
28
- CHAT_URL = os.environ.get("ALI_CHAT_BASE", "").rstrip("/") + "/chat/completions"
29
- CHAT_KEY = os.environ.get("DASHSCOPE_API_KEY", "")
30
- MODELS = {"weak": "qwen-turbo", "mid": "qwen-plus", "strong": "qwen-max"}
31
- THEMES = ["考勤", "排班", "加班", "车辆", "保险"] # 5 主题(K=5 量 latency)
32
- RETRIEVE_K = 45 # FW1 已证 K=45 达标
33
- ACC_TARGET_P = 0.85 # 准确性目标(精度/召回)
34
- _LINE_CLEAN = re.compile(r"^[\-\*\d\.\)、\s]+")
35
- _CODE_TOK = re.compile(r"[A-Za-z_][\w./\-:]{2,}")
36
-
37
-
38
- def chat_timed(model, prompt, max_tokens=500, timeout=90):
39
- """返回 (text, total_tokens, latency_ms)。"""
40
- body = json.dumps({"model": model,
41
- "messages": [{"role": "system", "content": "你是车辆物联网系统(ICS)的代码/业务知识助手。只回答真实存在的对象,绝不编造。"},
42
- {"role": "user", "content": prompt}],
43
- "temperature": 0.0, "max_tokens": max_tokens}).encode()
44
- t0 = time.time()
45
- for attempt in range(3):
46
- try:
47
- req = urllib.request.Request(CHAT_URL, data=body,
48
- headers={"Authorization": f"Bearer {CHAT_KEY}", "Content-Type": "application/json"})
49
- d = json.loads(urllib.request.urlopen(req, timeout=timeout).read())
50
- return d["choices"][0]["message"]["content"], int(d.get("usage", {}).get("total_tokens", 0)), round((time.time() - t0) * 1000)
51
- except (urllib.error.URLError, TimeoutError, OSError, KeyError) as e:
52
- if attempt < 2:
53
- time.sleep(1.0 * (attempt + 1))
54
- else:
55
- return f"[ERR {repr(e)[:50]}]", 0, round((time.time() - t0) * 1000)
56
-
57
-
58
- def _q(s, sql, params=None):
59
- try:
60
- return s.execute(text(sql), params or {}).fetchall()
61
- except Exception:
62
- try:
63
- s.rollback()
64
- except Exception:
65
- pass
66
- return []
67
-
68
-
69
- def load_universe_and_gold():
70
- s = SessionLocal()
71
- rows = _q(s, "SELECT canonical, cn FROM entities WHERE canonical IS NOT NULL")
72
- UNI = set()
73
- for c, n in rows:
74
- if c: UNI.add(c.strip())
75
- if n: UNI.add(n.strip())
76
- GOLD = {}
77
- for th in THEMES:
78
- rs = _q(s, "SELECT canonical, cn FROM entities WHERE (canonical ILIKE :p OR cn ILIKE :p) LIMIT 40", {"p": f"%{th}%"})
79
- GOLD[th] = [(c, n) for c, n in rs if c]
80
- s.close()
81
- return UNI, GOLD
82
-
83
-
84
- def retrieve(theme, k=RETRIEVE_K):
85
- s = SessionLocal()
86
- rs = _q(s, "SELECT canonical, cn FROM entities WHERE (canonical ILIKE :p OR cn ILIKE :p) LIMIT :k",
87
- {"p": f"%{theme}%", "k": k})
88
- s.close()
89
- return [(c, n) for c, n in rs if c]
90
-
91
-
92
- def score(ans, gold, UNI):
93
- cands = set()
94
- for line in ans.splitlines():
95
- line = _LINE_CLEAN.sub("", line).strip()
96
- if line and len(line) <= 60:
97
- m = _CODE_TOK.findall(line)
98
- if m:
99
- cands.add(m[0])
100
- real = {x for x in cands if x in UNI}
101
- precision = (len(real) / len(cands)) if cands else 0.0
102
- hit = sum(1 for c, n in gold if (c and c in ans) or (n and n in ans))
103
- recall = (hit / len(gold)) if gold else 0.0
104
- return precision, recall
105
-
106
-
107
- def prompt_bare(theme):
108
- return (f"在我们的车辆物联网系统(ICS)里,找出和「{theme}」相关的具体对象(表/Java类/接口/按钮等)。\n"
109
- f"逐行列出你确信真实存在的名称,每行一个,不要编造。")
110
-
111
-
112
- def prompt_scaffold(theme, ctx):
113
- return (f"在我们的车辆物联网系统(ICS)里,找出和「{theme}」相关的具体对象。\n"
114
- f"知识图谱检索到相关实体(均真实存在):\n{ctx}\n\n"
115
- f"请把检索结果里的实体【尽量全部逐行列出】,每行一个,直接用名称,勿筛选/省略,勿编造。")
116
-
117
-
118
- def pctile(vals, p):
119
- if not vals:
120
- return 0
121
- vals = sorted(vals)
122
- k = max(0, min(len(vals) - 1, int(round((p / 100.0) * (len(vals) - 1)))))
123
- return vals[k]
124
-
125
-
126
- def run_cell(model, mode, UNI, GOLD):
127
- """跑 5 主题,返回 (P, R, p50_lat, p95_lat, avg_tok)。"""
128
- Ps, Rs, lats, toks = [], [], [], []
129
- for th in THEMES:
130
- gold = GOLD[th]
131
- if mode == "bare":
132
- p = prompt_bare(th)
133
- else:
134
- ents = retrieve(th)
135
- ctx = "\n".join("%s (%s)" % (c, n) for c, n in ents)
136
- p = prompt_scaffold(th, ctx)
137
- ans, tok, lat = chat_timed(model, p)
138
- prec, rec = score(ans, gold, UNI)
139
- Ps.append(prec); Rs.append(rec); lats.append(lat); toks.append(tok)
140
- n = len(THEMES) or 1
141
- return {"P": round(sum(Ps)/n, 3), "R": round(sum(Rs)/n, 3),
142
- "p50_lat": pctile(lats, 50), "p95_lat": pctile(lats, 95),
143
- "avg_tok": round(sum(toks)/n)}
144
-
145
-
146
- def run():
147
- UNI, GOLD = load_universe_and_gold()
148
- cells = {}
149
- for tier, model in MODELS.items():
150
- for mode in ("bare", "scaffold"):
151
- c = run_cell(model, mode, UNI, GOLD)
152
- cells[f"{tier}/{mode}"] = c
153
- print(" %s/%s: P=%.2f R=%.2f p50=%dms p95=%dms tok=%d" % (
154
- tier, mode, c["P"], c["R"], c["p50_lat"], c["p95_lat"], c["avg_tok"]))
155
- return {"cells": cells, "acc_target": ACC_TARGET_P}
156
-
157
-
158
- def main():
159
- import argparse
160
- ap = argparse.ArgumentParser()
161
- ap.add_argument("--json", action="store_true")
162
- args = ap.parse_args()
163
- r = run()
164
- if args.json:
165
- print(json.dumps(r, ensure_ascii=False, indent=2))
166
- return
167
- c = r["cells"]
168
- print("\n## FW6 又快又准对齐矩阵(准确性 P/R × 速度 P50/P95 × token)")
169
- print("| 档 | 准 P | 全 R | 速 P50 | 速 P95 | token | 准达标? |")
170
- print("|---|---|---|---|---|---|---|")
171
- for tier in MODELS:
172
- for mode in ("bare", "scaffold"):
173
- x = c[f"{tier}/{mode}"]
174
- acc_ok = x["P"] >= ACC_TARGET_P and x["R"] >= ACC_TARGET_P
175
- print("| %s/%s | %.2f | %.2f | %dms | %dms | %d | %s |" % (
176
- tier, mode, x["P"], x["R"], x["p50_lat"], x["p95_lat"], x["avg_tok"], "✓" if acc_ok else "✗"))
177
- # Pareto:够准的档里最快最省
178
- qual = [(k, v) for k, v in c.items() if v["P"] >= ACC_TARGET_P and v["R"] >= ACC_TARGET_P]
179
- print("\n准达标的档:", [k for k, _ in qual] or "无")
180
- if qual:
181
- knee = min(qual, key=lambda kv: kv[1]["p50_lat"] + kv[1]["avg_tok"] * 0.5)
182
- print("Pareto 最优档(准达标里 最快+最省): %s → P%.2f R%.2f p50=%dms tok=%d" % (
183
- knee[0], knee[1]["P"], knee[1]["R"], knee[1]["p50_lat"], knee[1]["avg_tok"]))
184
- print("→ 卖点速度证据:弱模型+工作流既准又快又省,无需上强模型(强模型更慢更贵不更准)。")
185
-
186
-
187
- if __name__ == "__main__":
188
- main()
@@ -1,124 +0,0 @@
1
- """task_flywheel.py — FW4 wl-task 操作类动作路由飞轮。
2
-
3
- 操作类维度(SPEC §1.1):成功率(操作成功/总试)/正确性(操作对对象)。
4
- 可代理的是【动作路由正确性】:自然语言任务短语 → 正确动作(create/list/show/start/finish/...)。
5
- bare("判断该走哪个动作") vs +wl-task dispatcher 表 → 路由准确率。
6
- 成功率(脚本真成功)/归属正确性(不改错队友任务)=执行流→🖥️。
7
-
8
- 跑法: docker exec -i wlinkj-workspace-backend-1 python /app/scripts/validation/eval/task_flywheel.py
9
- """
10
- import os
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
- CHAT_URL = os.environ.get("ALI_CHAT_BASE", "").rstrip("/") + "/chat/completions"
22
- CHAT_KEY = os.environ.get("DASHSCOPE_API_KEY", "")
23
- MODELS = {"weak": "qwen-turbo", "mid": "qwen-plus", "strong": "qwen-max"}
24
- ACTIONS = ["create", "list", "show", "start", "finish", "archive", "rank", "plan", "sync", "zentao"]
25
-
26
- # gold 任务短语 → 正确动作(含语义类:无动作词靠意图)
27
- GOLD = [
28
- ("建任务 报价单导出", "create"),
29
- ("把R-231报价单导出拆成任务", "create"),
30
- ("看任务", "list"),
31
- ("接下来干啥", "list"),
32
- ("看下登录页怎么样了", "show"),
33
- ("开始做登录", "start"),
34
- ("做完了", "finish"),
35
- ("今天干了3小时", "zentao"),
36
- ("归档 老需求", "archive"),
37
- ("排优先级", "rank"),
38
- ("排期 甘特图", "plan"),
39
- ("PRD发禅道", "zentao"),
40
- ("同步到禅道", "sync"),
41
- ]
42
-
43
- SPEC_OLD = "按用户这句话的意图,判断 /wl-task 该走哪个动作(建/看/启动/完成/归档/排优先级/排期/同步/禅道)。"
44
- SPEC_NEW = """按 $ARGUMENTS 第一个词/意图确定性选动作(按表查,不要"判断"):
45
- - create / "建任务" / "拆成任务" / "把XX建成任务" → create
46
- - list / "看任务" / "有哪些任务" / "接下来干啥" / "现在要做哪些" → list
47
- - show / "看下XX" / "XX怎么样了" → show
48
- - start / "开始做" / "启动XX" → start
49
- - finish / "做完了" / "完成了" → finish
50
- - archive / "归档" → archive
51
- - rank / "排优先级" / "排一下" → rank
52
- - plan / "排期" / "甘特图" / "排个日期" → plan
53
- - sync / "同步到禅道" → sync
54
- - zentao / "PRD发禅道" / "记工时" / "改任务状态" / "今天干了X小时" → zentao"""
55
-
56
-
57
- def chat(model, prompt, max_tokens=30, timeout=60):
58
- body = json.dumps({"model": model,
59
- "messages": [{"role": "user", "content": prompt}],
60
- "temperature": 0.0, "max_tokens": max_tokens}).encode()
61
- for attempt in range(3):
62
- try:
63
- req = urllib.request.Request(CHAT_URL, data=body,
64
- headers={"Authorization": f"Bearer {CHAT_KEY}", "Content-Type": "application/json"})
65
- return json.loads(urllib.request.urlopen(req, timeout=timeout).read())["choices"][0]["message"]["content"].strip()
66
- except (urllib.error.URLError, TimeoutError, OSError, KeyError) as e:
67
- if attempt < 2:
68
- time.sleep(1.0 * (attempt + 1))
69
- else:
70
- return f"[ERR {repr(e)[:50]}]"
71
-
72
-
73
- def _norm(ans):
74
- a = ans.lower()
75
- for act in ACTIONS:
76
- if act in a:
77
- return act
78
- return "__none__"
79
-
80
-
81
- def run_spec(model, spec, label):
82
- correct = 0
83
- for query, gold in GOLD:
84
- prompt = (f"你是任务管理命令 /wl-task 的路由器。严格按【路由规则】判断这条操作走哪个动作。\n\n"
85
- f"【路由规则】\n{spec}\n\n【操作】{query}\n\n"
86
- f"只输出一个动作名,从这些里选: {', '.join(ACTIONS)}。不要解释。")
87
- got = _norm(chat(model, prompt))
88
- ok = (got == gold)
89
- correct += int(ok)
90
- print(" [%s] %-22s gold=%-8s got=%-8s %s" % (label, query[:22], gold, got, "✓" if ok else "✗"))
91
- return round(correct / len(GOLD), 3)
92
-
93
-
94
- def run():
95
- rep = {"models": MODELS, "n": len(GOLD), "matrix": {}}
96
- for tier, model in MODELS.items():
97
- old = run_spec(model, SPEC_OLD, "old")
98
- new = run_spec(model, SPEC_NEW, "new")
99
- rep["matrix"][tier] = {"old_acc": old, "new_acc": new, "gain_pp": round((new - old) * 100, 1)}
100
- print(" >> %s(%s): old=%.1f%% new=%.1f%% gain=%+.1fpp" % (tier, model, old*100, new*100, (new-old)*100))
101
- return rep
102
-
103
-
104
- def main():
105
- import argparse
106
- ap = argparse.ArgumentParser()
107
- ap.add_argument("--json", action="store_true")
108
- args = ap.parse_args()
109
- r = run()
110
- if args.json:
111
- print(json.dumps(r, ensure_ascii=False, indent=2))
112
- return
113
- m = r["matrix"]
114
- print("\n## FW4 wl-task 操作类动作路由飞轮(选对动作准确率)")
115
- print("| 模型档 | 旧'判断意图' | +dispatcher 表 | 增益 |")
116
- print("|---|---|---|---|")
117
- for t in MODELS:
118
- c = m[t]
119
- print("| %s(%s) | %.1f%% | %.1f%% | %+.1fpp |" % (t, MODELS[t], c["old_acc"]*100, c["new_acc"]*100, c["gain_pp"]))
120
- print("\n成功率(脚本真成功)/归属正确性(不改错队友任务) = 执行流维度 → 🖥️ 宿主 wl-task 真跑")
121
-
122
-
123
- if __name__ == "__main__":
124
- main()