Files
AgentLab/backend/app/auth/chat_policy.py
T
ThearchyHelios 7739340607 普通用户建应用集成收尾:
- 问数据和个人应用的图放行绑定只读查询等合规工具的调用工具节点,只读和遮罩在这条路径上生效;被治理成写入的工具在只读运行里直接拒;知识库范围在工具层固定(图级默认值、参数名纠错、Agent 都改不了)
- 个人应用的刷新和个人蓝图的试运行只有拥有者能读:运行、事件、证据、工件、文件、运行与审批列表、对比、可验证报告、质询、假设分析、提取工作流一并收紧;个人蓝图的试运行不论谁发起都按个人应用约束
- 共享蓝图只给拥有者、对应应用的管理者、管理员,看不到的一律 404;审核副本归审核人;管理员看别人的个人应用只有元数据、可以归档
- 记录表跨版本汇总(只读挂载旧版本、按当前列 UNION ALL、各版本各自固定高水位,缺的版本照实写进印章);停用前发起的运行可以继续写旧版本;更正沿用原提交人、更正人记审计
- 约定收口:待办种类 review、事件 file.extracted、表单错误 file、模板定义带示例与预置;前端类型一次补齐、去掉临时的局部类型;审核待办卡不再给打不开的链接;读附件事件有中文说法;检查脚本补断言
2026-10-15 13:55:19 +08:00

98 lines
7.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 绑的、调用工具节点调的同一份白名单,engine/launch.tool_bindings 一并列出)只许数据源工具(db_query__ /
db_schema__,查询结果按 MASK_RESULTS_OPTION 在工具边界遮)和只读的内置工具(计算、当前时间、JSON 取值、知识库检索、
记忆读取)。自定义工具、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
#: 问数据运行里 user 角色能用的节点类型(engine/schema.py 的 NodeType 取值)
CHAT_NODE_TYPES = frozenset({"input", "llm", "agent", "report", "output", "retrieve", "transform", "branch",
"metrics", "merge", "tool"})
#: 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, *, 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"]