@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.
- package/bin/cli.js +0 -7
- package/package.json +29 -29
- package/templates/qoder/commands/optional/wl-spec.md +0 -8
- package/templates/qoder/commands/optional/wl-status.md +0 -8
- package/templates/qoder/commands/wl-code.md +0 -8
- package/templates/qoder/commands/wl-commit.md +0 -8
- package/templates/qoder/commands/wl-init.md +129 -129
- package/templates/qoder/commands/wl-search.md +0 -8
- package/templates/qoder/commands/wl-task.md +0 -8
- package/templates/qoder/commands/wl-test.md +0 -8
- package/templates/qoder/config.yaml +2 -3
- package/templates/qoder/contracts/insight.md +55 -55
- package/templates/qoder/hooks/pre-tool-use-commit.py +124 -124
- package/templates/qoder/hooks/session-start.py +384 -384
- package/templates/qoder/hooks/stop-eval.py +383 -383
- package/templates/qoder/scripts/capability/adapters/mcp.py +322 -322
- package/templates/qoder/scripts/capability/registry.py +245 -245
- package/templates/qoder/scripts/deployment/setup/install_qoderwork.py +14 -0
- package/templates/qoder/scripts/deployment/setup/setup.py +0 -11
- package/templates/qoder/scripts/domain/task/wlkj_panel.py +1503 -1503
- package/templates/qoder/scripts/foundation/bootstrap.py +145 -156
- package/templates/qoder/scripts/foundation/io/context_cache.py +94 -94
- package/templates/qoder/scripts/orchestration/wlkj.py +295 -306
- package/templates/qoder/scripts/tool_guide.md +70 -70
- package/templates/qoder/scripts/validation/eval/alignment_matrix.py +176 -176
- package/templates/qoder/scripts/validation/eval/bf2_content_fidelity.py +110 -110
- package/templates/qoder/scripts/validation/eval/bf2_llmjudge.py +104 -104
- package/templates/qoder/scripts/validation/eval/bf_score.py +218 -218
- package/templates/qoder/scripts/validation/eval/code_flywheel.py +150 -150
- package/templates/qoder/scripts/validation/eval/dispatcher_ab.py +156 -156
- package/templates/qoder/scripts/validation/eval/dispatcher_ab_2026-07-21.json +23 -23
- package/templates/qoder/scripts/validation/eval/feature_fidelity_flywheel.py +143 -143
- package/templates/qoder/scripts/validation/eval/gradient_matrix.py +261 -261
- package/templates/qoder/scripts/validation/eval/gradient_matrix_baseline_2026-07-21.json +33 -33
- package/templates/qoder/scripts/validation/eval/metrics_dashboard.py +105 -105
- package/templates/qoder/scripts/validation/eval/multi_turn_flywheel.py +118 -118
- package/templates/qoder/scripts/validation/eval/prd_fidelity_flywheel.py +128 -128
- package/templates/qoder/scripts/validation/eval/prd_flywheel.py +148 -148
- package/templates/qoder/scripts/validation/eval/prototype_fidelity_flywheel.py +166 -166
- package/templates/qoder/scripts/validation/eval/recall_flywheel.py +148 -148
- package/templates/qoder/scripts/validation/eval/robustness_flywheel.py +139 -139
- package/templates/qoder/scripts/validation/eval/speed_accuracy_flywheel.py +188 -188
- package/templates/qoder/scripts/validation/eval/task_flywheel.py +124 -124
- package/templates/qoder/scripts/validation/eval/token_flywheel.py +88 -88
- package/templates/qoder/scripts/validation/metrics/eval_code_ac.py +177 -177
- package/templates/qoder/scripts/validation/metrics/lint_cases.py +170 -170
- package/templates/qoder/scripts/validation/test/test_context_cache.py +78 -78
- package/templates/qoder/scripts/validation/test/test_lint_cases.py +60 -60
- package/templates/qoder/scripts/validation/test/test_pre_tool_use_commit.py +70 -70
- package/templates/qoder/commands/wl-data.md +0 -46
- package/templates/qoder/commands/wl-fix.md +0 -47
- package/templates/qoder/commands/wl-knowledge.md +0 -41
- package/templates/qoder/commands/wl-review.md +0 -49
- 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()
|