# 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` 可能占用大量存储，建议制定保留策略
