mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 01:11:59 +00:00
102 lines
7.7 KiB
Python
102 lines
7.7 KiB
Python
"""开登录后 user 角色的问数据运行能用什么(D-c,§3.12「问数据、助手」那一行,Q2)。
|
||
|
||
管理员开了「使用者可以问数据」之后,user 角色经 POST /api/runs 发起的只能是问数据当场搭的探索运行(api/runs.py 的
|
||
start_run 先查来源和运行类别)。图本身再按这里收窄,理由是同一条:对 user 角色来说遮罩就是边界(A7 第 6 条),它能
|
||
碰的只有「读数据、整理、撰写」这一类,不能执行代码、不能调外部系统、不能写任何东西:
|
||
|
||
- 节点只许:输入、模型、Agent、报告、输出、知识库检索、数据整形、条件分支、口径卡、合并查询、分析卡、调用工具。代码、协作、
|
||
子工作流、循环、人工审批、记忆写入、校验、双盲复算、反方审稿一律不许——后几种不是危险,而是问数据用不到,多放一种
|
||
就多一处要单独核对遮罩的地方;
|
||
- 工具(Agent 绑的、调用工具节点调的同一份白名单,engine/launch.tool_bindings 一并列出)只许数据源工具(db_query__ /
|
||
db_schema__,查询结果按 MASK_RESULTS_OPTION 在工具边界遮)和只读的内置工具(计算、当前时间、JSON 取值、知识库检索、
|
||
记忆读取、分析)。分析工具不查库,只读本节点查过、在工具边界遮过的结果,遮罩列不当维度、度量(app/tools/analysis.py);
|
||
分析卡读上游查询节点的快照,同一套遮罩。自定义工具、MCP、文件、网页、HTTP、代码执行、平台的写入能力(records_append
|
||
这类)一律不许;模板拼出来的工具名({{ vars.tool }})发起时不知道是谁,也不许。调用工具节点放行的理由:它和 Agent 调同一个查询工具等价——同一个
|
||
ToolContext 带着遮罩、只读两个开关(engine/nodes/tools.py 的 _tool_ctx),参数由人写死反而比模型挑的更好核对;
|
||
周期看板、异常处置这些模板的取数节点就是它(个人应用和问数据同一份白名单,blueprints/personal.policy_problem);
|
||
- 数据源查询一律只读:可写源上的写语句由守卫拒掉,不交给审批(api/runs.py、个人应用刷新、个人蓝图试运行给运行打上
|
||
READ_ONLY_OPTION)——Agent、调用工具节点的 approval 是发起人自己写在图里的,写成 never 就不问人了。能力治理里改成
|
||
「写入」的工具在只读运行里同样不执行(调用工具节点在走动作流程之前拒,Agent 建工具时拒);
|
||
- 记忆范围、知识库不由发起人挑:记忆和知识库里的内容不经遮罩,搭建者的 Agent 存进去的查询原文,换个范围就读到了。
|
||
记忆固定为这位使用者自己的范围(chat_memory_scope;问数据里写不了记忆,它读到的只会是空的),知识库固定为设置里的
|
||
运行默认值。节点上、图级 defaults 里、调用工具节点的参数里写了 memory_scope / collection 的图不许发起,请求体里带的
|
||
同样拒(api/runs.py)。这只是发起前的一道:根子在运行时——遮罩、只读的运行里范围由运行定,节点配置改不了,kb_search
|
||
的 collection 参数(参数名纠错改出来的、模型挑的)一律拒(tools/registry.ToolContext.fixed_scope)。
|
||
|
||
没在这里列出的节点类型、工具,以后加了也默认不许(白名单),要放开得先想清楚遮罩怎么守。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from typing import Any
|
||
|
||
from app.analysis.contract import TOOL_NAMES
|
||
|
||
#: 问数据运行里 user 角色能用的节点类型(engine/schema.py 的 NodeType 取值)
|
||
CHAT_NODE_TYPES = frozenset({"input", "llm", "agent", "report", "output", "retrieve", "transform", "branch",
|
||
"metrics", "merge", "analysis", "tool"})
|
||
#: Agent 能绑的只读内置工具(tools/builtin/):五个分析工具也在内(contract.TOOL_NAMES,不查库)
|
||
CHAT_BUILTIN_TOOLS = frozenset({"calculator", "current_time", "json_query", "kb_search", "memory_recall",
|
||
*TOOL_NAMES.values()})
|
||
#: 运行时改记忆范围、知识库的配置键(engine/nodes 的 ctx.cfg("memory_scope") / ctx.cfg("collection"),图级 defaults 同样生效)
|
||
SCOPE_KEYS = ("memory_scope", "collection")
|
||
SCOPE_REFUSED = "没有权限:使用者在问数据中不能指定记忆范围和知识库,使用的是管理员设好的默认值"
|
||
|
||
|
||
def chat_memory_scope(actor: str | None) -> str:
|
||
"""user 角色问数据运行的记忆范围:每人一份,和搭建者的不相通。"""
|
||
return f"chat:{actor or 'anonymous'}"
|
||
|
||
|
||
def _tool_allowed(name: str) -> bool:
|
||
from app.tools.datasource import source_of_tool
|
||
|
||
return name in CHAT_BUILTIN_TOOLS or bool(source_of_tool(name))
|
||
|
||
|
||
def _set(value: Any) -> bool:
|
||
"""配置里填了值。和运行时 NodeContext.cfg 同一个规则:只有 None、空串算没填——全是空白的串运行时也照用。"""
|
||
return value not in (None, "")
|
||
|
||
|
||
def chat_graph_problem(graph: Any, *, where: str = "问数据") -> str | None:
|
||
"""这张图 user 角色能不能在问数据里运行:能用返回 None,不能用交回一句给人看的原因(403 的 detail)。
|
||
|
||
图不成形(GraphSpec 校验不过)时也返回 None:交给 launch_run 照常报校验问题——两边用的是同一个 GraphSpec,过不了
|
||
校验的图本来就发起不了。
|
||
|
||
where 是说法里的「在哪」:个人应用(blueprints/personal.py)用的是同一份白名单,只把「问数据」换成「个人应用」。"""
|
||
from app.engine.launch import node_setting, tool_bindings
|
||
from app.engine.schema import GraphSpec, type_label
|
||
|
||
if not isinstance(graph, dict):
|
||
return None
|
||
try:
|
||
spec = GraphSpec.model_validate(graph)
|
||
except ValueError:
|
||
return None
|
||
scope_refused = SCOPE_REFUSED.replace("问数据", where)
|
||
if any(_set(spec.defaults.get(key)) for key in SCOPE_KEYS):
|
||
return scope_refused
|
||
for node in spec.nodes:
|
||
if str(node.type) not in CHAT_NODE_TYPES:
|
||
return (f"没有权限:使用者在{where}中不能使用「{type_label(node.type)}」节点(「{node.title}」)。"
|
||
"如需这类处理,请联系搭建者做成应用")
|
||
if any(_set(node.config.get(key)) for key in SCOPE_KEYS):
|
||
return f"{scope_refused}(「{node.title}」)"
|
||
# 调用工具节点的参数直接交给工具:kb_search 的 collection 这类参数写了,同样是自己挑了范围。取值和运行时同一个规则
|
||
# (node_setting:节点上没写就取图级 defaults 里的 args)。参数名写错、运行时被纠成 collection 的,这里认不出——
|
||
# 运行时那一道由工具自己拒(tools/registry.ToolContext.fixed_scope,kb_search 只能检索运行定下的知识库)
|
||
args = node_setting(spec, node, "args") if str(node.type) == "tool" else None
|
||
if isinstance(args, dict) and any(_set(args.get(key)) for key in SCOPE_KEYS):
|
||
return f"{scope_refused}(「{node.title}」)"
|
||
for who, name in tool_bindings(spec):
|
||
if "{{" in name:
|
||
return f"没有权限:{who}的工具名由模板拼成,发起前无法确定是哪个工具,使用者在{where}中不能这样绑定工具"
|
||
if not _tool_allowed(name):
|
||
return (f"没有权限:{who}绑定了工具「{name}」。使用者在{where}中只能使用数据源查询和只读的内置工具"
|
||
"(计算、当前时间、JSON 取值、知识库检索、记忆读取、分析)")
|
||
return None
|
||
|
||
|
||
__all__ = ["CHAT_BUILTIN_TOOLS", "CHAT_NODE_TYPES", "SCOPE_REFUSED", "chat_graph_problem", "chat_memory_scope"]
|