Skip to content

成员5TimeFlow MVP 函数级接口设计 #73

Description

@znnnnnnn-wil

TimeFlow MVP 函数级接口设计

负责模块:OCR 识别模块、ASR 语音识别模块、对象存储模块、用户账号模块、全局用户画像管理模块、任务级画像管理模块
技术选型:LangGraph、百炼 qwen3-vl-flash、百炼 qwen3-asr-flash、MinIO、PostgreSQL、Pydantic、短期无状态 JWT
对齐依据:《TimeFlow MVP 函数级接口合作方式》《TimeFlow MVP 数据库设计》及本轮确认结论
名称说明:统一使用“对象存储模块”,具体实现为 MinIO。

1. 模块职责说明

1.1 总体数据流

客户端选择图片或音频
  → 使用本地 URI 立即显示预览
  → POST /api/media(multipart/form-data)

后端
  → 从登录凭据取得 user_id
  → 创建 media_assets(status=uploading)
  → 校验声明信息和文件头
  → 边读取边统计大小、计算 SHA-256、流式写入 MinIO
  → 更新 media_assets(status=uploaded)
  → 创建 conversation_messages(processing)
  ← 返回 media_id + message_id + source_url + processing

异步识别
  → 创建 agent_run_records(started)
  → 生成仅供百炼读取的短期 model_input_url
  → 调用 qwen3-vl-flash / qwen3-asr-flash
  ← raw_content + usage + provider_request_id
  → 更新 conversation_messages(raw_content, succeeded)
  → 更新 agent_run_records(succeeded/failed)
  → 可选:将脱敏后的详细模型响应、usage、provider_request_id 发送到 LangSmith 或同类可观测平台
  → 将 raw_content 作为 LangGraph 状态输入,母 AI 编排模块继续理解意图

识别失败时,原图片或音频仍然保留在聊天窗口。消息状态改为 failed,用户可点击重试;每次重试产生新的 agent_run_records,不重复创建聊天消息和媒体对象。

1.2 数据库复用与新增

数据职责 最终承载位置 结论
用户、密码哈希、业务用户 ID users 现有结构可支撑注册登录基础能力
显式全局画像 user_profiles 现有结构可支撑 P0
聊天媒体消息与最终 OCR/ASR 文本 conversation_messages 复用,但需补 media_idprocessing_status,并允许处理中的媒体消息 raw_content 暂为空
原始文件元数据、MinIO 对象键和存储状态 media_assets 当前数据库缺失,建议新增
每次 OCR/ASR 调用状态与失败信息 agent_run_records 复用,不新增 media_recognitions
完整模型输入输出、Token、耗时和百炼请求详情 可选的 LangSmith 等可观测平台 不复制到业务库;未接入或故障不得影响主流程
用户明确表达的任务级画像证据 task_profile_observations 现有结构可支撑
经确认生效的任务级画像 task_profile_snapshots 现有结构可支撑
全局/任务画像待确认内容 write_requests 复用,不新增通用 profile_candidates
用户确认 operation_confirmations 现有结构可支撑
关键业务审计 audit_events 现有结构可支撑

1.3 核心边界

  • 客户端不保存 PostgreSQL、MinIO 或百炼密钥。
  • 客户端只通过后端 API 上传和读取媒体,不直接访问 MinIO。
  • MinIO bucket 默认私有。
  • source_url 是稳定的应用层地址,例如 /api/media/{media_id}/content
  • model_input_url 是 5 分钟左右有效的 MinIO 预签名 GET URL,只在后端内部生成,不返回客户端、不落库。
  • OCR/ASR 只做识别,不判断最终业务意图,不生成数据库动作,不创建 Schedule/Todo/Goal/Task。
  • conversation_messages 保存母 AI 实际读取的 raw_content;LangGraph 负责母 AI 工作流编排;详细模型日志可选进入 LangSmith 或同类平台。
  • 全局画像是业务事实,更新必须通过确认链。
  • 任务级原始观察可以追加保存;AI 归纳快照必须确认后才能成为当前画像。

1.4 各模块职责边界

标准模块名 负责做什么 明确不做什么
对象存储模块 接收后端文件流;生成安全对象键;校验大小、文件头和哈希;流式写入 MinIO;通过稳定资源接口返回媒体;为百炼生成短期读取 URL;处理软删除和孤儿对象 不保存文件二进制到 PostgreSQL;不做 OCR/ASR;不向客户端暴露 MinIO 密钥或地址
OCR 识别模块 校验图片媒体;构造视觉识别请求;调用 qwen3-vl-flash;返回原始文字、可选结构化提取和警告 不读取业务表;不直接创建业务对象;不把不确定内容补造成事实
ASR 语音识别模块 校验音频规格;调用 qwen3-asr-flash;返回转写文本、语言/时长等技术结果 不做实时流式 ASR;不处理超过 5 分钟或 10MB 的音频;不理解业务意图
用户账号模块 注册、登录、密码验证、用户状态查询、业务用户唯一 ID 管理,以及短期无状态 JWT 的签发和验证 不管理服务端登录会话;不签发 refresh token;不维护 token 黑名单或设备会话;不保存明文密码;不把 token 交给 Agent
全局用户画像管理模块 查询 user_profiles;通过确认流程更新显式画像;向拆分、重排和复盘提供当前生效画像 MVP 不静默学习长期行为;不维护未经确认的全局画像事实
任务级画像管理模块 追加 task_profile_observations;归纳任务级画像候选;通过确认流程生成新的 task_profile_snapshots;查询当前快照 不跨长目标合并;不自动写入全局画像;不阻塞拆分和重排主流程

1.5 URL 与资源标识定义

