Files
story/docs/content-memory.md
T

144 lines
18 KiB
Markdown
Raw Normal View History

2026-08-26 06:41:07 +08:00
# 字幕、故事审核与已确认记忆设计
## 1. 目标与边界
本批次实现“最终转写入库—通话整理—故事候选—人工审核—已确认记忆—索引生效”的完整业务链。系统把模型输出始终视为候选内容;只有具备`story.review`能力的家庭成员明确确认,并且长者当前存在有效`personalized_memory`授权时,候选才可转为长期记忆。
当前抽取器支持两类可切换适配器:默认的`local-deterministic-extractor`用于本地业务验证,`ark:<model-id>`通过火山方舟OpenAI兼容Chat Completions接口生成结构化候选。向量生成与查询同样支持`local-deterministic-hash-v1`和`ark:<embedding-model-id>`两类适配器,并强制使用数据库既定的1536维。生产配置门禁禁止内容生成、索引或查询继续使用本地适配器;真实方舟账号效果仍需外部验收。
## 2. 功能设计
| 功能 | 输入 | 处理规则 | 输出 |
| --- | --- | --- | --- |
| 最终话轮接收 | 供应商签名回调、最终话轮数组 | 只接受`voice_chat.transcript.final/FINAL`;校验话轮键、序号、角色、时间和正文;按`conversation_id + turn_key`去重;同键不同内容拒绝 | `conversation_turn` |
| 通话整理 | 已结束通话、冻结授权快照、最终话轮 | 无`story_extraction`授权时不处理;只使用长者话轮作为故事和摘要来源;同一生成版本幂等 | 通话摘要、开放话题、故事候选、事实候选 |
| 方舟模型调用 | 本地安全策略通过的长者话轮 | 使用Bearer API Key调用`${ARK_BASE_URL}/chat/completions`;要求结构化JSON;网络、超时、408/425/429/5xx可靠重试,永久4xx进入死信 | 带模型编号、提示词版本和置信度的候选结果 |
| 分层内容安全过滤 | 长者最终话轮及模型结构化输出 | 验证码、支付账户、提示注入和高敏医疗先在本地拦截且不外发;其余输入在生成前、模型输出在落库前调用托管策略;BLOCK/REVIEW改为受控提示,策略服务异常时可靠重试且不落库 | 策略版本、非敏感提示和可审计请求引用 |
| 故事修订 | 候选故事、人工正文、原因、强ETag | 新增人工修订版本并复制来源关系,不覆盖模型版本;终态故事禁止再改 | 新的`story_revision`和`IN_REVIEW`状态 |
| 故事确认 | 当前故事、审核原因、强ETag | 必须有原话来源、具备审核权限且个性化记忆授权当前有效;确认故事及候选事实 | `CONFIRMED`故事和`PENDING`记忆 |
| 故事拒绝 | 当前故事、审核原因、强ETag | 拒绝故事及仍为候选状态的事实,不创建记忆 | `REJECTED`故事 |
2026-08-29 21:56:51 +08:00
| 事实候选细粒度审核 | 单条事实、修订内容、敏感级别、问题类型、原因、强ETag | 事实可独立编辑、确认、拒绝或停用;每次变化保存不可覆盖版本,不要求整篇故事一起处理 | 新的`fact_candidate_revision`与质量反馈 |
2026-08-26 06:41:07 +08:00
| 记忆索引 | 待索引记忆版本 | 再次校验记忆为当前版本、状态有效且授权仍有效;不满足条件时清空向量并标记`STALE` | `ACTIVE`或`STALE`索引 |
| 生产向量化 | 普通敏感级别的当前记忆正文或新通话检索语句 | API与Worker使用同一方舟Embedding模型;请求1536维float向量并校验维度和有限数值;网络、超时、408/425/429/5xx可靠重试 | `ark:<model-id>`索引或固定边界查询向量 |
| 记忆修订与停用 | 当前记忆、正文或状态、原因、强ETag | 正文变化创建新版本并立即使旧索引失效;停用后不再召回;重新启用必须重新校验授权并重新索引;`PENDING_DELETE/SUPERSEDED/DELETED`均禁止修改 | 新记忆版本或状态 |
| 记忆永久删除 | 当前记忆、删除原因、强ETag | 事务内先进入`PENDING_DELETE`并清空活动向量,使新通话立即停止召回;该状态不可通过修改接口恢复;Outbox后台把索引标记删除、清除所有记忆版本正文并记录完成审计 | `DELETED`记忆和索引 |
| 新通话记忆检索 | 服务端已确定的客户组织、长者和通话目标 | SQL同时限制租户、长者、当前授权、活动记忆、当前版本、普通敏感级别、有效期和活动索引;最多5条 | 冻结到`context_snapshot.memory_versions`的版本引用和最小背景摘要 |
2026-09-06 17:04:11 +08:00
| 通话中记忆工具 | 供应商持有的单通话短期签名令牌、自然语言查询 | 令牌只绑定服务端既有通话和RTC任务或硬件智能体会话;租户、家庭和长者边界从数据库反查,模型不能传入或扩大;单条240字、总计800字、最多5条;Embedding失败返回空结果并标记降级,不中断通话 | `/internal/v1/tools/memory-search`、`memory_tool_call`和实际使用追踪 |
2026-08-29 21:56:51 +08:00
| 可选供应商原生记忆 | 当前人工确认记忆、有效授权、普通敏感级别、受控HTTP适配器 | 数据库始终是主数据;开启后用Outbox同步,修订先清理旧引用,停用、授权撤回、单条删除和主体删除均可靠删除并对账;关闭功能后通话继续走数据库检索 | `provider`索引的`PENDING/ACTIVE/FAILED/DELETED`状态 |
| 内容质量反馈 | 故事、事实或记忆的人工确认、修改、拒绝、停用及九类标准问题 | 不复制私密正文;按生成模型和提示词版本聚合候选数、审核动作和问题类型;跨租户统一拒绝 | 维护端“内容质量”与`content_review_signal` |
| 跨通话连续上下文 | 最近已完成通话同一最新生成版本的就绪摘要与开放话题、当前授权快照、交流偏好 | 只有`story_extraction`和`personalized_memory`在新通话冻结快照中同时有效时,才按同租户同长者读取最近3次内容;最多选一个最新的非空开放话题作为低优先级建议,摘要保留约170字、已确认记忆每条保留约90字,总计不超过1000字;撤回任一授权后不再注入 | 冻结到新通话`context_snapshot.sanitized_summary`的短期连续上下文 |
2026-08-29 21:56:51 +08:00
| 避免主动提及 | 长者档案`conversation_preferences.avoided_topics` | 最多8项、每项50字;从下一次新通话起进入冻结快照;仅限制智能体主动开启话题,长者主动提及时温和转向,紧急安全响应不受限制 | 新通话的交流边界 |
2026-08-26 06:41:07 +08:00
| 授权撤回 | `personalized_memory`授权 | 在同一事务内停用该长者的活动记忆,清空活动/待处理索引向量并标记失效 | `DISABLED`记忆、`STALE`索引 |
## 3. 业务流程
```mermaid
flowchart TD
A[供应商最终转写回调] --> B{字幕保存授权允许?}
B -- 否 --> C[不保存转写正文]
B -- 是 --> D[校验并去重最终话轮]
D --> E[通话进入COMPLETED或FAILED]
E --> F{故事整理授权允许?}
F -- 否 --> G[结束且不生成候选]
F -- 是 --> H[仅提取长者话轮]
H --> I[摘要与开放话题]
H --> J[故事和事实候选]
J --> K{家庭成员人工审核}
K -- 修订 --> L[新增人工版本并保留来源]
L --> K
K -- 拒绝 --> M[故事和候选事实拒绝]
K -- 确认 --> N{来源完整且记忆授权有效?}
N -- 否 --> O[返回冲突且不建记忆]
N -- 是 --> P[确认故事与事实]
P --> Q[创建记忆版本和待处理索引]
Q --> R{后台再次校验当前状态与授权}
R -- 合格 --> S[索引ACTIVE]
R -- 不合格 --> T[清空向量并标记STALE]
S --> U[创建下一次通话]
U --> V[数据库固定边界检索]
V --> W[冻结记忆版本和背景摘要]
2026-08-29 21:56:51 +08:00
S --> X{供应商记忆功能已验证并开启?}
X -- 是 --> Y[可靠同步外部引用]
X -- 否 --> Z[只使用数据库检索]
Y --> AA[停用/撤回/删除时外部删除对账]
2026-08-26 06:41:07 +08:00
```
供应商停止事件和最终转写事件可能存在先后竞争。停止事件会发布内容整理待办;如果话轮尚未到达,处理器返回可重试错误,由Outbox退避重试,避免把“暂无话轮”错误当成永久空内容。
内容处理器先在无事务状态读取已结束通话、冻结授权和有序最终话轮,执行本地敏感内容判断;只有普通内容才调用外部模型。模型返回后才开启短事务并锁定通话,重新读取状态、授权和全部话轮并比较SHA-256输入指纹。若模型调用期间授权撤回,则不写入结果;若话轮发生变化,则本次任务进入可靠重试并基于新输入重新生成。该顺序满足“数据库事务不得跨越大模型网络调用”的需求约束。
记忆索引也先在无事务状态读取当前版本和授权,事务外生成Embedding,返回后才锁定记忆与索引行重新校验;修订、停用或撤回授权发生在模型调用期间时,旧结果不会被激活。新通话创建前仅在设备绑定、语音授权和个性化记忆授权均可用时生成查询向量;事务内仍以固定租户、长者、授权、状态和敏感级别条件检索。Embedding临时不可用时,本次通话以`pgvector-degraded-v1`记录降级且不注入记忆,不阻断基本语音通话。普通顺序幂等重放和同键正文冲突会先读取已完成幂等记录,不重复调用Embedding;并发首请求仍由数据库唯一键保证只创建一个通话。
2026-08-29 21:56:51 +08:00
通话中工具令牌由平台在启动智能体时签发,令牌只包含通话编号、固定供应商任务编号和过期时间。工具接口先重新读取权威通话、任务、租户和长者关系,再执行与通话前召回相同的数据库边界;请求正文没有租户、家庭或长者字段。查询摘要、结果数量、延迟和降级状态进入最小审计,原始查询和记忆正文不进入普通日志。供应商原生记忆是可选增强,不取代该工具或通话前数据库召回;外部服务失败时,本地主数据和本地召回仍可继续工作。
最近通话摘要是短期连续上下文,不会自动升级为长期记忆,也不会绕过故事人工审核。平台在每次开房时重新检查并冻结当前授权,只读取同一客户组织、同一长者、已完成通话的最新就绪摘要;隐私删除或授权撤回后的摘要不会继续进入后续通话。纯端到端豆包模式把这份冻结上下文写入`S2SConfig.ProviderParams.dialog.system_role`,级联模式继续写入`LLMConfig.SystemMessages`,两种链路使用同一上下文快照。
2026-08-26 06:41:07 +08:00
## 4. 接口设计
| 方法与路径 | 权限 | 并发/校验 | 说明 |
| --- | --- | --- | --- |
| `GET /api/v1/conversations/{id}/turns` | `conversation.read` | HMAC游标、1—100条 | 最终话轮分页 |
| `GET /api/v1/conversations/{id}/summaries` | `conversation.read` | 资源隔离 | 摘要和开放话题 |
| `GET /api/v1/subjects/{id}/stories` | `subject.read` | 可按状态过滤 | 故事列表,不展开子项 |
| `GET /api/v1/stories/{id}` | `subject.read` | 返回强ETag | 当前故事、来源和事实 |
| `PATCH /api/v1/stories/{id}` | `story.review` | 必须`If-Match`;正文和原因必填 | 创建人工修订版本 |
| `POST /api/v1/stories/{id}/confirm` | `story.review` | 必须`If-Match`;来源与授权门禁 | 确认故事并创建记忆 |
| `POST /api/v1/stories/{id}/reject` | `story.review` | 必须`If-Match`;原因必填 | 拒绝候选 |
2026-08-29 21:56:51 +08:00
| `PATCH /api/v1/stories/{story_id}/facts/{fact_id}` | `story.review` | 必须`If-Match`;单条事实状态机、原因及可选标准问题类型 | 独立编辑、确认、拒绝或停用事实 |
2026-08-26 06:41:07 +08:00
| `GET /api/v1/subjects/{id}/memories` | `subject.read` | 过滤已删除记录;可用`q`在固定长者边界内搜索 | 已确认记忆列表 |
| `GET /api/v1/memories/{id}` | `subject.read` | 返回强ETag | 当前记忆版本和索引状态 |
| `PATCH /api/v1/memories/{id}` | `story.review` | 必须`If-Match`;正文/状态至少一项且原因必填;删除中及历史终态拒绝修改 | 修订、停用或重新启用记忆 |
| `DELETE /api/v1/memories/{id}` | `story.review` | 必须`If-Match`;删除原因必填;返回202 | 可靠受理永久删除任务 |
2026-08-29 21:56:51 +08:00
| `POST /internal/v1/tools/memory-search` | 单通话短期Bearer令牌 | 固定通话/任务/租户/长者边界;限流、长度和结果上限 | 通话中受控记忆检索,失败安全降级 |
| `GET /api/v1/ops/content-quality` | `ops_viewer`及以上 | 必须选择有权客户组织;不返回正文 | 按模型和提示词版本聚合审核反馈 |
2026-08-26 06:41:07 +08:00
统一冲突码包括`STORY_SOURCE_REQUIRED`、`PERSONALIZED_MEMORY_CONSENT_REQUIRED`和`STORY_REVIEW_STATE_CONFLICT`。跨租户或非家庭成员访问统一返回403,不借404差异泄露资源是否存在。
## 5. 数据库设计
| 表 | 作用 | 关键约束 |
| --- | --- | --- |
| `conversation_turn` | 最终话轮 | 通话内`turn_key`和`sequence_no`唯一;角色、时间顺序和正文长度约束 |
| `conversation_summary` | 摘要与开放话题 | 通话、类型、生成版本唯一;必须有源话轮 |
| `story_draft` | 故事聚合根和审核状态 | 通话与生成版本唯一;强版本号;指向当前修订 |
| `story_revision` | 不可覆盖的故事版本 | 故事内修订号唯一;区分模型/人工作者;保存基础版本和原因 |
| `story_source` | 故事版本到原话的追溯关系 | 版本、话轮和文本偏移组合唯一 |
| `fact_candidate` | 可审核事实候选 | 区分长者陈述与模型推断;保存源话轮、敏感级别和状态 |
2026-08-29 21:56:51 +08:00
| `fact_candidate_revision` | 事实不可覆盖版本 | 每条事实版本号唯一;保留内容、状态、原因和标准问题类型 |
2026-08-26 06:41:07 +08:00
| `curated_memory` | 长期记忆聚合根 | 当前版本、状态、有效期、敏感级别和强版本号 |
| `memory_version` | 不可覆盖的记忆版本 | 记忆内版本号唯一;确认人、确认时间、原因、来源和内容摘要 |
| `memory_index_entry` | 记忆检索索引状态 | 记忆版本和索引类型唯一;向量维度1536;状态可失效和删除 |
2026-08-29 21:56:51 +08:00
| `conversation_memory_use` | 实际记忆使用追踪 | 按通话、版本和使用方式唯一,不复制正文 |
| `memory_tool_call` | 通话中工具可观测性 | 只保存查询摘要、数量、状态、延迟和引擎模式 |
| `content_review_signal` | 内容质量反馈 | 按租户和长者隔离;保存资源、动作、问题类型、模型和提示词版本,不复制正文 |
2026-08-26 06:41:07 +08:00
2026-08-29 21:56:51 +08:00
内容与记忆数据库结构目前覆盖到V41。迁移在独立临时数据库执行`up → down 1 → up`并核对最终版本与`dirty=false`,测试数据库随后删除,不对开发或生产数据库做回滚验证。
2026-08-26 06:41:07 +08:00
## 6. 安全、隐私与可观测性
- 供应商事件记录只保存事件类型、状态、时间和话轮数量,不把转写正文复制进事件审计正文。
- 普通故事生成前执行本地敏感规则基线;验证码、支付账户和高敏感医疗信息默认不进入普通故事。该规则集只是生产内容安全服务接入前的最低门禁。
- 内容生成API Key由Worker环境注入,Embedding API Key由API和Worker环境注入;密钥不写入数据库、候选记录、HTTP响应或错误正文,业务记录只保留`ark:<model-id>`和提示词版本。
- Embedding API Key由API和Worker环境注入;记忆只记录模型引用,不记录密钥。只有普通敏感级别、当前有效且已授权的记忆进入外部向量化和语音召回。
- 只有冻结快照允许保存字幕时才落`conversation_turn`;故事整理和个性化记忆分别使用独立授权门禁。
- 故事、事实和记忆均保留租户、长者和来源边界;所有网页接口从OIDC主体重新解析家庭成员关系,不信任客户端传入租户。
- 人工修订、确认、拒绝和授权撤回记录审计事件;写操作使用强ETag防止并发覆盖。
2026-08-29 21:56:51 +08:00
- 通话创建和通话中工具均使用服务端固定的租户、长者、授权和版本边界;供应商或模型不能传入自由租户、家庭或长者过滤条件。
- 供应商原生记忆默认关闭,只有完成真实账号的数据条款、隔离、同步、删除和容量验证后才配置`PROVIDER_MEMORY_MODE=managed_http`;数据库仍是唯一主数据。
2026-08-26 06:41:07 +08:00
## 7. 验证结果
2026-08-29 21:56:51 +08:00
- 新增迁移已在隔离数据库完成升库与数据库组合测试;OpenAPI 0.70.0可生成TypeScript类型,React类型检查和生产构建通过。
2026-08-26 06:41:07 +08:00
- 本地内容策略单元测试覆盖验证码、银行卡号、高敏感医疗词和普通生活经历。
- 方舟兼容适配器轻量验证覆盖Bearer鉴权、请求路径、结构化响应校验、HTTP 429重试分类;真实PostgreSQL组合验证模型调用期间不持有通话行锁,模型返回后正常生成候选,敏感话轮不会调用外部模型。
- 方舟Embedding适配器轻量验证覆盖Bearer鉴权、`/embeddings`请求、模型/维度参数、1536维结果和错误维度拒绝;真实PostgreSQL组合验证向量生成期间不持有索引行锁,返回后可激活索引。
- 端到端通话冒烟验证最终话轮去重、两类整理结果、故事来源、人工确认前零记忆、确认后记忆生成及索引生效;下一次通话冻结1个记忆版本。
- 记忆修订生成第2版并重新索引;停用后索引立即失效且新通话召回0条;重新启用再次进入索引流程。
- 永久删除验证覆盖`PENDING_DELETE → DELETED`、删除中重新激活稳定拒绝、向量清理、数据库正文替换为删除标记和删除后新通话召回0条。
2026-08-29 21:56:51 +08:00
- 通话中记忆工具验证覆盖短期令牌签名、固定通话/任务边界、跨长者不可选、结果长度上限、实际使用追踪和Embedding异常降级。
- 供应商记忆适配器验证覆盖Bearer鉴权、幂等同步、旧版本删除、单条删除、主体隐私删除前对账和失败状态保留;当前只使用模拟HTTP/内存适配器,真实供应商账号仍待外部验收。
- 内容质量反馈验证覆盖九类问题、事实不可覆盖版本、按模型/提示词聚合和跨租户拒绝;维护端只展示汇总,不显示故事、事实或记忆正文。
2026-08-26 06:41:07 +08:00
- React故事与记忆页以页面内管理区提供正文修订、启停和永久删除;未修改正文、空原因、未确认删除均不能提交,删除中和历史终态只读,不再依赖浏览器弹窗。
- OIDC、SaaS、设备、成员和通话五组冒烟全部通过;测试结束后内容表残留为0,Outbox无待处理、处理中或死信事件。
- 当前方舟内容与Embedding协议均使用本地模拟端点验证,尚未使用业务方真实API Key核验模型效果、召回质量、额度和数据条款;真实语音供应商与真实设备也仍待验收。