Files
story/docs/content-memory.md
T

13 KiB

字幕、故事审核与已确认记忆设计

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故事
记忆索引 待索引记忆版本 再次校验记忆为当前版本、状态有效且授权仍有效;不满足条件时清空向量并标记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的版本引用和最小背景摘要
授权撤回 personalized_memory授权 在同一事务内停用该长者的活动记忆,清空活动/待处理索引向量并标记失效 DISABLED记忆、STALE索引

3. 业务流程

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:<model-id>和提示词版本。
  • 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核验模型效果、召回质量、额度和数据条款;真实语音供应商与真实设备也仍待验收。