名称 示例 使用方 是否落库 生命周期
media_id UUID 所有后端模块、消息关联 跟随媒体记录
object_key users/{user_id}/2026/07/{media_id}.png 对象存储模块 是,位于 media_assets 跟随 MinIO 对象
source_url /api/media/{media_id}/content 客户端聊天展示 现有消息表可保存;也可由 media_id 生成 稳定
model_input_url MinIO 预签名 GET URL 百炼模型 约 5 分钟
客户端本地预览 URI file://...content://... 或 Web Blob URL 客户端 当前页面/本地生命周期

公开 API 不返回 download_url、MinIO endpoint、bucket 或 object key。客户端请求 source_url 后,后端鉴权并从 MinIO 流式返回图片或音频。图片响应支持 ETag 和私有缓存;音频响应支持 HTTP Range。

2. 前后端 API 表

页面 / 场景 API 名称 HTTP 方法 路径建议 调用方 承接模块 请求参数 响应数据 数据来源 是否写入 是否需要确认 数据库缺口 数据流转说明
AI 对话发送图片/语音 上传媒体消息 POST /api/media 前端交互与展示模块 对象存储模块 multipart:file, media_type, idempotency_keyuser_id 从访问令牌取得 media_id, message_id, source_url, processing_status 请求文件流、MinIO、media_assetsconversation_messages 是:媒体、消息、运行记录 否;“发送”本身是用户明确操作 新增 media_assets;调整消息表 后端先校验声明,再创建 uploading 记录;边读取边校验、计算 SHA-256 并写 MinIO;成功后创建 processing 消息并异步投递 OCR/ASR。接口不等待模型,不创建时间业务事实
聊天回看图片/音频 获取媒体内容 GET /api/media/{media_id}/content 前端交互与展示模块 对象存储模块 path:media_id;header:Range? 二进制流、Content-Type, Content-Length, ETag, Accept-Ranges media_assets、MinIO 依赖新增 media_assets 后端从登录态取得 user_id,校验归属后流式读取;图片支持私有缓存,音频支持 206 Range;不返回 MinIO 地址或密钥
识别失败后的用户操作 重试媒体识别 POST /api/media/{media_id}/recognition/retry 前端交互与展示模块 OCR 识别模块 / ASR 语音识别模块 path:media_id;body:message_id, idempotency_key message_id, processing_status, accepted_at media_assetsconversation_messagesagent_run_records 是:运行摘要、消息状态 否;点击重试即明确操作 消息表需 processing_status 只允许 failed 且可重试的消息;按 modality 路由识别;创建新运行摘要,不重复创建媒体和消息,不直接触发业务写入
对话页轮询识别结果 查询媒体消息状态 GET /api/messages/{message_id} 前端交互与展示模块 基础业务模块 path:message_id message_id, raw_content?, source_url, modality, processing_status, error_code? conversation_messages 消息表需 media_id, processing_status 且 processing 时允许 raw_content 为空 基础业务模块按当前用户查询消息并返回;也可由消息推送模块通知。该接口只展示消息,不重新识别
注册页 注册用户 POST /api/auth/register 前端交互与展示模块 用户账号模块 email, password, display_name?, timezone? user_id, business_user_id, access_token, expires_at 请求数据、users 是:用户记录 否;提交注册即明确操作 基础注册无缺口;刷新会话策略待定 规范邮箱、校验密码、生成业务用户 ID、哈希密码并通过全局数据模块保存;不返回密码哈希
登录页 用户登录 POST /api/auth/login 前端交互与展示模块 用户账号模块 email, password user_id, business_user_id, access_token, expires_at 请求数据、users、服务端 JWT 签名配置 否;提交登录即明确操作 无;不新增会话表 查询用户并验证密码哈希和状态,签发短期 access token;统一失败文案以避免账号枚举。token 不落库、不传给 Agent;到期后用户重新登录
个人主页 查询当前用户 GET /api/users/me 前端交互与展示模块 用户账号模块 无;user_id 从访问令牌取得 id, business_user_id, email, display_name, timezone, status users 依据认证用户查询并返回安全字段;不返回 password_hash
设置页/母 AI 上下文 查询全局画像 GET /api/users/me/profile 前端交互与展示模块 全局用户画像管理模块 无;user_id 从访问令牌取得 profile_data, version, source, updated_at user_profiles 查询当前显式画像;无记录返回空画像;不基于行为静默推断
设置页保存画像 创建全局画像写入请求 PATCH /api/users/me/profile 前端交互与展示模块 全局用户画像管理模块 base_version, profile_patch, idempotency_key write_request_id, payload_hash, preview, expires_at user_profileswrite_requests 是:仅写候选请求 校验 patch 和 base_version,以 update_user_profile 创建写入候选;确认后才由全局数据模块落盘,本接口不直接修改画像
长目标拆分/重排 查询当前任务级画像 GET /api/long-goals/{long_goal_id}/task-profile 前端交互与展示模块 任务级画像管理模块 path:long_goal_id current_snapshot?, recent_observations[], version long_goalstask_profile_snapshotstask_profile_observations 校验目标归属,只返回当前目标的快照与观察;不跨目标合并
用户明确表达目标习惯 追加任务级观察 POST /api/long-goals/{long_goal_id}/task-profile/observations 前端交互与展示模块 任务级画像管理模块 source, raw_text, structured_observation, source_message_id?, idempotency_key observation_id, created_at long_goalstask_profile_observations 是:原始观察 否;原消息/表单是用户明确表达 建议补 idempotency_key 或来源唯一约束 校验目标归属和来源后只追加原始证据;不覆盖旧观察,也不直接生成生效快照
AI 归纳任务画像 创建任务级画像写入请求 POST /api/long-goals/{long_goal_id}/task-profile/snapshot-request 前端交互与展示模块 任务级画像管理模块 base_version, profile_summary, observation_ids, idempotency_key write_request_id, payload_hash, preview, expires_at 当前快照、观察、write_requests 是:仅写候选请求 将 LangGraph 工作流产生的结构化候选写入 write_requests;确认后插入新快照并切换 is_current;不在本接口直接生效

