# 字幕、故事审核与已确认记忆设计 ## 1. 目标与边界 本批次实现“最终转写入库—通话整理—故事候选—人工审核—已确认记忆—索引生效”的完整业务链。系统把模型输出始终视为候选内容;只有具备`story.review`能力的家庭成员明确确认,并且长者当前存在有效`personalized_memory`授权时,候选才可转为长期记忆。 当前抽取器支持两类可切换适配器:默认的`local-deterministic-extractor`用于本地业务验证,`ark:`通过火山方舟OpenAI兼容Chat Completions接口生成结构化候选。向量生成与查询同样支持`local-deterministic-hash-v1`和`ark:`两类适配器,并强制使用数据库既定的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`故事 | | 记忆索引 | 待索引记忆版本 | 再次校验记忆为当前版本、状态有效且授权仍有效;不满足条件时清空向量并标记`STALE` | `ACTIVE`或`STALE`索引 | | 生产向量化 | 普通敏感级别的当前记忆正文或新通话检索语句 | API与Worker使用同一方舟Embedding模型;请求1536维float向量并校验维度和有限数值;网络、超时、408/425/429/5xx可靠重试 | `ark:`索引或固定边界查询向量 | | 记忆修订与停用 | 当前记忆、正文或状态、原因、强ETag | 正文变化创建新版本并立即使旧索引失效;停用后不再召回;重新启用必须重新校验授权并重新索引;`PENDING_DELETE/SUPERSEDED/DELETED`均禁止修改 | 新记忆版本或状态 | | 记忆永久删除 | 当前记忆、删除原因、强ETag | 事务内先进入`PENDING_DELETE`并清空活动向量,使新通话立即停止召回;该状态不可通过修改接口恢复;Outbox后台把索引标记删除、清除所有记忆版本正文并记录完成审计 | `DELETED`记忆和索引 | | 新通话记忆检索 | 服务端已确定的客户组织、长者和通话目标 | SQL同时限制租户、长者、当前授权、活动记忆、当前版本、普通敏感级别、有效期和活动索引;最多5条 | 冻结到`context_snapshot.memory_versions`的版本引用和最小背景摘要 | | 授权撤回 | `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[冻结记忆版本和背景摘要] ``` 供应商停止事件和最终转写事件可能存在先后竞争。停止事件会发布内容整理待办;如果话轮尚未到达,处理器返回可重试错误,由Outbox退避重试,避免把“暂无话轮”错误当成永久空内容。 内容处理器先在无事务状态读取已结束通话、冻结授权和有序最终话轮,执行本地敏感内容判断;只有普通内容才调用外部模型。模型返回后才开启短事务并锁定通话,重新读取状态、授权和全部话轮并比较SHA-256输入指纹。若模型调用期间授权撤回,则不写入结果;若话轮发生变化,则本次任务进入可靠重试并基于新输入重新生成。该顺序满足“数据库事务不得跨越大模型网络调用”的需求约束。 记忆索引也先在无事务状态读取当前版本和授权,事务外生成Embedding,返回后才锁定记忆与索引行重新校验;修订、停用或撤回授权发生在模型调用期间时,旧结果不会被激活。新通话创建前仅在设备绑定、语音授权和个性化记忆授权均可用时生成查询向量;事务内仍以固定租户、长者、授权、状态和敏感级别条件检索。Embedding临时不可用时,本次通话以`pgvector-degraded-v1`记录降级且不注入记忆,不阻断基本语音通话。普通顺序幂等重放和同键正文冲突会先读取已完成幂等记录,不重复调用Embedding;并发首请求仍由数据库唯一键保证只创建一个通话。 ## 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`;原因必填 | 拒绝候选 | | `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 | 可靠受理永久删除任务 | 统一冲突码包括`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` | 可审核事实候选 | 区分长者陈述与模型推断;保存源话轮、敏感级别和状态 | | `curated_memory` | 长期记忆聚合根 | 当前版本、状态、有效期、敏感级别和强版本号 | | `memory_version` | 不可覆盖的记忆版本 | 记忆内版本号唯一;确认人、确认时间、原因、来源和内容摘要 | | `memory_index_entry` | 记忆检索索引状态 | 记忆版本和索引类型唯一;向量维度1536;状态可失效和删除 | V8迁移已经在独立空数据库执行`up → down -all → up`,最终为`8|dirty=false`,九张内容与记忆表均存在。测试数据库随后已删除。 ## 6. 安全、隐私与可观测性 - 供应商事件记录只保存事件类型、状态、时间和话轮数量,不把转写正文复制进事件审计正文。 - 普通故事生成前执行本地敏感规则基线;验证码、支付账户和高敏感医疗信息默认不进入普通故事。该规则集只是生产内容安全服务接入前的最低门禁。 - 内容生成API Key由Worker环境注入,Embedding API Key由API和Worker环境注入;密钥不写入数据库、候选记录、HTTP响应或错误正文,业务记录只保留`ark:`和提示词版本。 - Embedding API Key由API和Worker环境注入;记忆只记录模型引用,不记录密钥。只有普通敏感级别、当前有效且已授权的记忆进入外部向量化和语音召回。 - 只有冻结快照允许保存字幕时才落`conversation_turn`;故事整理和个性化记忆分别使用独立授权门禁。 - 故事、事实和记忆均保留租户、长者和来源边界;所有网页接口从OIDC主体重新解析家庭成员关系,不信任客户端传入租户。 - 人工修订、确认、拒绝和授权撤回记录审计事件;写操作使用强ETag防止并发覆盖。 - 通话创建已接入内部数据库召回并冻结实际使用的记忆版本;调用方不能传入自由租户或长者过滤条件。当前尚未开放通话中工具检索接口。 ## 7. 验证结果 - Go全仓测试通过,OpenAPI 0.10.0可生成TypeScript类型,React生产构建通过。 - 本地内容策略单元测试覆盖验证码、银行卡号、高敏感医疗词和普通生活经历。 - 方舟兼容适配器轻量验证覆盖Bearer鉴权、请求路径、结构化响应校验、HTTP 429重试分类;真实PostgreSQL组合验证模型调用期间不持有通话行锁,模型返回后正常生成候选,敏感话轮不会调用外部模型。 - 方舟Embedding适配器轻量验证覆盖Bearer鉴权、`/embeddings`请求、模型/维度参数、1536维结果和错误维度拒绝;真实PostgreSQL组合验证向量生成期间不持有索引行锁,返回后可激活索引。 - 端到端通话冒烟验证最终话轮去重、两类整理结果、故事来源、人工确认前零记忆、确认后记忆生成及索引生效;下一次通话冻结1个记忆版本。 - 记忆修订生成第2版并重新索引;停用后索引立即失效且新通话召回0条;重新启用再次进入索引流程。 - 永久删除验证覆盖`PENDING_DELETE → DELETED`、删除中重新激活稳定拒绝、向量清理、数据库正文替换为删除标记和删除后新通话召回0条。 - React故事与记忆页以页面内管理区提供正文修订、启停和永久删除;未修改正文、空原因、未确认删除均不能提交,删除中和历史终态只读,不再依赖浏览器弹窗。 - OIDC、SaaS、设备、成员和通话五组冒烟全部通过;测试结束后内容表残留为0,Outbox无待处理、处理中或死信事件。 - 当前方舟内容与Embedding协议均使用本地模拟端点验证,尚未使用业务方真实API Key核验模型效果、召回质量、额度和数据条款;真实语音供应商与真实设备也仍待验收。