mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 06:02:01 +00:00
62aac187d8
MCP / 自定义工具的审批三档(tools/trust.py) - 平台看不出这两类工具做什么,以前审批关卡一律当它们安全、照跑不误。现在默认每次调用都等审批; 工具页可以逐个改成「始终允许 · 门控把关」或「始终允许」,审批卡上的「始终允许」等于前者 - 门控把关:每次调用先由门控模型审一遍工具说明、参数和节点任务;判可疑、出错、超时、答不成格式, 一律交还给人,不会变成放行。判定包进 checkpoint,审批恢复重放时不再问一遍 - agent 节点、工具节点、协作团队三处关卡都接上;团队成员停不下来等人,门控没放行的调用不执行 - 每次运行在发起时快照档位(runs.tool_trust),恢复和接着跑沿用同一份;正式运行不看档位,一律等审批; 升级前发起的运行照旧不问——它们可能正停在某个审批上,多问一次答复就会对错号 - 接口:GET /api/tools 带 trust / trust_key,PUT /api/tools/trust;设置新增门控模型;新事件 tool.gated - 界面:工具页三选一、审批卡「始终允许」、设置页门控模型、运行记录解码 tool.gated; 顺手修了 MCP 工具详情 404、工具审批被驳回却显示成放行 智能体护栏(engine/guards.py),取代固定的「最多 12 步」 - 步数只留兜底:默认 100,设置里可改;以前画布自动写进节点的 12 按「没填」处理 - 同一个工具、同样的参数第二次不再执行,把上次的结果交还给它;连续 3 步拿不到新信息就收尾 - 每个节点的令牌预算(默认 200 万)和金额预算(默认不限),都可以在设置里改成不限 - 上下文用到窗口的 60% 先压缩早期的工具结果,到 85% 就收尾;临近任何一个上限先提醒模型一次 - 下游结构化校验失败时写明是上游哪个 agent 提前收了尾;复核信号带上收尾原因, 问数据页只在真是步数用满时才给「放宽步数重跑」 - 上限在发起时快照(runs.agent_limits);升级前发起的运行一字不改走旧逻辑 检查脚本 - check-all 默认 4 道并行:量时间最多的两项互不重叠;并行时没过的最后单独重跑一遍,重跑才过的 单列成「偶发」。16 项全量从约 21 分钟降到约 7 分钟(加上一次重跑约 10 分钟);--lanes 1 回到串行 顺带修的 - 设置接口里的 limits 改为只读:库里存着旧版设置页写进去的一份(max_agent_steps: 25),从不生效, 却盖住了真实上限,问数据页照着错的数许诺 - test_kb 里嵌入模型那条测试不再真连 api.openai.com(代理慢时一条要等近 4 分钟) - check-stream 读滚动容器前先等它挂上,并行跑时不再偶发 验证(沙箱):tsc 干净,生产构建通过,pytest 1655 passed(两遍),check-all 16 项检查、3562 项全过; 新增后端测试 53 条,关键逻辑做了 22 处变异验证、全部抓到。
86 lines
3.3 KiB
Python
86 lines
3.3 KiB
Python
"""人工审批的回复怎么读。全引擎只此一份。
|
|
|
|
以前有四份:human 节点、agent 节点、工具节点、代码节点各读各的,口径不一。
|
|
后果不是风格问题:工具节点和代码节点对字符串一律 bool(),回复"拒绝"、"no"
|
|
都被当成批准,工具真的执行了;而 {"decision": "approve"} 反被判成拒绝。
|
|
|
|
规则只有一条:**只认明确的批准,看不懂的一律按拒绝处理。** 审批这道门放错了
|
|
的代价(危险工具被执行)远大于拦错了的代价(再批一次)。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Any
|
|
|
|
_YES = frozenset({
|
|
"approve", "approved", "yes", "y", "true", "ok", "allow",
|
|
"同意", "通过", "批准", "允许", "可以",
|
|
})
|
|
_NO = frozenset({
|
|
"reject", "rejected", "deny", "denied", "no", "n", "false",
|
|
"拒绝", "驳回", "不同意", "不通过", "不允许", "不可以",
|
|
})
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Decision:
|
|
#: 明确批准了。审批类的关卡(工具、代码、human 的 approve 模式)只认这个。
|
|
approved: bool
|
|
#: 明确拒绝了。填内容类的关卡(human 的 input / edit 模式)只在这时才停:
|
|
#: 那两种模式的回复本身就是内容,没说"不"就是交了稿。
|
|
rejected: bool
|
|
note: str = ""
|
|
#: 人工改过的工具参数
|
|
args: dict[str, Any] | None = None
|
|
#: 人工改过的代码
|
|
code: str | None = None
|
|
#: human 节点收到的内容(input / edit 模式)
|
|
value: Any = None
|
|
#: 审批卡上点的是「始终允许」:批准这次,并且这个工具以后改由门控把关(tools/trust.py)。
|
|
#: 只认明确的 True,而且只跟着批准走
|
|
always: bool = False
|
|
|
|
|
|
def _word(value: Any) -> str:
|
|
return value.strip().lower() if isinstance(value, str) else ""
|
|
|
|
|
|
def _verdict(value: Any) -> bool | None:
|
|
"""True / False / None(没表态)。"""
|
|
if isinstance(value, bool):
|
|
return value
|
|
word = _word(value)
|
|
if word in _YES:
|
|
return True
|
|
if word in _NO:
|
|
return False
|
|
return None
|
|
|
|
|
|
def read_decision(response: Any) -> Decision:
|
|
if isinstance(response, dict):
|
|
verdict = _verdict(response["approved"]) if "approved" in response \
|
|
else _verdict(response.get("decision"))
|
|
args = response.get("args")
|
|
code = response.get("code")
|
|
return Decision(
|
|
approved=verdict is True,
|
|
rejected=verdict is False,
|
|
note=str(response.get("note") or response.get("comment") or ""),
|
|
args=args if isinstance(args, dict) else None,
|
|
code=code if isinstance(code, str) and code else None,
|
|
value=response.get("value"),
|
|
always=verdict is True and response.get("always") is True,
|
|
)
|
|
verdict = _verdict(response)
|
|
# 不是一个是/否词的字符串("拒绝,这个表是生产库")既算不上批准,本身又
|
|
# 是理由——留作备注,agent 转告模型时才不会只剩一句"未说明"
|
|
note = response.strip() if isinstance(response, str) and verdict is None else ""
|
|
return Decision(
|
|
approved=verdict is True,
|
|
rejected=verdict is False,
|
|
note=note,
|
|
# 字符串回复本身就是内容:input 模式下用户可能直接回一句话
|
|
value=response,
|
|
)
|