# BD_Manual:biz Schema AI 表(对话记录 + 消息 + 缓存) > 目标库:`test_zqyy_app`(通过 `APP_DB_DSN` 连接) > 迁移脚本: > - `db/zqyy_app/migrations/2026-03-08__create_ai_tables.sql`(初始建表) > - `db/zqyy_app/migrations/2026-03-20__rns14_chat_module_extend.sql`(RNS1.4 CHAT 扩展) > 关联 SPEC:`05-miniapp-ai-integration`(P5 AI 集成层)、`rns1-chat-integration`(RNS1.4 CHAT 对齐与联调收尾) --- ## 1. 变更说明 ### 新增表(3 张,P5 初始建表) | # | 表名 | 用途 | 字段数(初始→当前) | |---|------|------|---------------------| | 1 | `biz.ai_conversations` | AI 对话记录:每次 AI 调用(用户主动或系统自动)创建一条 | 8 → 13 | | 2 | `biz.ai_messages` | AI 消息记录:对话中的每条消息(输入/输出/系统) | 6 → 7 | | 3 | `biz.ai_cache` | AI 应用缓存:各应用的结构化输出结果 | 9 | ### RNS1.4 CHAT 模块扩展字段(2026-03-20) | # | 表名 | 新增字段 | 用途 | |---|------|---------|------| | 1 | `biz.ai_conversations` | `context_type`, `context_id`, `title`, `last_message`, `last_message_at` | 多入口对话复用 + 历史列表展示与排序 | | 2 | `biz.ai_messages` | `reference_card` | 引用卡片 JSON(客户概览等结构化上下文数据) | ### 表字段明细 #### biz.ai_conversations(13 字段) | 字段 | 类型 | 约束 | 说明 | |------|------|------|------| | `id` | BIGSERIAL | PK | 自增主键 | | `user_id` | VARCHAR(50) | NOT NULL | 用户 ID,系统自动调用时为 `system` | | `nickname` | VARCHAR(100) | NOT NULL DEFAULT '' | 用户昵称 | | `app_id` | VARCHAR(30) | NOT NULL | 应用标识:app1_chat / app2_finance / app3_clue / app4_analysis / app5_tactics / app6_note / app7_customer / app8_consolidation | | `site_id` | BIGINT | NOT NULL | 门店 ID(多门店隔离) | | `source_page` | VARCHAR(100) | 可空 | 来源页面标识 | | `source_context` | JSONB | 可空 | 页面上下文 JSON | | `created_at` | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 创建时间 | | `context_type` | VARCHAR(20) | 可空 | **RNS1.4 新增** — 对话关联上下文类型:task(任务)/ customer(客户)/ coach(助教)/ general(通用) | | `context_id` | VARCHAR(50) | 可空 | **RNS1.4 新增** — 关联上下文 ID:task 入口为 taskId,customer 入口为 customerId,coach 入口为 coachId,general 为 NULL | | `title` | VARCHAR(200) | 可空 | **RNS1.4 新增** — 对话标题:自定义 > 上下文名称 > 首条消息前20字 | | `last_message` | TEXT | 可空 | **RNS1.4 新增** — 最后一条消息内容摘要(截断至100字) | | `last_message_at` | TIMESTAMPTZ | 可空 | **RNS1.4 新增** — 最后消息时间,用于历史列表排序和对话复用时限判断 | #### biz.ai_messages(7 字段) | 字段 | 类型 | 约束 | 说明 | |------|------|------|------| | `id` | BIGSERIAL | PK | 自增主键 | | `conversation_id` | BIGINT | NOT NULL, FK → `biz.ai_conversations(id)` ON DELETE CASCADE | 关联对话 | | `role` | VARCHAR(10) | NOT NULL, CHECK IN ('user', 'assistant', 'system') | 消息角色 | | `content` | TEXT | NOT NULL | 消息内容 | | `tokens_used` | INTEGER | 可空 | 本条消息消耗的 token 数 | | `created_at` | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 创建时间 | | `reference_card` | JSONB | 可空 | **RNS1.4 新增** — 引用卡片 JSON:`{type, title, summary, data}`,用于展示客户概览等结构化上下文数据 | #### biz.ai_cache(9 字段) | 字段 | 类型 | 约束 | 说明 | |------|------|------|------| | `id` | BIGSERIAL | PK | 自增主键 | | `cache_type` | VARCHAR(30) | NOT NULL, CHECK 7 个枚举值 | 缓存类型(见下方枚举) | | `site_id` | BIGINT | NOT NULL | 门店 ID(多门店隔离) | | `target_id` | VARCHAR(100) | NOT NULL | 目标 ID,含义因 cache_type 而异 | | `result_json` | JSONB | NOT NULL | 结构化输出结果 | | `score` | INTEGER | 可空 | 评分:仅应用 6 使用(1-10 分) | | `triggered_by` | VARCHAR(100) | 可空 | 触发来源标识 | | `created_at` | TIMESTAMPTZ | NOT NULL DEFAULT NOW() | 创建时间 | | `expires_at` | TIMESTAMPTZ | 可空 | 可选过期时间 | ### cache_type 枚举值与 target_id 约定 | cache_type | target_id 格式 | 示例 | |---|---|---| | `app2_finance` | 时间维度编码 | `this_month`, `last_week` | | `app3_clue` | member_id | `12345` | | `app4_analysis` | `{assistant_id}_{member_id}` | `100_12345` | | `app5_tactics` | `{assistant_id}_{member_id}` | `100_12345` | | `app6_note_analysis` | member_id | `12345` | | `app7_customer_analysis` | member_id | `12345` | | `app8_clue_consolidated` | member_id | `12345` | ### 约束与索引 | 表 | 约束/索引名 | 类型 | 说明 | |----|-----------|------|------| | `ai_conversations` | `idx_ai_conv_user_site` | INDEX | `(user_id, site_id, created_at DESC)` — 用户历史对话列表查询 | | `ai_conversations` | `idx_ai_conv_app_site` | INDEX | `(app_id, site_id, created_at DESC)` — 按应用查询对话 | | `ai_messages` | FK `conversation_id` | FK | → `biz.ai_conversations(id)` ON DELETE CASCADE | | `ai_messages` | `chk_ai_msg_role` | CHECK | `role IN ('user', 'assistant', 'system')` | | `ai_messages` | `idx_ai_msg_conv` | INDEX | `(conversation_id, created_at)` — 对话消息列表 | | `ai_cache` | `chk_ai_cache_type` | CHECK | 7 个枚举值 | | `ai_cache` | `idx_ai_cache_lookup` | INDEX | `(cache_type, site_id, target_id, created_at DESC)` — 查询最新缓存 | | `ai_cache` | `idx_ai_cache_cleanup` | INDEX | `(cache_type, site_id, target_id, created_at)` — 清理超限记录(ASC 排序便于删除最旧) | | `ai_conversations` | `idx_ai_conv_context` | INDEX(条件) | **RNS1.4 新增** — `(user_id, site_id, context_type, context_id, last_message_at DESC NULLS LAST) WHERE context_type IS NOT NULL` — 上下文对话查找(多入口复用) | | `ai_conversations` | `idx_ai_conv_last_msg` | INDEX | **RNS1.4 新增** — `(user_id, site_id, last_message_at DESC NULLS LAST)` — 历史列表排序优化(CHAT-1 倒序) | --- ## 2. 兼容性影响 | 组件 | 影响 | |------|------| | ETL 任务 | 无影响。AI 表属于 `biz` Schema,不参与 ETL 流程 | | 后端 API | 直接依赖。P5 AI 模块(`apps/backend/app/ai/`)将基于这三张表实现对话持久化、缓存读写、SSE 流式对话等功能 | | 后端 API(RNS1.4) | **直接依赖**。CHAT 模块(`apps/backend/app/routers/xcx_chat.py`、`apps/backend/app/services/chat_service.py`)依赖 `ai_conversations` 的 5 个新字段(`context_type`/`context_id`/`title`/`last_message`/`last_message_at`)实现多入口对话复用、历史列表展示与排序;依赖 `ai_messages.reference_card` 存储引用卡片 JSON | | 小程序 | 间接依赖。小程序通过后端 AI API 间接使用对话和缓存数据 | | 小程序(RNS1.4) | **间接依赖**。`pages/chat/chat.ts` 和 `pages/chat-history/chat-history.ts` 通过 CHAT-1/2/3/4 端点间接依赖新字段(`title`→对话标题、`lastMessage`→摘要、`timestamp`→排序、`referenceCard`→引用卡片渲染) | | 管理后台 | 暂无影响。后续可能增加 AI 调用统计和缓存管理界面 | | `member_retention_clue` | 间接关联。App8(维客线索整理)的结果同时写入 `ai_cache` 和 `member_retention_clue` 表 | | 现有 `biz` Schema | 兼容。P5 新增 3 张表;RNS1.4 仅在已有表上 ADD COLUMN / CREATE INDEX,不修改已有字段或约束 | --- ## 3. 回滚策略 ### 3a. 回滚 RNS1.4 CHAT 扩展(2026-03-20 迁移) 按逆序 DROP 新增索引和字段: ```sql -- 删除 RNS1.4 新增索引 DROP INDEX IF EXISTS biz.idx_ai_conv_context; DROP INDEX IF EXISTS biz.idx_ai_conv_last_msg; -- 回退 ai_messages 新增字段 ALTER TABLE biz.ai_messages DROP COLUMN IF EXISTS reference_card; -- 回退 ai_conversations 新增字段(逆序) ALTER TABLE biz.ai_conversations DROP COLUMN IF EXISTS last_message_at; ALTER TABLE biz.ai_conversations DROP COLUMN IF EXISTS last_message; ALTER TABLE biz.ai_conversations DROP COLUMN IF EXISTS title; ALTER TABLE biz.ai_conversations DROP COLUMN IF EXISTS context_id; ALTER TABLE biz.ai_conversations DROP COLUMN IF EXISTS context_type; ``` 注意: - 回滚后 CHAT 模块(xcx_chat 路由、chat_service)将无法正常工作 - 如果新字段中已有数据,需先备份再执行回滚 - 回滚不影响 P5 初始建表的 8 个原始字段 ### 3b. 回滚 P5 初始建表(2026-03-08 迁移) 按逆序 DROP(CASCADE 处理外键依赖): ```sql -- 删除索引 DROP INDEX IF EXISTS biz.idx_ai_cache_cleanup; DROP INDEX IF EXISTS biz.idx_ai_cache_lookup; DROP INDEX IF EXISTS biz.idx_ai_msg_conv; DROP INDEX IF EXISTS biz.idx_ai_conv_app_site; DROP INDEX IF EXISTS biz.idx_ai_conv_user_site; -- 删除表(ai_messages 依赖 ai_conversations,需先删或用 CASCADE) DROP TABLE IF EXISTS biz.ai_cache CASCADE; DROP TABLE IF EXISTS biz.ai_messages CASCADE; DROP TABLE IF EXISTS biz.ai_conversations CASCADE; ``` 注意: - `ai_messages` 通过 FK 依赖 `ai_conversations`,CASCADE 会级联删除消息 - 如果表中已有数据,需先备份再执行回滚 - 回滚不会删除 `biz` Schema 本身 --- ## 4. 验证 SQL ### 4a. P5 初始建表验证 ```sql -- 1. 验证 3 张 AI 表全部存在 SELECT table_name FROM information_schema.tables WHERE table_schema = 'biz' AND table_name IN ('ai_conversations', 'ai_messages', 'ai_cache') ORDER BY table_name; -- 预期:返回 3 行(ai_cache, ai_conversations, ai_messages) -- 2. 验证 ai_conversations 字段数量和关键字段 SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_schema = 'biz' AND table_name = 'ai_conversations' ORDER BY ordinal_position; -- 预期:返回 13 行(P5 原始 8 字段 + RNS1.4 新增 5 字段) -- 3. 验证 ai_messages 的外键和 CHECK 约束 SELECT conname, contype, pg_get_constraintdef(oid) AS constraint_def FROM pg_constraint WHERE conrelid = 'biz.ai_messages'::regclass ORDER BY conname; -- 预期:包含 chk_ai_msg_role(CHECK role IN ...)和 ai_messages_conversation_id_fkey(FK → ai_conversations) -- 4. 验证 ai_cache 的 CHECK 约束(7 个枚举值) SELECT conname, pg_get_constraintdef(oid) AS constraint_def FROM pg_constraint WHERE conrelid = 'biz.ai_cache'::regclass AND contype = 'c'; -- 预期:返回 1 行 chk_ai_cache_type,包含 7 个枚举值 -- 5. 验证 P5 初始索引全部存在(5 个) SELECT indexname FROM pg_indexes WHERE schemaname = 'biz' AND indexname IN ( 'idx_ai_conv_user_site', 'idx_ai_conv_app_site', 'idx_ai_msg_conv', 'idx_ai_cache_lookup', 'idx_ai_cache_cleanup' ) ORDER BY indexname; -- 预期:返回 5 行 ``` ### 4b. RNS1.4 CHAT 扩展验证 ```sql -- 6. 验证 ai_conversations 新增 5 个字段存在 SELECT column_name, data_type, character_maximum_length FROM information_schema.columns WHERE table_schema = 'biz' AND table_name = 'ai_conversations' AND column_name IN ('context_type', 'context_id', 'title', 'last_message', 'last_message_at'); -- 预期:返回 5 行 -- context_type | character varying | 20 -- context_id | character varying | 50 -- title | character varying | 200 -- last_message | text | NULL -- last_message_at | timestamp with time zone | NULL -- 7. 验证 ai_messages 新增 reference_card 字段存在 SELECT column_name, data_type FROM information_schema.columns WHERE table_schema = 'biz' AND table_name = 'ai_messages' AND column_name = 'reference_card'; -- 预期:返回 1 行,data_type = 'jsonb' -- 8. 验证 RNS1.4 新增 2 个索引存在 SELECT indexname, indexdef FROM pg_indexes WHERE schemaname = 'biz' AND tablename = 'ai_conversations' AND indexname IN ('idx_ai_conv_context', 'idx_ai_conv_last_msg'); -- 预期:返回 2 行 -- idx_ai_conv_context — 含 WHERE context_type IS NOT NULL 条件 -- idx_ai_conv_last_msg — (user_id, site_id, last_message_at DESC NULLS LAST) ```