# 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 相关旧测试与旧文档契约已移除。 - 新方案设计文档明确覆盖六项要求并可进入实现阶段。