说明:画像确认可以复用系统级确认 API,不在成员 5 内重复设计。所有画像应用必须接收 write_request_id,不能只传 is_confirmed=true。MVP 不提供 /api/auth/refresh 和服务端 /api/auth/logout;用户退出登录由客户端删除本地 access token 完成。

3. 模块对外函数表

所属模块 函数中文名称 函数类型 调用方 输入参数 返回值 依赖数据 依赖模块 是否写入 是否需要确认 失败处理 数据库缺口 数据流转说明
对象存储模块 流式保存媒体消息 写入运行记录 前端交互与展示模块 user_id, file_stream, file_name, declared_content_type, declared_size?, media_type, idempotency_key MediaMessageUploadDTO 文件流、用户状态、媒体/消息记录 用户账号模块、全局数据模块、系统日志模块 否;发送是明确操作 超限 413、类型非法 415;单边成功执行补偿 新增 media_assets;调整消息表 取得认证用户后流式写 MinIO,并请求全局数据模块保存元数据和 processing 消息;输出交给前端及异步识别,不等待模型,不生成业务事实
对象存储模块 流式读取媒体 查询 前端交互与展示模块 user_id, media_id, range_header? MediaContentStreamDTO media_assets、MinIO 对象 用户账号模块、全局数据模块 越权、已删除或对象不存在统一 404 依赖新增 media_assets 校验归属后从 MinIO 返回二进制流;不返回 object_key、预签名地址或密钥
对象存储模块 生成模型访问地址 查询 OCR 识别模块、ASR 语音识别模块 user_id, media_id, expires_seconds=300 ModelAccessURLDTO 媒体归属、状态、object_key 用户账号模块、全局数据模块 非 uploaded、过期或不可达则失败 依赖新增 media_assets 校验媒体后生成短期 model_input_url 交给百炼;只在内存流转,不返回客户端、不落库
对象存储模块 软删除媒体 确认落盘 母 AI 编排模块、前端交互与展示模块 user_id, media_id, write_request_id DeleteMediaResultDTO 媒体状态、消息引用、已确认写请求 全局数据模块、系统日志模块 引用冲突则拒绝;物理删除失败进入补偿 media_assets 需软删除字段 验证确认后先软删除数据库记录;MinIO 物理清理由后台任务完成,不级联破坏消息
OCR 识别模块 识别图片输入 生成识别文本 对象存储模块、母 AI 编排模块 OCRRecognizeInput OCRRecognizeOutput 图片元数据、短期模型 URL、提示词版本 对象存储模块、LLM 管理模块、系统日志模块 是:消息状态和运行摘要 有限重试;Schema 失败或超时标记 failed 消息表需状态字段 从对象存储取得短期 URL,经 LLM 管理模块调用 qwen3-vl-flash,校验输出后保存 raw_content,再把消息交给 LangGraph;不直接写任务事实
ASR 语音识别模块 识别短语音输入 生成识别文本 对象存储模块、母 AI 编排模块 ASRRecognizeInput ASRRecognizeOutput 音频元数据、短期模型 URL、ASR 选项 对象存储模块、LLM 管理模块、系统日志模块 是:消息状态和运行摘要 超 300 秒/10MB 拒绝;超时标记 failed 消息表需状态字段 校验规格后调用 qwen3-asr-flash,保存转写文本并交给 LangGraph;不自动换模型、不理解业务意图
用户账号模块 注册业务用户 写入 前端交互与展示模块 RegisterUserRequest AuthResultDTO 注册字段、邮箱唯一性 全局数据模块、系统日志模块 否;提交注册是明确操作 邮箱冲突 409;密码不合规 422 规范邮箱、生成业务用户 ID、哈希密码并保存;仅返回安全用户字段和 token
用户账号模块 验证访问令牌 校验 对象存储模块、基础业务模块、全局用户画像管理模块、任务级画像管理模块 access_token AuthenticatedUserDTO token 签名/过期、用户状态 全局数据模块 过期 401、disabled 403 验签后查询必要用户状态,把可信 user_id 交给后端模块;不把 token 传给 Agent
用户账号模块 校验资源归属 校验 对象存储模块、全局用户画像管理模块、任务级画像管理模块 user_id, resource_type, resource_id OwnershipCheckDTO 对应资源的 user_id 全局数据模块 不匹配统一 404 媒体归属依赖 media_assets.user_id 查询资源归属并返回布尔/规范错误;不泄漏资源是否属于其他用户
全局用户画像管理模块 查询生效全局画像 查询 母 AI 编排模块、长任务拆分 Agent、重排 Agent、复盘 Agent、前端交互与展示模块 user_id UserProfileDTO user_profiles 当前版本 全局数据模块 无记录返回空画像 读取显式画像,作为 LangGraph 上下文或页面数据返回;不静默推断
全局用户画像管理模块 创建全局画像更新请求 生成候选 母 AI 编排模块、前端交互与展示模块 user_id, base_version, patch, idempotency_key ProfileWritePreviewDTO 当前画像、版本、允许字段 全局数据模块、系统日志模块 是:write_requests 版本冲突返回最新画像 校验 patch 后只创建候选和预览;不直接修改 user_profiles
全局用户画像管理模块 应用全局画像更新 确认落盘 母 AI 编排模块 user_id, write_request_id, confirmation_id UserProfileDTO 写请求、确认记录、当前画像版本 全局数据模块、系统日志模块 确认无效、过期或版本冲突则回滚 调用全局数据模块校验确认并原子更新画像、增加版本、记录审计
任务级画像管理模块 追加任务级观察 写入运行记录 母 AI 编排模块、前端交互与展示模块 TaskProfileObservationInput TaskProfileObservationDTO 长目标归属、明确用户表达 全局数据模块 否;来源已是明确表达 来源不合法或目标不匹配则拒绝 建议补幂等/来源唯一约束 校验后只追加观察;输出供后续归纳,不直接成为当前画像
任务级画像管理模块 查询当前任务级画像 查询 母 AI 编排模块、长任务拆分 Agent、重排 Agent、复盘 Agent user_id, long_goal_id TaskProfileContextDTO 当前快照、近期观察、目标归属 用户账号模块、全局数据模块 目标不存在或越权返回 404 仅组装同一长目标上下文供 LangGraph/Agent 使用;禁止跨目标聚合
任务级画像管理模块 生成任务级画像快照候选 生成候选 母 AI 编排模块 TaskProfileInductionInput ProfileWritePreviewDTO | NoCandidateDTO 明确观察、当前快照 LLM 管理模块、全局数据模块、系统日志模块 是:write_requests 证据不足返回 no_candidate;模型失败可重试 LangGraph 触发归纳,通过 LLM 管理模块生成结构化候选并保存写请求;不直接生效且不阻塞主业务
任务级画像管理模块 应用任务级画像快照 确认落盘 母 AI 编排模块 user_id, long_goal_id, write_request_id, confirmation_id TaskProfileSnapshotDTO 当前快照、已确认写请求 全局数据模块、系统日志模块 版本冲突全部回滚 同一事务中关闭旧 current 快照并插入新版本,记录审计后返回当前快照

