@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,150 +1,150 @@
|
|
|
1
|
-
"""code_flywheel.py — FW3 wl-code 变更类精准度飞轮(改对实体 / 误改率)。
|
|
2
|
-
|
|
3
|
-
变更类维度(SPEC §1.1):正确性(过测试)/安全(gate_check)/精准(改对文件-误改率)。
|
|
4
|
-
本尺子量【精准】:给功能加改动,模型列"该改的代码实体",量
|
|
5
|
-
- 精准 P = 列出的实体 ∈ KG 真实体的比例(bare 编造→低 P=误改率高;scaffold 接地→高 P)
|
|
6
|
-
- 召回 R = gold 实体被列出比例
|
|
7
|
-
bare vs +get_impact grounding scaffold。安全(gate)/正确性(过测试)是执行流→🖥️。
|
|
8
|
-
|
|
9
|
-
跑法: docker exec -i wlinkj-workspace-backend-1 python /app/scripts/validation/eval/code_flywheel.py
|
|
10
|
-
"""
|
|
11
|
-
import os
|
|
12
|
-
import re
|
|
13
|
-
import sys
|
|
14
|
-
import json
|
|
15
|
-
import time
|
|
16
|
-
import urllib.request
|
|
17
|
-
import urllib.error
|
|
18
|
-
|
|
19
|
-
for _p in ("/app", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))):
|
|
20
|
-
if os.path.isdir(_p) and _p not in sys.path:
|
|
21
|
-
sys.path.insert(0, _p)
|
|
22
|
-
|
|
23
|
-
from sqlalchemy import text # noqa: E402
|
|
24
|
-
from app.db import SessionLocal # noqa: E402
|
|
25
|
-
|
|
26
|
-
CHAT_URL = os.environ.get("ALI_CHAT_BASE", "").rstrip("/") + "/chat/completions"
|
|
27
|
-
CHAT_KEY = os.environ.get("DASHSCOPE_API_KEY", "")
|
|
28
|
-
MODEL = "qwen-turbo"
|
|
29
|
-
FEATURES = [("车辆保养提醒", "车辆"), ("考勤异常导出", "考勤"), ("保险续保预警", "保险")]
|
|
30
|
-
_LINE_CLEAN = re.compile(r"^[\-\*\d\.\)、\s]+")
|
|
31
|
-
_CODE_TOK = re.compile(r"[A-Za-z_][\w./\-:]{2,}")
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def chat(prompt, max_tokens=500, timeout=80):
|
|
35
|
-
body = json.dumps({"model": MODEL,
|
|
36
|
-
"messages": [{"role": "user", "content": prompt}],
|
|
37
|
-
"temperature": 0.0, "max_tokens": max_tokens}).encode()
|
|
38
|
-
for attempt in range(3):
|
|
39
|
-
try:
|
|
40
|
-
req = urllib.request.Request(CHAT_URL, data=body,
|
|
41
|
-
headers={"Authorization": f"Bearer {CHAT_KEY}", "Content-Type": "application/json"})
|
|
42
|
-
return json.loads(urllib.request.urlopen(req, timeout=timeout).read())["choices"][0]["message"]["content"]
|
|
43
|
-
except (urllib.error.URLError, TimeoutError, OSError, KeyError) as e:
|
|
44
|
-
if attempt < 2:
|
|
45
|
-
time.sleep(1.0 * (attempt + 1))
|
|
46
|
-
else:
|
|
47
|
-
return f"[ERR {repr(e)[:60]}]"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def _q(s, sql, params=None):
|
|
51
|
-
try:
|
|
52
|
-
return s.execute(text(sql), params or {}).fetchall()
|
|
53
|
-
except Exception:
|
|
54
|
-
try:
|
|
55
|
-
s.rollback()
|
|
56
|
-
except Exception:
|
|
57
|
-
pass
|
|
58
|
-
return []
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def load_universe_and_gold():
|
|
62
|
-
"""UNI=所有真实体名(canonical+cn, 精度校验); GOLD=每主题的真实体(召回校验)。"""
|
|
63
|
-
s = SessionLocal()
|
|
64
|
-
rows = _q(s, "SELECT canonical, cn FROM entities WHERE canonical IS NOT NULL")
|
|
65
|
-
UNI = set()
|
|
66
|
-
for c, n in rows:
|
|
67
|
-
if c:
|
|
68
|
-
UNI.add(c.strip())
|
|
69
|
-
if n:
|
|
70
|
-
UNI.add(n.strip())
|
|
71
|
-
GOLD = {}
|
|
72
|
-
for feat, theme in FEATURES:
|
|
73
|
-
rs = _q(s, "SELECT canonical, cn FROM entities WHERE (canonical ILIKE :p OR cn ILIKE :p) "
|
|
74
|
-
"AND type IN ('CONTROLLER','SERVICE','FUNCTION','COLUMN','TABLE') LIMIT 20", {"p": f"%{theme}%"})
|
|
75
|
-
GOLD[feat] = [(c, n) for c, n in rs if c]
|
|
76
|
-
s.close()
|
|
77
|
-
return UNI, GOLD
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def extract_names(ans):
|
|
81
|
-
cands = set()
|
|
82
|
-
for line in ans.splitlines():
|
|
83
|
-
line = _LINE_CLEAN.sub("", line).strip()
|
|
84
|
-
if line and len(line) <= 60:
|
|
85
|
-
m = _CODE_TOK.findall(line)
|
|
86
|
-
if m:
|
|
87
|
-
cands.add(m[0])
|
|
88
|
-
return cands
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def score(ans, gold, UNI):
|
|
92
|
-
cands = extract_names(ans)
|
|
93
|
-
real = {x for x in cands if x in UNI}
|
|
94
|
-
precision = (len(real) / len(cands)) if cands else 0.0
|
|
95
|
-
hit = 0
|
|
96
|
-
for c, n in gold:
|
|
97
|
-
if (c and c in ans) or (n and n in ans):
|
|
98
|
-
hit += 1
|
|
99
|
-
recall = (hit / len(gold)) if gold else 0.0
|
|
100
|
-
return round(precision, 3), round(recall, 3), len(cands)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def run():
|
|
104
|
-
UNI, GOLD = load_universe_and_gold()
|
|
105
|
-
rep = {"features": []}
|
|
106
|
-
for feat, theme in FEATURES:
|
|
107
|
-
gold = GOLD[feat]
|
|
108
|
-
gold_ctx = "\n".join("%s (%s)" % (c, n) for c, n in gold) or "(无)"
|
|
109
|
-
bare = chat("你是开发。要在车辆物联网系统(ICS)给「%s」加功能。\n"
|
|
110
|
-
"逐行列出【需要修改的代码文件/Java类/方法名】,每行一个,只列确信真实存在的,不要编造。" % feat)
|
|
111
|
-
scaff = chat("你是开发。要在车辆物联网系统(ICS)给「%s」加功能。\n"
|
|
112
|
-
"get_impact 检索到该功能相关的真实代码实体(均真实存在):\n%s\n\n"
|
|
113
|
-
"请把以上检索结果里的实体【尽量全部逐行列出】,每行一个,直接用检索结果里的名称,"
|
|
114
|
-
"不要自行筛选/合并/省略,不要编造。检索到几个就列几个。" % (feat, gold_ctx))
|
|
115
|
-
bP, bR, bN = score(bare, gold, UNI)
|
|
116
|
-
sP, sR, sN = score(scaff, gold, UNI)
|
|
117
|
-
rep["features"].append({"feature": feat, "gold_n": len(gold),
|
|
118
|
-
"bare_P": bP, "bare_R": bR, "scaffold_P": sP, "scaffold_R": sR})
|
|
119
|
-
print(" %s: gold=%d | bare P=%.2f R=%.2f | scaffold P=%.2f R=%.2f" % (
|
|
120
|
-
feat, len(gold), bP, bR, sP, sR))
|
|
121
|
-
n = len(FEATURES) or 1
|
|
122
|
-
rep["agg"] = {
|
|
123
|
-
"bare_P": round(sum(f["bare_P"] for f in rep["features"]) / n, 3),
|
|
124
|
-
"scaffold_P": round(sum(f["scaffold_P"] for f in rep["features"]) / n, 3),
|
|
125
|
-
"bare_R": round(sum(f["bare_R"] for f in rep["features"]) / n, 3),
|
|
126
|
-
"scaffold_R": round(sum(f["scaffold_R"] for f in rep["features"]) / n, 3),
|
|
127
|
-
}
|
|
128
|
-
return rep
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def main():
|
|
132
|
-
import argparse
|
|
133
|
-
ap = argparse.ArgumentParser()
|
|
134
|
-
ap.add_argument("--json", action="store_true")
|
|
135
|
-
args = ap.parse_args()
|
|
136
|
-
r = run()
|
|
137
|
-
a = r["agg"]
|
|
138
|
-
if args.json:
|
|
139
|
-
print(json.dumps(r, ensure_ascii=False, indent=2))
|
|
140
|
-
return
|
|
141
|
-
print("\n## FW3 wl-code 变更类精准飞轮(改对实体 P / 召回 R)")
|
|
142
|
-
print("| 维度 | bare(弱模型裸) | +get_impact scaffold |")
|
|
143
|
-
print("|---|---|---|")
|
|
144
|
-
print("| 精准 P(改对·反误改) | %.1f%% | %.1f%% |" % (a["bare_P"] * 100, a["scaffold_P"] * 100))
|
|
145
|
-
print("| 召回 R(改全) | %.1f%% | %.1f%% |" % (a["bare_R"] * 100, a["scaffold_R"] * 100))
|
|
146
|
-
print("\n安全(gate_check 前置)/正确性(过测试) = 执行流维度 → 🖥️ 宿主 wl-code 真跑")
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if __name__ == "__main__":
|
|
150
|
-
main()
|
|
1
|
+
"""code_flywheel.py — FW3 wl-code 变更类精准度飞轮(改对实体 / 误改率)。
|
|
2
|
+
|
|
3
|
+
变更类维度(SPEC §1.1):正确性(过测试)/安全(gate_check)/精准(改对文件-误改率)。
|
|
4
|
+
本尺子量【精准】:给功能加改动,模型列"该改的代码实体",量
|
|
5
|
+
- 精准 P = 列出的实体 ∈ KG 真实体的比例(bare 编造→低 P=误改率高;scaffold 接地→高 P)
|
|
6
|
+
- 召回 R = gold 实体被列出比例
|
|
7
|
+
bare vs +get_impact grounding scaffold。安全(gate)/正确性(过测试)是执行流→🖥️。
|
|
8
|
+
|
|
9
|
+
跑法: docker exec -i wlinkj-workspace-backend-1 python /app/scripts/validation/eval/code_flywheel.py
|
|
10
|
+
"""
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
import sys
|
|
14
|
+
import json
|
|
15
|
+
import time
|
|
16
|
+
import urllib.request
|
|
17
|
+
import urllib.error
|
|
18
|
+
|
|
19
|
+
for _p in ("/app", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))):
|
|
20
|
+
if os.path.isdir(_p) and _p not in sys.path:
|
|
21
|
+
sys.path.insert(0, _p)
|
|
22
|
+
|
|
23
|
+
from sqlalchemy import text # noqa: E402
|
|
24
|
+
from app.db import SessionLocal # noqa: E402
|
|
25
|
+
|
|
26
|
+
CHAT_URL = os.environ.get("ALI_CHAT_BASE", "").rstrip("/") + "/chat/completions"
|
|
27
|
+
CHAT_KEY = os.environ.get("DASHSCOPE_API_KEY", "")
|
|
28
|
+
MODEL = "qwen-turbo"
|
|
29
|
+
FEATURES = [("车辆保养提醒", "车辆"), ("考勤异常导出", "考勤"), ("保险续保预警", "保险")]
|
|
30
|
+
_LINE_CLEAN = re.compile(r"^[\-\*\d\.\)、\s]+")
|
|
31
|
+
_CODE_TOK = re.compile(r"[A-Za-z_][\w./\-:]{2,}")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def chat(prompt, max_tokens=500, timeout=80):
|
|
35
|
+
body = json.dumps({"model": MODEL,
|
|
36
|
+
"messages": [{"role": "user", "content": prompt}],
|
|
37
|
+
"temperature": 0.0, "max_tokens": max_tokens}).encode()
|
|
38
|
+
for attempt in range(3):
|
|
39
|
+
try:
|
|
40
|
+
req = urllib.request.Request(CHAT_URL, data=body,
|
|
41
|
+
headers={"Authorization": f"Bearer {CHAT_KEY}", "Content-Type": "application/json"})
|
|
42
|
+
return json.loads(urllib.request.urlopen(req, timeout=timeout).read())["choices"][0]["message"]["content"]
|
|
43
|
+
except (urllib.error.URLError, TimeoutError, OSError, KeyError) as e:
|
|
44
|
+
if attempt < 2:
|
|
45
|
+
time.sleep(1.0 * (attempt + 1))
|
|
46
|
+
else:
|
|
47
|
+
return f"[ERR {repr(e)[:60]}]"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _q(s, sql, params=None):
|
|
51
|
+
try:
|
|
52
|
+
return s.execute(text(sql), params or {}).fetchall()
|
|
53
|
+
except Exception:
|
|
54
|
+
try:
|
|
55
|
+
s.rollback()
|
|
56
|
+
except Exception:
|
|
57
|
+
pass
|
|
58
|
+
return []
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def load_universe_and_gold():
|
|
62
|
+
"""UNI=所有真实体名(canonical+cn, 精度校验); GOLD=每主题的真实体(召回校验)。"""
|
|
63
|
+
s = SessionLocal()
|
|
64
|
+
rows = _q(s, "SELECT canonical, cn FROM entities WHERE canonical IS NOT NULL")
|
|
65
|
+
UNI = set()
|
|
66
|
+
for c, n in rows:
|
|
67
|
+
if c:
|
|
68
|
+
UNI.add(c.strip())
|
|
69
|
+
if n:
|
|
70
|
+
UNI.add(n.strip())
|
|
71
|
+
GOLD = {}
|
|
72
|
+
for feat, theme in FEATURES:
|
|
73
|
+
rs = _q(s, "SELECT canonical, cn FROM entities WHERE (canonical ILIKE :p OR cn ILIKE :p) "
|
|
74
|
+
"AND type IN ('CONTROLLER','SERVICE','FUNCTION','COLUMN','TABLE') LIMIT 20", {"p": f"%{theme}%"})
|
|
75
|
+
GOLD[feat] = [(c, n) for c, n in rs if c]
|
|
76
|
+
s.close()
|
|
77
|
+
return UNI, GOLD
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def extract_names(ans):
|
|
81
|
+
cands = set()
|
|
82
|
+
for line in ans.splitlines():
|
|
83
|
+
line = _LINE_CLEAN.sub("", line).strip()
|
|
84
|
+
if line and len(line) <= 60:
|
|
85
|
+
m = _CODE_TOK.findall(line)
|
|
86
|
+
if m:
|
|
87
|
+
cands.add(m[0])
|
|
88
|
+
return cands
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def score(ans, gold, UNI):
|
|
92
|
+
cands = extract_names(ans)
|
|
93
|
+
real = {x for x in cands if x in UNI}
|
|
94
|
+
precision = (len(real) / len(cands)) if cands else 0.0
|
|
95
|
+
hit = 0
|
|
96
|
+
for c, n in gold:
|
|
97
|
+
if (c and c in ans) or (n and n in ans):
|
|
98
|
+
hit += 1
|
|
99
|
+
recall = (hit / len(gold)) if gold else 0.0
|
|
100
|
+
return round(precision, 3), round(recall, 3), len(cands)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def run():
|
|
104
|
+
UNI, GOLD = load_universe_and_gold()
|
|
105
|
+
rep = {"features": []}
|
|
106
|
+
for feat, theme in FEATURES:
|
|
107
|
+
gold = GOLD[feat]
|
|
108
|
+
gold_ctx = "\n".join("%s (%s)" % (c, n) for c, n in gold) or "(无)"
|
|
109
|
+
bare = chat("你是开发。要在车辆物联网系统(ICS)给「%s」加功能。\n"
|
|
110
|
+
"逐行列出【需要修改的代码文件/Java类/方法名】,每行一个,只列确信真实存在的,不要编造。" % feat)
|
|
111
|
+
scaff = chat("你是开发。要在车辆物联网系统(ICS)给「%s」加功能。\n"
|
|
112
|
+
"get_impact 检索到该功能相关的真实代码实体(均真实存在):\n%s\n\n"
|
|
113
|
+
"请把以上检索结果里的实体【尽量全部逐行列出】,每行一个,直接用检索结果里的名称,"
|
|
114
|
+
"不要自行筛选/合并/省略,不要编造。检索到几个就列几个。" % (feat, gold_ctx))
|
|
115
|
+
bP, bR, bN = score(bare, gold, UNI)
|
|
116
|
+
sP, sR, sN = score(scaff, gold, UNI)
|
|
117
|
+
rep["features"].append({"feature": feat, "gold_n": len(gold),
|
|
118
|
+
"bare_P": bP, "bare_R": bR, "scaffold_P": sP, "scaffold_R": sR})
|
|
119
|
+
print(" %s: gold=%d | bare P=%.2f R=%.2f | scaffold P=%.2f R=%.2f" % (
|
|
120
|
+
feat, len(gold), bP, bR, sP, sR))
|
|
121
|
+
n = len(FEATURES) or 1
|
|
122
|
+
rep["agg"] = {
|
|
123
|
+
"bare_P": round(sum(f["bare_P"] for f in rep["features"]) / n, 3),
|
|
124
|
+
"scaffold_P": round(sum(f["scaffold_P"] for f in rep["features"]) / n, 3),
|
|
125
|
+
"bare_R": round(sum(f["bare_R"] for f in rep["features"]) / n, 3),
|
|
126
|
+
"scaffold_R": round(sum(f["scaffold_R"] for f in rep["features"]) / n, 3),
|
|
127
|
+
}
|
|
128
|
+
return rep
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def main():
|
|
132
|
+
import argparse
|
|
133
|
+
ap = argparse.ArgumentParser()
|
|
134
|
+
ap.add_argument("--json", action="store_true")
|
|
135
|
+
args = ap.parse_args()
|
|
136
|
+
r = run()
|
|
137
|
+
a = r["agg"]
|
|
138
|
+
if args.json:
|
|
139
|
+
print(json.dumps(r, ensure_ascii=False, indent=2))
|
|
140
|
+
return
|
|
141
|
+
print("\n## FW3 wl-code 变更类精准飞轮(改对实体 P / 召回 R)")
|
|
142
|
+
print("| 维度 | bare(弱模型裸) | +get_impact scaffold |")
|
|
143
|
+
print("|---|---|---|")
|
|
144
|
+
print("| 精准 P(改对·反误改) | %.1f%% | %.1f%% |" % (a["bare_P"] * 100, a["scaffold_P"] * 100))
|
|
145
|
+
print("| 召回 R(改全) | %.1f%% | %.1f%% |" % (a["bare_R"] * 100, a["scaffold_R"] * 100))
|
|
146
|
+
print("\n安全(gate_check 前置)/正确性(过测试) = 执行流维度 → 🖥️ 宿主 wl-code 真跑")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
if __name__ == "__main__":
|
|
150
|
+
main()
|
|
@@ -1,156 +1,156 @@
|
|
|
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
|
+
"""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()
|