Files
group_fqcd_jr/客服agent/D2.7-客服Agent中长期记忆与画像联动设计-2026-09-20.md
T
张胜宇 bcedf71e12 docs(W16): 中长期记忆与画像联动设计成文(D2.7)—— 三问直答 / 五道闸门取证 / INV-M1~M6 / 废弃理由更正
新增 客服agent\D2.7-客服Agent中长期记忆与画像联动设计-2026-09-20.md(域 2,D2.x 续号)。

- 主设计主张:让记忆改变「系统行为」而非「模型输入」——记忆只作确定性信号(检索分区加权 / 澄清候选排序 / E2 计算入参 / 适当性过滤),永不进生成上下文(FR-CS-009 + FR-CS-049)。
- 新增记忆子域不变量 INV-M1~INV-M6(INV-M6:记忆只能收窄、不能放宽)。
- 登记两处过期理由:D2.2 §1.7 第 12 项 + 第 998 行澄清框仍引「投顾已整体清除」,而投顾已于 2026-09-20 恢复(D4.7)。
- 计数同步:D1.1 v1.5→v1.6,58→59 份(57→58 份编号),客服agent\ 6→7 份;§4.0 总表与 §4.1 明细各新增 D2.7 行;注入校验 54→55 份;新增 §25 轮次段。
- D2.1 标题 v6.30→v6.31(新增 v6.31 段);D1.6 新增 §4.44。
- 本轮不改代码、不改行为;门禁:check_authoritative_docs.py 通过(54 份无编号冲突)+ _consistency.py GATE PASS。
2026-09-20 16:31:29 +08:00

17 KiB
Raw Blame History

客服 Agent 中长期记忆与画像联动设计(2026-09-20)

体系编号:D2.7 · 域:二、对外交付 · 编号体系见 D1.1 §4.0 读者:答辩评委 + 答辩当天答疑的人 + 接手记忆子系统的开发者。 性质:回答一个问题 —— 「这个 Agent 跟用户画像是什么关系?对话能不能更新画像?要不要做中长期记忆?」 口径:本文所有代码级结论均为逐行读码取证(2026-09-20,本文件不改任何代码)。 配套:D2.2 §1.7 与 §1.8.3(记忆 / 画像口径,含第 12 / 18 / 21 项)、D7.3 §1.3 与 §6.2(三层记忆模型上游依据)、D4.7(投顾恢复)、D6.4.1(研判规则与熔断)、D2.4 与 D3.5(知识库侧)、D2.6(答辩主文档)、D1.6 §4.43(本轮咨询记录)。


0. 一句话结论

Agent 与画像的关系是「读,不写」;「不写」不是技术限制,是 DEC-19 的合规裁定。

「对话能不能更新画像」——链路已经在仓库里(memory_unit → user_facts → fin_customer_profile → 快照 + 投影),技术上完全可行。真正卡住它的是三件事:

# 卡点 性质
1 客服是重构前唯一的画像候选来源,而 DEC-19 裁定「客服不产生画像候选」 合规裁定
2 FR-CS-009(仅基于检索内容回答)+ FR-CS-049(证据约束生成)要求答案能追到 chunk id,记忆注入生成上下文会直接破坏证据约束 结构性冲突
3 investor_type(风险等级)只准来自问卷,是代码级红线 合规底线

因此本文的主设计主张是一句话:让记忆改变「系统行为」,而不是改变「模型输入」。


1. 三问直答

# 用户的问题 直接回答
1 我们这个 Agent 跟用户画像有什么关系? 单向只读。Agent 可读本人画像的白名单字段(risk_level / customer_level),且仅用于确定性规则(适当性过滤 / 转人工优先级 / 画像问答字段直返)。不写、不回写、不把画像原文塞进生成上下文。
2 用户跟 Agent 的对话能否作为更新画像的依据? 能,且代码已在(见 §4)。但必须遵守字段分域:3 个字段可由对话更新,1 个字段绝对不可(见 §5)。当前整体关闭,原因是 DEC-19,不是"做不到"。
3 如果可以做,是不是要做中长期记忆? 要做,但不做"把记忆塞进 prompt"那种。要做的是「面向行为的记忆」:记忆只作为路由与排序的输入,不作为回答的素材。见 §6。

2. 现状取证:客服侧记忆相关的五道闸门

(逐行读码,2026-09-20;均为显式不变量,不是"暂时没写")

