@hupan56/wlkj 3.3.13 → 3.3.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +1 -23
- package/package.json +29 -29
- package/templates/qoder/agents/design-agent.md +20 -0
- package/templates/qoder/agents/spec-generator.md +21 -0
- package/templates/qoder/commands/optional/wl-spec.md +2 -12
- 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 +1 -11
- package/templates/qoder/commands/wl-init.md +129 -129
- package/templates/qoder/commands/wl-prd.md +0 -24
- package/templates/qoder/commands/wl-search.md +11 -26
- package/templates/qoder/commands/wl-task.md +1 -11
- package/templates/qoder/commands/wl-test.md +0 -8
- package/templates/qoder/config.yaml +8 -3
- package/templates/qoder/hooks/session-start.py +365 -384
- package/templates/qoder/hooks/stop-eval.py +254 -383
- package/templates/qoder/scripts/capability/adapters/mcp.py +314 -322
- package/templates/qoder/scripts/capability/registry.py +248 -245
- package/templates/qoder/scripts/capability/registry_mcp.py +4 -10
- package/templates/qoder/scripts/capability/smoke_test_report.json.new +94 -0
- package/templates/qoder/scripts/deployment/setup/init_doctor.py +2 -6
- package/templates/qoder/scripts/deployment/setup/install_qoderwork.py +14 -0
- package/templates/qoder/scripts/deployment/setup/setup.py +2 -16
- package/templates/qoder/scripts/domain/integration/return_to_platform.py +2 -4
- package/templates/qoder/scripts/domain/integration/spec_upload.py +4 -4
- package/templates/qoder/scripts/domain/kg/build/build_entity_registry.py +12 -12
- package/templates/qoder/scripts/domain/kg/build/build_relations.py +12 -12
- package/templates/qoder/scripts/domain/kg/extract/asset/__init__.py +10 -0
- package/templates/qoder/scripts/domain/kg/extract/asset/asset_tree.py +57 -0
- package/templates/qoder/scripts/domain/kg/extract/asset/discussion_importer.py +62 -0
- package/templates/qoder/scripts/domain/kg/extract/asset/prd_importer.py +146 -0
- package/templates/qoder/scripts/domain/kg/extract/asset/prototype_importer.py +64 -0
- package/templates/qoder/scripts/domain/kg/extract/asset/returns_importer.py +52 -0
- package/templates/qoder/scripts/domain/kg/extract/build_goal3.py +104 -0
- package/templates/qoder/scripts/domain/kg/extract/build_goal4.py +55 -0
- package/templates/qoder/scripts/domain/kg/extract/build_goal5.py +95 -0
- package/templates/qoder/scripts/domain/kg/extract/db/__init__.py +8 -0
- package/templates/qoder/scripts/domain/kg/extract/db/data_profile.py +22 -0
- package/templates/qoder/scripts/domain/kg/extract/db/fk_extractor.py +55 -0
- package/templates/qoder/scripts/domain/kg/extract/db/schema_extractor.py +90 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/__init__.py +9 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/community_summarizer.py +206 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/embed_builder.py +132 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/naming_matcher.py +80 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/promote.py +59 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/recompute.py +93 -0
- package/templates/qoder/scripts/domain/kg/extract/inference/weak_link.py +421 -0
- package/templates/qoder/scripts/domain/kg/extract/mybatis/__init__.py +9 -0
- package/templates/qoder/scripts/domain/kg/extract/mybatis/all.py +79 -0
- package/templates/qoder/scripts/domain/kg/extract/mybatis/mapper_parser.py +99 -0
- package/templates/qoder/scripts/domain/kg/extract/mybatis/relation_builder.py +69 -0
- package/templates/qoder/scripts/domain/kg/extract/mybatis/sql_extractor.py +78 -0
- package/templates/qoder/scripts/domain/kg/extract/prd/__init__.py +8 -0
- package/templates/qoder/scripts/domain/kg/extract/prd/prd_chunk_embed.py +105 -0
- package/templates/qoder/scripts/domain/kg/extract/prd/prd_llm_extract.py +153 -0
- package/templates/qoder/scripts/domain/kg/extract/prd/req_anchor.py +120 -0
- package/templates/qoder/scripts/domain/kg/graph/kg_link_db.py +235 -0
- package/templates/qoder/scripts/domain/kg/graph/kg_semantic.py +5 -41
- package/templates/qoder/scripts/domain/kg/kg.py +0 -14
- package/templates/qoder/scripts/domain/kg/search/enrich_prompt.py +238 -0
- package/templates/qoder/scripts/domain/kg/server/perf_bench.py +197 -0
- package/templates/qoder/scripts/domain/kg/switch_project.py +2 -2
- package/templates/qoder/scripts/domain/kg/sync_repowiki.py +109 -0
- package/templates/qoder/scripts/domain/task/wlkj_panel.py +1348 -1503
- package/templates/qoder/scripts/engine/poller.py +219 -0
- package/templates/qoder/scripts/foundation/bootstrap.py +145 -156
- package/templates/qoder/scripts/orchestration/wlkj.py +189 -306
- package/templates/qoder/settings.json +0 -8
- package/templates/qoder/skills/wl-spec/SKILL.md +1 -1
- 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/qoder/contracts/insight.md +0 -55
- package/templates/qoder/hooks/pre-tool-use-commit.py +0 -124
- package/templates/qoder/scripts/foundation/io/context_cache.py +0 -94
- package/templates/qoder/scripts/tool_guide.md +0 -70
- package/templates/qoder/scripts/validation/eval/alignment_matrix.py +0 -176
- package/templates/qoder/scripts/validation/eval/bf2_content_fidelity.py +0 -110
- package/templates/qoder/scripts/validation/eval/bf2_llmjudge.py +0 -104
- package/templates/qoder/scripts/validation/eval/bf_score.py +0 -218
- package/templates/qoder/scripts/validation/eval/code_flywheel.py +0 -150
- package/templates/qoder/scripts/validation/eval/dispatcher_ab.py +0 -156
- package/templates/qoder/scripts/validation/eval/dispatcher_ab_2026-07-21.json +0 -23
- package/templates/qoder/scripts/validation/eval/feature_fidelity_flywheel.py +0 -143
- package/templates/qoder/scripts/validation/eval/gradient_matrix.py +0 -261
- package/templates/qoder/scripts/validation/eval/gradient_matrix_baseline_2026-07-21.json +0 -33
- package/templates/qoder/scripts/validation/eval/metrics_dashboard.py +0 -105
- package/templates/qoder/scripts/validation/eval/multi_turn_flywheel.py +0 -118
- package/templates/qoder/scripts/validation/eval/prd_fidelity_flywheel.py +0 -128
- package/templates/qoder/scripts/validation/eval/prd_flywheel.py +0 -148
- package/templates/qoder/scripts/validation/eval/prototype_fidelity_flywheel.py +0 -166
- package/templates/qoder/scripts/validation/eval/recall_flywheel.py +0 -148
- package/templates/qoder/scripts/validation/eval/robustness_flywheel.py +0 -139
- package/templates/qoder/scripts/validation/eval/speed_accuracy_flywheel.py +0 -188
- package/templates/qoder/scripts/validation/eval/task_flywheel.py +0 -124
- package/templates/qoder/scripts/validation/eval/token_flywheel.py +0 -88
- package/templates/qoder/scripts/validation/metrics/eval_code_ac.py +0 -177
- package/templates/qoder/scripts/validation/metrics/lint_cases.py +0 -170
- package/templates/qoder/scripts/validation/test/test_context_cache.py +0 -78
- package/templates/qoder/scripts/validation/test/test_lint_cases.py +0 -60
- package/templates/qoder/scripts/validation/test/test_pre_tool_use_commit.py +0 -70
- package/templates/workspace/specs/REQ-TEST-CTX/.context-cache.json +0 -1
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
"""bf2_content_fidelity.py — FW10 BF2 PRD 内容保真(生成PRD正文 vs 真实 ICS PRD 语义相似)。
|
|
2
|
-
|
|
3
|
-
BF2 = 生成 PRD 正文 → rag_search(kind='prd') 找最近真 PRD chunk → 相似度。
|
|
4
|
-
高=生成内容是真业务域(车辆/保险等,贴近真PRD正文);低=泛泛/编造(不贴近真PRD)。
|
|
5
|
-
bare(无接地) vs +schema接地。复用 rag_search 打分。
|
|
6
|
-
|
|
7
|
-
跑法: docker exec -i wlinkj-workspace-backend-1 python /app/scripts/validation/eval/bf2_content_fidelity.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
|
-
from app.routers import mcp_handlers as H # noqa: E402
|
|
21
|
-
try:
|
|
22
|
-
H._init_handlers()
|
|
23
|
-
except Exception:
|
|
24
|
-
pass
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
def chat(prompt, max_tokens=800, timeout=90):
|
|
33
|
-
body = json.dumps({"model": MODEL, "messages": [{"role": "user", "content": prompt}],
|
|
34
|
-
"temperature": 0.2, "max_tokens": max_tokens}).encode()
|
|
35
|
-
for a in range(3):
|
|
36
|
-
try:
|
|
37
|
-
req = urllib.request.Request(CHAT_URL, data=body,
|
|
38
|
-
headers={"Authorization": f"Bearer {CHAT_KEY}", "Content-Type": "application/json"})
|
|
39
|
-
return json.loads(urllib.request.urlopen(req, timeout=timeout).read())["choices"][0]["message"]["content"]
|
|
40
|
-
except (urllib.error.URLError, TimeoutError, OSError, KeyError) as e:
|
|
41
|
-
if a < 2: time.sleep(1.0*(a+1))
|
|
42
|
-
else: return ""
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def content_fidelity(prd_text):
|
|
46
|
-
"""生成 PRD 正文 → rag_search(kind=prd) 最近真 PRD chunk 平均分。"""
|
|
47
|
-
if not prd_text or len(prd_text) < 20:
|
|
48
|
-
return 0.0, 0
|
|
49
|
-
# 用 PRD 正文做语义查询(截断前 500 字,避免过长)
|
|
50
|
-
q = prd_text[:500].replace("\n", " ")
|
|
51
|
-
r = H.dispatch("rag_search", {"query": q, "kind": "prd", "top_k": 3}) or {}
|
|
52
|
-
items = r.get("items") or []
|
|
53
|
-
scores = [float(it.get("score", 0)) for it in items if it.get("score") is not None]
|
|
54
|
-
avg = round(sum(scores) / len(scores), 3) if scores else 0.0
|
|
55
|
-
return avg, len(items)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def real_prd_template(theme):
|
|
59
|
-
"""取一条真 ICS PRD 正文 chunk 做 house-style 写作范本(BF2 杠杆)。"""
|
|
60
|
-
r = H.dispatch("rag_search", {"query": theme, "kind": "prd", "top_k": 1}) or {}
|
|
61
|
-
items = r.get("items") or []
|
|
62
|
-
if items:
|
|
63
|
-
return (items[0].get("text") or "")[:600]
|
|
64
|
-
return ""
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def run():
|
|
68
|
-
rep = {"features": []}
|
|
69
|
-
for feat, theme in FEATURES:
|
|
70
|
-
tpl = real_prd_template(theme)
|
|
71
|
-
bare = chat(f"你是产品经理。为「{feat}」写一份 PRD 需求文档(含背景/功能/字段/流程)。直接输出正文。")
|
|
72
|
-
# BF2 杠杆:喂真 PRD 正文做写作范本(学 ICS house style/系统级深度,非泛产品文)
|
|
73
|
-
scaff = chat(f"你是 ICS(车辆物联网)产品经理。为「{feat}」写 PRD。\n"
|
|
74
|
-
f"【写作范本接地】以下是真实 ICS PRD 的正文(学它的写法/系统级字段级深度/业务术语,"
|
|
75
|
-
f"不要泛泛产品宣传文):\n{tpl}\n\n参考这个 house style 为「{feat}」写 PRD,"
|
|
76
|
-
f"字段/流程贴合 {theme} 真业务。直接输出正文。") if tpl else chat(
|
|
77
|
-
f"为「{feat}」写 PRD,贴合 {theme} 真业务。直接输出。")
|
|
78
|
-
b_score, b_n = content_fidelity(bare)
|
|
79
|
-
s_score, s_n = content_fidelity(scaff)
|
|
80
|
-
rep["features"].append({"feature": feat, "bare_score": b_score, "scaffold_score": s_score,
|
|
81
|
-
"bare_hits": b_n, "scaffold_hits": s_n, "has_template": bool(tpl)})
|
|
82
|
-
print(" %s: 内容保真 bare=%.3f → +真PRD范本=%.3f (范本%d字)" % (
|
|
83
|
-
feat, b_score, s_score, len(tpl)))
|
|
84
|
-
n = len(FEATURES) or 1
|
|
85
|
-
rep["agg"] = {"bare": round(sum(f["bare_score"] for f in rep["features"])/n, 3),
|
|
86
|
-
"scaffold": round(sum(f["scaffold_score"] for f in rep["features"])/n, 3)}
|
|
87
|
-
return rep
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def main():
|
|
91
|
-
import argparse
|
|
92
|
-
ap = argparse.ArgumentParser()
|
|
93
|
-
ap.add_argument("--json", action="store_true")
|
|
94
|
-
args = ap.parse_args()
|
|
95
|
-
r = run()
|
|
96
|
-
a = r["agg"]
|
|
97
|
-
if args.json:
|
|
98
|
-
print(json.dumps(r, ensure_ascii=False, indent=2))
|
|
99
|
-
return
|
|
100
|
-
print("\n## FW10 BF2 PRD 内容保真(生成PRD正文 vs 真实ICS PRD 语义相似度)")
|
|
101
|
-
print("| 模式 | 内容保真(语义cos,越接近真PRD越高) |")
|
|
102
|
-
print("|---|---|")
|
|
103
|
-
print("| bare(无业务接地) | %.3f |" % a["bare"])
|
|
104
|
-
print("| +真业务接地 | %.3f |" % a["scaffold"])
|
|
105
|
-
print("\n注:分数是 rag_search(kind=prd) 的 top-3 平均相似度(0-1)。"
|
|
106
|
-
"高=生成内容贴近真实 ICS PRD 正文(真业务域);低=泛泛/偏离。")
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if __name__ == "__main__":
|
|
110
|
-
main()
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
"""bf2_llmjudge.py — FW10 BF2 换度量:LLM-judge 量 PRD 内容业务保真(余弦不分好坏,换强模型裁判)。
|
|
2
|
-
|
|
3
|
-
BF2 余弦~0.03 不分好坏→换 LLM-judge:qwen-max 对照真 ICS PRD 范本,给生成 PRD 的【业务保真度】
|
|
4
|
-
打 1-5 分(5=完全 ICS 业务写法 系统级/字段级/真流程;1=通用产品宣传文)。能区分 bare(泛) vs +范本(ICS)。
|
|
5
|
-
|
|
6
|
-
跑法: docker exec -i wlinkj-workspace-backend-1 python /app/scripts/validation/eval/bf2_llmjudge.py
|
|
7
|
-
"""
|
|
8
|
-
import os
|
|
9
|
-
import re
|
|
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
|
-
from app.routers import mcp_handlers as H # noqa: E402
|
|
21
|
-
try:
|
|
22
|
-
H._init_handlers()
|
|
23
|
-
except Exception:
|
|
24
|
-
pass
|
|
25
|
-
|
|
26
|
-
CHAT_URL = os.environ.get("ALI_CHAT_BASE", "").rstrip("/") + "/chat/completions"
|
|
27
|
-
CHAT_KEY = os.environ.get("DASHSCOPE_API_KEY", "")
|
|
28
|
-
GEN_MODEL = "qwen-turbo" # 生成(弱模型,卖点)
|
|
29
|
-
JUDGE_MODEL = "qwen-max" # 裁判(强模型)
|
|
30
|
-
FEATURES = [("车辆保险管理", "保险"), ("异常记录管理", "异常"), ("合同维护", "合同")]
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def chat(model, prompt, max_tokens=800, timeout=90):
|
|
34
|
-
body = json.dumps({"model": model, "messages": [{"role": "user", "content": prompt}],
|
|
35
|
-
"temperature": 0.0 if model == JUDGE_MODEL else 0.2, "max_tokens": max_tokens}).encode()
|
|
36
|
-
for a in range(3):
|
|
37
|
-
try:
|
|
38
|
-
req = urllib.request.Request(CHAT_URL, data=body,
|
|
39
|
-
headers={"Authorization": f"Bearer {CHAT_KEY}", "Content-Type": "application/json"})
|
|
40
|
-
return json.loads(urllib.request.urlopen(req, timeout=timeout).read())["choices"][0]["message"]["content"]
|
|
41
|
-
except (urllib.error.URLError, TimeoutError, OSError, KeyError) as e:
|
|
42
|
-
if a < 2: time.sleep(1.0*(a+1))
|
|
43
|
-
else: return ""
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def real_prd(theme):
|
|
47
|
-
r = H.dispatch("rag_search", {"query": theme, "kind": "prd", "top_k": 1}) or {}
|
|
48
|
-
items = r.get("items") or []
|
|
49
|
-
return (items[0].get("text") or "")[:600] if items else ""
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def judge(prd, ref, feat):
|
|
53
|
-
"""qwen-max 打业务保真分 1-5。"""
|
|
54
|
-
p = ("你是 ICS(车辆物联网系统)资深产品专家。对照下面【真实 ICS PRD 范本】,给这份【生成 PRD】"
|
|
55
|
-
"的业务保真度打分 1-5:\n"
|
|
56
|
-
"5=完全 ICS 业务写法(系统级/字段级深度、真实业务流程、车辆物联网术语)\n"
|
|
57
|
-
"3=部分 ICS(有业务要素但有泛化)\n"
|
|
58
|
-
"1=通用产品宣传文(泛泛、非 ICS 真业务)\n\n"
|
|
59
|
-
f"【真实 ICS PRD 范本】:\n{ref}\n\n"
|
|
60
|
-
f"【生成 PRD({feat})】:\n{prd[:1000]}\n\n"
|
|
61
|
-
f"只输出一个整数分(1-5),不要解释。")
|
|
62
|
-
out = chat(JUDGE_MODEL, p, max_tokens=10)
|
|
63
|
-
m = re.search(r"[1-5]", out)
|
|
64
|
-
return int(m.group()) if m else 0
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def run():
|
|
68
|
-
rep = {"features": []}
|
|
69
|
-
for feat, theme in FEATURES:
|
|
70
|
-
ref = real_prd(theme)
|
|
71
|
-
bare = chat(GEN_MODEL, f"你是产品经理。为「{feat}」写一份 PRD 需求文档。直接输出正文。")
|
|
72
|
-
scaff = (chat(GEN_MODEL, f"你是 ICS 产品经理。为「{feat}」写 PRD。\n"
|
|
73
|
-
f"【写作范本】真 ICS PRD 正文(学其系统级/字段级写法,勿泛产品文):\n{ref}\n直接输出 PRD 正文。")
|
|
74
|
-
if ref else bare)
|
|
75
|
-
b = judge(bare, ref, feat)
|
|
76
|
-
s = judge(scaff, ref, feat)
|
|
77
|
-
rep["features"].append({"feature": feat, "bare_score": b, "scaffold_score": s})
|
|
78
|
-
print(" %s: 业务保真分 bare=%d/5 → +真PRD范本=%d/5" % (feat, b, s))
|
|
79
|
-
n = len(FEATURES) or 1
|
|
80
|
-
rep["agg"] = {"bare": round(sum(f["bare_score"] for f in rep["features"])/n, 2),
|
|
81
|
-
"scaffold": round(sum(f["scaffold_score"] for f in rep["features"])/n, 2)}
|
|
82
|
-
return rep
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def main():
|
|
86
|
-
import argparse
|
|
87
|
-
ap = argparse.ArgumentParser()
|
|
88
|
-
ap.add_argument("--json", action="store_true")
|
|
89
|
-
args = ap.parse_args()
|
|
90
|
-
r = run()
|
|
91
|
-
a = r["agg"]
|
|
92
|
-
if args.json:
|
|
93
|
-
print(json.dumps(r, ensure_ascii=False, indent=2))
|
|
94
|
-
return
|
|
95
|
-
print("\n## FW10 BF2(换 LLM-judge 度量)PRD 内容业务保真")
|
|
96
|
-
print("| 模式 | 业务保真分(/5, qwen-max 裁判 vs 真 PRD) |")
|
|
97
|
-
print("|---|---|")
|
|
98
|
-
print("| bare(通用) | %.2f |" % a["bare"])
|
|
99
|
-
print("| +真PRD范本(ICS house style) | %.2f |" % a["scaffold"])
|
|
100
|
-
print("\n目标≥4/5。LLM-judge 能区分(余弦不能):bare 通用文低分,+范本 ICS 写法高分。")
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if __name__ == "__main__":
|
|
104
|
-
main()
|
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
"""bf_score.py - 业务保真(BF)统一打分器:吃【真命令产物文件】打 BF 分。
|
|
4
|
-
|
|
5
|
-
把 FW9-11 的 BF 尺子从"代理生成+打分"改成"读真产物文件+打分"。
|
|
6
|
-
宿主真跑 /wl-prd /wl-design /wl-code /wl-search 出的真产物 → 本工具打 BF 分(对真 ICS 业务数据)。
|
|
7
|
-
|
|
8
|
-
用法:
|
|
9
|
-
python bf_score.py prd <PRD.md> [--feature 保险] # BF1 schema保真 + BF2 内容保真(LLM-judge)
|
|
10
|
-
python bf_score.py proto <原型.html> # BF3 组件保真 + BF4 颜色保真
|
|
11
|
-
python bf_score.py code <实体列表.txt|diff> --feature 保险 # BF5 feature精准
|
|
12
|
-
python bf_score.py search <结果.json或txt> --feature 保险 # BF6 feature相关率
|
|
13
|
-
|
|
14
|
-
跑法(容器内,接真业务数据):
|
|
15
|
-
docker exec -i wlinkj-workspace-backend-1 python /app/scripts/validation/eval/bf_score.py prd /path/REQ.md --feature 保险
|
|
16
|
-
"""
|
|
17
|
-
import os
|
|
18
|
-
import re
|
|
19
|
-
import sys
|
|
20
|
-
import json
|
|
21
|
-
import colorsys
|
|
22
|
-
import urllib.request
|
|
23
|
-
import urllib.error
|
|
24
|
-
|
|
25
|
-
for _p in ("/app", os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", ".."))):
|
|
26
|
-
if os.path.isdir(_p) and _p not in sys.path:
|
|
27
|
-
sys.path.insert(0, _p)
|
|
28
|
-
|
|
29
|
-
from sqlalchemy import text # noqa: E402
|
|
30
|
-
from app.db import SessionLocal # noqa: E402
|
|
31
|
-
|
|
32
|
-
_H = None # lazy: 避免与 mcp_handlers 循环 import
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def _hub():
|
|
36
|
-
"""惰性取 mcp_handlers(dispatch rag_search/get_design_system),避免循环 import。"""
|
|
37
|
-
global _H
|
|
38
|
-
if _H is None:
|
|
39
|
-
from app.routers import mcp_handlers as _h
|
|
40
|
-
try:
|
|
41
|
-
_h._init_handlers()
|
|
42
|
-
except Exception:
|
|
43
|
-
pass
|
|
44
|
-
_H = _h
|
|
45
|
-
return _H
|
|
46
|
-
|
|
47
|
-
CHAT_URL = os.environ.get("ALI_CHAT_BASE", "").rstrip("/") + "/chat/completions"
|
|
48
|
-
CHAT_KEY = os.environ.get("DASHSCOPE_API_KEY", "")
|
|
49
|
-
JUDGE_MODEL = "qwen-max"
|
|
50
|
-
_NEUTRAL = {(255, 255, 255), (0, 0, 0), (51, 51, 51), (102, 102, 102), (153, 153, 153),
|
|
51
|
-
(240, 240, 240), (250, 250, 250), (217, 217, 217), (232, 232, 232), (245, 245, 245)}
|
|
52
|
-
_TABLE_RE = re.compile(r'\b(t_[a-z_]+|tb_[a-z_]+|report_[a-z_]+|salary_[a-z_]+|[a-z]+_[a-z_]+_table)\b', re.IGNORECASE)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def _read(path):
|
|
56
|
-
try:
|
|
57
|
-
with open(path, encoding="utf-8", errors="replace") as f:
|
|
58
|
-
return f.read()
|
|
59
|
-
except Exception as e:
|
|
60
|
-
return ""
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def _q(s, sql, p=None):
|
|
64
|
-
try:
|
|
65
|
-
return s.execute(text(sql), p or {}).fetchall()
|
|
66
|
-
except Exception:
|
|
67
|
-
try:
|
|
68
|
-
s.rollback()
|
|
69
|
-
except Exception:
|
|
70
|
-
pass
|
|
71
|
-
return []
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def chat(model, prompt, max_tokens=10, timeout=60):
|
|
75
|
-
body = json.dumps({"model": model, "messages": [{"role": "user", "content": prompt}],
|
|
76
|
-
"temperature": 0.0, "max_tokens": max_tokens}).encode()
|
|
77
|
-
for a in range(3):
|
|
78
|
-
try:
|
|
79
|
-
req = urllib.request.Request(CHAT_URL, data=body,
|
|
80
|
-
headers={"Authorization": f"Bearer {CHAT_KEY}", "Content-Type": "application/json"})
|
|
81
|
-
return json.loads(urllib.request.urlopen(req, timeout=timeout).read())["choices"][0]["message"]["content"]
|
|
82
|
-
except Exception:
|
|
83
|
-
if a < 2:
|
|
84
|
-
import time
|
|
85
|
-
time.sleep(1.0 * (a + 1))
|
|
86
|
-
else:
|
|
87
|
-
return ""
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
# ── BF1: PRD schema 保真 ──
|
|
91
|
-
def bf1_schema(prd_text):
|
|
92
|
-
mentioned = set(m.lower() for m in _TABLE_RE.findall(prd_text))
|
|
93
|
-
if not mentioned:
|
|
94
|
-
return {"score": 0.0, "mentioned": 0, "note": "PRD 未提表名"}
|
|
95
|
-
s = SessionLocal()
|
|
96
|
-
all_tabs = set(r[0].lower() for r in _q(s, "SELECT table_name FROM db_tables"))
|
|
97
|
-
s.close()
|
|
98
|
-
real = mentioned & all_tabs
|
|
99
|
-
return {"score": round(len(real) / len(mentioned), 3), "mentioned": len(mentioned), "real": len(real)}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
# ── BF2: PRD 内容保真(LLM-judge) ──
|
|
103
|
-
def bf2_content(prd_text, feature):
|
|
104
|
-
r = _hub().dispatch("rag_search", {"query": feature, "kind": "prd", "top_k": 1}) or {}
|
|
105
|
-
items = r.get("items") or []
|
|
106
|
-
ref = (items[0].get("text") or "")[:600] if items else ""
|
|
107
|
-
if not ref:
|
|
108
|
-
return {"score": 0, "note": "无真 PRD 范本"}
|
|
109
|
-
p = ("你是 ICS(车辆物联网系统)资深产品专家。对照【真实 ICS PRD 范本】,给【生成 PRD】业务保真度打分 1-5:\n"
|
|
110
|
-
"5=完全 ICS 业务写法(系统级/字段级/真业务流程);3=部分;1=通用产品宣传文。\n"
|
|
111
|
-
f"【真 ICS PRD 范本】:\n{ref}\n\n【待评 PRD】:\n{prd_text[:1000]}\n\n只输出一个整数(1-5)。")
|
|
112
|
-
out = chat(JUDGE_MODEL, p)
|
|
113
|
-
m = re.search(r"[1-5]", out)
|
|
114
|
-
return {"score": int(m.group()) if m else 0, "ref_len": len(ref)}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
# ── BF3/4: 原型保真 ──
|
|
118
|
-
def _design():
|
|
119
|
-
r = _hub().dispatch("get_design_system", {}) or {}
|
|
120
|
-
proj = next((p for p in r.get("projects", []) if p.get("project") == "fywl-ui"), {})
|
|
121
|
-
colors = proj.get("colors", {}) or {}
|
|
122
|
-
palette = set()
|
|
123
|
-
for v in colors.values():
|
|
124
|
-
if isinstance(v, str) and v.startswith("hsl"):
|
|
125
|
-
m = re.match(r"hsl\(\s*([\d.]+)[,\s]+([\d.]+)%[,\s]+([\d.]+)%", v)
|
|
126
|
-
if m:
|
|
127
|
-
h, s_, l = float(m.group(1)) / 360, float(m.group(2)) / 100, float(m.group(3)) / 100
|
|
128
|
-
r_, g_, b_ = colorsys.hls_to_rgb(h, l, s_)
|
|
129
|
-
palette.add((round(r_ * 255), round(g_ * 255), round(b_ * 255)))
|
|
130
|
-
comps = set()
|
|
131
|
-
for d in (proj.get("commonComponents", {}), proj.get("formComponents", {})):
|
|
132
|
-
if isinstance(d, dict):
|
|
133
|
-
comps.update(k.lower() for k in d)
|
|
134
|
-
return palette, comps
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
def _extract_colors(html):
|
|
138
|
-
out = set()
|
|
139
|
-
for m in re.findall(r"#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})\b", html):
|
|
140
|
-
h = m if len(m) == 6 else m * 2
|
|
141
|
-
out.add((int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16)))
|
|
142
|
-
for m in re.findall(r"hsl\(\s*([\d.]+)[,\s]+([\d.]+)%[,\s]+([\d.]+)%", html):
|
|
143
|
-
h, s_, l = float(m[0]) / 360, float(m[1]) / 100, float(m[2]) / 100
|
|
144
|
-
r_, g_, b_ = colorsys.hls_to_rgb(h, l, s_)
|
|
145
|
-
out.add((round(r_ * 255), round(g_ * 255), round(b_ * 255)))
|
|
146
|
-
for m in re.findall(r"rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)", html):
|
|
147
|
-
out.add((int(m[0]), int(m[1]), int(m[2])))
|
|
148
|
-
return out
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
def _color_real(c, palette):
|
|
152
|
-
if c in _NEUTRAL:
|
|
153
|
-
return True
|
|
154
|
-
return any(all(abs(a - b) <= 40 for a, b in zip(c, p)) for p in palette)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def bf34_proto(html):
|
|
158
|
-
palette, comps = _design()
|
|
159
|
-
cols = _extract_colors(html)
|
|
160
|
-
color_score = (sum(1 for c in cols if _color_real(c, palette)) / len(cols)) if cols else 0.0
|
|
161
|
-
used = set(m.lower() for m in re.findall(r"<(?:el|van|a|n)-([\w-]+)", html))
|
|
162
|
-
comp_score = (sum(1 for c in used if c in comps) / len(used)) if used and comps else (1.0 if not comps else 0.0)
|
|
163
|
-
return {"bf3_component": round(comp_score, 3), "bf4_color": round(color_score, 3),
|
|
164
|
-
"colors": len(cols), "components": len(used)}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
# ── BF5/6: feature 实体精准/相关率 ──
|
|
168
|
-
def _feature_gold(feature):
|
|
169
|
-
s = SessionLocal()
|
|
170
|
-
rows = _q(s, "SELECT canonical, cn FROM entities WHERE (canonical ILIKE :p OR cn ILIKE :p) "
|
|
171
|
-
"AND type IN ('CONTROLLER','SERVICE','TABLE','COLUMN','FUNCTION') LIMIT 30", {"p": f"%{feature}%"})
|
|
172
|
-
s.close()
|
|
173
|
-
return set(c for c, _ in rows if c)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
def bf56_entities(text, feature):
|
|
177
|
-
gold = _feature_gold(feature)
|
|
178
|
-
cands = set(re.findall(r"[A-Za-z_][\w]{2,}", text))
|
|
179
|
-
if not cands:
|
|
180
|
-
return {"score": 0.0, "candidates": 0, "gold": len(gold)}
|
|
181
|
-
hit = sum(1 for c in cands if c in gold)
|
|
182
|
-
return {"score": round(hit / len(cands), 3), "candidates": len(cands), "gold_hit": hit, "gold": len(gold)}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
def main():
|
|
186
|
-
if len(sys.argv) < 3:
|
|
187
|
-
sys.stderr.write(__doc__)
|
|
188
|
-
return 1
|
|
189
|
-
kind, path = sys.argv[1], sys.argv[2]
|
|
190
|
-
feature = None
|
|
191
|
-
if "--feature" in sys.argv:
|
|
192
|
-
feature = sys.argv[sys.argv.index("--feature") + 1]
|
|
193
|
-
text = _read(path)
|
|
194
|
-
if not text:
|
|
195
|
-
sys.stderr.write("读不到文件: %s\n" % path)
|
|
196
|
-
return 1
|
|
197
|
-
out = {"file": os.path.basename(path), "kind": kind}
|
|
198
|
-
if kind == "prd":
|
|
199
|
-
out["BF1_schema"] = bf1_schema(text)
|
|
200
|
-
if feature:
|
|
201
|
-
out["BF2_content"] = bf2_content(text, feature)
|
|
202
|
-
elif kind == "proto":
|
|
203
|
-
out.update(bf34_proto(text))
|
|
204
|
-
elif kind in ("code", "search"):
|
|
205
|
-
if not feature:
|
|
206
|
-
sys.stderr.write("code/search 需 --feature\n")
|
|
207
|
-
return 1
|
|
208
|
-
bf = bf56_entities(text, feature)
|
|
209
|
-
out["BF5_code" if kind == "code" else "BF6_search"] = bf
|
|
210
|
-
else:
|
|
211
|
-
sys.stderr.write("未知 kind: %s (prd/proto/code/search)\n" % kind)
|
|
212
|
-
return 1
|
|
213
|
-
print(json.dumps(out, ensure_ascii=False, indent=2))
|
|
214
|
-
return 0
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if __name__ == "__main__":
|
|
218
|
-
sys.exit(main())
|
|
@@ -1,150 +0,0 @@
|
|
|
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()
|