Files
social-app/docs/plans/2026-02-25-agent-chat-crewai-ag-ui-plan.md
T

529 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Backend Agent Chat Core (CrewAI + AG-UI) Implementation Plan
> 实施建议:按任务顺序执行,每个任务先测试失败再实现通过。
**Goal:** 为后端新增基于 AG-UI 协议的 Agent Chat 核心能力,使用 CrewAI 三阶段模型链路,支持成本追踪、会话落库、多模态输入与 ASR。
**Architecture:** 采用分层架构:`v1/agent_chat` 作为 AG-UI 协议边界,`core/agent_chat` 承担编排与能力层,`models + repository` 承担持久化。所有调用以 `sessions.id` 作为链路标识,并在调用级与会话级记录 token/cost。
**Tech Stack:** FastAPI, Pydantic, SQLAlchemy, Alembic, CrewAI, AG-UI 官方 CrewAI 集成与 Python SDK, DashScope Python SDK, Supabase Storage, pytest。
**Replaces:** `docs/plans/PLAN-agent-chat-crewai-ag-ui-2026-02-25.md`
---
## Scope
- In Scope
- CrewAI 三阶段链路:Intent -> Execution -> Organization
- LLM 成本采集与会话级聚合
- `llm_factory``llms``sessions``messages` 四张核心表
- AG-UI 协议路由与事件映射
- CrewAI 模板与静态配置
- 图片/音频/文本文档输入支持
- FunASR 工具接入 `fun-asr-realtime-2025-11-07`
- Out of Scope
- 前端 UI 改造
- 新增向量数据库与长期记忆系统
- 多租户账单结算系统
## Acceptance Criteria
- AG-UI 路由可完成文本和附件会话,并输出标准事件流。
- 三阶段模型可配置替换,并在会话中完整落库模型链路。
- 每次调用记录 token/cost,会话聚合成本可审计。
- `llm_factory` 初始包含 6 厂商,`llms` 初始包含 2 模型。
- CrewAI agents/tasks/workflow 模板可按配置加载并执行。
- ASR 工具可将音频转换文本并参与回答。
- 单测 + 集成 + E2E 通过,覆盖率达到仓库要求。
## Requirement Traceability Matrix
| Requirement | Description | Tasks | Tests |
|---|---|---|---|
| R1 | 三阶段模型(意图/执行/整理) | Task 5 | `backend/tests/unit/core/agent_chat/test_orchestrator_pipeline.py` |
| R2 | LLM 与工具调用成本采集(统一写入 messages | Task 5, Task 8 | `backend/tests/unit/core/agent_chat/test_cost_tracker.py`, `backend/tests/integration/test_agent_chat_session_persistence.py` |
| R3 | `llm_factory` + `llms` 表 | Task 2, Task 3 | `backend/tests/integration/test_agent_chat_migration.py`, `backend/tests/integration/test_agent_chat_seed_data.py` |
| R4 | `sessions` + `messages` 会话与历史落库 | Task 2, Task 8 | `backend/tests/integration/test_agent_chat_session_persistence.py` |
| R5 | AG-UI 协议路由与事件 | Task 6 | `backend/tests/unit/core/agent_chat/test_agui_adapter.py`, `backend/tests/integration/test_agent_chat_routes.py` |
| R6 | 三阶段 prompt/llm/tools 静态配置 | Task 4 | `backend/tests/unit/core/agent_chat/test_crewai_template_loader.py` |
| R7 | CrewAI templates 加载并供编排执行 | Task 4, Task 5 | `backend/tests/unit/core/agent_chat/test_crewai_template_loader.py`, `backend/tests/unit/core/agent_chat/test_orchestrator_pipeline.py` |
| R8 | 图片/音频/文档输入支持 | Task 7 | `backend/tests/unit/core/agent_chat/test_multimodal.py`, `backend/tests/e2e/test_agent_chat_flow.py` |
| R9 | FunASR 工具接入 qwen 模型 | Task 7 | `backend/tests/unit/core/agent_chat/test_asr_fun_asr_tool.py`, `backend/tests/e2e/test_agent_chat_flow.py` |
## Data Model Design
### llm_factory (LLM 厂商表)
继承 `TimestampMixin` + `SoftDeleteMixin`
| Field | Type | Constraints | Description |
|-------|------|-------------|-------------|
| id | UUID | PK, auto | 主键 |
| name | String(50) | UNIQUE, NOT NULL | 厂商名称:qwen/minimax/kimi/deepseek/doubao/zai |
| request_url | String(255) | NOT NULL | API 请求 URL |
| avatar | String(255) | NULL | 厂商图标 CDN URL |
| created_at | DateTime | NOT NULL, DEFAULT now | 创建时间 |
| updated_at | DateTime | NOT NULL | 更新时间 |
| deleted_at | DateTime | NULL | 软删除时间 |
### llms (大模型表)
继承 `TimestampMixin` + `SoftDeleteMixin`
| Field | Type | Constraints | Description |
|-------|------|-------------|-------------|
| id | UUID | PK, auto | 主键 |
| factory_id | UUID | FK -> llm_factory(id), NOT NULL | 关联厂商 |
| model_code | String(50) | UNIQUE, NOT NULL | 模型代码:qwen3.5-flash、deepseek-v3.2 |
| created_at | DateTime | NOT NULL, DEFAULT now | 创建时间 |
| updated_at | DateTime | NOT NULL | 更新时间 |
| deleted_at | DateTime | NULL | 软删除时间 |
### sessions (会话表)
继承 `TimestampMixin` + `SoftDeleteMixin`。表示一个完整的对话会话。
| Field | Type | Constraints | Description |
|-------|------|-------------|-------------|
| id | UUID | PK, auto | 主键 |
| user_id | UUID | FK -> users(id), NOT NULL | 关联用户 |
| title | String(255) | NULL | 会话标题(首条消息摘要) |
| status | Enum | DEFAULT 'pending' | 状态:pending/running/completed/failed |
| last_activity_at | DateTime | NOT NULL | 最近活跃时间(首页排序) |
| message_count | Integer | DEFAULT 0, CHECK >= 0 | 消息数量冗余字段 |
| total_tokens | Integer | DEFAULT 0, CHECK >= 0 | 会话总 token 冗余字段 |
| total_cost | Decimal(12,6) | DEFAULT 0, CHECK >= 0 | 会话总成本冗余字段 |
| created_at | DateTime | NOT NULL, DEFAULT now | 创建时间 |
| updated_at | DateTime | NOT NULL | 更新时间 |
| deleted_at | DateTime | NULL | 软删除时间 |
**Indexes:**
- `idx_sessions_user_created`: (user_id, created_at DESC)
- `idx_sessions_user_last_activity`: (user_id, last_activity_at DESC)
### messages (对话历史表)
继承 `TimestampMixin` + `SoftDeleteMixin`。表示会话中的单条对话记录(一对多)。
| Field | Type | Constraints | Description |
|-------|------|-------------|-------------|
| id | UUID | PK, auto | 主键 |
| session_id | UUID | FK -> sessions(id), NOT NULL | 关联会话 |
| seq | Integer | NOT NULL | 会话内顺序号(唯一:session_id + seq |
| role | Enum | NOT NULL | 角色:user/assistant/system/tool |
| content | Text | NOT NULL | 对话内容 |
| model_code | String(50) | NULL | 使用的模型代码 |
| tool_name | String(100) | NULL | 若为工具结果消息,记录工具名 |
| input_tokens | Integer | DEFAULT 0, CHECK >= 0 | 输入 token 数 |
| output_tokens | Integer | DEFAULT 0, CHECK >= 0 | 输出 token 数 |
| cost | Decimal(12,6) | DEFAULT 0, CHECK >= 0 | 本条成本 |
| currency | String(3) | DEFAULT 'USD' | 货币单位 |
| latency_ms | Integer | NULL | 本条耗时(毫秒) |
| metadata | JSONB | NULL | 扩展字段(工具调用、附件信息等) |
| created_at | DateTime | NOT NULL, DEFAULT now | 创建时间 |
| updated_at | DateTime | NOT NULL | 更新时间 |
| deleted_at | DateTime | NULL | 软删除时间 |
**Indexes:**
- `idx_messages_session_created`: (session_id, created_at DESC)
- `idx_messages_session_role`: (session_id, role)
- `uq_messages_session_seq`: UNIQUE(session_id, seq)
## LLM Seed Configuration
- 配置文件位置:`backend/src/core/config/static/agent_chat/llm_catalog.yaml`
- 此文件作为 `init_data.py` 的唯一种子源,包含 `llm_factory``llms` 初始化数据。
```yaml
factories:
- name: qwen
request_url: https://dashscope.aliyuncs.com/compatible-mode/v1
avatar: https://cdn.simpleicons.org/alibabacloud/FF6A00
- name: minimax
request_url: https://api.minimax.chat/v1
avatar: https://cdn.simpleicons.org/minimax/1A1A1A
- name: kimi
request_url: https://api.moonshot.cn/v1
avatar: https://cdn.simpleicons.org/moonrepo/3B82F6
- name: deepseek
request_url: https://api.deepseek.com/v1
avatar: https://cdn.simpleicons.org/deepseek/4D6BFE
- name: doubao
request_url: https://ark.cn-beijing.volces.com/api/v3
avatar: https://cdn.simpleicons.org/volkswagen/001E50
- name: zai
request_url: https://api.z.ai/v1
avatar: https://cdn.simpleicons.org/zotero/CC2936
llms:
- model_code: qwen3.5-flash
factory_id: qwen
- model_code: deepseek-v3.2
factory_id: deepseek
```
- 说明:`llms.factory_id` 在配置中使用厂商 `name` 作为逻辑标识;`init_data.py` 先 upsert `llm_factory(name, request_url, avatar)`,再按 `name` 解析真实数据库 `llm_factory.id` 填充 `llms.factory_id`
## Attachment Storage Policy
- 用户上传附件与 AI 生成附件均保存到 **Supabase Storage**(对象存储),不存数据库二进制。
- 建议 bucket`agent-chat-attachments`(私有桶,签名 URL 短时访问)。
- 路径规范:`agent-chat/{user_id}/{session_id}/{message_seq}/{sha256}.{ext}`
- 访问规范:后端仅保存 `object_path`,读取时签发短时 URL(默认 10 分钟)。
- 生命周期:
- 原始附件保留 30 天(可配置)。
- 超期由定时任务清理对象并同步标记 `messages.metadata.attachments[].expired=true`
- 安全规范:
- 仅后端 service_role 可写对象。
- 前端不得直传 service_role;如需直传使用受限 upload token。
- 所有附件写入前必须完成 MIME/大小校验并记录 `checksum_sha256`
- 数据库存储位置:`messages.metadata.attachments[]`,保存以下元数据:
- `object_path`
- `mime_type`
- `size`
- `checksum_sha256`
- `origin`user_upload/assistant_output
- `preview_text`(可选,截断)
## Environment Variables Policy
- `.env.example` 不新增任何厂商 API 变量(qwen/minimax/kimi/deepseek/doubao/zai 均不写入)。
- `.env.example` 仅保留通用基础设施变量(本需求新增仅限存储相关):
- `SOCIAL_STORAGE__PROVIDER=supabase`
- `SOCIAL_STORAGE__BUCKET=agent-chat-attachments`
- `SOCIAL_STORAGE__SIGNED_URL_TTL_SECONDS=600`
- `SOCIAL_STORAGE__MAX_FILE_SIZE_MB=20`
- `SOCIAL_STORAGE__RETENTION_DAYS=30`
- 厂商密钥与 Base URL 通过部署平台密文配置注入,运行时统一由 `Settings()` 读取。
## CrewAI Template Configuration
- 模板目录:`backend/src/core/config/static/agent_chat/crewai/`
- 约定文件:
- `agents.yaml`intent/execution/organization 三个 agent 的 role/goal/backstory/default_model
- `tasks.yaml`:三阶段 task 描述、输入映射、输出 schema
- `workflow.yaml`:阶段顺序、短路规则、重试策略、超时策略
- `../tools.yaml`:可调用工具白名单与参数 schema(位于 `agent_chat` 根配置)
- `prompts/*.md`:各阶段 prompt 模板
## CrewAI <-> AG-UI Event Bridge
- 适配入口:`backend/src/core/agent_chat/agui_adapter.py`
- AG-UI 适配来源:优先使用 `ag-ui-protocol/ag-ui` 官方仓库中的 `integrations/crewai` 与 Python SDK(以官方发布版本为准,避免自定义协议漂移)。
- 运行流程:
1. 接收 AG-UI 请求并转为内部命令。
2. CrewAI 每个阶段通过回调产出内部事件(start/progress/tool/final/error)。
3. `agui_adapter.py` 将内部事件映射为 AG-UI 事件(`message.delta/tool.started/tool.completed/run.completed/run.failed`)。
4. `service.py` 在同一事务边界内写入 `messages`(含模型、token、cost、tool 信息),并增量更新 `sessions` 汇总字段。
5. 事件流输出与 DB 落库共用 `session.id + message.seq` 保证顺序一致。
## Session and Cost Rules
- Title 生成
- 在首条 `user` 消息写入后同步生成 `sessions.title`
- 策略:取首条用户消息前 24 个可见字符,去空白与换行,不调用额外 LLM。
- 若首条消息为空或仅附件:兜底为 `新对话 YYYY-MM-DD HH:MM`
- 工具调用记账
- 工具调用不单独建表,统一写入 `messages``role=tool`,并在 `metadata.tool` 记录 name/status/args_digest)。
- 若工具内部触发 LLM,请将 `input_tokens/output_tokens/cost` 写入该条 tool message。
- 会话级成本采用增量聚合:`sessions.total_cost = sum(messages.cost)`
- 失败调用也可记成本(例如上游已计费但业务失败),通过 `metadata.tool.status` 区分。
- 首页“最近打开会话”选择
- 定义:最近一次有消息或工具活动的会话(按 `sessions.last_activity_at DESC`)。
- 查询:`WHERE user_id=:uid AND deleted_at IS NULL ORDER BY last_activity_at DESC LIMIT 1`
- 会话列表同样按 `last_activity_at DESC` 分页,默认高亮首条结果。
## Naming and File Convention
- 文档命名统一:`docs/plans/YYYY-MM-DD-<topic>-plan.md`
- 本计划文件:`docs/plans/2026-02-25-agent-chat-crewai-ag-ui-plan.md`
- 配置目录统一:`backend/src/core/config/static/agent_chat/`
- 术语约定:代码实体统一使用 `AgentChatSession``AgentChatMessage`,避免与 SQLAlchemy `Session` 混淆。
## Milestones
1. M1: Spike 完成,依赖与接口可用
2. M2: 数据层落地(表、迁移、种子)
3. M3: 编排层与成本追踪可运行
4. M4: AG-UI 事件流打通
5. M5: 多模态 + ASR 打通
6. M6: 全链路验证 + 文档门禁
### Task 1: Spike 与接口基线确认
**Files:**
- Modify: `docs/plans/2026-02-25-agent-chat-crewai-ag-ui-plan.md`
- Create: `docs/plans/2026-02-25-agent-chat-crewai-ag-ui-spike-notes.md`
**Step 1: 写兼容性验证用例(文档化)**
- 验证点:`crewai`、AG-UI 官方 CrewAI 集成与 Python SDK、DashScope FunASR SDK 返回 usage 字段可得性。
**Step 2: 运行最小可用验证命令**
- Run: `uv run python -m pip show crewai`
- Expected: 能看到版本信息;若无则列入依赖安装任务。
**Step 3: 记录替代策略**
- 若 AG-UI 官方 CrewAI 集成不可用,保留最小自定义事件映射适配层(仅实现标准事件,不扩展私有字段)。
**Step 4: 提交 spike 结论到 notes**
### Task 2: 数据模型与迁移(llm_factory, llms, sessions, messages
**Files:**
- Create: `backend/src/models/llm_factory.py`
- Create: `backend/src/models/llm.py`
- Create: `backend/src/models/agent_chat_session.py`
- Create: `backend/src/models/agent_chat_message.py`
- Modify: `backend/src/models/__init__.py`
- Create: `backend/alembic/versions/<timestamp>_create_agent_chat_core_tables.py`
- Modify: `backend/alembic/env.py`
- Test: `backend/tests/integration/test_agent_chat_migration.py`
**Step 1: 先写失败的迁移测试**
- 覆盖:建表成功、索引存在、降级可回滚。
**Step 2: 运行测试确认失败**
- Run: `uv run pytest backend/tests/integration/test_agent_chat_migration.py -v`
- Expected: FAIL(缺少模型或迁移)。
**Step 3: 实现 ORM 与 Alembic 迁移**
- `llm_factory`:唯一 `name`,新增 `request_url``avatar`,并继承 `TimestampMixin` + `SoftDeleteMixin`
- `llms`:关联 `factory_id`,仅保留 `model_code`,并继承 `TimestampMixin` + `SoftDeleteMixin`
- `sessions`:使用 `id` 作为链路标识,状态为 `pending/running/completed/failed`,维护 `last_activity_at``message_count``total_tokens``total_cost`
- `messages`:按 `session_id + seq` 一对多存储对话历史,记录单条模型与 token/cost;工具调用统一写 `role=tool` + `metadata.tool`
**Step 4: 重新运行测试确认通过**
- Run: `uv run pytest backend/tests/integration/test_agent_chat_migration.py -v`
- Expected: PASS。
### Task 3: 初始化种子数据
**Files:**
- Create: `backend/src/core/config/static/agent_chat/llm_catalog.yaml`
- Modify: `backend/src/core/initialization/init_data.py`
- Test: `backend/tests/integration/test_agent_chat_seed_data.py`
**Step 1: 写失败测试**
- 断言 `llm_catalog.yaml` 可被加载,且 `factories` 包含 `name/request_url/avatar`
- 断言 `llms` 仅包含 `model_code/factory_id`(逻辑标识)。
- 断言落库后 `llm_factory` 包含:`qwen,minimax,kimi,deepseek,doubao,zai`
- 断言落库后 `llms` 包含:`qwen3.5-flash,deepseek-v3.2` 且能正确关联到厂商。
**Step 2: 跑测试确认失败**
- Run: `uv run pytest backend/tests/integration/test_agent_chat_seed_data.py -v`
- Expected: FAIL。
**Step 3: 实现种子插入逻辑(幂等)**
-`backend/src/core/config/static/agent_chat/llm_catalog.yaml` 读取初始化数据。
- 先 upsert `llm_factory(name, request_url, avatar)`,再解析逻辑 `factory_id` 写入 `llms.factory_id`
**Step 4: 再跑测试确认通过**
- Run: `uv run pytest backend/tests/integration/test_agent_chat_seed_data.py -v`
- Expected: PASS。
### Task 4: 静态配置与 CrewAI 模板加载器
**Files:**
- Create: `backend/src/core/config/static/agent_chat/crewai/agents.yaml`
- Create: `backend/src/core/config/static/agent_chat/crewai/tasks.yaml`
- Create: `backend/src/core/config/static/agent_chat/crewai/workflow.yaml`
- Create: `backend/src/core/config/static/agent_chat/crewai/prompts/intent.md`
- Create: `backend/src/core/config/static/agent_chat/crewai/prompts/execution.md`
- Create: `backend/src/core/config/static/agent_chat/crewai/prompts/organization.md`
- Create: `backend/src/core/config/static/agent_chat/tools.yaml`
- Create: `backend/src/core/agent_chat/crewai/template_loader.py`
- Modify: `backend/src/core/config/settings.py`
- Test: `backend/tests/unit/core/agent_chat/test_crewai_template_loader.py`
**Step 1: 写模板加载器失败测试**
- 覆盖:模板缺失、workflow 非法阶段、非法工具、合法加载。
**Step 2: 运行测试确认失败**
- Run: `uv run pytest backend/tests/unit/core/agent_chat/test_crewai_template_loader.py -v`
- Expected: FAIL。
**Step 3: 实现配置文件与加载器**
- 支持按任务阶段加载 agent/task/workflow/prompt 与工具白名单。
**Step 4: 运行测试确认通过**
- Run: `uv run pytest backend/tests/unit/core/agent_chat/test_crewai_template_loader.py -v`
- Expected: PASS。
### Task 5: CrewAI 编排与成本追踪
**Files:**
- Create: `backend/src/core/agent_chat/orchestrator.py`
- Create: `backend/src/core/agent_chat/cost_tracker.py`
- Create: `backend/src/core/agent_chat/events.py`
- Test: `backend/tests/unit/core/agent_chat/test_cost_tracker.py`
- Test: `backend/tests/unit/core/agent_chat/test_orchestrator_pipeline.py`
**Step 1: 写失败测试(成本计算与阶段顺序)**
- 验证调用级 usage 记录、会话级 total_cost 聚合。
**Step 2: 运行测试确认失败**
- Run: `uv run pytest backend/tests/unit/core/agent_chat/test_cost_tracker.py backend/tests/unit/core/agent_chat/test_orchestrator_pipeline.py -v`
- Expected: FAIL。
**Step 3: 实现最小可用编排**
- Intent/Execution/Organization 三阶段串行。
- 每阶段记录 `model_id/factory_id/tokens/cost`
**Step 4: 运行测试确认通过**
- Run: `uv run pytest backend/tests/unit/core/agent_chat/test_cost_tracker.py backend/tests/unit/core/agent_chat/test_orchestrator_pipeline.py -v`
- Expected: PASS。
### Task 6: AG-UI 路由与事件映射
**Files:**
- Create: `backend/src/core/agent_chat/agui_adapter.py`
- Create: `backend/src/core/agent_chat/event_bridge.py`
- Create: `backend/src/v1/agent_chat/schemas.py`
- Create: `backend/src/v1/agent_chat/service.py`
- Create: `backend/src/v1/agent_chat/dependencies.py`
- Create: `backend/src/v1/agent_chat/router.py`
- Modify: `backend/src/v1/router.py`
- Test: `backend/tests/unit/core/agent_chat/test_agui_adapter.py`
- Test: `backend/tests/unit/core/agent_chat/test_event_bridge.py`
- Test: `backend/tests/integration/test_agent_chat_routes.py`
- Test: `backend/tests/integration/test_agent_chat_event_persistence.py`
**Step 1: 写失败测试(事件映射)**
- 覆盖 `message.delta/tool.started/tool.completed/run.completed/run.failed`
**Step 2: 写失败测试(路由集成)**
- 覆盖鉴权、正常会话、错误返回、事件与 `messages.seq` 顺序一致。
**Step 3: 运行测试确认失败**
- Run: `uv run pytest backend/tests/unit/core/agent_chat/test_agui_adapter.py backend/tests/integration/test_agent_chat_routes.py -v`
- Expected: FAIL。
**Step 4: 实现适配与路由**
- AG-UI 请求转换为内部 command。
- 使用 AG-UI 官方 CrewAI 集成 SDK 接收/发送标准事件,CrewAI 回调事件通过 `event_bridge.py` 统一转换。
- 内部事件转换为 AG-UI 事件流。
-`service.py` 中同事务写入 `messages` 并更新 `sessions.last_activity_at/message_count/total_tokens/total_cost`
**Step 5: 运行测试确认通过**
- Run: `uv run pytest backend/tests/unit/core/agent_chat/test_agui_adapter.py backend/tests/unit/core/agent_chat/test_event_bridge.py backend/tests/integration/test_agent_chat_routes.py backend/tests/integration/test_agent_chat_event_persistence.py -v`
- Expected: PASS。
### Task 7: 多模态输入与 ASR 工具
**Files:**
- Create: `backend/src/core/agent_chat/multimodal.py`
- Create: `backend/src/core/agent_chat/storage_adapter.py`
- Create: `backend/src/core/agent_chat/tools/asr_fun_asr.py`
- Test: `backend/tests/unit/core/agent_chat/test_multimodal.py`
- Test: `backend/tests/unit/core/agent_chat/test_storage_adapter.py`
- Test: `backend/tests/unit/core/agent_chat/test_asr_fun_asr_tool.py`
**Step 1: 写失败测试(文件校验与解析)**
- 覆盖图片/音频/文档 MIME、大小、异常分支。
- 覆盖附件对象存储路径生成、签名 URL 获取与元数据回写 `messages.metadata.attachments`
**Step 2: 写失败测试(ASR 工具)**
- 覆盖 DashScope SDK 请求构造、响应解析、超时降级。
**Step 3: 运行测试确认失败**
- Run: `uv run pytest backend/tests/unit/core/agent_chat/test_multimodal.py backend/tests/unit/core/agent_chat/test_storage_adapter.py backend/tests/unit/core/agent_chat/test_asr_fun_asr_tool.py -v`
- Expected: FAIL。
**Step 4: 实现最小可用功能**
- 音频走 DashScope Python SDK`fun-asr-realtime-2025-11-07`qwen)。
- 输出统一 `AttachmentContext` 结构。
- 附件二进制写入 Supabase Storage 私有桶 `agent-chat-attachments`,数据库仅写元数据。
**Step 5: 运行测试确认通过**
- Run: `uv run pytest backend/tests/unit/core/agent_chat/test_multimodal.py backend/tests/unit/core/agent_chat/test_storage_adapter.py backend/tests/unit/core/agent_chat/test_asr_fun_asr_tool.py -v`
- Expected: PASS。
### Task 8: 会话落库、可观测性与安全约束
**Files:**
- Modify: `backend/src/v1/agent_chat/service.py`
- Modify: `backend/src/core/agent_chat/orchestrator.py`
- Test: `backend/tests/unit/core/agent_chat/test_session_title_strategy.py`
- Test: `backend/tests/integration/test_agent_chat_session_recent_selection.py`
- Test: `backend/tests/integration/test_agent_chat_session_persistence.py`
**Step 1: 写失败测试(sessions 写入)**
- 断言 `session.id` 链路可追踪、`title/status/last_activity_at`、消息与工具成本明细均入库。
- 用例 A(title 生成):首条用户消息自动生成 `title`,超过长度截断,空文本回退 `新对话 YYYY-MM-DD HH:MM`
- 用例 B(工具成本聚合):工具调用写 `role=tool` 消息,`sessions.total_cost = sum(messages.cost)`,失败调用可记费。
- 用例 C(最近会话选择):同一用户多个会话按 `last_activity_at DESC` 返回首页默认会话。
**Step 2: 运行测试确认失败**
- Run: `uv run pytest backend/tests/integration/test_agent_chat_session_persistence.py -v`
- Expected: FAIL。
**Step 3: 实现持久化与审计字段写入**
- 禁止明文存储密钥与敏感音频原始数据。
- 新增路由级限流与滥用保护策略(按用户或 token)。
- 附件审计日志记录拒绝原因(MIME、大小、扩展名、解析失败)。
**Step 4: 运行测试确认通过**
- Run: `uv run pytest backend/tests/integration/test_agent_chat_session_persistence.py -v`
- Expected: PASS。
### Task 9: 全链路验证与文档更新
**Files:**
- Create: `backend/tests/e2e/test_agent_chat_flow.py`
- Create: `backend/tests/e2e/test_agent_chat_recent_session_home.py`
- Modify: `.env.example`
- Modify: `docs/runtime/runtime-runbook.md`
**Step 1: 写 E2E 失败测试**
- 场景:文本、图片+文本、音频+ASR、文档问答、最近会话首页默认选中。
**Step 2: 运行 E2E 确认失败**
- Run: `uv run pytest backend/tests/e2e/test_agent_chat_flow.py -v`
- Expected: FAIL(功能未完全联通)。
**Step 3: 修补缺口直至通过**
**Step 3.1: 更新环境变量样例**
- 仅新增存储相关变量到 `.env.example`,不新增任何厂商 API 变量。
**Step 4: 全量回归验证**
- Run: `bash infra/scripts/app-up.sh` (or `docker compose run --rm init-job bootstrap` for production)
- Run: `uv run pytest backend/tests/unit/core/agent_chat -v`
- Run: `uv run pytest backend/tests/integration -k agent_chat -v`
- Run: `uv run pytest backend/tests/e2e/test_agent_chat_flow.py -v`
- Expected: 全部 PASS。
**Step 5: 供应链与安全检查**
- Run: `uv run pip check`
- Run: `uv run pytest backend/tests/integration -k security -v`
- Expected: 依赖冲突为 0,关键安全测试通过。
## Dependency Graph
- Task 1 -> Task 2/4(先确认依赖与接口)
- Task 2 -> Task 3/5/8(数据层先行)
- Task 4 -> Task 5(编排依赖静态配置与 CrewAI 模板)
- Task 5 -> Task 6/7(路由与多模态依赖编排核心)
- Task 6 + Task 7 + Task 8 -> Task 9(全链路验证)
## Risk Controls
- AG-UI 官方 CrewAI 集成版本波动:固定版本并保留 `agui_adapter.py` 最小兜底映射。
- usage 字段不一致:`cost_tracker.py` 保留 `raw_usage` 并标准化。
- 附件安全:严格 MIME/大小限制,禁止敏感原文落库。
- 路由滥用风险:对 `agent_chat` 接口增加限流与失败惩罚策略。
- 供应链风险:新增依赖必须完成许可证和漏洞扫描。
- 延迟风险:三阶段支持短路策略(简单意图可跳过整理阶段)。
## Verification Checklist
- [ ] Alembic upgrade/downgrade 成功
- [ ] init-data 幂等且种子完整
- [ ] AG-UI 事件流字段符合标准
- [ ] sessions 审计字段完整
- [ ] 多模态与 ASR 路径可用
- [ ] 覆盖率与关键测试通过