层 载体 客服侧现状 证据(仓库相对路径:行号)
短期会话记忆 conversation_message + conversation_session(MySQL) ✅ 开(读 / 写) DEC-19 裁定 (b);D1.6 §4.40
中期记忆 memory_unit(+ memory_evidence) ❌ 客服不写 app\worker\runtime.py:93 NO_LONG_TERM_MEMORY_AGENT_TYPES = frozenset({"customer_service"})
画像候选 memory_unit.status = 'candidate' ❌ 整体关闭 app\worker\runtime.py:84 PROFILE_CANDIDATE_AGENT_TYPES: frozenset[str] = frozenset()
长期记忆召回 memory_unit / user_facts ❌ 关 app\service\agent\implementations\customer_service.py:606 recalls_customer_memory=False;基类闸门 app\service\agent\base.py:143
画像读取 fin_customer_profile → 快照 → 投影 ⚠️ 字段级只读 query_customer_profile(self 作用域)+ app\core\profile_projection.py:36 PROFILE_FIELD_POLICY

runtime.py:81-83 的注释原文(关键):

当前为空集,且不是「重构期临时状态」:DEC-19 裁定 (a) 明确「客服不产生画像候选」,而客服是重构前唯一的候选来源,故候选链路整体关闭。要重新打开必须先改 DEC-19 口径,而不是往本集合里加 agent_type —— 那样等于绕过合规裁定。

🔎 这句话决定了本文的设计边界:任何"打开记忆"的方案,第一步都是改裁定,不是改代码。


3. 上游模型:三层记忆(D7.3 §1.3)

特性 人类记忆 AI Agent 映射 技术实现
短期保持 工作记忆(几秒到几分钟) 会话上下文 Redis · 30 分钟 TTL
中期巩固 反复激活的记忆变牢固 用户画像 / 偏好标签 MySQL + Redis 缓存
长期遗忘 不用的记忆逐渐模糊 知识库 / 历史经验向量 Milvus + 时间衰减

D7.3 是通用技术教材体裁,但 §6.2「记忆单元与身份标识」是被正式引用的上游依据(D2.2 §0.2、D2.4 §0.2 / §1.8.3、FR-CS-042 裁决理由)。文档头部状态标注为 保留,不归档(2026-09-17 曾误归档,同日 16:40 撤销)。

⚠️ 读 D7.3 的正确姿势:把它当方法论来源,不要当本项目的实施方案 —— 它的三层划分是通用的,本项目对客服侧的裁剪(哪层开、哪层关)是 DEC-19 决定的。


4. 关键发现①:「对话 → 画像」链路已经在仓库里(不是待开发)

对话 → memory_unit(中期) → user_facts(长期) → fin_customer_profile(画像)
     → profile_snapshots(版本留痕) + Milvus / Neo4j 投影
       门槛:evidence_count ≥ 2  或  confidence ≥ 0.90

证据(三份服务,全部既有):

文件 职责
app\service\memory_taxonomy.py 受控记忆词表:13 个记忆键 + 显式信号表述表;MEMORY_TYPES = {preference, constraint, profile, goal, fact}
app\service\profile_assembly_service.py 模块文档首句即写「这是记忆系统为画像服务的落地环节」;三段职责 = ① 事实提升(中期→长期)② 画像组装(长期→画像,按白名单)③ 版本留痕
app\service\profile_generation_service.py 同事务写新版本 + 两条同步事件(milvus / neo4j);is_current 先清旧再插新;snapshot_hash 未变则不写行

profile_assembly_service.py 关键常量:MIN_EVIDENCE = 2(第 38 行)、HIGH_CONFIDENCE = 0.90(第 39 行)。

⇒ 「能不能做」在技术上是既有能力,卡点全在合规裁定。这也是本文档存在的意义:把"能不能"和"该不该"分开讲清楚。


5. 关键发现②:字段分域 —— 谁能被对话更新,谁绝对不能

画像字段 对话能否更新 机制
preferred_asset_class ✅ 能 FACT_TO_PROFILE_FIELD["preference:asset_class"]
investment_horizon ✅ 能 FACT_TO_PROFILE_FIELD["preference:horizon"]
risk_tags(自述标签) ✅ 能,标注来源「自述」 SELF_REPORTED_PREFIXES = ("preference:risk_level", "preference:", "profile:")
investor_type(风险等级) ❌ 绝对不可 只来自 fin_risk_assessment 最新一条
total_asset / trading_frequency / behavior_score ❌ 不可 交易侧所有(字段所有权)
customer_tier ❌ 不参与 快照按设计排除该字段(D-11)

profile_assembly_service.py 第 15 行的红线原话:

investor_type 只来自问卷测评(fin_risk_assessment 最新一条)。……客户在对话里说"我是激进型"不会改变它 —— 这是合规底线,不能只靠约定。

PROFILE_OWNED_FIELDS = ("investor_type", "preferred_asset_class", "investment_horizon", "risk_tags")(第 59 行)—— 按字段所有权分域写入;CRITICAL_FACTS = {preference:risk_level, preference:horizon, preference:asset_class}(第 54 行)。