4. 模块依赖函数表

所属模块 依赖模块 需要的函数中文名称 依赖原因 输入参数 返回值 是否强依赖 缺失时处理
对象存储模块 用户账号模块 验证用户与状态 上传、读取前鉴权 access_token/user_id AuthenticatedUserDTO 拒绝请求
对象存储模块 全局数据模块 创建/更新媒体与消息记录 保存元数据和处理状态 媒体、消息字段 media_id, message_id 清理对象或标记 failed
OCR 识别模块 对象存储模块 生成模型访问地址 百炼读取私有图片 user_id, media_id ModelAccessURLDTO 识别失败,保留原消息
OCR 识别模块 LLM 管理模块 调用百炼视觉模型 统一模型、地域、密钥和重试 model, messages, schema 模型结果 标记消息 failed
ASR 语音识别模块 对象存储模块 生成模型访问地址 百炼读取私有音频 user_id, media_id ModelAccessURLDTO 识别失败
ASR 语音识别模块 LLM 管理模块 调用百炼 ASR 统一 DashScope 接入 model, audio_url, asr_options 模型结果 标记可重试错误
OCR 识别模块 系统日志模块 创建并结束 Agent 运行摘要 复用 agent_run_records 追踪图片识别 source_message_id, correlation_id, component_name, status agent_run_id 进入本地补偿日志,但不覆盖已成功识别结果
ASR 语音识别模块 系统日志模块 创建并结束 Agent 运行摘要 复用 agent_run_records 追踪语音识别 source_message_id, correlation_id, component_name, status agent_run_id 进入本地补偿日志,但不覆盖已成功识别结果
OCR 识别模块 消息推送模块 推送识别完成/失败 更新客户端图片消息气泡 message_id, status 发送结果 客户端轮询消息
ASR 语音识别模块 消息推送模块 推送识别完成/失败 更新客户端语音消息气泡 message_id, status 发送结果 客户端轮询消息
用户账号模块 全局数据模块 查询/写入用户 注册登录 用户字段 用户 DTO 认证不可用
全局用户画像管理模块 全局数据模块 查询画像、创建写请求、应用画像 版本和确认控制 user_id, patch, base_version 画像/写请求 DTO 只读降级或拒绝写入
任务级画像管理模块 全局数据模块 查询目标、观察和快照 构造目标画像上下文 user_id, long_goal_id 观察/快照 DTO 不生成候选
任务级画像管理模块 LLM 管理模块 归纳画像摘要 从明确观察生成结构化候选 观察列表、当前快照 结构化摘要 返回 no_candidate 或稍后重试

5. 模块内部函数表

