@hupan56/wlkj 3.3.13 → 3.3.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/bin/cli.js +0 -7
  2. package/package.json +29 -29
  3. package/templates/qoder/commands/optional/wl-spec.md +0 -8
  4. package/templates/qoder/commands/optional/wl-status.md +0 -8
  5. package/templates/qoder/commands/wl-code.md +0 -8
  6. package/templates/qoder/commands/wl-commit.md +0 -8
  7. package/templates/qoder/commands/wl-init.md +129 -129
  8. package/templates/qoder/commands/wl-search.md +0 -8
  9. package/templates/qoder/commands/wl-task.md +0 -8
  10. package/templates/qoder/commands/wl-test.md +0 -8
  11. package/templates/qoder/config.yaml +2 -3
  12. package/templates/qoder/contracts/insight.md +55 -55
  13. package/templates/qoder/hooks/pre-tool-use-commit.py +124 -124
  14. package/templates/qoder/hooks/session-start.py +384 -384
  15. package/templates/qoder/hooks/stop-eval.py +383 -383
  16. package/templates/qoder/scripts/capability/adapters/mcp.py +322 -322
  17. package/templates/qoder/scripts/capability/registry.py +245 -245
  18. package/templates/qoder/scripts/deployment/setup/install_qoderwork.py +14 -0
  19. package/templates/qoder/scripts/deployment/setup/setup.py +0 -11
  20. package/templates/qoder/scripts/domain/task/wlkj_panel.py +1503 -1503
  21. package/templates/qoder/scripts/foundation/bootstrap.py +145 -156
  22. package/templates/qoder/scripts/foundation/io/context_cache.py +94 -94
  23. package/templates/qoder/scripts/orchestration/wlkj.py +295 -306
  24. package/templates/qoder/scripts/tool_guide.md +70 -70
  25. package/templates/qoder/scripts/validation/eval/alignment_matrix.py +176 -176
  26. package/templates/qoder/scripts/validation/eval/bf2_content_fidelity.py +110 -110
  27. package/templates/qoder/scripts/validation/eval/bf2_llmjudge.py +104 -104
  28. package/templates/qoder/scripts/validation/eval/bf_score.py +218 -218
  29. package/templates/qoder/scripts/validation/eval/code_flywheel.py +150 -150
  30. package/templates/qoder/scripts/validation/eval/dispatcher_ab.py +156 -156
  31. package/templates/qoder/scripts/validation/eval/dispatcher_ab_2026-07-21.json +23 -23
  32. package/templates/qoder/scripts/validation/eval/feature_fidelity_flywheel.py +143 -143
  33. package/templates/qoder/scripts/validation/eval/gradient_matrix.py +261 -261
  34. package/templates/qoder/scripts/validation/eval/gradient_matrix_baseline_2026-07-21.json +33 -33
  35. package/templates/qoder/scripts/validation/eval/metrics_dashboard.py +105 -105
  36. package/templates/qoder/scripts/validation/eval/multi_turn_flywheel.py +118 -118
  37. package/templates/qoder/scripts/validation/eval/prd_fidelity_flywheel.py +128 -128
  38. package/templates/qoder/scripts/validation/eval/prd_flywheel.py +148 -148
  39. package/templates/qoder/scripts/validation/eval/prototype_fidelity_flywheel.py +166 -166
  40. package/templates/qoder/scripts/validation/eval/recall_flywheel.py +148 -148
  41. package/templates/qoder/scripts/validation/eval/robustness_flywheel.py +139 -139
  42. package/templates/qoder/scripts/validation/eval/speed_accuracy_flywheel.py +188 -188
  43. package/templates/qoder/scripts/validation/eval/task_flywheel.py +124 -124
  44. package/templates/qoder/scripts/validation/eval/token_flywheel.py +88 -88
  45. package/templates/qoder/scripts/validation/metrics/eval_code_ac.py +177 -177
  46. package/templates/qoder/scripts/validation/metrics/lint_cases.py +170 -170
  47. package/templates/qoder/scripts/validation/test/test_context_cache.py +78 -78
  48. package/templates/qoder/scripts/validation/test/test_lint_cases.py +60 -60
  49. package/templates/qoder/scripts/validation/test/test_pre_tool_use_commit.py +70 -70
  50. package/templates/qoder/commands/wl-data.md +0 -46
  51. package/templates/qoder/commands/wl-fix.md +0 -47
  52. package/templates/qoder/commands/wl-knowledge.md +0 -41
  53. package/templates/qoder/commands/wl-review.md +0 -49
  54. package/templates/workspace/specs/REQ-TEST-CTX/.context-cache.json +0 -1
@@ -1,188 +1,188 @@
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
+ """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 +1,124 @@
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()
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()