5.1 设计亮点(值得在答辩里主动讲)

自述等级不丢 —— 客户说"我是激进型",这句话不会被丢弃:它进 risk_tags 并标注来源「自述」。

于是当出现「问卷 C4 / 自述稳健 / 行为买 R4」这种三方不一致时,这个矛盾本身即风控信号(D3.1 FR-CS-024 的转人工摘要也含画像关键标签)。

一句话对外说法:我们不是"不 update",而是"分层 update + 矛盾留痕"。问卷管结论,对话管线索。


6. 关键设计主张:让记忆改变「系统行为」,而不是改变「模型输入」

6.1 五条允许的消费通道(记忆只到这里为止)

记忆键 消费点(代码位置) 效果 碰生成上下文
preference:asset_class 检索层分区 / 档位加权(_answer_from_knowledge 的检索入参装配,customer_service.py:784) 问「有什么产品」时优先生命中族 ❌ 不碰
preference:horizon E1 澄清候选排序(_exit_clarify,customer_service.py:2071) 少问一轮 ❌ 不碰
constraint:* / goal:* E2 计算型入参装配(费率参数位 / 匹配矩阵,customer_service.py:952 / 1341 / _exit_calc_miss:1232) 试算带上已知约束,不必追问 ❌ 不碰
preference:risk_level 适当性过滤(已实现) 无需改动 ❌ 不碰
画像白名单字段 画像问答字段直返(render_profile,customer_service.py:228) 已有能力 ❌ 不碰

6.2 明确禁止

禁止项 理由
❌ 把任意记忆键注入生成上下文 / prompt 违反 FR-CS-009 + FR-CS-049:生成式回答必须能追到 chunk id
❌ 用记忆提升知识档位 档位由鉴权结果推导(knowledge_contracts.tiers_for_roles),customer_service.py 内零个档位字面量,不得由记忆旁路
❌ 用记忆放宽适当性 / 降低转人工门槛 记忆只能"收窄"不能"放宽"(见 §7 INV-M6)
❌ 用记忆改动 investor_type §5 红线

6.3 为什么「不进生成上下文」是硬要求,不是偏好

这条冲突是结构性的,不会因为换模型或调 prompt 而消失:

  1. FR-CS-009:仅基于检索内容回答;
  2. FR-CS-049:证据约束生成;
  3. 输出数字一致性校验:答案里的数字要在已授权来源里找得到。

一旦把 memory_unit 的文本塞进 prompt,模型就会说出**"没有出处、但读起来像事实"**的句子 —— 这正是零容忍里的「无出处数字」与事实正确率的直接杀手。

🔑 对外一句话:记忆是「路由与排序的输入」,不是「回答的素材」。


7. 安全设计:记忆子系统不变量 INV-M1 ~ INV-M6

(与 D3.6 的 INV-1~INV-5 并列,属记忆子域)

编号 不变量 违反后果
INV-M1 记忆永不进入生成上下文;只作为确定性信号改变检索 / 排序 / 入参 无出处数字、事实正确率崩塌
INV-M2 investor_type 永不因对话改变;只来自 fin_risk_assessment 最新一条 合规事故
INV-M3 短期会话记忆恒开,与长期记忆开关解耦 多轮指代消解失效(H-01/H-02 类问题暴增)
INV-M4 任何记忆写入须过 evidence_count ≥ 2 或 confidence ≥ 0.90,且不得绕过 FM-01~FM-05 画像被噪声 / 单次口误污染
INV-M5 只允许写 13 个受控记忆键;客服侧不写 profile:occupation / profile:family / profile:income_stability 三个 PII 键 个人信息越界采集
INV-M6 记忆只能收窄、不能放宽:不得提升知识档位、不得放宽适当性、不得降低转人工门槛 安全边界被记忆旁路

FM-01~FM-05(D6.4.1 第三章)速查:年龄限制 / 无收入且低资产 / 风险评估过期(>12 月冻结购买)/ 身份信息异常 / 异常交易熔断。


8. 已过期理由的更正登记(如实记录)

8.1 D2.2 §1.7 第 12 项(D2.2-客服Agent需求文档.html 第 986 行)

原文理由 2026-09-20 现状
① 「收益方(投顾)已整体清除」 🔴 已失效 —— 投顾模块 2026-09-20 已随合并恢复(见 D4.7)
② 「注入生成上下文与证据约束生成直接冲突」 ✅ 仍然成立,且是结构性的(见 §6.3)

8.2 同文档第 998 行的澄清框(一并登记)

该框内同样写着「收益方投顾已清除」——与 8.1 属同一处过期依据,应与第 12 项一并更正。

