## 新入库(`docs/演示用/`)
- `代码库全面审查报告-2026-09-14.md`
- `代码修改方案-2026-09-14.md`
- `记忆系统排查报告-2026-09-14.md`
- `记忆系统修复文档-2026-09-14.md`
- `文档一致性审计报告-2026-09-14.md`
- `多Worker接入方案-2026-09-14.md`
## 全量校对(32 个既有文档 + `AGENTS.md`)
跨 39 个文件、**1125 insertions / 148 deletions**。
⚠️ **这批改动同样不是本次会话写的**。我抽样核对过性质:是**实质内容补充**而不是
格式/换行转换。例如 `docs/44-演示流程.md` 新增两条"2026-09-14 补注":
- `启动金融Agent平台.bat` 只在**桌面**上,仓库里只有 `启动平台.bat` 这一份
(两份由同一个 `tools/make_launcher_bat.py` 产出,改完 `start.ps1` 重跑它一起更新);
- `advisor_t`(9020) 与 `offsite_t`(9006) **不在 `tools/seed_test_rbac.py` 的演示用户里**
(那里只有 `cust_t`/`risk_t`/`admin_t`/`review_t` 四个),由 `grant_*.py` 系列创建,
**重跑种子不会重建它们** —— 换机器时这两个账号登录失败,要先查 `sys_user` 有没有这两行,
而不是查密码。
这两条都是对的地方,与我这一路踩到的现象一致(我确实用到了 `advisor_t`/`offsite_t`)。
**我没有逐字审阅全部 39 个文件**,只抽样确认了改动性质与规模。若其中有需要复核的段落,
请指明文件,我逐处核对。
13 KiB
讨论记录:范围重定位、画像与外部数据源(2026-09-10 第四次会话)
🗂 过程产物 · 结论已归档(2026-09-14 批注) 本文是需求澄清期的讨论记录,其中的决策已被实现(含画像投影链路,见
docs/23/docs/37/docs/38)。 判断当前进度请看docs/验收与审计/phase1-acceptance-report.md,不要用本文。 保留本文是为了留存"当时为什么这样定范围"的原始讨论。
参与:用户(袁聪)+ 接手 AI 性质:需求澄清与范围修正记录,不是设计文档。设计文档见
docs/superpowers/plans/2026-09-10-客服Agent与RAG实施计划-qyqy版.md。 按用户要求生成,用于固化本轮讨论中的事实、决策与待办。
0. 本轮最重要的认知修正:项目范围比我原先理解的大
我接手时的理解(错误):本项目 = 智能客服 Agent。 实际范围:用户负责整个用户端前端,智能客服只是其中一个模块,同层级还有 仪表盘、个人信息、产品推荐等。其他同组同事分别负责投顾端、风控端,各自做自己的 Agent。
这个误解的来源(如实记录):我接手时的全部输入——docs/superpowers/handoff/*、两份 spec、
胜宇的 智能客服Agent专项设计方案.html——全都是客服 Agent 的。没有任何材料提到用户端其他模块。
所以我按"只做客服 + RAG"开工,是依据既有材料的合理推断,不是凭空缩小。
影响:客服 Agent 不是白做,它是用户端的一个模块,且其产出(安全路由、合规话术、RAG 知识库) 会被用户端其他模块复用;但它只是拼图的一块,不能当作整个项目的完成标准。
1. 本轮确定的范围与顺序
| # | 决策 | 用户原话/依据 |
|---|---|---|
| 1 | 用户负责范围 = 用户端(含画像、仪表盘、个人信息、产品推荐、智能客服);投顾端/风控端由其他同事各自负责 | 「用户端是我分配的任务,我还有其他同组的同事负责投顾端,风控端,他们的 agent 都是自己负责」 |
| 2 | 画像字段与采集规则按底座做(不采用老师需求文档里的四维加权打分版) | 「至于用户画像的字段和采集规则就按照底座来做」 |
| 3 | 画像虚拟数据先做 5 条 | 「画像数据先做5条吧」 |
| 4 | 执行顺序:客服一期 → 画像 → 行情落库(落库之后再讨论) | 「先收尾客服一期 → 再做画像,落库之后再讨论吧」 |
| 5 | 与胜宇独立竞标,不合并 | 早前会话已定 |
1.1 画像采用"底座版"的技术理由(补充说明)
底座版与老师版字段不兼容:
| 老师需求文档要求 | 底座现状 | 差异 |
|---|---|---|
risk_level(保守型/稳健型/平衡型/进取型/激进型) |
investor_type(C1-C5) |
等价,命名口径不同 |
risk_score(0-100) |
❌ 无 | 底座 behavior_score 是行为异常分,不是风险分 |
investment_experience(0-1年/1-3年…) |
❌ 无 | |
annual_income_range |
❌ 无 | |
asset_allocation(资产配置偏好 JSON) |
❌ 无(有 preferred_asset_class,口径不同) |
|
product_preference(产品偏好 JSON) |
❌ 无 | |
confidence_score(画像置信度 0-1) |
❌ 表级无(user_facts/memory_unit 有行级 confidence) |
部分 |
项目铁律禁止改动已有字段的类型、可空性与业务含义(AGENTS.md 第 4 条),
所以采用老师版意味着只能在底座表上加字段或建新表,会牵动数据库基线与迁移。
用户选择按底座做,避免了这一改动。
2. 画像读取:底座实际已有的机制(本轮查清)
2.1 HTTP 端点早就存在(我第一次回答时说"没有"是错的,此处纠正)
docs/05-接口文档.md 权威列出两个画像端点:
| 编号 | 端点 | 权限 | 文档要求(L614 原文摘要) |
|---|---|---|---|
| M001 | GET /api/v1/users/me/memory-profile |
memory:read:self |
「客户只能查询自己的画像;投顾、客服或授权员工必须通过客户归属和数据范围校验。返回经过字段策略过滤的当前画像、有效期、来源摘要和版本,不返回模型原始推理、Prompt、其他客户信息或未解决冲突的内部详情」 |
| M002 | GET /api/v1/customers/{customer_id}/memory-profile |
memory:read:customer |
同上,需客户归属校验 |
实现已存在(app/service/public_platform_service.py 的 memory()),包含:
权限校验(AuthorizationService.require)→ 数据范围校验(permission_scopes + context.customer_ids)
→ 审计留痕(写 interaction_audit,action_type="memory.profile_read")。
RBAC 也已种好:customer 角色含 memory:read:self;risk_operator 含 memory:read:customer。
2.2 但端点返回的 profile 是空的(关键实现落差)
# app/service/public_platform_service.py:278-284
# Safe minimum projection; arbitrary snapshot JSON needs field-policy expansion.
data = {
"customer_id": str(customer_id),
"version": ...,
"generated_at": ...,
"profile": {}, # ← 永远返回空字典
}
代码注释自己承认:"任意 snapshot JSON 需要字段策略扩展"——这是个未完成的 TODO。
2.3 数据流(底座设计意图)
fin_risk_assessment(风评问卷)──┐
├─→ fin_customer_profile(画像主表,16 字段)
fin_holding / fin_transaction ────┘ │
(资产/交易汇总) │ 生成"画像版本投影"
▼
profile_snapshots(当前投影,含 version/hash)
│
▼
GET /users/me/memory-profile → profile: {...} ← 这里是空的
中间缺一环:底座规划了 profile_snapshots 表与"新画像和两条同步事件必须在同一 MySQL 事务中写入、
旧画像 is_current 同时置 0"的规则(docs/00 §6.4.6),但没有生成器。
2.4 画像相关的表全部为 0 行(本轮实测)
| 表 | 行数 | 用途 |
|---|---|---|
fin_customer_profile |
0 | 画像主表(16 字段) |
fin_risk_assessment |
0 | 风险测评历史 |
user_facts |
0 | 事实候选(带 confidence) |
memory_unit |
0 | 中期记忆单元 |
profile_snapshots |
0 | 画像版本投影 |
2.5 造虚拟数据时的两个技术坑(已实测)
profile_snapshots.current_customer_id不是生成列(extra为空),是带唯一键uk_profile_snapshot_current的普通列;docs/00描述它"当is_current=1时等于客户 ID, 否则 NULL"。所以造数据时必须显式写入,且每客户只能有一条is_current=1。 → 这与 Task 1 遇到的agent_reply_template.active_key(那个是真生成列)不同,别混淆。uk_profile_snapshot_version (customer_id, version)要求版本号按客户唯一。
3. 为什么 Agent 不能直接读库(用户提问,本轮答清)
3.1 铁律原文
AGENTS.md第 7 条:业务 Agent 必须由AgentFactory创建,不得绕过公共鉴权、记忆、模型路由、 工具、合规、审计和事件流程docs/14-Agent组员统一接入说明书.mdL262:禁止直接创建 SQLAlchemy Session、访问 MySQL、 Redis、Milvus 或 Neo4j Driverdocs/16-Agent组员入门易懂版说明.mdL383:不要直接访问 MySQL、Redis、Milvus、Neo4j
3.2 设计意图:所有数据访问收口到工具层,否则会绕过四件事
| 若 Agent 自己查库,会绕过 | 底座对应的已有机制 |
|---|---|
| 鉴权(谁能读谁的数据) | ToolDefinition.allowed_roles + required_permission + context.permissions |
| 数据范围(防越权看别人) | 服务层 _assert_customer_scope(非管理员只能查自己/自己名下客户) |
| 审计(谁何时读了什么) | ToolExecutor._audit() 写 interaction_audit;适当性的"拒绝"也留痕 |
| 来源引用(回答可溯源) | ToolExecutor 自动生成 SourceReference;governance 校验引用必须来自本次已授权召回 |
3.3 技术强制点(不是靠自觉)
BaseAgent.__init_subclass__禁止子类覆盖call_tool——Agent 拿不到绕过工具的入口ToolRegistry是闭合清单,只有bootstrap.py注册过的工具才存在;未注册调用直接ForbiddenAgentErrortests/unit/api/test_architecture.py用 AST 检查 Controller 不得 importapp.model/app.repositoryToolRegistry.register强制read_only=True
3.4 ⚠️ 一个如实指出的缺口
上述架构测试只检查 Controller,不检查 Agent。也就是说 Agent 直接建 Session 在技术上没有被自动拦截,
依赖的是 code review 与铁律。建议后续补一个针对 Agent 的架构测试(AST 检查
app/service/agent/** 不得 import app.repository/app.infrastructure.db)。
4. 外部数据源:东方财富(用户提问,本轮查清)
4.1 用户的记忆是对的
develop 分支上有 hq.py,抓的正是东方财富(天天基金):
NAV_API = "https://api.fund.eastmoney.com/f10/lsjz" # 历史净值
RETURN_API = "https://api.fund.eastmoney.com/pinzhong/LJSYLZS" # 历史收益率
QUOTE_API = "https://push2.eastmoney.com/api/qt/ulist.np/get" # 实时行情
DETAIL_API = "https://fund.eastmoney.com/pingzhongdata/{code}.js" # 基金详情
HEADERS = {"User-Agent": "Mozilla/5.0", "Referer": "https://fund.eastmoney.com/"}
抓取对象是南方基金旗下一批基金(SOUTHERN_FUND_CODES),覆盖货币/债券/混合/股票四类。
4.2 这批能力在新分支的遭遇
develop 上的文件 |
qyqy_develop(当前分支) |
说明 |
|---|---|---|
hq.py(东方财富抓取) |
已删除,但能力保留 | 重构为 app/infrastructure/fund_market_adapter.py(121 行),同一数据源,改为 Protocol + async 适配器 |
nl2sql_yc.py(NL2SQL 引擎) |
完全删除 | 无替代 |
app/service/financial_nl2sql_service.py |
完全删除 | 无替代 |
app/core/nl2sql_catalog.py、nl2sql_contracts.py |
完全删除 | 无替代 |
tests/.../test_financial_nl2sql_*.py(4 个) |
删除 | |
docs/15-金融NL2SQL工具接入说明.md |
删除 | |
| 场外基金 4 个适配器(邮件/OCR/SMTP/NL2SQL) | 删除 |
结论:负责 NL2SQL 的组员的成果活在 develop 分支上,新分支没有。
那位同事面临与本项目相同的抉择:切到新底座重做,还是留在旧底座。
4.3 ⚠️ 行情数据当前不落库(本轮发现,待讨论)
现库实测:fin_market_price = 0 行、fin_nav_history = 0 行。
而 FundQuoteService 的做法是:每次查询实时调东方财富 → 合并 → 写 Redis 缓存
(盘中 60 秒 / 收盘 900 秒),不写 MySQL。
影响:
- 行情与净值无历史留存,仅存活于 Redis 缓存(TTL 外即消失)
- 用户端仪表盘的收益走势图缺本地历史数据
- 组员的 NL2SQL 若要查行情,那两张表是空的
- 风控端算"资产偏离画像"也需要历史
用户决定:落库之后再讨论(本轮不展开)。
5. 本轮其他已确认事项
| 事项 | 状态 |
|---|---|
| 客服 Agent 一期(Task 1-9 计划) | 继续执行;Task 1 已完成并通过两轮审查 |
| 计划新增两个 Task 2 必改项 | ① 治理层 applicable_agents 空值 fail-open 缺陷;② 话术替换后不得被负面词再过滤一次(防"自绊"循环) |
| 子项目 C(游客版客服) | 已写入计划,一期之后再做(需动身份认证,属高风险区) |
| 画像虚拟数据 5 条 | 待做(顺序:客服一期之后) |
| 行情落库 | 待讨论 |
6. 待办清单(按已定顺序)
- 客服 Agent 一期:Task 2-9(Task 1 ✅ 已完成)
- 画像模块:
- 造 5 条虚拟数据(写
fin_customer_profile+fin_risk_assessment+profile_snapshots三张表) - 补
memory-profile端点的profile字段投影(把{}换成真实字段) - 新增
query_customer_profile只读工具(照搬SuitabilityService的鉴权+范围+失败关闭范式)
- 造 5 条虚拟数据(写
- 行情落库:待与用户讨论后再定方案
- (建议)补 Agent 层架构测试:AST 检查
app/service/agent/**不得 import Repository/Session
7. 本轮我犯的两个错误(如实记录,供后续核对)
- 说"API 层无画像接口"——错的。
docs/05权威文档里 M001/M002 两个端点早已列出, 实现也已存在。我漏查了docs/05就直接下结论。 - 说"底座没提及画像读取"——同样是错的,同上。
两处均已在本文件 §2.1 纠正。教训:先查权威文档(docs/05 接口文档、docs/00 基线设计)
再下"不存在"的结论。