Files
ThearchyHelios 601b84918d 分析能力入口(二):五个 agent 分析工具(analysis_decompose / baseline / trend / differ / comparable,名字取 contract.TOOL_NAMES),参数只有「算什么」(引用本节点查过的结果 Q3、列的角色、期次、总数 headline、下钻 parent、度量叫法、在验证什么),没有阈值参数;五个工具共用一份「分析方法」说明(ToolSpec 新增 guide:方法说明 + 去重用的 id,register / build_tools 透传进工具的 metadata),Agent 节点、协作成员绑了任一分析工具就接在 Skill 后面注入 system,同一份只注入一次,模型调用节点不注入。Agent 建工具时经 ToolContext.extra 交一个本节点的分析容器(app/tools/analysis.py 的 AnalysisScope),每次查询记台账时往里放一份输入快照(编号和「【证据 Qn】」同一个),分析结论落成 analysis_result 工件、台账记 contract.ledger_entry(带 call_id),交回模型「【分析 An】」加判定语、判定标准、关键数、附带说明、算不了的原因、结果表前几段;tool.end 带 analysis_artifact;下钻按 An 取本节点的结论;调用写错(contract.CallError,含这个节点的分析次数用完 max_calls)作为工具错误交回、不记台账、不占编号;算不了照样记结论并发一条 analysis_refused。编号只在记台账时放进容器,审批恢复重放时分析不再执行、编号不错位。遮罩列按证据面板同一个函数认(数据源设的、按 SQL 认出改了名字的),遮罩运行交给算子的是遮过的快照。阈值读发起时的快照,没有就现读设置;运行期依赖给运行当天、按运行目录视图读的目录、剖析事实、现查表范围的探查(W1-a 的 make_prober,还没有时不给)。协作成员同样注入方法、同样编号(协作节点不记台账)。使用者问数据的白名单加上五个分析工具和分析卡;调用工具节点调分析工具在画布上就报错(analysis.tool_node)。能力登记不把它们当查询(is_platform_query 不认)
2026-10-21 13:02:30 +08:00

102 lines
7.7 KiB
Python
Raw Permalink 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 取值、知识库检索、
记忆读取、分析)。分析工具不查库,只读本节点查过、在工具边界遮过的结果,遮罩列不当维度、度量(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"]