⚠️ 为什么必须更正:答辩追问「为什么关长期记忆」时,如果引的是已失效的理由①,等于把一个已经被事实推翻的依据当论据 —— 风险远大于收益。关闭的正确依据只剩理由②(证据约束)+ DEC-19 裁定本身。


9. 分期实施路径

期 时点 内容 前置
P0 演示前 什么都不开。只做 §8 的文档更正与 §10 的反向守卫单测(加测试不改行为) 无
P1 演示后 ① 先改 DEC-19 口径(书面);② 只开 preference:* / constraint:* / goal:*,不开 3 个 PII 键;③ 只接 §6.1 的五条确定性消费通道 P0 完成 + DEC-19 修订
P2 可选 记忆→澄清排序的效果量化(用 E1 过度触发 14/46 这条基线做对照,目标:少问一轮) P1 有数据

🔴 P1 的硬前置是"先改裁定、再改代码" —— 直接往 PROFILE_CANDIDATE_AGENT_TYPES 里加 customer_service 等于绕过合规裁定(runtime.py:81-83 注释已明确禁止)。


10. 验收与守卫清单

# 守卫 类型 现状
1 对话说「我是激进型」后,fin_customer_profile.investor_type 必须不变 单测(INV-M2) 🔴 缺失 —— 这条红线目前只在服务文档里写着,没有测试固定
2 生成上下文中不含任何 memory_unit 片段 单测(INV-M1) 🔴 待补
3 带记忆请求 registered 内容仍被拒(记忆不放宽档位) 单测(INV-M6) 🔴 待补
4 46 条金标不回归(转人工率 10.9% / 出口准确率 100% / 事实正确率 100% / 四项零容忍全 0) 回归 ✅ 既有基线
5 P0 / P1 / P2 / 明确要求 四类仍确定性拦下 单测 + 回归 ✅ 既有

📌 第 1 条是本文档最推荐的立即动作:成本为零、不影响演示行为、却把一条"只写在文档里的合规底线"变成"代码可验证的合规底线"。


11. 与知识库设计的关系(D2.4 / D3.5)

记忆与知识库是两层不同的东西,答辩时最容易被混为一谈:

维度 知识库(D2.4 / D3.2) 记忆(本文 D2.7)
内容 公开 / 通用的产品、规则、FAQ 个人的偏好、约束、目标
生命周期 版本化发布,不被对话改变 随对话累积(若开启)
用途 回答的素材(要能引用 chunk id) 行为的路由输入(不作出处)
当前状态 ✅ 已建成(3 集合 / 3 档可见性 / 分区隔离) ❌ 客服侧整体关闭(DEC-19)

「知识库还有没有更优方案」属另一个议题,备选池与各方案否决理由见 D3.5 与 D2.4,本文不重复。


12. 待决项与我方最优建议

# 待决事项 我方最优建议
1 是否打开画像候选 + 中期记忆 演示后(P1)再做;只开 preference:* / constraint:* / goal:*;不开 profile:occupation / profile:family / profile:income_stability 三个 PII 键
2 记忆消费方式 限定为确定性信号,并把「记忆不进生成上下文」写进 D2.2 §1.7 第 12 项,成为可测试的不变量
3 是否更正 D2.2 §1.7 第 12 项已过期的理由①(含第 998 行澄清框) 更正 —— 否则答辩被追问时引的是失效依据
4 investor_type 红线加回归 加一条单测:对话说「我是激进型」后画像 investor_type 必须不变

13. 诚实声明

已做 未做(因此哪些结论仍是推断)
逐行读码:runtime.py / customer_service.py / base.py / profile_assembly_service.py / profile_generation_service.py / memory_taxonomy.py / profile_projection.py ❌ 本文件未改任何代码、未跑任何测试
本文件所有常量、行号、类名、注释原文均已逐条核对 ❌ 「记忆→检索加权的效果增益」尚无实测数字(P2 才有)
与 D2.2 / D7.3 / D6.4.1 / D4.7 的口径对读 ❌ DEC-19 的书面修订文本尚未起草(P1 前置)

引用本文件时请注意:§6.1 的五条消费通道是设计主张,除 preference:risk_level(适当性过滤)与画像字段直返已实现外,其余三条尚未实现 —— 不要读成"已经能做"。


维护责任:本文件为活文档。DEC-19 口径修订、或记忆消费通道落地后,须回填 §9 / §10 的状态列,并同步 D2.1(看板)与 D1.6(会话记录)。 同步方向:权威副本(D:\桌面\金融\客服agent\)→ 仓库(group_fqcd_jr\客服agent\)单向覆盖(D1.6 §4.38 既定)。