mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 04:41:57 +00:00
4fdd58e3be
formal 运行的全部语义就一条规则:必须引用一个不可变的已发布 WorkflowVersion(按 graph_hash 钉住);传裸 graph 的运行自动是 exploratory。发布后改图不影响已发布版本——formal 永远执行快照。 子图节点支持 workflow_version 钉版,这就是方法卡的机制核心: 钉住后上游改方法卡不会让口径悄悄漂移。钉住的方法卡出现新版本时, formal 启动被拒,除非节点已声明 upgrade_policy(recompute 回算 / dual 双印 / incomparable 标注不可比,无默认值);已声明的升版 以 caliber.upgrade 事件记入流水。 发布端点带两级门禁:published 只拦基础错误,governed 额外过治理 lint(禁 supervisor、方法卡必须钉版、agent 不得关闭全部审批、 output 必须声明出具契约)。X-Actor 记录发布人与运行发起人。
125 lines
4.9 KiB
Python
125 lines
4.9 KiB
Python
from __future__ import annotations
|
||
|
||
from typing import Any
|
||
|
||
from sqlalchemy import func, select
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
from app.engine.schema import GraphSpec, NodeType, ValidationResult
|
||
|
||
# 口径升版的三种合法处置。没有默认值是刻意的:升版最容易静默翻车的地方
|
||
# 就是"上周和这周的数其实不可比,但报表看起来一切正常"。
|
||
UPGRADE_POLICIES = {"recompute", "dual", "incomparable"}
|
||
|
||
UPGRADE_POLICY_LABELS = {
|
||
"recompute": "用新口径回算历史",
|
||
"dual": "并排双印新旧口径",
|
||
"incomparable": "标注与历史不可比",
|
||
}
|
||
|
||
|
||
def lint_for_publish(spec: GraphSpec, *, level: str) -> ValidationResult:
|
||
"""发布门禁。
|
||
|
||
published:基础可运行性之外只给警告。
|
||
governed:受管模板,出具用的那一档 —— 把关节层的自由度收进边界里,
|
||
错误会挡住发布。
|
||
"""
|
||
result = ValidationResult()
|
||
strict = level == "governed"
|
||
|
||
def flag(message: str, *, node_id: str | None = None, hard: bool = False) -> None:
|
||
result.add(message, level="error" if (hard and strict) else "warning", node_id=node_id)
|
||
|
||
has_contract_output = False
|
||
for node in spec.nodes:
|
||
cfg = node.config
|
||
if node.type == NodeType.SUPERVISOR:
|
||
# 全动态规划属于探索层。受管模板里出现它,等于把 XAgent 搬上了生产线。
|
||
flag(
|
||
"受管模板不允许 supervisor 节点(全动态规划属于探索层)",
|
||
node_id=node.id, hard=True,
|
||
)
|
||
elif node.type == NodeType.SUBGRAPH:
|
||
if not cfg.get("workflow_version"):
|
||
flag(
|
||
"子图(方法卡)引用没有钉版本,口径会随上游最新版漂移",
|
||
node_id=node.id, hard=True,
|
||
)
|
||
elif node.type == NodeType.AGENT:
|
||
tools = cfg.get("tools") or []
|
||
if cfg.get("approval") == "never" and tools:
|
||
flag(
|
||
"agent 关闭了全部工具审批(approval=never),受管模板要求危险工具至少人工确认",
|
||
node_id=node.id, hard=True,
|
||
)
|
||
if not tools:
|
||
flag("agent 没有配任何工具,通常意味着它该是个 llm 节点", node_id=node.id)
|
||
elif len(tools) > 10:
|
||
flag(
|
||
f"工具白名单有 {len(tools)} 个,边界过宽(白名单衰减的前兆)",
|
||
node_id=node.id,
|
||
)
|
||
elif node.type == NodeType.OUTPUT:
|
||
if cfg.get("contract"):
|
||
has_contract_output = True
|
||
elif node.type == NodeType.CODE:
|
||
if cfg.get("network"):
|
||
flag("代码节点开了网络,受管模板建议把取数收敛到工具节点(有快照)", node_id=node.id)
|
||
|
||
if strict and not has_contract_output:
|
||
result.add(
|
||
"受管模板的 output 节点必须声明出具契约(contract),否则三档出具无从谈起",
|
||
level="error",
|
||
)
|
||
return result
|
||
|
||
|
||
async def unresolved_caliber_upgrades(
|
||
session: AsyncSession, spec: GraphSpec
|
||
) -> tuple[list[str], list[dict[str, Any]]]:
|
||
"""检查钉了版本的子图(方法卡)是否有未处置的新版本。
|
||
|
||
返回 (阻断性错误, 需记录的升版事件)。有新版本但没声明 upgrade_policy
|
||
的直接阻断 formal 启动 —— 这是"被迫处置",不是提醒。
|
||
"""
|
||
from app.db.models import WorkflowVersion
|
||
|
||
errors: list[str] = []
|
||
events: list[dict[str, Any]] = []
|
||
for node in spec.nodes:
|
||
if node.type != NodeType.SUBGRAPH:
|
||
continue
|
||
pinned = node.config.get("workflow_version")
|
||
wf_id = node.config.get("workflow_id")
|
||
if not pinned or not wf_id:
|
||
continue
|
||
latest = (
|
||
await session.execute(
|
||
select(func.max(WorkflowVersion.version)).where(
|
||
WorkflowVersion.workflow_id == wf_id
|
||
)
|
||
)
|
||
).scalar()
|
||
if latest is None or latest <= int(pinned):
|
||
continue
|
||
policy = node.config.get("upgrade_policy")
|
||
if policy not in UPGRADE_POLICIES:
|
||
errors.append(
|
||
f"节点 {node.title}({node.id})钉在方法卡 v{pinned},但已有 v{latest}。"
|
||
f"formal 运行前必须声明 upgrade_policy:"
|
||
+ " / ".join(f"{k}({v})" for k, v in UPGRADE_POLICY_LABELS.items())
|
||
)
|
||
else:
|
||
events.append(
|
||
{
|
||
"node_id": node.id,
|
||
"workflow_id": wf_id,
|
||
"pinned": int(pinned),
|
||
"latest": int(latest),
|
||
"policy": policy,
|
||
"policy_label": UPGRADE_POLICY_LABELS[policy],
|
||
}
|
||
)
|
||
return errors, events
|