mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 04:51:57 +00:00
82 lines
5.3 KiB
Python
82 lines
5.3 KiB
Python
"""开登录后 user 角色的问数据运行能用什么(D-c,§3.12「问数据、助手」那一行,Q2)。
|
||
|
||
管理员开了「使用者可以问数据」之后,user 角色经 POST /api/runs 发起的只能是问数据当场搭的探索运行(api/runs.py 的
|
||
start_run 先查来源和运行类别)。图本身再按这里收窄,理由是同一条:对 user 角色来说遮罩就是边界(A7 第 6 条),它能
|
||
碰的只有「读数据、整理、撰写」这一类,不能执行代码、不能调外部系统、不能写任何东西:
|
||
|
||
- 节点只许:输入、模型、Agent、报告、输出、知识库检索、数据整形、条件分支、口径卡、合并查询。调用工具(会写外部系统)、
|
||
代码、协作、子工作流、循环、人工审批、记忆写入、校验、双盲复算、反方审稿一律不许——后几种不是危险,而是问数据用不到,
|
||
多放一种就多一处要单独核对遮罩的地方;
|
||
- Agent 的工具只许数据源工具(db_query__ / db_schema__,查询结果按 MASK_RESULTS_OPTION 在工具边界遮)和只读的内置工具
|
||
(计算、当前时间、JSON 取值、知识库检索、记忆读取)。自定义工具、MCP、文件、网页、HTTP、代码执行一律不许;模板拼出来的
|
||
工具名({{ vars.tool }})发起时不知道是谁,也不许。数据源查询一律只读:可写源上的写语句由守卫拒掉,不交给审批(api/runs.py
|
||
给运行打上 READ_ONLY_OPTION)——Agent 的 approval 是发起人自己写在图里的,写成 never 就不问人了;
|
||
- 记忆范围、知识库不由发起人挑:记忆和知识库里的内容不经遮罩,搭建者的 Agent 存进去的查询原文,换个范围就读到了。
|
||
记忆固定为这位使用者自己的范围(chat_memory_scope;问数据里写不了记忆,它读到的只会是空的),知识库固定为设置里的
|
||
运行默认值。节点上、图级 defaults 里写了 memory_scope / collection 的图不许发起,请求体里带的同样拒(api/runs.py)。
|
||
|
||
没在这里列出的节点类型、工具,以后加了也默认不许(白名单),要放开得先想清楚遮罩怎么守。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from typing import Any
|
||
|
||
#: 问数据运行里 user 角色能用的节点类型(engine/schema.py 的 NodeType 取值)
|
||
CHAT_NODE_TYPES = frozenset({"input", "llm", "agent", "report", "output", "retrieve", "transform", "branch",
|
||
"metrics", "merge"})
|
||
#: Agent 能绑的只读内置工具(tools/builtin/)
|
||
CHAT_BUILTIN_TOOLS = frozenset({"calculator", "current_time", "json_query", "kb_search", "memory_recall"})
|
||
#: 运行时改记忆范围、知识库的配置键(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) -> str | None:
|
||
"""这张图 user 角色能不能在问数据里运行:能用返回 None,不能用交回一句给人看的原因(403 的 detail)。
|
||
|
||
图不成形(GraphSpec 校验不过)时也返回 None:交给 launch_run 照常报校验问题——两边用的是同一个 GraphSpec,过不了
|
||
校验的图本来就发起不了。"""
|
||
from app.engine.launch import 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
|
||
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"没有权限:使用者在问数据中不能使用「{type_label(node.type)}」节点(「{node.title}」)。"
|
||
"如需这类处理,请联系搭建者做成应用")
|
||
if any(_set(node.config.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}的工具名由模板拼成,发起前无法确定是哪个工具,使用者在问数据中不能这样绑定工具"
|
||
if not _tool_allowed(name):
|
||
return (f"没有权限:{who}绑定了工具「{name}」。使用者在问数据中只能使用数据源查询和只读的内置工具"
|
||
"(计算、当前时间、JSON 取值、知识库检索、记忆读取)")
|
||
return None
|
||
|
||
|
||
__all__ = ["CHAT_BUILTIN_TOOLS", "CHAT_NODE_TYPES", "SCOPE_REFUSED", "chat_graph_problem", "chat_memory_scope"]
|