# 技术方案 - AI 回复三态开关(REQ-坐席-010) > **REQ编号**: REQ-坐席-010 > **版本**: v1.0 > **优先级**: P0(含现状行为缺口修复) > **日期**: 2026-08-04 > **架构师**: 高见远(Gao) > **上游 PRD**: `docs/01-产品文档/04-坐席工作台/PRD-REQ-坐席-010-AI回复三态开关.md` > **代码事实核验**: 已逐文件 grep/read 确认(见附录 A「代码事实核验表」) --- ## 一、实现方案(Implementation Approach) ### 1.1 核心难点 | # | 难点 | 解法 | |---|------|------| | D1 | AI 自动回复存在**两条完全独立路径**,历史上门控口径不一致(路径 A 有 status 门控、路径 B 零门控) | 抽取**单一门控判定函数** `ai_reply_gate.py`,两条路径共用同一套判定,杜绝口径漂移 | | D2 | 路径 B 是 `asyncio.create_task` 后台任务,任务内部再判断会**白白占用 DB session + Dify 配额** | 门控前置到**调用点**(`h5.py` / `ws.py`),`create_task` 之前拦截 | | D3 | `employee_and_agent` 的"推坐席"在两条路径上机制完全不同:路径 A **根本没有**坐席 WS 广播(只 `wecom send`),路径 B **默认就广播**(需反向收紧) | 路径 A **补齐**广播;路径 B **加条件**收紧。两侧都收敛到同一 helper `should_push_to_agent()` | | D4 | 路径 B 的坐席广播点分散在 `h5_ai_task.py` 的 4 个函数中,逐点改易漏 | 统一用 helper 包装,任务清单**逐行号列出全部 4 处**(附录 B) | | D5 | 字段需与 `status` 状态机严格正交,且存量数据要有默认值 | 新增独立 `String(20)` 字段(**不用 DB 原生 ENUM**,与既有 `status` 写法一致,兼容 SQLite/PG),Alembic 迁移带 `server_default` | ### 1.2 框架选型(全部沿用既有,无新增) | 层 | 技术 | 说明 | |----|------|------| | 后端 Web | FastAPI + Pydantic v2 | 沿用;新端点复用 `success_response` / `AppException` / `@require_permission` | | ORM | SQLAlchemy 2.0 (`Mapped` / `mapped_column`) | 沿用;新字段写法对齐既有 `status` 字段 | | 迁移 | **Alembic**(已确认:`src/backend/alembic/`,`alembic.ini`,当前 head = `057_troubleshooting_templates`) | 新增 revision `058_add_ai_reply_mode` | | 实时通道 | 进程内单例 `app.services.ws_manager.manager` | 沿用 `broadcast()`(全坐席)/ `broadcast_to_employees()`(指定员工) | | 前端 | Vue3 `