Files
group_fqcd_jr/app/service/agent/implementations/platform_probe.py
T
张胜宇 e239eb778b docs: 品牌全量口径统一为「南方基金」+ 作废文档清理
1) 客服 Agent 四份交付文档 + 构建脚手架:品牌由包装占位 XX科技 / 旧名 南方财富
   统一为南方基金(热线 400-889-8899 / 官网 nffund.com),系统名改为「智能服务系统」;
   同步追加 §0.4 修订记录行,工程记录行保留原占位字面以支撑硬编码扫描验收。
2) 开发文档:清理 28 份已作废/残留文档(14 份移出归档 + 14 份仓库副本),
   新增《文档规整方案与开发前待决事项-2026-09-17》。
3) 客服agent 四份交付文档首次纳入本分支。
2026-09-17 15:15:22 +08:00

80 lines
3.5 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.
"""基座验证探针:端到端触发 `ToolExecutor` 的四种拒绝分支。
**为什么专门做一个 Agent**:`ToolExecutor` 的四种拒绝(意图未配置工具白名单 / 工具不在
白名单 / 缺权限 / 角色不符)在真实链路上很难**安全**触发 —— 要么去改客服、风控的生效配置,
要么去动 RBAC,两条路都会影响正在工作的 Agent。用一个只读、无副作用的探针把这件事隔离出来。
**它不碰任何业务数据**:唯一的工具只回显调用方给的参数,不查库、不调模型、不写状态。
角色限定为 `admin`,意图只有 `probe` 一个。
用法见 `tools/verify_tool_executor_denials.py`:它按顺序调整探针的意图配置与工具白名单,
分别触发四种拒绝,最后把配置恢复到验证前的状态。
"""
from typing import Any
from pydantic import BaseModel, ConfigDict
from app.core.contracts import (
AgentDefinition,
AgentRequest,
CoreResult,
RequestContext,
)
from app.service.agent.base import BaseAgent
AGENT_TYPE = "platform_probe"
INTENT_PROBE = "probe"
PROBE_TOOL = "probe_echo"
# 第二个探针工具。**存在的唯一理由**:让"工具不在白名单"这一分支可被触发。
# `governance.resolve` 取的是「发布白名单 ∩ 代码声明的 allowed_tools」,配置**只能缩小
# 不能放大**;所以 Agent 只有一个工具时,白名单要么为空、要么正好包含它,
# 永远构造不出"有白名单、但不含被调用的那个工具"的场景——这是我做端到端验证时撞到的。
PROBE_ALT_TOOL = "probe_alt"
PROBE_PERMISSION = "probe:read"
class ProbeEchoArgs(BaseModel):
"""严格入参:探针不接收自由文本,避免被当成通用执行入口。"""
model_config = ConfigDict(extra="forbid", frozen=True)
note: str = ""
async def probe_echo_tool(arguments: BaseModel, context: RequestContext) -> dict[str, Any]:
"""回显参数。**只读且无副作用** —— 不查库、不写状态、不调外部服务。"""
note = getattr(arguments, "note", "")
return {"echo": note, "trace_id": context.trace_id}
async def probe_alt_tool(arguments: BaseModel, context: RequestContext) -> dict[str, Any]:
"""备用探针工具:只为让"工具不在白名单"的分支可被构造,与 probe_echo 同样只读。"""
return {"alt": True, "trace_id": context.trace_id}
class PlatformProbeAgent(BaseAgent):
"""基座探针:只用于验证工具链路的拒绝行为,不承载任何业务。"""
definition = AgentDefinition(
agent_type=AGENT_TYPE,
version="1.0.0",
allowed_roles=("admin",),
allowed_portals=("api",),
allowed_tools=(PROBE_TOOL, PROBE_ALT_TOOL),
supported_intents=(INTENT_PROBE,),
)
async def handle(self, request: AgentRequest, context: RequestContext) -> CoreResult:
# 按消息里的 "alt" 决定调哪个探针工具。两个工具都必须可被指定调用:
# 分支 2(工具不在白名单)要调白名单之外的那个,分支 4(角色不符)要调角色更窄的
# 那个——硬编码其中一个的话,这两个分支会互相干扰(实测踩到过)。
tool = PROBE_ALT_TOOL if "alt" in request.message.lower() else PROBE_TOOL
output = await self.call_tool(
tool,
{"note": request.message[:50]},
intent=INTENT_PROBE,
context=context,
)
return CoreResult(text=f"probe ok: {output}", intent=self._classified_intent)