所属模块 内部函数中文名称 上游函数 下游函数 输入参数 返回值 依赖数据 是否调用 LLM 是否写入 失败处理 数据流转说明
对象存储模块 校验上传声明 流式保存媒体消息 创建媒体占位记录 media_type, file_name, declared_type, declared_size? 规范化元信息 白名单、大小上限 超限或扩展名非法立即拒绝 接收 API 元数据并输出可信声明;不读取完整文件、不写数据库
对象存储模块 创建媒体占位记录 校验上传声明 生成对象键/读取文件头 user_id, media_type, file_name, idempotency_key media_id, object_key, status=uploading users, media_assets 幂等键重复返回原记录 通过全局数据模块创建 uploading 状态,供后续 MinIO 上传关联
对象存储模块 检测真实媒体类型 创建媒体占位记录 流式校验和上传 文件头、声明类型 detected_content_type, extension 文件头魔数、类型白名单 声明与真实类型不符返回 415 仅读取最小文件头并把检测结果交给上传函数;不相信扩展名
对象存储模块 流式校验和上传 检测真实媒体类型 完成媒体与消息记录 file_stream, max_bytes, object_key size_bytes, sha256, etag 输入流、MinIO 是:MinIO 超限立即停止并清理未完成对象 每个数据块同时累计大小、更新 SHA-256 并写 MinIO;不整文件读入内存
对象存储模块 完成媒体与消息记录 流式校验和上传 投递异步识别 media_id, size_bytes, sha256, etag, source_url, modality MediaMessageUploadDTO media_assets, conversation_messages DB 失败登记孤儿对象并补偿删除 原子补齐媒体元数据并创建 processing 消息,输出给前端和识别队列
对象存储模块 构造媒体流响应 流式读取媒体 返回客户端 media, range_header? 状态码、响应头、二进制流 media_assets、MinIO 对象 非法 Range 返回 416 根据媒体类型和 Range 构造 200/206 响应;不泄漏对象存储内部标识
OCR 识别模块 构造 OCR 指令 识别图片输入 调用百炼视觉模型 model_input_url, extraction_mode, prompt_version 多模态 messages、JSON Schema 短期 URL、固定提示词 配置缺失立即失败 只描述图片可见内容和输出结构,交给 LLM 管理模块;不注入数据库写入指令
OCR 识别模块 解析 OCR 输出 调用百炼视觉模型 完成图片识别消息 provider response raw_content, extracted_fields, warnings 百炼响应、输出 Schema Schema 失败最多修复一次 验证并规范模型结果;不把不确定值补造成事实
ASR 语音识别模块 校验 ASR 规格 识别短语音输入 构造 ASR 选项 size_bytes, duration?, content_type 校验结果 media_assets、ASR 上限 >10MB 或 >300 秒拒绝 校验媒体记录与解析时长,合格后才允许调用模型
ASR 语音识别模块 构造 ASR 选项 校验 ASR 规格 调用百炼语音模型 language?, enable_itn, context_terms? asr_options 允许语种、有限实体词 未知/混合语种不传 language 构造 qwen3-asr-flash 参数;不注入完整画像或敏感信息
OCR 识别模块 原子完成图片识别消息 解析 OCR 输出 母 AI 编排模块 message_id, raw_content, metadata, agent_run_id 已更新消息 conversation_messages, agent_run_records 更新失败不触发 LangGraph,并进行补偿重试 先提交 raw_content 和成功状态,再将消息交给 LangGraph;不直接创建事项
ASR 语音识别模块 原子完成语音识别消息 解析 ASR 输出 母 AI 编排模块 message_id, raw_content, metadata, agent_run_id 已更新消息 conversation_messages, agent_run_records 更新失败不触发 LangGraph,并进行补偿重试 先提交转写文本和成功状态,再将消息交给 LangGraph;不直接创建事项
用户账号模块 规范化邮箱 注册业务用户/用户登录 查询用户 email 规范邮箱 输入字符串、邮箱规则 格式非法返回 422 规范用于唯一查询的邮箱;不记录原密码
用户账号模块 哈希或验证密码 注册业务用户/用户登录 创建认证结果 password, password_hash? hashbool Argon2id 配置 算法异常拒绝认证 注册时生成强哈希,登录时常量时间验证;明文只在请求内短暂存在
全局用户画像管理模块 校验画像 patch 创建全局画像更新请求 生成差异预览 current_profile, patch 规范 patch 当前画像、允许字段 未知字段或无效时间段返回 422 校验后输出最小变更集合;不直接应用画像
任务级画像管理模块 筛选明确观察 生成任务级画像快照候选 调用画像归纳模型 observations, long_goal_id 可用证据列表 任务级观察、来源信息 模糊推断或跨目标证据被排除 只保留用户明确表达且属于当前目标的证据,交给归纳模型
任务级画像管理模块 应用新快照版本 应用任务级画像快照 返回当前任务级画像 confirmed_request, current_snapshot 新快照 写请求、确认、当前版本 锁定失败或版本冲突全部回滚 在全局数据模块事务内切换 current 快照并记录审计;不跨目标写入

6. Python 数据结构声明

from datetime import datetime
from enum import Enum
from typing import Any, Literal
from uuid import UUID

from pydantic import BaseModel, Field


class MediaType(str, Enum):
    IMAGE = "image"
    AUDIO = "audio"


class MediaStorageStatus(str, Enum):
    UPLOADING = "uploading"
    UPLOADED = "uploaded"
    FAILED = "failed"
    DELETED = "deleted"


class MessageProcessingStatus(str, Enum):
    PROCESSING = "processing"
    SUCCEEDED = "succeeded"
    FAILED = "failed"


class MediaAssetDTO(BaseModel):
    id: UUID
    user_id: UUID
    media_type: MediaType
    bucket_name: str
    object_key: str
    original_file_name: str
    content_type: str | None = None
    size_bytes: int | None = None
    sha256: str | None = None
    etag: str | None = None
    duration_seconds: float | None = None
    status: MediaStorageStatus
    upload_idempotency_key: str
    version: int
    created_at: datetime
    uploaded_at: datetime | None = None
    is_deleted: bool = False
    deleted_at: datetime | None = None


class MediaUploadMetadata(BaseModel):
    media_type: MediaType
    file_name: str
    declared_content_type: str
    declared_size_bytes: int | None = Field(default=None, gt=0)
    idempotency_key: str


class MediaMessageUploadDTO(BaseModel):
    media_id: UUID
    message_id: UUID
    source_url: str
    processing_status: Literal["processing"] = "processing"
    content_type: str
    size_bytes: int
    sha256: str


class MediaContentStreamDTO(BaseModel):
    media_id: UUID
    content_type: str
    content_length: int
    etag: str | None = None
    status_code: Literal[200, 206] = 200
    content_range: str | None = None
    accept_ranges: Literal["bytes"] | None = None
    # binary_stream 由 Web 框架的流式响应承载,不进入 JSON。


class ModelAccessURLDTO(BaseModel):
    media_id: UUID
    url: str
    expires_at: datetime
    content_type: str
    size_bytes: int


class OCRExtractedFields(BaseModel):
    title_candidates: list[str] = Field(default_factory=list)
    datetime_texts: list[str] = Field(default_factory=list)
    participants: list[str] = Field(default_factory=list)
    locations: list[str] = Field(default_factory=list)
    action_items: list[str] = Field(default_factory=list)


class OCRRecognizeInput(BaseModel):
    user_id: UUID
    media_id: UUID
    message_id: UUID
    correlation_id: UUID
    extraction_mode: Literal["plain_text", "timeflow_input"] = "timeflow_input"
    locale: str | None = "zh-CN"


