# P5.1→NS3 缺失项 #10:AI 生成内容的审计日志 ## 简要结论 - 状态:✅ 已解决 - 风险等级:🟡 低 - `biz.ai_conversations` + `biz.ai_messages` 两张表已完整记录"谁在什么时候对哪个客户生成了什么",满足审计日志需求 ## 详细审查 ### 审查范围 - `apps/backend/app/ai/conversation_service.py` — 对话持久化服务 - `apps/backend/app/services/chat_service.py` — Chat 模块服务 - `docs/database/ddl/zqyy_app__biz.sql` — biz schema DDL 基线 - `db/zqyy_app/migrations/2026-03-20__rns14_chat_module_extend.sql` — Chat 模块扩展迁移 - `apps/backend/app/ai/apps/app5_tactics.py` — 典型 App 调用流程(审计写入示例) ### 发现 #### ✅ 审计信息完整记录 **1. `biz.ai_conversations` 表 — 对话级审计** | 字段 | 类型 | 审计用途 | |------|------|----------| | `id` | bigint PK | 对话唯一标识 | | `user_id` | varchar(50) NOT NULL | **谁**:操作用户 ID(系统自动调用时为 `'system'`) | | `nickname` | varchar(100) | 用户昵称 | | `app_id` | varchar(30) NOT NULL | **什么应用**:app1_chat / app2_finance / ... / app8_consolidation | | `site_id` | bigint NOT NULL | **哪个门店** | | `source_page` | varchar(100) | 触发来源页面 | | `source_context` | jsonb | 上下文信息(含 `assistant_id`、`member_id` 等 → **对哪个客户**) | | `context_type` | varchar(20) | 对话关联类型:task/customer/coach/general | | `context_id` | varchar(50) | 关联 ID(taskId/customerId/coachId) | | `title` | varchar(200) | 对话标题 | | `created_at` | timestamptz | **什么时候** | **2. `biz.ai_messages` 表 — 消息级审计** | 字段 | 类型 | 审计用途 | |------|------|----------| | `id` | bigint PK | 消息唯一标识 | | `conversation_id` | bigint FK | 关联对话 | | `role` | varchar(10) | system / user / assistant | | `content` | text NOT NULL | **生成了什么**:完整的 AI 输入和输出内容 | | `tokens_used` | integer | Token 消耗量 | | `reference_card` | jsonb | 引用卡片数据 | | `created_at` | timestamptz | 消息时间戳 | **3. 写入时机** 所有 8 个 AI 应用在每次调用时都通过 `ConversationService` 写入完整审计链: ``` create_conversation(user_id, nickname, app_id, site_id, source_context) → add_message(role="system", content=prompt) → add_message(role="user", content=user_input) → 调用百炼 API → add_message(role="assistant", content=ai_output, tokens_used=N) ``` **4. 索引支持审计查询** ```sql -- 按用户+门店查询历史对话 CREATE INDEX idx_ai_conv_user_site ON biz.ai_conversations (user_id, site_id, created_at DESC); -- 按应用+门店查询 CREATE INDEX idx_ai_conv_app_site ON biz.ai_conversations (app_id, site_id, created_at DESC); -- 按上下文查询(客户/任务/助教维度) CREATE INDEX idx_ai_conv_context ON biz.ai_conversations (user_id, site_id, context_type, context_id, ...); -- 按对话查询消息 CREATE INDEX idx_ai_msg_conv ON biz.ai_messages (conversation_id, created_at); ``` ### 证据 **App5 审计写入示例(app5_tactics.py L240-268)**: ```python # 创建对话记录 conversation_id = conv_svc.create_conversation( user_id=user_id, nickname=nickname, app_id=APP_ID, site_id=site_id, source_context={"assistant_id": assistant_id, "member_id": member_id}, ) # 写入 system + user 消息 conv_svc.add_message(conversation_id=conversation_id, role="system", content=...) conv_svc.add_message(conversation_id=conversation_id, role="user", content=...) # 调用 AI 后写入 assistant 消息 result, tokens_used = await bailian.chat_json(messages) conv_svc.add_message( conversation_id=conversation_id, role="assistant", content=json.dumps(result, ensure_ascii=False), tokens_used=tokens_used, ) ``` **DDL 基线确认(zqyy_app__biz.sql)**: ```sql CREATE TABLE biz.ai_conversations ( id bigint ... NOT NULL, user_id character varying(50) NOT NULL, nickname character varying(100) ..., app_id character varying(30) NOT NULL, site_id bigint NOT NULL, source_page character varying(100), source_context jsonb, context_type character varying(20), context_id character varying(50), ... created_at timestamp with time zone DEFAULT now() NOT NULL ); CREATE TABLE biz.ai_messages ( id bigint ... NOT NULL, conversation_id bigint NOT NULL, role character varying(10) NOT NULL, content text NOT NULL, tokens_used integer, reference_card jsonb, created_at timestamp with time zone DEFAULT now() NOT NULL ); ``` ### 建议 审计日志功能已完整实现,以下为可选增强方向(非必须): 1. **审计查询 API**:当前仅有面向用户的历史对话查询(`get_conversations`),可增加面向管理员的审计查询接口(按时间范围、app_id、site_id 筛选) 2. **数据保留策略**:当前无自动清理机制,长期运行后 `ai_messages.content` 可能占用大量存储,建议制定保留策略