@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,261 +1,261 @@
|
|
|
1
|
-
"""gradient_matrix.py — T1.0 低模型梯度评测尺子(飞轮 ruler)。
|
|
2
|
-
|
|
3
|
-
证卖点:工作流的 KG grounding 让【弱模型】不编造、召回真实体,追上强模型裸跑。
|
|
4
|
-
|
|
5
|
-
矩阵 = {弱/中/强模型} × {裸, +grounding},每格跨 10 个业务主题算:
|
|
6
|
-
- grounding 精度(P) : 模型列出的"具体名称"里有多少是真 KG 实体(反幻觉)
|
|
7
|
-
- 召回(R) : gold 实体被答出多少
|
|
8
|
-
- 幻觉率(H=1-P) : 编造的占比
|
|
9
|
-
- token : 单次总 token
|
|
10
|
-
|
|
11
|
-
跑法(容器内):
|
|
12
|
-
docker exec -i wlinkj-workspace-backend-1 python /app/scripts/validation/eval/gradient_matrix.py
|
|
13
|
-
... --themes 考勤,排班 --json # 子集/机器可读
|
|
14
|
-
"""
|
|
15
|
-
import os
|
|
16
|
-
import re
|
|
17
|
-
import sys
|
|
18
|
-
import json
|
|
19
|
-
import time
|
|
20
|
-
import urllib.request
|
|
21
|
-
import urllib.error
|
|
22
|
-
|
|
23
|
-
# 容器内 /app 在 sys.path(from app.db import)
|
|
24
|
-
for _p in ("/app", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))):
|
|
25
|
-
if os.path.isdir(_p) and _p not in sys.path:
|
|
26
|
-
sys.path.insert(0, _p)
|
|
27
|
-
|
|
28
|
-
from sqlalchemy import text # noqa: E402
|
|
29
|
-
from app.db import SessionLocal # noqa: E402
|
|
30
|
-
|
|
31
|
-
# ── 配置 ─────────────────────────────────────────────────────────────
|
|
32
|
-
CHAT_URL = os.environ.get("ALI_CHAT_BASE", "").rstrip("/") + "/chat/completions"
|
|
33
|
-
CHAT_KEY = os.environ.get("DASHSCOPE_API_KEY", "")
|
|
34
|
-
MODELS = {"weak": "qwen-turbo", "mid": "qwen-plus", "strong": "qwen-max"}
|
|
35
|
-
DEFAULT_THEMES = ["考勤", "排班", "加班", "车辆", "报警", "保险", "能耗", "预算", "导出", "审批"]
|
|
36
|
-
RETRIEVE_K = 45 # +grounding 喂的检索条数(FW1: 30→45 覆盖 gold-40,解 R 封顶 30/40=0.75)
|
|
37
|
-
SYS = "你是车辆物联网系统(ICS)的代码/业务知识助手。只回答你确信真实存在的系统对象,绝不编造名称。"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def chat(model, prompt, max_tokens=600, timeout=90):
|
|
41
|
-
"""同步 chat,返 (text, total_tokens)。失败重试 3 次。"""
|
|
42
|
-
body = json.dumps({
|
|
43
|
-
"model": model,
|
|
44
|
-
"messages": [{"role": "system", "content": SYS},
|
|
45
|
-
{"role": "user", "content": prompt}],
|
|
46
|
-
"temperature": 0.0,
|
|
47
|
-
"max_tokens": max_tokens,
|
|
48
|
-
}).encode()
|
|
49
|
-
for attempt in range(3):
|
|
50
|
-
try:
|
|
51
|
-
req = urllib.request.Request(
|
|
52
|
-
CHAT_URL, data=body,
|
|
53
|
-
headers={"Authorization": f"Bearer {CHAT_KEY}", "Content-Type": "application/json"})
|
|
54
|
-
resp = urllib.request.urlopen(req, timeout=timeout)
|
|
55
|
-
d = json.loads(resp.read())
|
|
56
|
-
return d["choices"][0]["message"]["content"], int(d.get("usage", {}).get("total_tokens", 0))
|
|
57
|
-
except (urllib.error.URLError, TimeoutError, OSError, KeyError) as e:
|
|
58
|
-
if attempt < 2:
|
|
59
|
-
time.sleep(1.0 * (attempt + 1))
|
|
60
|
-
else:
|
|
61
|
-
return f"[ERR {repr(e)[:80]}]", 0
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def load_universe_and_gold(themes):
|
|
65
|
-
"""载入实体名称全集(精度校验) + 每主题 gold 实体(召回校验)。"""
|
|
66
|
-
s = SessionLocal()
|
|
67
|
-
rows = _q(s, "SELECT canonical, cn FROM entities WHERE canonical IS NOT NULL")
|
|
68
|
-
UNI = set()
|
|
69
|
-
for canon, cn in rows:
|
|
70
|
-
if canon:
|
|
71
|
-
UNI.add(canon.strip())
|
|
72
|
-
if cn:
|
|
73
|
-
UNI.add(cn.strip())
|
|
74
|
-
GOLD = {}
|
|
75
|
-
for th in themes:
|
|
76
|
-
rs = _q(s, "SELECT canonical, cn, type FROM entities "
|
|
77
|
-
"WHERE (cn ILIKE :p OR canonical ILIKE :p) LIMIT 40", {"p": f"%{th}%"})
|
|
78
|
-
GOLD[th] = [{"canonical": c, "cn": n, "type": t} for c, n, t in rs]
|
|
79
|
-
s.close()
|
|
80
|
-
return UNI, GOLD
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def retrieve_entities(theme, k=RETRIEVE_K):
|
|
84
|
-
"""模拟工作流 KG 检索(wl-search 工具会做的事):返回真实体 dict 列表。"""
|
|
85
|
-
s = SessionLocal()
|
|
86
|
-
rs = _q(s, "SELECT canonical, cn, type FROM entities "
|
|
87
|
-
"WHERE (cn ILIKE :p OR canonical ILIKE :p) LIMIT :k",
|
|
88
|
-
{"p": f"%{theme}%", "k": k})
|
|
89
|
-
s.close()
|
|
90
|
-
return [{"canonical": c, "cn": n, "type": t} for c, n, t in rs]
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def ctx_from_entities(ents):
|
|
94
|
-
"""把检索实体格式化成喂给 LLM 的上下文文本。"""
|
|
95
|
-
out = []
|
|
96
|
-
for e in ents:
|
|
97
|
-
c, n, t = e["canonical"], e["cn"], e["type"]
|
|
98
|
-
out.append(f"{t}: {c}" + (f" ({n})" if n else ""))
|
|
99
|
-
return "\n".join(out)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def _q(s, sql, params=None):
|
|
103
|
-
try:
|
|
104
|
-
return s.execute(text(sql), params or {}).fetchall()
|
|
105
|
-
except Exception:
|
|
106
|
-
try:
|
|
107
|
-
s.rollback()
|
|
108
|
-
except Exception:
|
|
109
|
-
pass
|
|
110
|
-
return []
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
# ── prompt ───────────────────────────────────────────────────────────
|
|
114
|
-
def prompt_bare(theme):
|
|
115
|
-
return (f"在我们的车辆物联网系统(ICS)里,找出和「{theme}」相关的具体对象"
|
|
116
|
-
f"(数据库表/Java类/接口/按钮/页面等)。\n"
|
|
117
|
-
f"请逐行列出你【确信真实存在】的具体名称,每行一个,不要解释,不要编造。"
|
|
118
|
-
f"不确定就少列。")
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def prompt_scaffold(theme, ctx):
|
|
122
|
-
return (f"在我们的车辆物联网系统(ICS)里,找出和「{theme}」相关的具体对象"
|
|
123
|
-
f"(数据库表/Java类/接口/按钮/页面等)。\n"
|
|
124
|
-
f"知识图谱已检索到以下相关实体(均为真实存在):\n{ctx}\n\n"
|
|
125
|
-
f"请把以上检索结果里的实体【尽量全部逐行列出】,每行一个,"
|
|
126
|
-
f"直接用检索结果里的名称,不要自行筛选/合并/省略,不要解释。检索到几个就列几个。")
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
# ── 评分 ─────────────────────────────────────────────────────────────
|
|
130
|
-
_LINE_CLEAN = re.compile(r"^[\-\*\d\.\)、\s]+")
|
|
131
|
-
_CODE_TOK = re.compile(r"[A-Za-z_][\w./\-:]{2,}")
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
def _clean_lines(ans):
|
|
135
|
-
"""'每行一个' 答案 → 清洗后的短行列表(模型声称存在的对象,每行一个)。"""
|
|
136
|
-
out = []
|
|
137
|
-
for l in ans.splitlines():
|
|
138
|
-
l = _LINE_CLEAN.sub("", l).strip()
|
|
139
|
-
if l and len(l) <= 60:
|
|
140
|
-
out.append(l)
|
|
141
|
-
return out
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
def score(ans, gold, UNI, ground_cn):
|
|
145
|
-
"""grounding 精度:每行算'真实'当其含某真代码标识符(∈UNI) 或 命中本格 ground_cn(中文)。
|
|
146
|
-
ground_cn = 该格可作'真实'依据的中文名集合(bare=gold, scaffold=retrieved),bare/scaffold 同口径公平。
|
|
147
|
-
"""
|
|
148
|
-
lines = _clean_lines(ans)
|
|
149
|
-
real_n = 0
|
|
150
|
-
for l in lines:
|
|
151
|
-
if set(_CODE_TOK.findall(l)) & UNI:
|
|
152
|
-
real_n += 1
|
|
153
|
-
elif any(g and g in l for g in ground_cn):
|
|
154
|
-
real_n += 1
|
|
155
|
-
precision = (real_n / len(lines)) if lines else 0.0
|
|
156
|
-
hit = 0
|
|
157
|
-
for g in gold:
|
|
158
|
-
c = g.get("canonical") or ""
|
|
159
|
-
n = g.get("cn") or ""
|
|
160
|
-
if (c and c in ans) or (n and n in ans):
|
|
161
|
-
hit += 1
|
|
162
|
-
recall = (hit / len(gold)) if gold else 0.0
|
|
163
|
-
return {
|
|
164
|
-
"precision": round(precision, 3),
|
|
165
|
-
"recall": round(recall, 3),
|
|
166
|
-
"halluc": round(1 - precision, 3),
|
|
167
|
-
"n_lines": len(lines),
|
|
168
|
-
"n_real": real_n,
|
|
169
|
-
"gold_hit": hit,
|
|
170
|
-
"gold_n": len(gold),
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
def aggregate(rows):
|
|
175
|
-
n = len(rows) or 1
|
|
176
|
-
return {
|
|
177
|
-
"P": round(sum(r["precision"] for r in rows) / n, 3),
|
|
178
|
-
"R": round(sum(r["recall"] for r in rows) / n, 3),
|
|
179
|
-
"H": round(sum(r["halluc"] for r in rows) / n, 3),
|
|
180
|
-
"tokens": round(sum(r["tokens"] for r in rows) / n, 0),
|
|
181
|
-
"n": len(rows),
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
def run(themes, verbose=True):
|
|
186
|
-
def log(*a):
|
|
187
|
-
if verbose:
|
|
188
|
-
print(*a)
|
|
189
|
-
|
|
190
|
-
if not CHAT_KEY:
|
|
191
|
-
raise RuntimeError("缺 DASHSCOPE_API_KEY")
|
|
192
|
-
log("[ruler] 载入 KG 全集 + gold…")
|
|
193
|
-
UNI, GOLD = load_universe_and_gold(themes)
|
|
194
|
-
log(f"[ruler] 实体名称全集 {len(UNI)} 条")
|
|
195
|
-
for th in themes:
|
|
196
|
-
log(f" theme {th}: gold={len(GOLD[th])}")
|
|
197
|
-
|
|
198
|
-
matrix = {}
|
|
199
|
-
t0 = time.time()
|
|
200
|
-
for tier, model in MODELS.items():
|
|
201
|
-
for mode in ("bare", "scaffold"):
|
|
202
|
-
rows = []
|
|
203
|
-
for th in themes:
|
|
204
|
-
gold = GOLD[th]
|
|
205
|
-
if mode == "bare":
|
|
206
|
-
ground_cn = {g["canonical"] for g in gold} | {g["cn"] for g in gold if g["cn"]}
|
|
207
|
-
p = prompt_bare(th)
|
|
208
|
-
else:
|
|
209
|
-
ents = retrieve_entities(th)
|
|
210
|
-
ground_cn = {e["canonical"] for e in ents} | {e["cn"] for e in ents if e["cn"]}
|
|
211
|
-
p = prompt_scaffold(th, ctx_from_entities(ents))
|
|
212
|
-
ans, tok = chat(model, p)
|
|
213
|
-
sc = score(ans, gold, UNI, ground_cn)
|
|
214
|
-
sc["tokens"] = tok
|
|
215
|
-
sc["theme"] = th
|
|
216
|
-
rows.append(sc)
|
|
217
|
-
log(f" [{tier}/{mode}] {th}: P={sc['precision']:.2f} R={sc['recall']:.2f} "
|
|
218
|
-
f"H={sc['halluc']:.2f} tok={tok} lines={sc['n_lines']}")
|
|
219
|
-
matrix[f"{tier}/{mode}"] = aggregate(rows)
|
|
220
|
-
log(f"[{tier}/{mode}] agg P={matrix[f'{tier}/{mode}']['P']:.3f} "
|
|
221
|
-
f"R={matrix[f'{tier}/{mode}']['R']:.3f} H={matrix[f'{tier}/{mode}']['H']:.3f} "
|
|
222
|
-
f"tok={matrix[f'{tier}/{mode}']['tokens']:.0f}")
|
|
223
|
-
log(f"[ruler] 总耗时 {int(time.time()-t0)}s")
|
|
224
|
-
return {"matrix": matrix, "themes": themes, "models": MODELS}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
def format_table(res):
|
|
228
|
-
m = res["matrix"]
|
|
229
|
-
lines = ["## 低模型梯度矩阵(grounding 精度P / 召回R / 幻觉H / token)",
|
|
230
|
-
"", "| 模型档 | 裸 P | 裸 R | 裸 H | 裸tok | +grounding P | +grounding R | +grounding H | +ground tok |",
|
|
231
|
-
"|---|---|---|---|---|---|---|---|---|"]
|
|
232
|
-
for tier in MODELS:
|
|
233
|
-
b = m[f"{tier}/bare"]
|
|
234
|
-
f = m[f"{tier}/scaffold"]
|
|
235
|
-
lines.append(f"| {tier}({MODELS[tier]}) | {b['P']:.2f} | {b['R']:.2f} | {b['H']:.2f} | {b['tokens']:.0f} | "
|
|
236
|
-
f"{f['P']:.2f} | {f['R']:.2f} | {f['H']:.2f} | {f['tokens']:.0f} |")
|
|
237
|
-
# 卖点提炼
|
|
238
|
-
lines.append("")
|
|
239
|
-
weak_gain_p = m["weak/scaffold"]["P"] - m["weak/bare"]["P"]
|
|
240
|
-
weak_catchup = m["weak/scaffold"]["P"] - m["strong/bare"]["P"]
|
|
241
|
-
lines.append(f"- 弱模型 +grounding 精度提升: **{weak_gain_p*100:+.1f}pp**(反幻觉增益)")
|
|
242
|
-
lines.append(f"- 弱模型+grounding vs 强模型裸 精度差: **{weak_catchup*100:+.1f}pp**(>0=追上/反超=卖点成立)")
|
|
243
|
-
return "\n".join(lines)
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
def main():
|
|
247
|
-
import argparse
|
|
248
|
-
ap = argparse.ArgumentParser()
|
|
249
|
-
ap.add_argument("--themes", default=",".join(DEFAULT_THEMES))
|
|
250
|
-
ap.add_argument("--json", action="store_true")
|
|
251
|
-
args = ap.parse_args()
|
|
252
|
-
themes = [t.strip() for t in args.themes.split(",") if t.strip()]
|
|
253
|
-
res = run(themes)
|
|
254
|
-
if args.json:
|
|
255
|
-
print(json.dumps(res, ensure_ascii=False, indent=2))
|
|
256
|
-
else:
|
|
257
|
-
print("\n" + format_table(res))
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if __name__ == "__main__":
|
|
261
|
-
main()
|
|
1
|
+
"""gradient_matrix.py — T1.0 低模型梯度评测尺子(飞轮 ruler)。
|
|
2
|
+
|
|
3
|
+
证卖点:工作流的 KG grounding 让【弱模型】不编造、召回真实体,追上强模型裸跑。
|
|
4
|
+
|
|
5
|
+
矩阵 = {弱/中/强模型} × {裸, +grounding},每格跨 10 个业务主题算:
|
|
6
|
+
- grounding 精度(P) : 模型列出的"具体名称"里有多少是真 KG 实体(反幻觉)
|
|
7
|
+
- 召回(R) : gold 实体被答出多少
|
|
8
|
+
- 幻觉率(H=1-P) : 编造的占比
|
|
9
|
+
- token : 单次总 token
|
|
10
|
+
|
|
11
|
+
跑法(容器内):
|
|
12
|
+
docker exec -i wlinkj-workspace-backend-1 python /app/scripts/validation/eval/gradient_matrix.py
|
|
13
|
+
... --themes 考勤,排班 --json # 子集/机器可读
|
|
14
|
+
"""
|
|
15
|
+
import os
|
|
16
|
+
import re
|
|
17
|
+
import sys
|
|
18
|
+
import json
|
|
19
|
+
import time
|
|
20
|
+
import urllib.request
|
|
21
|
+
import urllib.error
|
|
22
|
+
|
|
23
|
+
# 容器内 /app 在 sys.path(from app.db import)
|
|
24
|
+
for _p in ("/app", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))):
|
|
25
|
+
if os.path.isdir(_p) and _p not in sys.path:
|
|
26
|
+
sys.path.insert(0, _p)
|
|
27
|
+
|
|
28
|
+
from sqlalchemy import text # noqa: E402
|
|
29
|
+
from app.db import SessionLocal # noqa: E402
|
|
30
|
+
|
|
31
|
+
# ── 配置 ─────────────────────────────────────────────────────────────
|
|
32
|
+
CHAT_URL = os.environ.get("ALI_CHAT_BASE", "").rstrip("/") + "/chat/completions"
|
|
33
|
+
CHAT_KEY = os.environ.get("DASHSCOPE_API_KEY", "")
|
|
34
|
+
MODELS = {"weak": "qwen-turbo", "mid": "qwen-plus", "strong": "qwen-max"}
|
|
35
|
+
DEFAULT_THEMES = ["考勤", "排班", "加班", "车辆", "报警", "保险", "能耗", "预算", "导出", "审批"]
|
|
36
|
+
RETRIEVE_K = 45 # +grounding 喂的检索条数(FW1: 30→45 覆盖 gold-40,解 R 封顶 30/40=0.75)
|
|
37
|
+
SYS = "你是车辆物联网系统(ICS)的代码/业务知识助手。只回答你确信真实存在的系统对象,绝不编造名称。"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def chat(model, prompt, max_tokens=600, timeout=90):
|
|
41
|
+
"""同步 chat,返 (text, total_tokens)。失败重试 3 次。"""
|
|
42
|
+
body = json.dumps({
|
|
43
|
+
"model": model,
|
|
44
|
+
"messages": [{"role": "system", "content": SYS},
|
|
45
|
+
{"role": "user", "content": prompt}],
|
|
46
|
+
"temperature": 0.0,
|
|
47
|
+
"max_tokens": max_tokens,
|
|
48
|
+
}).encode()
|
|
49
|
+
for attempt in range(3):
|
|
50
|
+
try:
|
|
51
|
+
req = urllib.request.Request(
|
|
52
|
+
CHAT_URL, data=body,
|
|
53
|
+
headers={"Authorization": f"Bearer {CHAT_KEY}", "Content-Type": "application/json"})
|
|
54
|
+
resp = urllib.request.urlopen(req, timeout=timeout)
|
|
55
|
+
d = json.loads(resp.read())
|
|
56
|
+
return d["choices"][0]["message"]["content"], int(d.get("usage", {}).get("total_tokens", 0))
|
|
57
|
+
except (urllib.error.URLError, TimeoutError, OSError, KeyError) as e:
|
|
58
|
+
if attempt < 2:
|
|
59
|
+
time.sleep(1.0 * (attempt + 1))
|
|
60
|
+
else:
|
|
61
|
+
return f"[ERR {repr(e)[:80]}]", 0
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def load_universe_and_gold(themes):
|
|
65
|
+
"""载入实体名称全集(精度校验) + 每主题 gold 实体(召回校验)。"""
|
|
66
|
+
s = SessionLocal()
|
|
67
|
+
rows = _q(s, "SELECT canonical, cn FROM entities WHERE canonical IS NOT NULL")
|
|
68
|
+
UNI = set()
|
|
69
|
+
for canon, cn in rows:
|
|
70
|
+
if canon:
|
|
71
|
+
UNI.add(canon.strip())
|
|
72
|
+
if cn:
|
|
73
|
+
UNI.add(cn.strip())
|
|
74
|
+
GOLD = {}
|
|
75
|
+
for th in themes:
|
|
76
|
+
rs = _q(s, "SELECT canonical, cn, type FROM entities "
|
|
77
|
+
"WHERE (cn ILIKE :p OR canonical ILIKE :p) LIMIT 40", {"p": f"%{th}%"})
|
|
78
|
+
GOLD[th] = [{"canonical": c, "cn": n, "type": t} for c, n, t in rs]
|
|
79
|
+
s.close()
|
|
80
|
+
return UNI, GOLD
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def retrieve_entities(theme, k=RETRIEVE_K):
|
|
84
|
+
"""模拟工作流 KG 检索(wl-search 工具会做的事):返回真实体 dict 列表。"""
|
|
85
|
+
s = SessionLocal()
|
|
86
|
+
rs = _q(s, "SELECT canonical, cn, type FROM entities "
|
|
87
|
+
"WHERE (cn ILIKE :p OR canonical ILIKE :p) LIMIT :k",
|
|
88
|
+
{"p": f"%{theme}%", "k": k})
|
|
89
|
+
s.close()
|
|
90
|
+
return [{"canonical": c, "cn": n, "type": t} for c, n, t in rs]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def ctx_from_entities(ents):
|
|
94
|
+
"""把检索实体格式化成喂给 LLM 的上下文文本。"""
|
|
95
|
+
out = []
|
|
96
|
+
for e in ents:
|
|
97
|
+
c, n, t = e["canonical"], e["cn"], e["type"]
|
|
98
|
+
out.append(f"{t}: {c}" + (f" ({n})" if n else ""))
|
|
99
|
+
return "\n".join(out)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _q(s, sql, params=None):
|
|
103
|
+
try:
|
|
104
|
+
return s.execute(text(sql), params or {}).fetchall()
|
|
105
|
+
except Exception:
|
|
106
|
+
try:
|
|
107
|
+
s.rollback()
|
|
108
|
+
except Exception:
|
|
109
|
+
pass
|
|
110
|
+
return []
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# ── prompt ───────────────────────────────────────────────────────────
|
|
114
|
+
def prompt_bare(theme):
|
|
115
|
+
return (f"在我们的车辆物联网系统(ICS)里,找出和「{theme}」相关的具体对象"
|
|
116
|
+
f"(数据库表/Java类/接口/按钮/页面等)。\n"
|
|
117
|
+
f"请逐行列出你【确信真实存在】的具体名称,每行一个,不要解释,不要编造。"
|
|
118
|
+
f"不确定就少列。")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def prompt_scaffold(theme, ctx):
|
|
122
|
+
return (f"在我们的车辆物联网系统(ICS)里,找出和「{theme}」相关的具体对象"
|
|
123
|
+
f"(数据库表/Java类/接口/按钮/页面等)。\n"
|
|
124
|
+
f"知识图谱已检索到以下相关实体(均为真实存在):\n{ctx}\n\n"
|
|
125
|
+
f"请把以上检索结果里的实体【尽量全部逐行列出】,每行一个,"
|
|
126
|
+
f"直接用检索结果里的名称,不要自行筛选/合并/省略,不要解释。检索到几个就列几个。")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# ── 评分 ─────────────────────────────────────────────────────────────
|
|
130
|
+
_LINE_CLEAN = re.compile(r"^[\-\*\d\.\)、\s]+")
|
|
131
|
+
_CODE_TOK = re.compile(r"[A-Za-z_][\w./\-:]{2,}")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _clean_lines(ans):
|
|
135
|
+
"""'每行一个' 答案 → 清洗后的短行列表(模型声称存在的对象,每行一个)。"""
|
|
136
|
+
out = []
|
|
137
|
+
for l in ans.splitlines():
|
|
138
|
+
l = _LINE_CLEAN.sub("", l).strip()
|
|
139
|
+
if l and len(l) <= 60:
|
|
140
|
+
out.append(l)
|
|
141
|
+
return out
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def score(ans, gold, UNI, ground_cn):
|
|
145
|
+
"""grounding 精度:每行算'真实'当其含某真代码标识符(∈UNI) 或 命中本格 ground_cn(中文)。
|
|
146
|
+
ground_cn = 该格可作'真实'依据的中文名集合(bare=gold, scaffold=retrieved),bare/scaffold 同口径公平。
|
|
147
|
+
"""
|
|
148
|
+
lines = _clean_lines(ans)
|
|
149
|
+
real_n = 0
|
|
150
|
+
for l in lines:
|
|
151
|
+
if set(_CODE_TOK.findall(l)) & UNI:
|
|
152
|
+
real_n += 1
|
|
153
|
+
elif any(g and g in l for g in ground_cn):
|
|
154
|
+
real_n += 1
|
|
155
|
+
precision = (real_n / len(lines)) if lines else 0.0
|
|
156
|
+
hit = 0
|
|
157
|
+
for g in gold:
|
|
158
|
+
c = g.get("canonical") or ""
|
|
159
|
+
n = g.get("cn") or ""
|
|
160
|
+
if (c and c in ans) or (n and n in ans):
|
|
161
|
+
hit += 1
|
|
162
|
+
recall = (hit / len(gold)) if gold else 0.0
|
|
163
|
+
return {
|
|
164
|
+
"precision": round(precision, 3),
|
|
165
|
+
"recall": round(recall, 3),
|
|
166
|
+
"halluc": round(1 - precision, 3),
|
|
167
|
+
"n_lines": len(lines),
|
|
168
|
+
"n_real": real_n,
|
|
169
|
+
"gold_hit": hit,
|
|
170
|
+
"gold_n": len(gold),
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def aggregate(rows):
|
|
175
|
+
n = len(rows) or 1
|
|
176
|
+
return {
|
|
177
|
+
"P": round(sum(r["precision"] for r in rows) / n, 3),
|
|
178
|
+
"R": round(sum(r["recall"] for r in rows) / n, 3),
|
|
179
|
+
"H": round(sum(r["halluc"] for r in rows) / n, 3),
|
|
180
|
+
"tokens": round(sum(r["tokens"] for r in rows) / n, 0),
|
|
181
|
+
"n": len(rows),
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def run(themes, verbose=True):
|
|
186
|
+
def log(*a):
|
|
187
|
+
if verbose:
|
|
188
|
+
print(*a)
|
|
189
|
+
|
|
190
|
+
if not CHAT_KEY:
|
|
191
|
+
raise RuntimeError("缺 DASHSCOPE_API_KEY")
|
|
192
|
+
log("[ruler] 载入 KG 全集 + gold…")
|
|
193
|
+
UNI, GOLD = load_universe_and_gold(themes)
|
|
194
|
+
log(f"[ruler] 实体名称全集 {len(UNI)} 条")
|
|
195
|
+
for th in themes:
|
|
196
|
+
log(f" theme {th}: gold={len(GOLD[th])}")
|
|
197
|
+
|
|
198
|
+
matrix = {}
|
|
199
|
+
t0 = time.time()
|
|
200
|
+
for tier, model in MODELS.items():
|
|
201
|
+
for mode in ("bare", "scaffold"):
|
|
202
|
+
rows = []
|
|
203
|
+
for th in themes:
|
|
204
|
+
gold = GOLD[th]
|
|
205
|
+
if mode == "bare":
|
|
206
|
+
ground_cn = {g["canonical"] for g in gold} | {g["cn"] for g in gold if g["cn"]}
|
|
207
|
+
p = prompt_bare(th)
|
|
208
|
+
else:
|
|
209
|
+
ents = retrieve_entities(th)
|
|
210
|
+
ground_cn = {e["canonical"] for e in ents} | {e["cn"] for e in ents if e["cn"]}
|
|
211
|
+
p = prompt_scaffold(th, ctx_from_entities(ents))
|
|
212
|
+
ans, tok = chat(model, p)
|
|
213
|
+
sc = score(ans, gold, UNI, ground_cn)
|
|
214
|
+
sc["tokens"] = tok
|
|
215
|
+
sc["theme"] = th
|
|
216
|
+
rows.append(sc)
|
|
217
|
+
log(f" [{tier}/{mode}] {th}: P={sc['precision']:.2f} R={sc['recall']:.2f} "
|
|
218
|
+
f"H={sc['halluc']:.2f} tok={tok} lines={sc['n_lines']}")
|
|
219
|
+
matrix[f"{tier}/{mode}"] = aggregate(rows)
|
|
220
|
+
log(f"[{tier}/{mode}] agg P={matrix[f'{tier}/{mode}']['P']:.3f} "
|
|
221
|
+
f"R={matrix[f'{tier}/{mode}']['R']:.3f} H={matrix[f'{tier}/{mode}']['H']:.3f} "
|
|
222
|
+
f"tok={matrix[f'{tier}/{mode}']['tokens']:.0f}")
|
|
223
|
+
log(f"[ruler] 总耗时 {int(time.time()-t0)}s")
|
|
224
|
+
return {"matrix": matrix, "themes": themes, "models": MODELS}
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def format_table(res):
|
|
228
|
+
m = res["matrix"]
|
|
229
|
+
lines = ["## 低模型梯度矩阵(grounding 精度P / 召回R / 幻觉H / token)",
|
|
230
|
+
"", "| 模型档 | 裸 P | 裸 R | 裸 H | 裸tok | +grounding P | +grounding R | +grounding H | +ground tok |",
|
|
231
|
+
"|---|---|---|---|---|---|---|---|---|"]
|
|
232
|
+
for tier in MODELS:
|
|
233
|
+
b = m[f"{tier}/bare"]
|
|
234
|
+
f = m[f"{tier}/scaffold"]
|
|
235
|
+
lines.append(f"| {tier}({MODELS[tier]}) | {b['P']:.2f} | {b['R']:.2f} | {b['H']:.2f} | {b['tokens']:.0f} | "
|
|
236
|
+
f"{f['P']:.2f} | {f['R']:.2f} | {f['H']:.2f} | {f['tokens']:.0f} |")
|
|
237
|
+
# 卖点提炼
|
|
238
|
+
lines.append("")
|
|
239
|
+
weak_gain_p = m["weak/scaffold"]["P"] - m["weak/bare"]["P"]
|
|
240
|
+
weak_catchup = m["weak/scaffold"]["P"] - m["strong/bare"]["P"]
|
|
241
|
+
lines.append(f"- 弱模型 +grounding 精度提升: **{weak_gain_p*100:+.1f}pp**(反幻觉增益)")
|
|
242
|
+
lines.append(f"- 弱模型+grounding vs 强模型裸 精度差: **{weak_catchup*100:+.1f}pp**(>0=追上/反超=卖点成立)")
|
|
243
|
+
return "\n".join(lines)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def main():
|
|
247
|
+
import argparse
|
|
248
|
+
ap = argparse.ArgumentParser()
|
|
249
|
+
ap.add_argument("--themes", default=",".join(DEFAULT_THEMES))
|
|
250
|
+
ap.add_argument("--json", action="store_true")
|
|
251
|
+
args = ap.parse_args()
|
|
252
|
+
themes = [t.strip() for t in args.themes.split(",") if t.strip()]
|
|
253
|
+
res = run(themes)
|
|
254
|
+
if args.json:
|
|
255
|
+
print(json.dumps(res, ensure_ascii=False, indent=2))
|
|
256
|
+
else:
|
|
257
|
+
print("\n" + format_table(res))
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
if __name__ == "__main__":
|
|
261
|
+
main()
|
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
{
|
|
2
|
-
"task": "T1.0 低模型梯度评测基线",
|
|
3
|
-
"date": "2026-07-21",
|
|
4
|
-
"ruler": "scripts/validation/eval/gradient_matrix.py",
|
|
5
|
-
"env": "云 10.89.7.120 容器 wlinkj-workspace-backend-1, DashScope compatible-mode, KG entities=81255 edges=96965",
|
|
6
|
-
"models": {"weak": "qwen-turbo", "mid": "qwen-plus", "strong": "qwen-max"},
|
|
7
|
-
"themes": ["考勤", "排班", "加班", "车辆", "报警", "保险", "能耗", "预算", "导出", "审批"],
|
|
8
|
-
"wall_seconds": 138,
|
|
9
|
-
"chat_calls": 60,
|
|
10
|
-
"matrix": {
|
|
11
|
-
"weak/bare": {"P": 0.02, "R": 0.00, "H": 0.98, "tokens": 210},
|
|
12
|
-
"weak/scaffold": {"P": 0.961, "R": 0.815, "H": 0.039, "tokens": 929},
|
|
13
|
-
"mid/bare": {"P": 0.00, "R": 0.00, "H": 1.00, "tokens": 134},
|
|
14
|
-
"mid/scaffold": {"P": 0.986, "R": 0.823, "H": 0.014, "tokens": 859},
|
|
15
|
-
"strong/bare": {"P": 0.032, "R": 0.00, "H": 0.968, "tokens": 136},
|
|
16
|
-
"strong/scaffold":{"P": 1.000,"R": 0.796, "H": 0.000, "tokens": 844}
|
|
17
|
-
},
|
|
18
|
-
"selling_point_proof": {
|
|
19
|
-
"all_bare_hallucinate": "三档裸跑 P 0-0.032 / R 0 / H 0.97-1.0 — 连 qwen-max 无 grounding 召回 0",
|
|
20
|
-
"weak_plus_workflow_vs_strong_bare_pp": 92.9,
|
|
21
|
-
"tier_converges_with_grounding": "+grounding 后三档 P 0.961/0.986/1.000, R 0.815/0.823/0.796 — 模型档位几乎不影响质量"
|
|
22
|
-
},
|
|
23
|
-
"flywheel_round1_gap": {
|
|
24
|
-
"shortboard": "召回天花板 R≈0.80 (ILIKE top-30 未覆盖全 gold + 模型偶过滤)",
|
|
25
|
-
"lever": "检索升级: 真实 rag_search 语义 + 1跳图扩展 (映射 T1.4)",
|
|
26
|
-
"target_R": 0.90
|
|
27
|
-
},
|
|
28
|
-
"honest_caveats": [
|
|
29
|
-
"token 维度本尺子不适用: +grounding 必然加 token(喂上下文换 P 0->0.96); §9.1 的 token<=裸×0.8 指命令层去重/缓存(T1.2/T1.3/T1.7 真命令transcript量)",
|
|
30
|
-
"🖥️ 宿主真梯度(QoderWork Haiku/Sonnet/Opus × 真命令) 待用户跑 (SPEC §12 V1.1); 本代理用 DashScope turbo/plus/max 同构证卖点方向"
|
|
31
|
-
],
|
|
32
|
-
"status": "尺子就绪 + API 代理基线完成; 🖥️ 宿主梯度列入 §12"
|
|
33
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"task": "T1.0 低模型梯度评测基线",
|
|
3
|
+
"date": "2026-07-21",
|
|
4
|
+
"ruler": "scripts/validation/eval/gradient_matrix.py",
|
|
5
|
+
"env": "云 10.89.7.120 容器 wlinkj-workspace-backend-1, DashScope compatible-mode, KG entities=81255 edges=96965",
|
|
6
|
+
"models": {"weak": "qwen-turbo", "mid": "qwen-plus", "strong": "qwen-max"},
|
|
7
|
+
"themes": ["考勤", "排班", "加班", "车辆", "报警", "保险", "能耗", "预算", "导出", "审批"],
|
|
8
|
+
"wall_seconds": 138,
|
|
9
|
+
"chat_calls": 60,
|
|
10
|
+
"matrix": {
|
|
11
|
+
"weak/bare": {"P": 0.02, "R": 0.00, "H": 0.98, "tokens": 210},
|
|
12
|
+
"weak/scaffold": {"P": 0.961, "R": 0.815, "H": 0.039, "tokens": 929},
|
|
13
|
+
"mid/bare": {"P": 0.00, "R": 0.00, "H": 1.00, "tokens": 134},
|
|
14
|
+
"mid/scaffold": {"P": 0.986, "R": 0.823, "H": 0.014, "tokens": 859},
|
|
15
|
+
"strong/bare": {"P": 0.032, "R": 0.00, "H": 0.968, "tokens": 136},
|
|
16
|
+
"strong/scaffold":{"P": 1.000,"R": 0.796, "H": 0.000, "tokens": 844}
|
|
17
|
+
},
|
|
18
|
+
"selling_point_proof": {
|
|
19
|
+
"all_bare_hallucinate": "三档裸跑 P 0-0.032 / R 0 / H 0.97-1.0 — 连 qwen-max 无 grounding 召回 0",
|
|
20
|
+
"weak_plus_workflow_vs_strong_bare_pp": 92.9,
|
|
21
|
+
"tier_converges_with_grounding": "+grounding 后三档 P 0.961/0.986/1.000, R 0.815/0.823/0.796 — 模型档位几乎不影响质量"
|
|
22
|
+
},
|
|
23
|
+
"flywheel_round1_gap": {
|
|
24
|
+
"shortboard": "召回天花板 R≈0.80 (ILIKE top-30 未覆盖全 gold + 模型偶过滤)",
|
|
25
|
+
"lever": "检索升级: 真实 rag_search 语义 + 1跳图扩展 (映射 T1.4)",
|
|
26
|
+
"target_R": 0.90
|
|
27
|
+
},
|
|
28
|
+
"honest_caveats": [
|
|
29
|
+
"token 维度本尺子不适用: +grounding 必然加 token(喂上下文换 P 0->0.96); §9.1 的 token<=裸×0.8 指命令层去重/缓存(T1.2/T1.3/T1.7 真命令transcript量)",
|
|
30
|
+
"🖥️ 宿主真梯度(QoderWork Haiku/Sonnet/Opus × 真命令) 待用户跑 (SPEC §12 V1.1); 本代理用 DashScope turbo/plus/max 同构证卖点方向"
|
|
31
|
+
],
|
|
32
|
+
"status": "尺子就绪 + API 代理基线完成; 🖥️ 宿主梯度列入 §12"
|
|
33
|
+
}
|