Files
AgentLab/backend/tests/fixtures/analysis_ops.py
T
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

62 lines
3.6 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.
"""测试用的分析算子替身(第 1 波各包并行:W1-a 的 app.analysis.run 落地前,入口的测试不等它)。
FakeOps 换掉模块上的 app.analysis.run(入口都按模块取 run),按请求交回 app.analysis.testing.fake_result 的假结论,
并记下每次调用拿到的请求、按名字解析到的输入快照、阈值、运行期依赖、上一层结论——测试据此断言入口交进去的东西。
"""
from __future__ import annotations
from typing import Any
import app.analysis as analysis_ops
from app.analysis.testing import fake_result
#: 每个算子缺省给的判定(fake_result 有现成判定语的那几个)
VERDICT = {"decompose": "driver", "baseline": "outside_band", "trend": "rising", "differ": "no_evidence",
"comparable": "comparable"}
class FakeOps:
"""errors:按调用的先后,每次要抛的异常(None 表示照常交回);用完了都照常交回。statuses / verdicts 同理。"""
def __init__(self, monkeypatch, *, errors: list[Any] | None = None, statuses: list[str] | None = None,
verdicts: list[str] | None = None, peek: list[str] | None = None, **overrides: Any) -> None:
self.calls: list[dict[str, Any]] = []
#: 每次调用时看一眼这几个名字能不能按 parents 取到上一层结论(visible)
self.peek = list(peek or [])
self.errors = list(errors or [])
self.statuses = list(statuses or [])
self.verdicts = list(verdicts or [])
self.overrides = overrides
monkeypatch.setattr(analysis_ops, "run", self.run)
async def run(self, request: dict[str, Any], resolve: Any, *, settings: Any, context: Any,
parents: Any = None) -> dict[str, Any]:
params = request.get("params") or {}
names = [params[k] for k in ("query", "base_query", "target_query") if params.get(k)]
snapshots = {name: resolve(name) for name in names}
parent = None
if isinstance(params.get("parent"), dict) and parents is not None:
parent = parents(params["parent"]["of"])
self.calls.append({"request": request, "snapshots": snapshots, "settings": dict(settings), "context": context,
"parent": parent, "parents": parents,
"visible": {n: parents is not None and parents(n) is not None for n in self.peek}})
error = self.errors.pop(0) if self.errors else None
if error is not None:
raise error
status = self.statuses.pop(0) if self.statuses else "ok"
verdict = self.verdicts.pop(0) if self.verdicts else VERDICT[request["op"]]
roles = {"query": "data", "base_query": "base", "target_query": "target"}
inputs = [{"ref": name, "artifact": snap["artifact"], "role": roles[key], "columns": snap["columns"],
"rows": "all"}
for key in ("query", "base_query", "target_query")
if (name := params.get(key)) and (snap := snapshots.get(name)) is not None]
if parent is not None:
from app.core.artifact_store import canonical_json, content_hash
inputs.append({"ref": params["parent"]["of"], "artifact": content_hash(canonical_json(parent)),
"role": "parent"})
return fake_result(op=request["op"], mode=request.get("mode"), verdict=verdict, status=status,
reason_code="history_short" if status == "refused" else None, inputs=inputs,
params=dict(params), settings=dict(settings), hypothesis=request.get("hypothesis"),
**self.overrides)