数据目录 F3 / B8:一键升级的语义层(assist_upgrade)自查、修正轮和最终判断都带上基于数据目录的 SQL 检查器,和发布门禁一个口径——助手改出一对多关联后求和这类 SQL 会交回去改,改不好就作废

This commit is contained in:
ThearchyHelios
2026-10-04 01:09:12 +08:00
parent 4c7cfa62b2
commit 6c30becd94
2 changed files with 60 additions and 3 deletions
+9 -3
View File
@@ -2251,13 +2251,18 @@ async def assist_upgrade(
if not feeders:
out["summary"] = "口径卡的输入均不来自负责计算的沙箱代码,无需交给助手改写"
return out
from app.data.sqlcheck import load_checkers
sources = await _sources(session, None)
# 基于数据目录的 SQL 检查器:自查和最终判断都带上,和发布门禁一个口径(受管级别下一对多关联后求和这类 error
# 会挡发布)。不带的话,助手改出这种 SQL 照样被采纳,交出去的图要到发布时才被拦
checkers = await load_checkers(session, sources)
before_nodes = {str(n["id"]): n for n in copy.deepcopy(base.get("nodes") or []) if isinstance(n, dict)}
before_edges = [e for e in copy.deepcopy(base.get("edges") or []) if isinstance(e, dict)]
baseline = list(copy.deepcopy(before_nodes).values())
defaults = base.get("defaults")
base_errors = _blocking_issues(before_nodes, before_edges, sources=sources, baseline=baseline, level=level,
defaults=defaults) or []
defaults=defaults, checkers=checkers) or []
known = {_sig(e) for e in base_errors}
out["warnings"] = _kept_code(spec)
request = GenerateIn(instruction="升级为可追溯结构", provider=provider, model=model)
@@ -2309,7 +2314,8 @@ async def assist_upgrade(
await collect(_upgrade_assist_request(base, spec, feeders), first=True)
# 自查:和搭图同一套规则,只把这一轮冒出来的 error 交回去改(原来就有的不归这一轮管)
for _ in range(_SELF_CHECK_ROUNDS):
now = _blocking_issues(nodes, edges, sources=sources, baseline=baseline, level=level, defaults=defaults)
now = _blocking_issues(nodes, edges, sources=sources, baseline=baseline, level=level, defaults=defaults,
checkers=checkers)
fresh = [e for e in now or [] if _sig(e) not in known]
if not effective or not fresh:
break
@@ -2326,7 +2332,7 @@ async def assist_upgrade(
proposed = _proposed_graph(base, nodes, edges)
reasons = forbidden_changes(base, proposed, tried)
after = _blocking_issues(_nodes_by_id(proposed), proposed.get("edges") or [], sources=sources,
baseline=baseline, level=level, defaults=proposed.get("defaults"))
baseline=baseline, level=level, defaults=proposed.get("defaults"), checkers=checkers)
if reasons:
out["reason"] = "助手的修改降低了要求,已作废:" + ";".join(reasons)
elif after is None:
+51
View File
@@ -241,3 +241,54 @@ async def test_clean_sql_costs_no_extra_call(scenic_source, monkeypatch):
final = events[-1]
assert final["autorun"] is True
assert not [i for i in final["issues"] if i.get("code") in ("fanout_sum", "missing_valid_filter")]
# --------------------------------------------------------------------------
# 一键升级的语义层(assist_upgrade):自查和最终判断同样带 SQL 检查器,和发布门禁一个口径
# --------------------------------------------------------------------------
def _upgrade_graph(source: str) -> dict:
"""查库 → 沙箱代码算比率 → 口径卡读代码的产出 → 报告撰写 → 出口。查库的 SQL 本身没问题。"""
def n(nid, ntype, x, **config):
return {"id": nid, "type": ntype, "position": {"x": x, "y": 80}, "data": {"label": nid, "config": config}}
nodes = [
n("start", "input", 0),
n("fetch", "tool", 280, tool=f"db_query__{source}", args={"sql": CLEAN_SQL}),
n("calc", "code", 560, language="python", assign_to="calc", code="import json\nprint(json.dumps({'ratio': 3 / 4}))"),
n("card", "metrics", 840, metrics=[{"id": "ratio", "name": "客单价", "expression": "vars.calc.ratio"}]),
n("write", "report", 1120, instructions="写一句话"),
n("out", "output", 1400, fields=[{"name": "周报", "value": "{{ nodes.write.text }}"}]),
]
return {"nodes": nodes, "edges": [{"id": f"e{i}", "source": a["id"], "target": b["id"]}
for i, (a, b) in enumerate(zip(nodes, nodes[1:]))]}
async def test_upgrade_assist_checks_sql_like_the_publish_gate(scenic_source, monkeypatch):
"""助手改写口径卡时顺手把查询改成了一对多关联后求和:受管级别的发布门禁会挡,升级的自查也得交回去改,
改不好就作废——以前自查和最终判断都没带 SQL 检查器,这样的改写会被采纳,交出去的图发布时才被拦。"""
import app.api.copilot as copilot
from app.db.base import SessionLocal
first = [{"op": "plan", "summary": "把纯算术的代码挪进口径卡"},
{"op": "update_node", "id": "card", "config": {"metrics": [
{"id": "ratio", "name": "客单价", "expression": "cell(nodes.fetch, 0, 'gmv') / 4"}]}},
{"op": "update_node", "id": "fetch", "config": {"args": {"sql": FANOUT_SQL}}},
{"op": "done", "explanation": "客单价改由口径卡直接算"}]
fix = [{"op": "update_node", "id": "fetch", "config": {"args": {"sql": FANOUT_SQL + " "}}},
{"op": "done", "explanation": "改了"}]
model = _Scripted(first, fix)
async def _model(*_a, **_k):
return model, "mock-fast"
monkeypatch.setattr(copilot, "get_chat_model", _model)
async with SessionLocal() as session:
out = await copilot.assist_upgrade(session, _upgrade_graph(scenic_source), level="governed")
# 自查把一对多求和交回模型,带着给模型的改法
assert len(model.calls) >= 2 and "上线前自查" in model.calls[1]
assert "orders 关联 order_items 是一对多" in model.calls[1]
# 改不好:整个作废,原因写的是这条 SQL 检查
assert out["accepted"] is False and out["ok"] is False
assert "一对多" in (out["reason"] or ""), out["reason"]