class OCRRecognizeOutput(BaseModel):
    raw_content: str
    extracted_fields: OCRExtractedFields | None = None
    warnings: list[str] = Field(default_factory=list)
    provider_request_id: str | None = None
    usage: dict[str, int | float] = Field(default_factory=dict)


class ASRRecognizeInput(BaseModel):
    user_id: UUID
    media_id: UUID
    message_id: UUID
    correlation_id: UUID
    language: str | None = None
    enable_itn: bool = True
    context_terms: list[str] = Field(default_factory=list)


class ASRRecognizeOutput(BaseModel):
    raw_content: str
    detected_language: str | None = None
    duration_seconds: float | None = None
    emotion: str | None = None
    warnings: list[str] = Field(default_factory=list)
    provider_request_id: str | None = None
    usage: dict[str, int | float] = Field(default_factory=dict)


class ConversationMediaMessageDTO(BaseModel):
    id: UUID
    user_id: UUID
    message_index: int
    role: Literal["user"] = "user"
    modality: Literal["voice_asr", "image_ocr"]
    raw_content: str | None = None
    source_url: str
    media_id: UUID
    processing_status: MessageProcessingStatus
    metadata: dict[str, Any] = Field(default_factory=dict)
    created_at: datetime


class RegisterUserRequest(BaseModel):
    email: str
    password: str = Field(min_length=10, max_length=128)
    display_name: str | None = Field(default=None, max_length=80)
    timezone: str = "Asia/Shanghai"


class LoginRequest(BaseModel):
    email: str
    password: str


class AuthenticatedUserDTO(BaseModel):
    user_id: UUID
    business_user_id: str
    email: str
    display_name: str | None = None
    timezone: str
    status: Literal["active", "disabled"]


class AuthResultDTO(BaseModel):
    user: AuthenticatedUserDTO
    access_token: str
    expires_at: datetime


class UserProfilePatch(BaseModel):
    working_hours: dict[str, list[str]] | None = None
    preferred_task_minutes: int | None = Field(default=None, ge=10, le=240)
    buffer_minutes: int | None = Field(default=None, ge=0, le=120)
    preferred_periods: list[str] | None = None
    unavailable_periods: list[str] | None = None
    planning_preferences: dict[str, Any] | None = None


class UserProfileDTO(BaseModel):
    user_id: UUID
    profile_data: dict[str, Any]
    version: int
    source: Literal["user_form", "user_explicit_input"]
    updated_at: datetime


class CreateUserProfileWriteRequest(BaseModel):
    base_version: int
    profile_patch: UserProfilePatch
    idempotency_key: str


class TaskProfileObservationInput(BaseModel):
    user_id: UUID
    long_goal_id: UUID
    source: Literal["user_explicit_input", "replan_reason"]
    raw_text: str
    structured_observation: dict[str, Any]
    source_message_id: UUID | None = None
    idempotency_key: str


class TaskProfileObservationDTO(BaseModel):
    id: UUID
    user_id: UUID
    long_goal_id: UUID
    source: str
    raw_text: str
    structured_observation: dict[str, Any]
    source_message_id: UUID | None = None
    created_at: datetime


class TaskProfileSnapshotDTO(BaseModel):
    id: UUID
    user_id: UUID
    long_goal_id: UUID
    profile_summary: dict[str, Any]
    version: int
    confirmation_id: UUID
    is_current: bool
    created_at: datetime


class TaskProfileContextDTO(BaseModel):
    current_snapshot: TaskProfileSnapshotDTO | None = None
    recent_observations: list[TaskProfileObservationDTO]


class TaskProfileInductionInput(BaseModel):
    user_id: UUID
    long_goal_id: UUID
    current_snapshot: TaskProfileSnapshotDTO | None = None
    observations: list[TaskProfileObservationDTO]
    idempotency_key: str


class ProfileWritePreviewDTO(BaseModel):
    write_request_id: UUID
    payload_hash: str
    base_version: int | None = None
    preview: dict[str, Any]
    expires_at: datetime


class NoCandidateDTO(BaseModel):
    reason: str


# 以下为前后端 API 的请求/响应包装;认证得到的 user_id 不允许由客户端 body 伪造。
class UploadMediaResponse(BaseModel):
    result: MediaMessageUploadDTO


class RetryMediaRecognitionRequest(BaseModel):
    message_id: UUID
    idempotency_key: str


class RetryMediaRecognitionResponse(BaseModel):
    message_id: UUID
    processing_status: Literal["processing"] = "processing"
    accepted_at: datetime


class MediaMessageStatusResponse(BaseModel):
    message: ConversationMediaMessageDTO
    error_code: str | None = None


class RegisterUserResponse(BaseModel):
    result: AuthResultDTO


class LoginResponse(BaseModel):
    result: AuthResultDTO


class CurrentUserResponse(BaseModel):
    user: AuthenticatedUserDTO


class QueryUserProfileResponse(BaseModel):
    profile: UserProfileDTO | None = None


class UpdateUserProfileResponse(BaseModel):
    write_request: ProfileWritePreviewDTO


class QueryTaskProfileResponse(BaseModel):
    context: TaskProfileContextDTO


class CreateTaskProfileObservationRequest(BaseModel):
    source: Literal["user_explicit_input", "replan_reason"]
    raw_text: str
    structured_observation: dict[str, Any]
    source_message_id: UUID | None = None
    idempotency_key: str


class CreateTaskProfileObservationResponse(BaseModel):
    observation: TaskProfileObservationDTO


class CreateTaskProfileSnapshotRequest(BaseModel):
    base_version: int
    profile_summary: dict[str, Any]
    observation_ids: list[UUID]
    idempotency_key: str


class CreateTaskProfileSnapshotResponse(BaseModel):
    write_request: ProfileWritePreviewDTO


