模块 1 · 理财师视角

代理人助手:
聊名下客户,不外发草稿

理财师(如 STAFF-10086)登录后打开「代理人助手」,通过 X-Agent-Type: advisor 进入顾问线。只能讨论名下已绑定的客户,生成的沟通草稿不会自动发给客户。

三条和 customer 线的硬区别

编排入口

走 顾问通用 tool→llm→guard 编排(agent_service.py)顾问通用 tool→llm→guard 编排,不是 登录客户 14 节点编排(customer_service.py)登录客户 14 节点编排。

客户范围

会话可绑定某个 customer_id;查数据前过 G-01 归属——必须是 customer_advisor_rel 里 active 的名下客户。

能力边界

System prompt 固化:可查持仓/流水/风评、搜产品知识库;草稿不外发、不做投资建议承诺。

Demo 账号: STAFF-10086 理财师 · 名下示例客户 CUST-9527。改功能时先确认会话里绑的是哪位客户,再测归属拒绝场景。

请求长什么样

前端 · chat.ts
POST /api/chat/stream
Authorization: Bearer <jwt>
"X-Agent-Type": "advisor"
{ "message": "客户持仓如何", "customer_id": "CUST-9527" }
白话

和对话 API 一样要带 JWT,但 Agent 类型填 advisor,不是 customer。

body 里可指定要服务的客户号;后端会校验这位客户是否在该理财师名下。

缺 X-Agent-Type 直接 401,和业务代码无关。

顾问线能调哪些 Tool(关键词意图)

query_holdings持仓 / 持有 / 仓位
query_recent_trades流水 / 交易记录
query_customer_profile风险测评 / 风险等级
search_knowledge基金 / 申购 / 费率 / 产品手册

理财师打开「代理人助手」对话,编排跑在哪?

模块 2 · 三张牌编排

tool → llm → guard
群聊走一遍

顾问线 LangGraph 只有三个业务节点(加 START/END): tool → llm → guard。比客户 14 节点图短,但归属校验在 对话 Tool 编排(tool_service.py)run_tool 里,和客户线铁律口径一致。

节点职责(背下来少踩坑)

tool_node

match_intent("advisor", message) 命中关键词 → 对话 Tool 编排(tool_service.py)run_tool 查 Core / 知识库 → 结果写入 tool_results。

llm_node

System prompt(顾问边界)+ Tool 摘要注入 + 历史窗口 + 本轮用户话 → DeepSeek 组织自然语言。

guard_node

顾问线不附客户/风控那种尾部免责声明(needs_disclaimer("advisor") 为 false)。

群聊:理财师问「客户 CUST-9527 持仓如何」

简化播放 tool → llm → guard 全链路(前面 四 Agent 对话 HTTP 入口(chat.py)已验 JWT + G-01)。

CODE · 顾问通用 tool→llm→guard 编排(agent_service.py)tool_node
tool_name = tool_service.match_intent(
    state["agent_type"], state["user_message"]
)
record = tool_service.run_tool(
    tool_name=tool_name,
    agent_type=state["agent_type"],
    customer_id=state.get("customer_id") or "",
    ...
)
白话

先靠关键词判断要不要查库;闲聊不命中就 tool_results 为空,LLM 纯聊。

customer_id 来自会话绑定(四 Agent 对话 HTTP 入口(chat.py)归属校验后的值),不是 LLM 生成的。

query_holdings 和 customer 线共用 Core 只读 Tool 定义(core_tools.py)定义,但跑在 顾问通用 tool→llm→guard 编排(agent_service.py)图里。

理财师问「这位客户仓位怎么样」,tool_node 如何选中 query_holdings?

模块 3 · 归属铁闸

G-01 归属校验 +
run_tool 留痕

理财师能查谁的数据,由 G-01 归属校验 决定。API 层越权抛 403;对话 Tool 层(对话 Tool 编排(tool_service.py))拒绝则落 blocked 行留痕,不抛异常挡整条链路。

两层校验,别混口径

