202 lines
8.7 KiB
Markdown
202 lines
8.7 KiB
Markdown
|
|
# Agent 后端硬切重构设计
|
|||
|
|
|
|||
|
|
## 目标
|
|||
|
|
|
|||
|
|
- 一次性移除现有 Agent 运行时代码、测试和旧文档契约,避免新旧方案并存。
|
|||
|
|
- 仅从后端重新设计 Agent 体系,不依赖前端实现细节。
|
|||
|
|
- 新方案必须满足以下六项要求:
|
|||
|
|
1. 配置层可通过 `.env` 驱动 LLM API Key。
|
|||
|
|
2. 对话与 resume 通过 Celery 队列处理,不阻塞 Web 主线程。
|
|||
|
|
3. `v1/agent` 仅负责路由组织与服务调用,核心逻辑在 `core/agent`。
|
|||
|
|
4. 按 CrewAI 官方模型组织 Agent/Task/Crew/Flow/Tools。
|
|||
|
|
5. 按 AG-UI 协议输出事件,优先使用 `ag-ui-crewai` 适配库。
|
|||
|
|
6. 使用 LiteLLM 统计每次 LLM 调用的 token 和 cost。
|
|||
|
|
|
|||
|
|
## 设计原则
|
|||
|
|
|
|||
|
|
- 单一职责:HTTP 层只做协议和鉴权,编排与执行下沉到核心层。
|
|||
|
|
- 异步优先:长耗时推理、工具调用、恢复流程全部异步化。
|
|||
|
|
- 协议优先:AG-UI 作为唯一事件契约,不维护自定义事件方言。
|
|||
|
|
- 可观测性优先:每次 run、每次 stage、每次 LLM 调用可追踪。
|
|||
|
|
- 配置单一来源:所有密钥和模型配置只走 `core.config.settings`。
|
|||
|
|
|
|||
|
|
## 目标架构
|
|||
|
|
|
|||
|
|
### 1) 分层
|
|||
|
|
|
|||
|
|
- `backend/src/v1/agent/`
|
|||
|
|
- `router.py`: 暴露 HTTP/SSE 接口。
|
|||
|
|
- `schemas.py`: 请求/响应 DTO 和输入校验。
|
|||
|
|
- `dependencies.py`: DI 装配。
|
|||
|
|
- `service.py`: 薄服务,仅调用 `core/agent` 应用服务。
|
|||
|
|
- `backend/src/core/agent/`
|
|||
|
|
- `application/`: run/resume 应用服务。
|
|||
|
|
- `domain/`: run 状态机、resume 幂等语义、错误模型。
|
|||
|
|
- `infrastructure/crewai/`: CrewAI Agent/Task/Crew/Flow 装配与执行。
|
|||
|
|
- `infrastructure/agui/`: AG-UI 事件映射与 SSE 序列化。
|
|||
|
|
- `infrastructure/litellm/`: LiteLLM 客户端与 usage/cost 拦截器。
|
|||
|
|
- `infrastructure/queue/`: Celery task producer/consumer。
|
|||
|
|
|
|||
|
|
### 1.1) 配置来源与合并策略
|
|||
|
|
|
|||
|
|
- Agent 运行配置由两部分组成:
|
|||
|
|
- 数据库存量配置:`system_agents`(每种 agent_type 对应 llm 与 llm_config)。
|
|||
|
|
- 静态模板配置:`backend/src/core/config/static/crewai/*.yaml`(角色描述、任务模板、workflow、tools)。
|
|||
|
|
- 合并策略:
|
|||
|
|
- `llm` 与 `llm_config` 以 `system_agents` 为准。
|
|||
|
|
- prompt 模板、task 描述、flow stage、tool 白名单以 static/crewai 为准。
|
|||
|
|
- 若任一 agent_type 在 `system_agents` 缺失,运行前失败并返回受控错误。
|
|||
|
|
|
|||
|
|
### 2) 核心运行链路
|
|||
|
|
|
|||
|
|
1. `POST /api/v1/agent/runs` 只负责参数校验和鉴权。
|
|||
|
|
2. 路由调用 `AgentRunAppService.enqueue_run()`,写入 run 记录并投递 Celery。
|
|||
|
|
3. Worker 执行 `run_agent_task`:
|
|||
|
|
- 读取 run 上下文。
|
|||
|
|
- 构建 CrewAI `Agent/Task/Crew/Flow`。
|
|||
|
|
- 通过 `ag-ui-crewai` 将执行事件转为 AG-UI 标准事件。
|
|||
|
|
- 每次 LLM 调用由 LiteLLM 中间层记录 token/cost。
|
|||
|
|
4. 事件落库并发布到事件通道(Redis Stream/Channel)。
|
|||
|
|
5. SSE 接口从事件通道读取并持续推送,直到 `RUN_FINISHED` 或 `RUN_ERROR`。
|
|||
|
|
|
|||
|
|
### 3) Resume 链路
|
|||
|
|
|
|||
|
|
1. `POST /api/v1/agent/runs/{run_id}/resume` 校验 `interrupt_id` 与决策 payload。
|
|||
|
|
2. 调用 `enqueue_resume()` 投递 `resume_agent_task`。
|
|||
|
|
3. Worker 在事务内做并发控制:
|
|||
|
|
- `run_id + interrupt_id` 幂等锁。
|
|||
|
|
- 过期校验与状态迁移。
|
|||
|
|
4. 恢复后继续 CrewAI Flow,事件按 AG-UI 继续输出。
|
|||
|
|
|
|||
|
|
### 4) Session 状态持久化
|
|||
|
|
|
|||
|
|
- 使用 `sessions.state_snapshot` 作为运行态单一快照来源。
|
|||
|
|
- 快照至少包含:
|
|||
|
|
- run 上下文(thread_id、run_id、stage)
|
|||
|
|
- pending_tool_calls(tool_call_id、tool_name、args、status、expires_at)
|
|||
|
|
- correlation 索引(tool_call_id -> message_id / step_id)
|
|||
|
|
- 所有中断/恢复均以 `state_snapshot` 事务更新为准,避免内存态漂移。
|
|||
|
|
|
|||
|
|
### 5) 会话与消息落库模型
|
|||
|
|
|
|||
|
|
- 会话主表:`sessions`
|
|||
|
|
- 新建 run 时写入:`id/user_id/session_type/status=running/last_activity_at`。
|
|||
|
|
- 运行中持续更新:`status`、`last_activity_at`、`message_count`、`total_tokens`、`total_cost`、`state_snapshot`。
|
|||
|
|
- 运行结束更新:
|
|||
|
|
- 成功:`status=completed`
|
|||
|
|
- 失败:`status=failed`
|
|||
|
|
- 消息表:`messages`
|
|||
|
|
- 用户输入落库为 `role=user`(每次 run 开始时先写入)。
|
|||
|
|
- 模型输出落库为 `role=assistant`(按最终聚合文本落库,保留 metadata 记录增量信息)。
|
|||
|
|
- 工具调用结果落库为 `role=tool`,并写入 `tool_name` 与 `metadata.tool_call_id`。
|
|||
|
|
- `seq` 由每个 `session_id` 内单调递增分配,满足 `uq_messages_session_seq`。
|
|||
|
|
- 计量落库:每次 LLM 调用的 usage/cost 先写消息级,再聚合更新到 session 级。
|
|||
|
|
|
|||
|
|
## 六项要求落地映射
|
|||
|
|
|
|||
|
|
### 要求 1: `.env` 驱动 LLM API Key
|
|||
|
|
|
|||
|
|
- 新增 `LLMSettings` 到 `core.config.settings.Settings`,统一定义:
|
|||
|
|
- `SOCIAL_LLM__PROVIDER_KEYS__DASHSCOPE`
|
|||
|
|
- `SOCIAL_LLM__PROVIDER_KEYS__MINIMAX`
|
|||
|
|
- `SOCIAL_LLM__PROVIDER_KEYS__MOONSHOT`
|
|||
|
|
- `SOCIAL_LLM__PROVIDER_KEYS__DEEPSEEK`
|
|||
|
|
- `SOCIAL_LLM__PROVIDER_KEYS__ARK`
|
|||
|
|
- `SOCIAL_LLM__PROVIDER_KEYS__ZAI`
|
|||
|
|
- 禁止 `os.environ` 直接读取密钥。
|
|||
|
|
|
|||
|
|
### 要求 2: 对话和 resume 走 Celery
|
|||
|
|
|
|||
|
|
- Web 层不直接执行编排。
|
|||
|
|
- `run`/`resume` 一律入队,Worker 处理,Web 仅做事件流转发。
|
|||
|
|
- 加入任务级超时、重试、死信策略。
|
|||
|
|
|
|||
|
|
### 要求 3: v1 仅路由与调用
|
|||
|
|
|
|||
|
|
- `v1/agent/service.py` 仅保留应用服务调用和错误映射。
|
|||
|
|
- 任何编排、状态机、工具执行逻辑禁止进入 `v1`。
|
|||
|
|
|
|||
|
|
### 要求 4: CrewAI 官方流程
|
|||
|
|
|
|||
|
|
- 采用 CrewAI 原生对象:`Agent`、`Task`、`Crew`、`Flow`。
|
|||
|
|
- tools 通过 CrewAI Tool 机制注册,不做平行实现。
|
|||
|
|
- 任务模板与 agent 配置集中化(静态模板 + 运行时拼装)。
|
|||
|
|
- 配置拼装明确依赖 `system_agents + static/crewai`,不再使用双套来源。
|
|||
|
|
|
|||
|
|
### 要求 5: AG-UI + ag-ui-crewai
|
|||
|
|
|
|||
|
|
- 事件集遵循 AG-UI 协议,生命周期闭环:
|
|||
|
|
- `RUN_STARTED`
|
|||
|
|
- 流式消息和工具事件
|
|||
|
|
- 终态 `RUN_FINISHED` 或 `RUN_ERROR`
|
|||
|
|
- 优先引入 `ag-ui-crewai` 做 CrewAI 到 AG-UI 的桥接,避免重复造轮子。
|
|||
|
|
|
|||
|
|
### 要求 6: LiteLLM token/cost 统计
|
|||
|
|
|
|||
|
|
- 所有 LLM 调用通过 LiteLLM 统一出入口。
|
|||
|
|
- 按调用粒度记录:`input_tokens`、`output_tokens`、`total_tokens`、`cost`、`currency`。
|
|||
|
|
- 按 run 粒度聚合并落库,支持后续计费和审计。
|
|||
|
|
|
|||
|
|
## 数据与可观测性
|
|||
|
|
|
|||
|
|
- 保留现有 Agent 相关表结构,不在本次硬切做数据库破坏性变更。
|
|||
|
|
- 新增事件日志与调用指标落点(如已有字段不足,后续增量迁移)。
|
|||
|
|
- 日志使用结构化字段:`run_id`、`task_id`、`stage`、`tool_name`、`llm_model`、`latency_ms`。
|
|||
|
|
- 持久化原则:run/resume 的关键状态变更必须可重放,禁止仅保存在内存。
|
|||
|
|
|
|||
|
|
## 事务边界
|
|||
|
|
|
|||
|
|
- `run` 入口事务:创建或加载 `session` + 写入用户消息。
|
|||
|
|
- `worker` 执行事务(可分阶段短事务):
|
|||
|
|
- 阶段开始:更新 `session.status/state_snapshot`。
|
|||
|
|
- LLM 返回:写 assistant/tool 消息 + 更新 token/cost 聚合。
|
|||
|
|
- 中断:写 `pending_tool_calls` 到 `state_snapshot` 并提交。
|
|||
|
|
- 完成:更新终态 `session.status` 并提交。
|
|||
|
|
- `resume` 事务:校验 `interrupt_id` 与 ownership,CAS 更新 `state_snapshot`,然后进入后续执行事务。
|
|||
|
|
|
|||
|
|
## 错误处理与安全
|
|||
|
|
|
|||
|
|
- API Key 缺失启动即失败,不进入运行态。
|
|||
|
|
- 外部工具入参统一白名单和 schema 校验。
|
|||
|
|
- resume 决策必须鉴权与会话所有权校验。
|
|||
|
|
- 错误响应遵循 RFC 7807,避免泄漏敏感上下文。
|
|||
|
|
|
|||
|
|
## 工具调用与恢复语义
|
|||
|
|
|
|||
|
|
- 工具分三类:
|
|||
|
|
- 前端工具:由 `RunAgentInput.tools` 提供能力声明,触发 interrupt,由客户端执行并回传 result。
|
|||
|
|
- 后端工具(需审批):先 interrupt 给前端审批;审批通过后由后端执行,不由前端执行。
|
|||
|
|
- 后端工具(直执):后端直接执行。
|
|||
|
|
- 一致性约束:
|
|||
|
|
- 每个 tool_result 必须携带 `tool_call_id`。
|
|||
|
|
- 后端仅接受当前 `state_snapshot.pending_tool_calls` 中存在且状态合法的 `tool_call_id`。
|
|||
|
|
- 若收到未知/已消费/过期 `tool_call_id`,立即产出 `RUN_ERROR` 并记录审计日志。
|
|||
|
|
|
|||
|
|
## 测试策略
|
|||
|
|
|
|||
|
|
- 单元测试:
|
|||
|
|
- 配置解析与 key 解析
|
|||
|
|
- run/resume 状态机与幂等
|
|||
|
|
- LiteLLM usage 聚合
|
|||
|
|
- 集成测试:
|
|||
|
|
- API 入队
|
|||
|
|
- Worker 消费
|
|||
|
|
- SSE 事件顺序与终态
|
|||
|
|
- E2E:
|
|||
|
|
- run 成功链路
|
|||
|
|
- interrupt + resume 链路
|
|||
|
|
- tool 调用链路
|
|||
|
|
|
|||
|
|
## 迁移策略
|
|||
|
|
|
|||
|
|
- 阶段 0(本次):硬切删除旧代码、旧测试、旧文档契约。
|
|||
|
|
- 阶段 1:搭建新架构骨架和最小可运行 run 流程。
|
|||
|
|
- 阶段 2:接入 CrewAI + ag-ui-crewai + LiteLLM 完整链路。
|
|||
|
|
- 阶段 3:补齐可观测性、压测与稳定性治理。
|
|||
|
|
|
|||
|
|
## 验收标准
|
|||
|
|
|
|||
|
|
- 后端仓库不存在旧 `v1/agent` 和 `core/agent` 旧实现。
|
|||
|
|
- 所有 Agent 相关旧测试与旧文档契约已移除。
|
|||
|
|
- 新方案设计文档明确覆盖六项要求并可进入实现阶段。
|