class DeleteMediaResultDTO(BaseModel):
    media_id: UUID
    deleted: bool
    physical_cleanup_pending: bool


class OwnershipCheckDTO(BaseModel):
    owned: bool

file_stream 和二进制响应不应塞进 Pydantic JSON 模型;由 Web 框架的 multipart 文件对象和流式响应对象承载。上面的 DTO 只描述元数据和跨模块契约。

7. 数据库查缺补漏表

本表严格按协作协议检查“每个接口或函数需要什么数据、数据从哪里来、现有数据库是否支撑”。表中“写入”仅表示保存媒体、消息、运行摘要或候选请求;OCR/ASR 本身不会创建日程、待办、长目标等业务事实。

接口 / 函数名称 需要的数据 当前数据来源 现有数据库是否支持 缺口类型 建议补充 影响模块
上传媒体消息 / 流式保存媒体消息 media_id, user_id, media_type, bucket_name, object_key, original_file_name, content_type, size_bytes, sha256, etag, status, idempotency_key 文件请求、登录态、MinIO 缺表 新增 media_assets,记录归属、对象键、校验结果、状态和软删除信息 对象存储模块、全局数据模块、基础业务模块
创建并展示处理中的媒体消息 message_id, user_id, modality, media_id, source_url, processing_status, raw_content? 上传结果、conversation_messages 部分支持 缺字段、约束冲突 增加 media_id, processing_status;允许 processing/failed 媒体消息 raw_content 为空,并增加状态 CHECK 对象存储模块、OCR 识别模块、ASR 语音识别模块、基础业务模块
获取媒体内容 / 生成模型访问地址 媒体归属、对象键、状态、类型、ETag media_assets、MinIO 依赖缺表 新增 media_assets 后由后端动态生成稳定 source_url 或短期 model_input_url 对象存储模块、OCR 识别模块、ASR 语音识别模块
识别图片输入 source_message_id, correlation_id, component_name, function_name, status, error_code?, trace_id? agent_run_records 复用运行摘要表;详细模型信息不复制到业务库 OCR 识别模块、系统日志模块
识别短语音输入 同上,另需媒体大小、时长、类型 agent_run_records、新增 media_assets 部分支持 媒体元数据缺表 运行摘要复用 agent_run_records;媒体规格来自新增表 ASR 语音识别模块、系统日志模块
注册、登录、查询当前用户 用户主键、业务用户 ID、邮箱、密码哈希、显示名、时区、状态 users 不新增用户基础表;密码只存强哈希 用户账号模块、全局数据模块
签发和验证短期 JWT user_id, business_user_id, status,以及 JWT 的 sub, iat, exp, iss, aud users、服务端 JWT 签名配置 不新增 auth_sessions;access token 不落库;到期后重新登录 用户账号模块、全局数据模块
查询/更新全局画像 profile_data, version, source, updated_at;写请求和确认 user_profiles, write_requests, operation_confirmations 复用现有表与 update_user_profile 确认链 全局用户画像管理模块、母 AI 编排模块、全局数据模块
追加任务级观察 user_id, long_goal_id, source, raw_text, structured_observation, source_message_id, idempotency_key task_profile_observations 部分支持 幂等字段或唯一约束缺失 增加 idempotency_key 和用户维度唯一约束,或对 source_message_id 建来源唯一约束 任务级画像管理模块、全局数据模块
查询/应用任务级画像快照 当前快照、版本、确认 ID、观察证据 task_profile_snapshots, task_profile_observations, write_requests, operation_confirmations 复用现有表;不得新增重复画像表 任务级画像管理模块、母 AI 编排模块、全局数据模块

7.1 已有结构可以直接复用

需要的数据 当前来源 结论
用户主键、业务用户 ID、邮箱、密码哈希、显示名、时区、状态 users 注册登录基础数据已具备
显式全局画像 JSON、版本、来源 user_profiles P0 画像可直接复用
单一对话消息、模态、识别文本、媒体 URL、metadata conversation_messages 基础字段存在,但异步媒体消息需要补列和约束
Agent 组件、函数、状态、错误和 trace agent_run_records 可记录每次 OCR/ASR 与重试摘要
原始任务级观察 task_profile_observations 可直接复用
经确认的任务级画像版本 task_profile_snapshots 可直接复用
画像待确认候选 write_requests 复用 update_user_profilesave_task_profile_snapshot
用户确认与精确载荷哈希 operation_confirmations 可直接复用
关键写入/拒绝/安全事件 audit_events 可直接复用

7.2 画像部分不新增重复表

  • 不新增 goal_scoped_profiles:复用 task_profile_observations + task_profile_snapshots
  • 不新增通用 profile_candidates:待确认画像内容放入 write_requests.payload
  • P0 全局画像只使用 user_profiles.source=user_form/user_explicit_input
  • 任务画像快照必须关联 operation_confirmations,与数据库触发器保持一致。
  • 当前 task_profile_observations 没有独立幂等键。若开放追加观察 API,建议新增 idempotency_key(user_id,idempotency_key) 唯一约束;或者对带 source_message_id 的观察建立来源唯一约束,避免同一消息被重复归纳。

7.3 百炼调用约束

7.3.1 OCR

  • 固定 model="qwen3-vl-flash"
  • 使用对象存储模块生成的短期 model_input_url
  • 关闭思考模式并要求 JSON Schema 结构化输出。
  • 提示词要求只提取图片可见内容;不确定字段返回 null 或 warning;禁止补造。
  • plain_text 返回文本;timeflow_input 可附带时间、地点、人物和行动项候选,但仍不是业务事实。

7.3.2 ASR

  • 固定 model="qwen3-asr-flash"
  • 单音频最大 5 分钟、10MB;上传字节和媒体时长分别校验。
  • 已知单一语种时传 language;未知或中英混合时不传。
  • enable_itn 默认开启。
  • context_terms 仅允许有限实体词,不注入完整画像或敏感信息。
  • 情感字段属于模型技术输出,不能自动成为 Feedback 事实。

