Files
AgentLab/backend/app/auth/chat_policy.py
T

82 lines
5.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""开登录后 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"]