# 用户画像置信度实时更新开发计划 版本:v2.0 日期:2026-09-13 状态:待开发 ## 1. 开发目标 用户完成风险问卷后,系统固定用户类型,并将初始画像置信度设置为 `0.90`。后续根据客服 Agent 已确认的对话证据和已确认的交易流水,实时调整当前画像置信度。 本功能有两个边界: 1. 用户类型只允许保持不变,不能被对话或交易重新分类。 2. 证据关系由 LLM 判断,置信度最终值由 `BaseConfidenceCalcTool.update()` 统一计算。 ## 2. 最终业务流程 ```text 风险问卷完成 -> 固定 user_type -> confidence_score = 0.90 交易状态变为“已确认” -> 生成交易证据事件 Agent 提取并确认对话事实 -> 生成对话证据事件 Worker 消费证据事件 -> 读取固定 user_type 及其画像定义 -> 调用 LLM 判断 support / conflict / neutral -> 校验 LLM 输出 -> neutral 或无效证据标记 ignored -> 读取数据库当前 confidence_score -> BaseConfidenceCalcTool.update() -> 更新 confidence_score -> 写入画像变更日志 -> 清理 Redis 画像缓存 -> 标记事件 processed ``` ## 3. 核心职责划分 ```text 原始数据读取器 读取 Agent 记忆、对话归档、交易流水和产品信息 EvidenceBuilder 将原始数据整理成统一证据输入 LLM EvidenceRelationJudge 根据固定用户类型判断 support / conflict / neutral BaseConfidenceCalcTool.update() 使用当前已有 confidence 计算新的 confidence ProfileConfidenceUpdater 负责事务、并发锁、日志、缓存和事件状态 ``` LLM 不直接写数据库,也不负责修改用户类型。 ## 4. 用户画像规则 画像主表使用: ```text fin_customer_profile.risk_level fin_customer_profile.confidence_score ``` 问卷完成后: ```text risk_level = 问卷评定结果 confidence_score = 0.90 ``` 后续事件只能修改: ```text confidence_score profile_version update_time ``` 禁止修改: ```text risk_level risk_score ``` ## 5. LLM 证据关系判断 ### 5.1 判断目标 LLM 不是重新判断客户属于哪种类型,而是回答:当前这条证据,相对于已经固定的用户类型,是支持、冲突,还是无法判断? 允许的关系只有: ```text support 支持当前固定用户类型 conflict 与当前固定用户类型冲突 neutral 与类型无关、信息不足或无法判断 ``` ### 5.2 LLM 输入 对话证据输入示例: ```json { "user_type": "稳健型", "profile_definition": { "risk_tolerance": "低到中等", "principal_safety": "高", "return_preference": "稳定优先" }, "evidence_source": "user_stated", "evidence": "我比较偏好本金安全,不希望本金出现明显亏损" } ``` 交易证据输入示例: ```json { "user_type": "稳健型", "profile_definition": { "risk_tolerance": "低到中等", "principal_safety": "高" }, "evidence_source": "behavior_inference", "evidence": { "transaction_type": "申购", "product_name": "某股票型基金", "product_risk_level": "R5", "transaction_status": "已确认" } } ``` ### 5.3 LLM 输出 必须使用结构化 JSON: ```json { "relation": "support", "valid": true, "reason": "客户明确表达重视本金安全,与稳健型偏好低波动和本金保护的特征一致", "evidence_summary": "客户偏好本金安全" } ``` 输出字段: ```text relation support / conflict / neutral valid true / false reason 判断理由 evidence_summary 证据摘要 ``` ### 5.4 LLM Prompt 约束 ```text 用户类型已经确定,禁止修改用户类型。 只能判断当前证据与固定用户类型的关系。 只能返回 support、conflict 或 neutral。 信息不足、表达含糊或与风险偏好无关时返回 neutral。 不要根据证据重新分类用户。 必须返回 JSON,不输出额外文本。 ``` ### 5.5 判断示例 固定类型为“稳健型”: ```text “我比较偏好本金安全” -> support “我可以接受本金大幅波动来追求高收益” -> conflict “最近基金市场行情怎么样?” -> neutral ``` 固定类型为“进取型”时: ```text “我比较偏好本金安全” -> conflict ``` 同一条证据必须结合当前固定的 `user_type` 判断,不能脱离用户类型单独判断。 ## 6. 统一证据结构 ```python class ProfileEvidence: event_id: str customer_id: int category: str source: str raw_content: dict | str relation: str | None valid: bool | None reason: str | None occurred_at: datetime ``` 在 LLM 判断前,`relation` 和 `valid` 为空;判断后必须由程序校验: ```python ALLOWED_RELATIONS = {"support", "conflict", "neutral"} if result.relation not in ALLOWED_RELATIONS: raise InvalidEvidenceResult() ``` ## 7. 对话和交易证据 ### 7.1 对话 数据来源: - `memory_unit` - `conversation_archive` 优先使用 Agent 已提取并确认的 `memory_unit`,避免直接处理全部原始对话。 ```text Agent 对话 -> Agent 提取画像事实 -> memory_unit 写入或确认 -> 生成画像证据事件 -> LLM 判断证据关系 ``` 来源映射: ```text user_confirmed -> user_confirmed user_stated -> user_stated ai_inferred -> ai_inferred ``` ### 7.2 交易 数据来源: - `fin_transaction` - `fin_product` 只有以下条件满足时才生成有效交易证据: ```text fin_transaction.status = 已确认 交易客户与画像客户一致 产品信息可读取 ``` 交易证据输入应包含: ```text 交易类型、产品名称、产品类型、产品风险等级、交易金额、交易确认时间 ``` 待确认、风控挂起、已撤单、失败的交易不更新画像。 ## 8. 置信度更新工具 ### 8.1 保留现有 calc() 现有 `calc()` 继续保留,用于问卷初始化或长期记忆的完整重算。 ### 8.2 新增 update() ```python BaseConfidenceCalcTool.update( current_confidence: float, source: str, relation: str, age_days: int = 0, threshold: float | None = None, calculated_at: datetime | None = None, ) -> ConfidenceResult ``` 核心要求: ```text current_confidence 必须来自数据库当前值 ``` 更新规则: ```text support -> 提高当前置信度 conflict -> 降低当前置信度 neutral -> 不更新 ``` 计算结果必须限制在 `0.0` 到 `1.0` 之间。 返回结果建议包含: ```python ConfidenceResult( previous_confidence=..., confidence=..., confidence_delta=..., qualified=..., reason=..., model_version=..., calculated_at=..., ) ``` ## 9. 事件触发机制 采用: ```text 应用层事务 Outbox + 后台 Worker ``` ### 9.1 对话触发 ```text Agent 提取并确认画像事实 -> 写入 memory_unit -> 同一事务写入 profile_confidence_event ``` ### 9.2 交易触发 ```text 交易状态变为“已确认” -> 同一事务写入 profile_confidence_event ``` 数据库 Trigger 不直接调用 LLM 或置信度工具。若未来存在绕过应用层的外部写库系统,再考虑增加数据库 Trigger 作为 Outbox 补充。 ## 10. 事件表设计 建议新增: ```text profile_confidence_event ``` 字段: ```text id event_id customer_id category source_type source_record_id status retry_count error_message create_time process_time ``` 事件状态: ```text pending 待处理 processing 处理中 processed 已完成画像更新 ignored 无效或无关证据 failed 处理失败,可重试 ``` 增加唯一约束: ```text UNIQUE(event_id, category) ``` 确保同一条对话或交易不会重复调整置信度。 ## 11. Worker 处理流程 ```text 1. 抢占 pending 事件 2. 读取 Agent 记忆、对话或交易原始记录 3. 构造 LLM 输入 4. 调用 LLM EvidenceRelationJudge 5. 校验 JSON 和 relation 枚举 6. relation=neutral 或 valid=false 时标记 ignored 7. 锁定 fin_customer_profile 8. 读取当前 confidence_score 9. 调用 BaseConfidenceCalcTool.update() 10. 更新 confidence_score 和 profile_version 11. 写入 customer_profile_change_log 12. 提交数据库事务 13. 清理 Redis 画像缓存 14. 标记事件 processed ``` 第 7 至第 12 步必须在同一个数据库事务中完成。读取画像时使用: ```sql SELECT * FROM fin_customer_profile WHERE customer_id = ? FOR UPDATE; ``` ## 12. 失败处理 LLM 超时或服务异常: ```text 事件状态 = failed retry_count + 1 保存 error_message 等待下次重试 ``` LLM 返回格式错误或未知关系: ```text 不更新画像 记录错误 事件进入 failed 或人工检查队列 ``` LLM 判断为无关或无法判断: ```text relation = neutral 事件状态 = ignored 不修改 confidence_score ``` ## 13. 数据库事务和缓存 画像更新、画像变更日志和事件状态更新必须在同一个 MySQL 事务中完成: ```text 更新画像 写画像变更日志 标记事件 processed ``` 事务成功提交后,再清理 Redis: ```python await CustomerProfileMemory().invalidate(customer_id) ``` ## 14. 代码模块计划 ### 14.1 置信度工具 文件: ```text tool/confidence.py ``` 任务: - 新增 `update()` - 扩展 `ConfidenceResult` - 增加 support、conflict、neutral 测试 - 保留 `calc()` 兼容现有长期记忆逻辑 ### 14.2 LLM 证据判断器 建议新增: ```text service/profile_confidence/relation_judge.py ``` 任务: - 维护 Prompt - 组织用户类型定义和证据输入 - 调用 LLM - 解析结构化 JSON - 校验 relation、valid 和必填字段 - 处理超时、格式错误和重试 ### 14.3 证据服务 建议新增: ```text service/profile_confidence/evidence.py ``` 任务: - 读取对话、Agent 记忆和交易流水 - 统一转换为 `ProfileEvidence` - 生成稳定的 `event_id` ### 14.4 画像更新服务 建议新增: ```text service/profile_confidence/updater.py service/profile_confidence/worker.py ``` 任务: - 消费事件 - 调用 LLM 判断关系 - 调用 `BaseConfidenceCalcTool.update()` - 加锁更新画像 - 写变更日志 - 清理缓存 - 更新事件状态 ### 14.5 仓储和模型 需要新增或调整: ```text model/profile_confidence_event.py model/fin_transaction.py repositories/customer_profile.py repositories/profile_confidence_event.py ``` 任务: - 支持画像带锁查询 - 支持置信度更新 - 支持事件抢占和状态流转 - 支持交易流水查询 - 支持幂等约束 ## 15. 测试计划 ### 15.1 LLM 关系判断 固定类型为“稳健型”: ```text “偏好本金安全” -> support “不能接受明显亏损” -> support “可以承担大幅波动追求高收益” -> conflict “最近基金行情怎么样” -> neutral ``` 固定类型为“进取型”: ```text “偏好本金安全” -> conflict ``` 还需测试: - 模糊表达返回 `neutral` - 非法枚举被拒绝 - 非 JSON 输出进入失败重试 - LLM 不得修改 `user_type` ### 15.2 置信度工具 - support 使用当前置信度向上更新 - conflict 使用当前置信度向下更新 - neutral 不更新 - 置信度始终在 `0.0~1.0` - 时间衰减生效 - 不同来源使用不同来源权重 ### 15.3 事件和事务 - 已确认交易生成事件 - 待确认交易不生成有效事件 - Agent 记忆确认后生成事件 - 重复事件不会重复更新 - 并发事件不会丢失更新 - 画像更新失败时事件不会标记为 processed - LLM 失败后可以重试 - 更新成功后 Redis 缓存失效 ## 16. 验收标准 1. 问卷完成后 `confidence_score = 0.90`。 2. LLM 只判断证据关系,不修改用户类型。 3. LLM 输出只能是 `support`、`conflict` 或 `neutral`。 4. Agent 有效对话证据可以实时更新置信度。 5. 已确认交易可以实时更新置信度。 6. `BaseConfidenceCalcTool.update()` 使用数据库中的当前置信度。 7. 无效或 `neutral` 证据不会更新画像。 8. 用户类型 `risk_level` 始终保持不变。 9. 同一事件重复消费不会重复调整置信度。 10. 并发事件不会造成置信度丢失。 11. 每次更新都能追溯到具体对话或交易。 12. 画像更新成功后事件才标记为 `processed`。 13. LLM 或数据库失败时事件可以重试。 14. 更新成功后 Redis 不会继续返回旧画像。 ## 17. 开发顺序 ```text 第一阶段:实现 BaseConfidenceCalcTool.update() 第二阶段:实现 LLM EvidenceRelationJudge 和结构化输出校验 第三阶段:补充画像仓储的带锁读取和更新 第四阶段:新增 ProfileEvidence 和事件表 第五阶段:实现 Worker 及失败重试 第六阶段:接入 Agent 记忆确认流程 第七阶段:接入交易确认流程 第八阶段:接入画像变更日志和 Redis 缓存失效 第九阶段:补充幂等、并发和端到端测试 ``` ## 18. 第一版范围控制 第一版支持: - 固定用户类型:`risk_level` - 画像置信度:`confidence_score` - 证据来源:Agent 对话、已确认交易 - LLM 关系判断:`support / conflict / neutral` - MySQL Outbox - 单 Worker 轮询 - 画像更新日志 第一版暂不支持: - LLM 自动修改用户类型 - 多类型概率分布 - Kafka 或 RabbitMQ - 复杂多证据联合推理 - 自动根据置信度重新分类客户