7.3.3 密钥与可观测性

  • DashScope API Key、Workspace ID、MinIO Access/Secret Key、数据库密码只保存在服务端密钥配置。
  • 客户端、业务数据库、日志正文和子 Agent 输入不得出现这些密钥。
  • 业务库只保存最小运行摘要;完整调用细节可选进入 LangSmith 或同类平台。可观测平台不可用时,LangGraph 主流程仍须正常运行。

7.4 写入与确认规则

  1. 用户发送媒体是明确交互,保存媒体、原始消息和 Agent 运行摘要不需要创建业务 write_requests
  2. OCR/ASR 只更新该消息的识别文本和技术状态,不修改时间管理业务事实。
  3. 母 AI 根据 raw_content 生成 Schedule/Todo/Goal/Task 等候选后,仍必须走 write_requests → operation_confirmations → assert_confirmed_write
  4. 用户手动保存全局画像也走 write_action=update_user_profile
  5. task_profile_observations 是用户明确表达的原始记录,可追加保存;AI 派生的 task_profile_snapshots 必须使用 write_action=save_task_profile_snapshot 并关联确认记录。
  6. 媒体删除先软删除;物理 MinIO 清理由后台补偿任务执行。
  7. media_assets 与 MinIO 无法共享 PostgreSQL 事务,必须使用状态机和孤儿对象扫描保证最终一致性。

8. 风险说明

8.1 关键失败处理

场景 系统处理
文件声明超过上限 读取前返回 413,不创建可见消息
真实文件头与声明类型不一致 返回 415,标记媒体 failed,清理对象
流式上传途中超过上限 立即停止读取和 MinIO 上传,标记 failed
MinIO 成功、数据库更新失败 记录孤儿对象,补偿任务根据 object_key 删除
数据库创建 uploading 成功、MinIO 失败 更新 media_assets=failed,不创建或撤销消息
OCR/ASR 超时 消息标记 failed,保留媒体,允许幂等重试
OCR/ASR 成功、更新消息失败 不触发母 AI;补偿任务依据 agent run/trace 重试消息更新
用户重复点击发送 使用 (user_id, upload_idempotency_key) 返回原结果,避免重复文件和消息
用户重复点击识别重试 以新的请求幂等键创建一次 Agent run,重复请求返回同一状态
客户端重新打开对话 消息 API 返回 source_url 和 processing_status;图片/音频仍可展示
跨用户读取媒体 统一返回 404,并按安全策略记录拦截

8.2 风险与控制

风险 等级 控制措施
后端中转上传占用带宽/连接 单文件限制 10MB;流式处理;并发、超时和请求体上限;不得默认整文件读内存
MinIO 私网地址百炼不可达 提供百炼可达的 HTTPS 网关;调用前 HEAD 验证短期 URL
模型临时 URL 被落库 source_url 只保存应用层地址;model_input_url 仅内存使用
恶意文件或伪造 MIME 扩展名、声明 MIME、文件头、大小四重检查;可选病毒扫描
OCR 补造时间或事项 非思考模式、结构化 Schema、禁止补造提示词、业务确认
模型重试重复计费 API 幂等、有限重试、agent_run_records 追踪;可选接入 LangSmith
消息 raw_content 暂空破坏旧约束 按 processing_status 重写数据库 CHECK;迁移前补充测试
密码或 access token 泄漏 Argon2id;全程 HTTPS;客户端安全存储;日志、LangGraph 状态和 Agent 输入不记录完整 token;使用短有效期并在过期后重新登录
无状态 JWT 无法单独撤销 MVP 不维护会话表和黑名单;缩短有效期;客户端退出时立即删除本地 token;高风险接口额外检查 users.status
任务级画像被跨目标复用 所有查询和外键都包含 (user_id,long_goal_id)
AI 画像未经确认生效 候选进入 write_requests;快照必须关联 confirmation_id

8.3 验收清单

  • 所有表格统一使用“对象存储模块”,实现明确为 MinIO。
  • 客户端只调用后端,不持有数据库、MinIO 或百炼密钥。
  • POST /api/media 使用流式上传,上传时统计大小、检测文件头并计算 SHA-256。
  • media_assets 能区分 uploading、uploaded、failed、deleted。
  • MinIO/数据库单边成功均有补偿处理。
  • 上传成功后立即产生可显示媒体的 processing 消息。
  • conversation_messages 能表示处理中、成功和失败,且 processing 媒体消息允许 raw_content 为空。
  • 图片使用稳定 source_url 和私有缓存;音频支持 HTTP Range。
  • 不新增 media_recognitions;每次识别和重试使用 agent_run_records
  • LangGraph 承担母 AI 工作流编排;LangSmith 仅为可选观测能力,未接入或故障不影响主流程。
  • 完整模型响应、Token 和 provider request ID 不复制到业务库;若接入观测平台,必须先脱敏。
  • OCR 固定使用 qwen3-vl-flash,输出经 Schema 校验。
  • ASR 固定使用 qwen3-asr-flash,严格执行 5 分钟/10MB 上限。
  • OCR/ASR 失败不删除原媒体,客户端可重试。
  • 母 AI 只有在 raw_content 成功落入消息后才继续处理。
  • 全局画像复用 user_profiles 和确认链。
  • 任务画像复用 observations/snapshots,不新增重复画像表。
  • 任务画像快照必须经过 save_task_profile_snapshot 确认请求。
  • MVP 只签发短期 access token,不签发 refresh token,也不新增 auth_sessions
  • 不提供 /api/auth/refresh 和服务端单会话撤销;退出登录由客户端删除本地 token。
  • access token 过期后要求用户重新登录;日志、LangGraph 状态和 Agent 输入不记录完整 token。

8.4 外部能力依据

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions