Files
group_fqcd_jr/docs/superpowers/analysis/2026-09-10-范围重定位与画像讨论记录.md
lzf_0626 36c7a9d8d2 文档:审查报告入库 + 全量校对补注
## 新入库(`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 个文件**,只抽样确认了改动性质与规模。若其中有需要复核的段落,
请指明文件,我逐处核对。
2026-09-14 20:36:00 +08:00

13 KiB
Raw Permalink Blame History

讨论记录:范围重定位、画像与外部数据源(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 造虚拟数据时的两个技术坑(已实测)

  1. 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(那个是真生成列)不同,别混淆。
  2. uk_profile_snapshot_version (customer_id, version) 要求版本号按客户唯一。

3. 为什么 Agent 不能直接读库(用户提问,本轮答清)

3.1 铁律原文

  • AGENTS.md 第 7 条:业务 Agent 必须由 AgentFactory 创建,不得绕过公共鉴权、记忆、模型路由、 工具、合规、审计和事件流程
  • docs/14-Agent组员统一接入说明书.md L262:禁止直接创建 SQLAlchemy Session、访问 MySQL、 Redis、Milvus 或 Neo4j Driver
  • docs/16-Agent组员入门易懂版说明.md L383:不要直接访问 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 注册过的工具才存在;未注册调用直接 ForbiddenAgentError
  • tests/unit/api/test_architecture.py 用 AST 检查 Controller 不得 import app.model/app.repository
  • ToolRegistry.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. 待办清单(按已定顺序)

  1. 客服 Agent 一期:Task 2-9(Task 1 ✅ 已完成)
  2. 画像模块:
    • 造 5 条虚拟数据(写 fin_customer_profile + fin_risk_assessment + profile_snapshots 三张表)
    • 补 memory-profile 端点的 profile 字段投影(把 {} 换成真实字段)
    • 新增 query_customer_profile 只读工具(照搬 SuitabilityService 的鉴权+范围+失败关闭范式)
  3. 行情落库:待与用户讨论后再定方案
  4. (建议)补 Agent 层架构测试:AST 检查 app/service/agent/** 不得 import Repository/Session

7. 本轮我犯的两个错误(如实记录,供后续核对)

  1. 说"API 层无画像接口"——错的。docs/05 权威文档里 M001/M002 两个端点早已列出, 实现也已存在。我漏查了 docs/05 就直接下结论。
  2. 说"底座没提及画像读取"——同样是错的,同上。

两处均已在本文件 §2.1 纠正。教训:先查权威文档(docs/05 接口文档、docs/00 基线设计) 再下"不存在"的结论。