层级位置拒绝时
API(四 Agent 对话 HTTP 入口(chat.py))assert_customer_accessHTTP 403 + 鉴权审计
Tool(对话 Tool 编排(tool_service.py))run_tool 内归属检查status='blocked' + agent_tool_call 留痕
advisor 规则: 角色含 advisor 时,customer_advisor_rel 里该客户与当前 actor_id 须为 active。查非名下客户 → API 403 或 Tool blocked(AUTH_403_NOT_ASSIGNED)。
CODE · 对话 Tool 编排(tool_service.py)run_tool(节选)
def run_tool(
    tool_name, agent_type, actor, customer_id,
    tool_input, session_id, trace_id=None
) -> dict:
    # 1. 归属校验(对齐 模块鉴权工厂(deps.py)assert_customer_access)
    denial = _check_access(agent_type, actor, customer_id)
    if denial:
        return _blocked_record(denial, ...)
    # 2. 执行 Core 只读 Tool 定义(core_tools.py)func
    data = spec["func"](customer_id, ...)
    # 3. agent_tool_call 落库(失败降级 warning)
    _persist_tool_call(status="success", ...)
白话

run_tool 是顾问线查数据的唯一编排入口:先验你有没有资格看这个 customer_id。

blocked 不是 403——对话里会呈现「查询被拒绝」,同时 MySQL 留一行 tool 调用记录。

customer_id 参数由 四 Agent 对话 HTTP 入口(chat.py)会话注入;Tool 不信 LLM 或用户消息里随便写的客户号。

相关文件

app/api/
模块鉴权工厂(deps.py)模块鉴权工厂 — app/api/deps.py · assert_customer_access(G-01 权威口径)
四 Agent 对话 HTTP 入口(chat.py)四 Agent 对话 HTTP 入口 — app/api/chat.py · 会话绑定 customer_id + 入口归属
app/service/
对话 Tool 编排(tool_service.py)Tool 编排 — match_intent + run_tool
顾问通用 tool→llm→guard 编排(agent_service.py)顾问通用 tool→llm→guard 编排 — tool_node 调用 run_tool
app/tool/
Core 只读 Tool 定义(core_tools.py)Core 只读 Tool 定义 — query_holdings 等

理财师在对话里查非名下客户,对话 Tool 编排(tool_service.py)会怎么做?

模块 4 · 未做与误区

T-20 还没做
Neo4j 也不是对话 Tool

顾问线 chat 骨架已通,但草稿复核工作台、合规巡检、Neo4j 实时查询都还没进对话链路。 课程中心卡片写 Neo4j,容易让 AI 去改不存在的 Cypher 节点——这模块专门纠偏。

T-20 未实现清单(2026-09 快照)

A-03 话术草稿

无 advisor_draft 表流程 · 无复核 UI · prompt 里「草稿不外发」≠ 有工作台。

A-05 合规巡检

合规专员抽检全量顾问会话 — 无前端/API。

A-06 跟进日志

确认后回写 L2 — 对话线未入库跟进草稿。

已有

顾问通用 tool→llm→guard 编排(agent_service.py)tool→llm→guard · G-01 归属 · search_knowledge(kb_product_rules)。

prompt 禁外发 ≠ 系统禁外发: 代码里没有「发给客户」API,但也没有强制 review_status 门禁——上线前必须补 T-20。

Neo4j:同步脚本,不是 LangGraph 节点

实际存在 · Neo4j 关系图离线同步(sync_neo4j.py)/ 顾问归属离线同步(sync_advisor_rel.py)离线同步脚本
python scripts/sync/sync_neo4j.py
python scripts/sync/sync_advisor_rel.py
# Neo4j 关系图离线同步(sync_neo4j.py)/ 顾问归属离线同步(sync_advisor_rel.py)离线同步脚本
白话

顾问对话不在运行时发 Cypher;名下客户靠 G-01 + Core 只读 Tool(core_ro_tool.py)。

指挥 AI「加 Neo4j Tool 查客户关系」= 新需求,不是修现有 bug。

Neo4j 数据来自 Neo4j 关系图离线同步(sync_neo4j.py)/ 顾问归属离线同步(sync_advisor_rel.py)batch sync,和 Chat 会话无实时联动。

群聊:顾问想「一键发给客户」

顾问 Chat 查「我名下有哪些客户」,代码走哪?