mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 02:31:58 +00:00
03c18c4d78
一、文案(前端约 1600 处,后端传到界面的约 880 处) - 术语统一:工作流层面用「运行」(继续运行、重新运行、试运行),节点和工具层面用「执行」,不再用「跑」; 总时长 / 执行时长 / 等待审批;服务端;助手;Agent;token;工作流;固定版本;模型接入;系统提示;批准 / 驳回;受管级别 - 口语改书面:取不到、没取回来、读不懂、连不上、多半、照样、兜底、揉成这类说法改成「无法 / 未能 / 失败 / 请」;去掉拟人写法 - 不再露出实现细节:界面上不写 checkpoint、同一拍、哈希(凭证处除外),不写改版历史(「以前…」「老数据」); 报错、校验、门禁、自动修复里裸写的配置键名和枚举值换成界面上的中文标签,由新的 backend/app/engine/labels.py 统一提供; 发布弹窗里结论句的选项不再是 off / require_citation - 同一件事只留一种说法:生成阶段文案、出具档位说明、画布锁定提示各合成一份 - 去掉界面上的工业场景示例(MES、ERP、「张工」「王工」)和真实产品名,换成通用示例 二、说错的提示(都有测试证明) - 数据库测连接失败时的专用提示以前从不出现(按报错文字全等比较,永远比不上):改为按错误类型判断,不再拼出重复的前缀 - 「设置 → 供应商」改指「设置 → 模型接入」;「在设置里建数据源」改指「数据」页 - 缺少 API Key、模型接入已停用的报错不再夹英文,写明去哪里填 - 两处原因说错(「这种引用在后续版本支持」「这一版还不支持」)改成真实原因 - 节点说明里不准确的说法改正:代码节点在 microVM 或系统沙箱中执行;报告撰写、口径卡的说明不再说过头 - 导出的证据 CSV 里两列同名,第三列改为「片段编号」 三、前后端对齐 - 前端按后端原文识别日志、报错的地方,新旧两种写法都认得(历史运行里存的是旧文字),逐条补了新旧原文的核对 - 后端交回模型的原指令放进单独的 for_model 字段,前端在解析响应时去掉,不会显示到界面上 四、检查 - 检查脚本里按原文匹配的断言改成新文案;改完会变成永远成立的反向断言逐条修正,并做了变异验证 - 新增 check-copy(静态扫描前端界面字符串和后端上界面的文案):再出现「跑」「后端」「多半」「令牌」、 界面上的 Copilot、裸写的配置键名、半角标点等就报错;确需保留的写进 scripts/copy-allow.json 并注明理由
103 lines
5.8 KiB
Python
103 lines
5.8 KiB
Python
"""画图时就能说破的几件证据链配置问题(validate_graph)。
|
||
|
||
- agent 写了 output_schema 却没开 cite_fields:Schema 不生效,assign_to 拿到的仍是自由文字
|
||
- 口径卡的输入来自沙箱代码、而它没标成取数(evidence_role 不是 source):算出来的数核对不了出处
|
||
- 报告撰写节点没指定模型:和 llm / agent 一样提示会回退到默认 provider
|
||
- 钉住别处口径卡的 caliber_from:三项要写全、版本要是具体的数,本地不用再写指标
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from app.engine.schema import GraphSpec, validate_graph
|
||
|
||
|
||
def node(nid, ntype, label=None, **config):
|
||
return {"id": nid, "type": ntype, "position": {"x": 0, "y": 0},
|
||
"data": {"label": label or nid, "config": config}}
|
||
|
||
|
||
def issues(*nodes, node_id=None):
|
||
graph = {"nodes": list(nodes),
|
||
"edges": [{"source": a["id"], "target": b["id"]} for a, b in zip(nodes, nodes[1:])]}
|
||
found = validate_graph(GraphSpec.model_validate(graph)).issues
|
||
return [i for i in found if node_id is None or i.node_id == node_id]
|
||
|
||
|
||
START = node("start", "input")
|
||
OUT = node("out", "output", fields=[{"name": "r", "value": "{{ last_message }}"}])
|
||
SCHEMA = {"type": "object", "properties": {"gmv": {"type": "number"}}}
|
||
|
||
|
||
def test_output_schema_without_cite_fields_warns():
|
||
bot = node("bot", "agent", prompt="查", tools=["db_query__shop"], output_schema=SCHEMA, model="m")
|
||
[issue] = [i for i in issues(START, bot, OUT, node_id="bot") if "按出处核对字段" in i.message]
|
||
assert issue.level == "warning" and issue.field == "output_schema"
|
||
assert issue.message.startswith("「结构化输出 Schema」需要同时开启「按出处核对字段」才会生效(会多一次抽取调用)")
|
||
|
||
cited = node("bot", "agent", prompt="查", tools=["db_query__shop"], output_schema=SCHEMA, cite_fields=True,
|
||
model="m")
|
||
assert not [i for i in issues(START, cited, OUT, node_id="bot") if "按出处核对字段" in i.message]
|
||
plain = node("bot", "agent", prompt="查", tools=["db_query__shop"], model="m")
|
||
assert not [i for i in issues(START, plain, OUT, node_id="bot") if "按出处核对字段" in i.message]
|
||
|
||
|
||
def test_caliber_fed_by_a_compute_code_node_warns():
|
||
def graph(**code):
|
||
fetch = node("fetch", "code", "取数脚本", code="print('{\"gmv\": 1}')", assign_to="agg", **code)
|
||
card = node("card", "metrics", caliber="周报口径", metrics=[
|
||
{"id": "gmv", "expression": "vars.agg.gmv"}, {"id": "gmv2", "expression": "nodes.fetch.gmv * 2"}])
|
||
return issues(START, fetch, card, OUT, node_id="card")
|
||
|
||
[issue] = [i for i in graph() if "「证据角色」" in i.message]
|
||
assert issue.level == "warning" and issue.field == "metrics[0].expression"
|
||
assert "「取数脚本」" in issue.message and "「取数」" in issue.message
|
||
assert [i for i in graph(evidence_role="compute") if "「证据角色」" in i.message]
|
||
assert not [i for i in graph(evidence_role="source") if "「证据角色」" in i.message]
|
||
|
||
|
||
def test_evidence_role_must_be_source_or_compute():
|
||
fetch = node("fetch", "code", code="print(1)", evidence_role="取数")
|
||
[issue] = [i for i in issues(START, fetch, OUT, node_id="fetch") if i.field == "evidence_role"]
|
||
assert issue.level == "error" and "「取数」" in issue.message and "「计算」" in issue.message
|
||
|
||
|
||
def test_report_without_a_model_gets_the_same_warning_as_llm():
|
||
card = node("card", "metrics", metrics=[{"id": "gmv", "expression": "1"}])
|
||
write = node("write", "report", instructions="写周报")
|
||
[issue] = [i for i in issues(START, card, write, OUT, node_id="write") if i.field == "model"]
|
||
assert issue.level == "warning" and "没有指定模型" in issue.message
|
||
|
||
|
||
def test_caliber_from_replaces_local_metrics():
|
||
pinned = {"workflow_id": "wf1", "workflow_version": 3, "node_id": "caliber"}
|
||
card = node("card", "metrics", caliber_from=pinned)
|
||
assert not [i for i in issues(START, card, OUT, node_id="card") if i.level == "error"]
|
||
|
||
both = node("card", "metrics", caliber_from=pinned, metrics=[{"id": "gmv", "expression": "1"}])
|
||
[issue] = [i for i in issues(START, both, OUT, node_id="card") if i.field == "metrics"]
|
||
assert issue.level == "warning" and "不会生效" in issue.message
|
||
|
||
for bad, field in ((["wf1"], "caliber_from"), ({"workflow_id": "wf1", "node_id": "c"}, "caliber_from.workflow_version"),
|
||
({"workflow_id": "wf1", "workflow_version": "最新", "node_id": "c"},
|
||
"caliber_from.workflow_version"),
|
||
({"workflow_id": "", "workflow_version": 1, "node_id": "c"}, "caliber_from")):
|
||
found = [i for i in issues(START, node("card", "metrics", caliber_from=bad), OUT, node_id="card")
|
||
if i.level == "error"]
|
||
assert [i.field for i in found] == [field], (bad, found)
|
||
|
||
policy = node("card", "metrics", caliber_from=pinned, upgrade_policy="auto")
|
||
[issue] = [i for i in issues(START, policy, OUT, node_id="card") if i.field == "upgrade_policy"]
|
||
assert issue.level == "error" and "「用新口径回算历史」" in issue.message
|
||
ok = node("card", "metrics", caliber_from=pinned, upgrade_policy="dual")
|
||
assert not [i for i in issues(START, ok, OUT, node_id="card") if i.level == "error"]
|
||
|
||
|
||
def test_template_eight_marks_its_fetch_code_as_a_source():
|
||
"""模板 ⑧ 的取数代码是「数据底座」:标成 source,口径卡读它不再被提示核对不了出处。"""
|
||
from app.seed import TEMPLATES
|
||
|
||
weekly = next(t for t in TEMPLATES if t["name"].startswith("⑧"))
|
||
fetch = next(n for n in weekly["nodes"] if n["id"] == "fetch")
|
||
assert fetch["data"]["config"]["evidence_role"] == "source"
|
||
spec = GraphSpec.model_validate({"nodes": weekly["nodes"], "edges": weekly["edges"]})
|
||
assert not [i for i in validate_graph(spec).issues if "evidence_role" in i.message]
|