# 复杂场景重构第二阶段 — 架构设计 ## 1. 文档信息 | 属性 | 值 | |------|-----| | 文档名称 | 复杂场景重构第二阶段 — 架构设计文档 | | 版本 | v1.0 | | 创建日期 | 2025-07-11 | | 作者 | 高见远(架构师) | | 关联文档 | Phase 2 PRD、Phase 1 架构设计 | --- ## 2. 实现方案 + 框架选型 ### 2.1 P2 上下文压缩 #### 2.1.1 Tokenizer 选择 | 方案 | 说明 | 优先级 | |------|------|--------| | `tiktoken`(cl100k_base) | OpenAI 官方编码器,与 Dify/GPT 系列模型一致,计数精确 | 首选 | | 字符估算兜底 | tiktoken 不可用时,按 1 token ≈ 1.5 个中文字符估算(误差 ≤10%) | 兜底 | - 封装为 `TokenCounter` 工具类,内部根据 `tiktoken` 可用性自动热切换 - 接口统一:`count_tokens(text: str) -> int`,上层无感知底层差异 #### 2.1.2 压缩引擎设计(ContextCompressor 类) **文件位置**:`backend/app/services/automation/context_compressor.py` **核心方法**: | 方法 | 签名 | 职责 | |------|------|------| | `count_tokens` | `(messages: list) -> int` | 计算消息列表的 token 总数 | | `should_compress` | `(session_id) -> bool` | 判断是否需要压缩(token > threshold) | | `compress` | `(session_id, messages, info_items, actions, task_node) -> CompressedContext` | 执行压缩,返回压缩后上下文 | | `_extract_key_info` | `(info_items, actions, task_node) -> str` | 提取关键信息(信息项当前值、已执行动作、任务节点) | | `_summarize_history` | `(messages_to_compress) -> str` | 调用 LLM 对历史消息生成摘要 | | `_build_compressed_context` | `(key_info, summary, recent_messages) -> str` | 组装压缩后上下文(结构化 Markdown) | | `_progressive_compress` | `(context, level=1) -> str` | 渐进式压缩(最多3级,超出降级截断) | **渐进式压缩策略**: | 级别 | 策略 | 说明 | |------|------|------| | Level 1 | LLM 摘要 + 保留最近4轮对话 | 标准压缩,token 降低约 60% | | Level 2 | LLM 摘要 + 保留最近2轮对话 | 二次压缩,token 降低约 80% | | Level 3 | 纯关键信息 + 保留最近1轮对话 | 极限压缩,token 降低约 90% | | 超出 | 截断最旧消息 | 降级保护,确保不超限 | #### 2.1.3 LLM 调用策略 - 复用现有 `DifyClient`,调用 `/v1/chat-messages` 端点 - **摘要 prompt** 设计要求:将对话历史压缩为结构化摘要,保留关键决策点和信息项 - 摘要调用超时 **30s**,失败则降级为截断最旧消息(`_progressive_compress` 的 Level 3 策略) #### 2.1.4 压缩触发时机 ``` AutoSessionService._run_background() ├── 1. SessionManager.appendMessage(新消息) ├── 2. TokenCounter.count_tokens(当前消息列表) ├── 3. if tokens > threshold: │ ├── ContextCompressor.compress() ← 压缩 │ ├── 用压缩后上下文替换原始消息列表 │ └── 写入 auto_context_compressions 表(日志) ├── 4. DifyClient.chat(压缩后/原始消息) ← 调LLM └── 5. 返回响应 ``` #### 2.1.5 压缩后上下文格式 压缩后生成一条结构化 Markdown 系统消息,替换原始历史: ```markdown ## 会话上下文摘要(系统压缩) ### 已收集信息项 - {name}: {value}(v{version}) ### 已执行动作 - ✅/⏳ {action_title}({time}) ### 当前任务节点 {task_node} ### 历史摘要 {LLM生成的摘要} ### 最近对话 [最近4轮原始对话保留] ``` --- ### 2.2 P3 多轮纠错 #### 2.2.1 版本管理 - 复用 Phase 1 已有的 `InformationItem.version` + `update_history` 机制 - **移除单次更正限制**:Phase 1 实际未硬编码限制,需在 `IntentRouter` 和 `SessionManager` 中确认无限制逻辑 - 每次 CORRECT / SUPPLEMENT 意图都正常 `version += 1`,无次数上限 #### 2.2.2 快照机制(SnapshotService 类) **文件位置**:`backend/app/services/automation/snapshot_service.py` **核心方法**: | 方法 | 签名 | 职责 | |------|------|------| | `create_snapshot` | `(session_id, trigger_item_key, correction_ids) -> InformationSnapshot` | 在每次更正前创建快照 | | `get_latest_snapshot` | `(session_id) -> Optional[InformationSnapshot]` | 获取最近未撤销的快照 | | `undo_correction` | `(session_id) -> dict` | 撤销最近一次更正(限制最近5次) | | `get_snapshot_history` | `(session_id) -> list` | 获取快照历史列表 | | `get_version_diff` | `(session_id, v1, v2) -> dict` | 对比两个版本的差异 | **撤销逻辑流程**: 1. 查询最近一条 `is_undone=False` 的快照 2. 将快照中记录的信息项值回滚到快照前的版本 3. 标记该快照 `is_undone=True` 4. 限制:最多撤销最近 5 次(通过计数 `is_undone=True` 的快照数判断,超过5次拒绝撤销) 5. 撤销后向 AI 上下文注入"更正撤销"系统消息,让 AI 感知用户撤销了更正 #### 2.2.3 依赖检查(混合方案) 采用**模板预定义 + AI 自动标注 + 运行时检查**三层混合方案: | 层次 | 机制 | 说明 | |------|------|------| | 模板预定义 | `ScenarioConfig.actions` 中预定义 `derived_from` 关系 | 如"设备分配人" derived_from "工号" | | AI 自动标注 | IntentRouter 识别 CORRECT 意图时,通过 Dify prompt 让 LLM 标注依赖关系 | 补充模板未覆盖的动态依赖 | | 运行时检查 | `CorrectionService.check_dependencies(session_id, item_key) -> list` | 查询所有 `derived_from` 包含该 item_key 的其他 item,返回联动提示列表 | **运行时检查逻辑**: - 查询当前 session 中所有 `derived_from` 数组包含该 `item_key` 的其他信息项 - 如果存在依赖项且已有值,生成联动提示(如"修改工号后,设备分配人可能需要同步更新") - 返回提示列表,由前端展示给用户/坐席确认 #### 2.2.4 批量更正 - **方法**:`CorrectionService.batch_correct(session_id, corrections: list) -> dict` - **原子性**:单事务内执行多个更正,任何一项失败则整体回滚(SQLAlchemy transaction rollback) - **快照策略**:只生成**一条快照**(记录本次批量更正涉及的所有信息项) - **结果返回**:包含所有已更正项 + 依赖联动警告列表 #### 2.2.5 CorrectionService 类 **文件位置**:`backend/app/services/automation/correction_service.py` **核心方法**: | 方法 | 签名 | 职责 | |------|------|------| | `batch_correct` | `(session_id, corrections: list[dict]) -> BatchCorrectResult` | 批量更正(单事务原子操作) | | `check_dependencies` | `(session_id, item_key) -> list[dict]` | 依赖检查,返回联动提示列表 | | `get_correction_history` | `(session_id) -> list` | 更正历史 | | `get_version_chain` | `(session_id, item_key) -> list` | 版本链(某信息项的所有版本) | | `get_version_diff` | `(session_id, v1, v2) -> dict` | 版本对比 | | `undo_correction` | `(session_id) -> dict` | 撤销更正(委托 SnapshotService) | --- ## 3. 数据结构和接口 ### 3.1 新增/变更 SQLAlchemy 模型 ```python # ============================================================ # 新增:auto_context_compressions 表(上下文压缩日志) # ============================================================ class ContextCompression(Base): """P2 上下文压缩记录表""" __tablename__ = "auto_context_compressions" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) session_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) tokens_before: Mapped[int] = mapped_column(Integer, nullable=False) tokens_after: Mapped[int] = mapped_column(Integer, nullable=False) compression_ratio: Mapped[float] = mapped_column(Numeric(5, 2), nullable=False) task_node: Mapped[Optional[str]] = mapped_column(String(128), nullable=True) duration_ms: Mapped[int] = mapped_column(Integer, nullable=False) compression_level: Mapped[int] = mapped_column(SmallInteger, nullable=False, default=1) summary: Mapped[Optional[str]] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=datetime.now ) # ============================================================ # 新增:auto_information_snapshots 表(信息项快照) # ============================================================ class InformationSnapshot(Base): """P3 信息项快照表(用于撤销更正)""" __tablename__ = "auto_information_snapshots" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) session_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) trigger_item_key: Mapped[str] = mapped_column(String(64), nullable=False) snapshot_data: Mapped[dict] = mapped_column( JSON, nullable=False ) # {item_key: {value, version}} correction_ids: Mapped[list] = mapped_column(JSON, nullable=False, default=list) is_undone: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, default=datetime.now ) # ============================================================ # 变更:InformationItem 新增2列 # ============================================================ class InformationItem(Base): """信息项模型(Phase 2 变更)""" # ... Phase 1 已有字段省略 ... # Phase 2 新增字段 derived_from: Mapped[Optional[list]] = mapped_column( JSON, nullable=True ) # 推导来源 item_key 数组,如 ["工号"] correction_reason: Mapped[Optional[str]] = mapped_column( String(200), nullable=True ) # 更正备注 ``` ### 3.2 Mermaid 类图 ```mermaid classDiagram class AutoSession { +str id +str scenario_id +str status +str current_task_node +datetime created_at +datetime updated_at +appendMessage(message) +getContext() +updateTaskNode(node) } class InformationItem { +str id +str session_id +str item_key +str item_name +str value +int version +dict update_history +Optional~list~ derived_from +Optional~str~ correction_reason +datetime created_at +updateValue(new_value, reason) +getHistory() +checkDependency() } class ContextCompression { +int id +str session_id +int tokens_before +int tokens_after +float compression_ratio +Optional~str~ task_node +int duration_ms +int compression_level +Optional~str~ summary +datetime created_at } class InformationSnapshot { +int id +str session_id +str trigger_item_key +dict snapshot_data +list correction_ids +bool is_undone +datetime created_at } class ContextCompressor { +TokenCounter token_counter +DifyClient dify_client +count_tokens(messages) int +should_compress(session_id) bool +compress(session_id, messages, info_items, actions, task_node) CompressedContext -_extract_key_info(info_items, actions, task_node) str -_summarize_history(messages) str -_build_compressed_context(key_info, summary, recent) str -_progressive_compress(context, level) str } class SnapshotService { +create_snapshot(session_id, trigger_item_key, correction_ids) InformationSnapshot +get_latest_snapshot(session_id) Optional~InformationSnapshot~ +undo_correction(session_id) dict +get_snapshot_history(session_id) list +get_version_diff(session_id, v1, v2) dict } class CorrectionService { +batch_correct(session_id, corrections) BatchCorrectResult +check_dependencies(session_id, item_key) list +get_correction_history(session_id) list +get_version_chain(session_id, item_key) list +get_version_diff(session_id, v1, v2) dict +undo_correction(session_id) dict } AutoSession "1" --> "*" InformationItem : session_id 关联 AutoSession "1" --> "*" ContextCompression : session_id 关联 AutoSession "1" --> "*" InformationSnapshot : session_id 关联 ContextCompressor --> AutoSession : 压缩会话上下文 ContextCompressor --> ContextCompression : 写入压缩日志 SnapshotService --> InformationSnapshot : 管理快照 SnapshotService --> InformationItem : 回滚信息项值 CorrectionService --> SnapshotService : 委托撤销 CorrectionService --> InformationItem : 批量更正 CorrectionService --> InformationSnapshot : 创建快照 ``` ### 3.3 新增 Pydantic Schema ```python # ============================================================ # 请求模型 # ============================================================ class BatchCorrectRequest(BaseModel): """批量更正请求""" corrections: List[CorrectRequest] # 批量更正数组 reason: Optional[str] = None # 更正备注 class UndoCorrectRequest(BaseModel): """撤销更正请求""" pass # 无参数,撤销最近一次 class VersionDiffRequest(BaseModel): """版本对比请求""" v1: int v2: int item_key: Optional[str] = None # 指定信息项,None则对比全局快照 # ============================================================ # 响应模型 # ============================================================ class ContextCompressionResponse(BaseModel): """上下文压缩响应""" id: int session_id: str tokens_before: int tokens_after: int compression_ratio: float task_node: Optional[str] duration_ms: int compression_level: int summary: Optional[str] created_at: Optional[str] class SnapshotResponse(BaseModel): """快照响应""" id: int session_id: str trigger_item_key: str snapshot_data: dict correction_ids: list is_undone: bool created_at: Optional[str] class VersionDiffResponse(BaseModel): """版本差异响应""" item_key: str v1: int v1_value: str v2: int v2_value: str changed: bool class BatchCorrectResponse(BaseModel): """批量更正响应""" corrected_items: List[InformationItemResponse] snapshot_id: int dependency_warnings: List[dict] = Field(default_factory=list) ``` --- ## 4. 程序调用流程 ### 4.1 P2 上下文压缩时序图 ```mermaid sequenceDiagram participant Employee as 员工 participant SM as SessionManager participant TC as TokenCounter participant CC as ContextCompressor participant DB as Database participant LLM as DifyClient / LLM Employee->>SM: 发送消息 SM->>SM: appendMessage(新消息) SM->>TC: count_tokens(当前消息列表) TC-->>SM: 返回 token 总数 alt token > 压缩阈值 SM->>CC: should_compress(session_id) CC-->>SM: true SM->>CC: compress(session_id, messages, info_items, actions, task_node) CC->>CC: _extract_key_info(info_items, actions, task_node) Note over CC: 提取已收集信息项当前值
已执行动作、当前任务节点 CC->>LLM: _summarize_history(messages_to_compress) Note over LLM: 摘要 prompt:
将历史压缩为结构化摘要
保留关键决策点和信息项 LLM-->>CC: 返回历史摘要文本 alt LLM 摘要超时/失败(30s) CC->>CC: 降级 _progressive_compress(level=3) Note over CC: 截断最旧消息,保留关键信息 end CC->>CC: _build_compressed_context(key_info, summary, recent_messages) Note over CC: 组装结构化 Markdown 上下文 CC->>DB: 写入 auto_context_compressions 表 Note over DB: 记录 tokens_before/after
compression_ratio, duration_ms CC-->>SM: 返回 CompressedContext SM->>SM: 用压缩后上下文替换原始消息列表 end SM->>LLM: chat(压缩后/原始消息) LLM-->>SM: 返回 AI 响应 SM-->>Employee: 返回响应 ``` ### 4.2 P3 多轮纠错时序图 ```mermaid sequenceDiagram participant Employee as 员工 participant IR as IntentRouter participant CS as CorrectionService participant SS as SnapshotService participant DB as Database participant AI as AI 上下文 Employee->>IR: 发送更正请求(如"工号改成12345") IR->>IR: 识别 CORRECT 意图 Note over IR: AI 自动标注 derived_from 依赖关系
(通过 Dify prompt) IR->>CS: batch_correct(session_id, corrections) CS->>SS: create_snapshot(session_id, trigger_item_key, correction_ids) Note over SS: 在更正前创建快照
记录所有涉及信息项的当前值和版本 SS->>DB: INSERT auto_information_snapshots SS-->>CS: 返回 snapshot_id CS->>DB: BEGIN TRANSACTION Note over CS: 单事务内批量更新信息项 loop 遍历 corrections 列表 CS->>DB: UPDATE InformationItem
SET value=new_value, version=version+1 alt 某项更新失败 CS->>DB: ROLLBACK CS-->>IR: 抛出异常,整体回滚 end end CS->>DB: COMMIT CS->>CS: check_dependencies(session_id, item_key) Note over CS: 查询所有 derived_from 包含该 item_key 的信息项
生成联动提示列表 CS-->>IR: 返回 BatchCorrectResult
{corrected_items, snapshot_id, dependency_warnings} IR->>AI: 注入更正结果系统消息 Note over AI: AI 感知信息项已更正
后续对话基于新值继续 IR-->>Employee: 返回更正结果 + 联动提示 ``` ### 4.3 P3 更正撤销时序图 ```mermaid sequenceDiagram participant User as 用户/坐席 participant API as CorrectionAPI participant SS as SnapshotService participant DB as Database participant AI as AI 上下文 User->>API: POST /undo-correction (session_id) API->>SS: undo_correction(session_id) SS->>DB: 查询最近一条 is_undone=False 的快照 DB-->>SS: 返回 latest_snapshot alt 无可撤销快照 SS-->>API: 返回错误"无可撤销的更正" API-->>User: 400 无可撤销 else 已撤销次数 >= 5 SS-->>API: 返回错误"撤销次数超限" API-->>User: 400 撤销次数超限 else 正常撤销 SS->>DB: 读取 snapshot_data
{item_key: {value, version}} Note over SS: 快照记录了更正前的信息项值和版本 loop 遍历 snapshot_data 中每个信息项 SS->>DB: UPDATE InformationItem
SET value=快照前值, version=快照前版本 Note over DB: 回滚信息项到更正前状态 end SS->>DB: UPDATE InformationSnapshot
SET is_undone=True Note over DB: 标记该快照为已撤销 SS->>AI: 注入"更正撤销"系统消息 Note over AI: 系统消息内容:
"用户撤销了最近一次更正,
信息项 {item_key} 已回滚到 v{version}"
AI 后续对话基于回滚后的值 SS-->>API: 返回撤销结果
{undone_items, restored_values} API-->>User: 200 撤销成功 end ``` --- ## 5. 文件列表及相对路径 ### 5.1 后端新增文件 | 文件路径 | 职责 | |----------|------| | `backend/app/services/automation/context_compressor.py` | P2 上下文压缩引擎(ContextCompressor 类),包含 token 计数、压缩判断、LLM 摘要、渐进式压缩、压缩日志记录 | | `backend/app/services/automation/snapshot_service.py` | P3 快照管理服务(SnapshotService 类),包含创建快照、撤销更正、快照历史查询、版本对比 | | `backend/app/services/automation/correction_service.py` | P3 纠错服务(CorrectionService 类),包含批量更正、依赖检查、更正历史、版本链、撤销委托 | | `backend/app/utils/token_counter.py` | Token 计数工具类(TokenCounter),封装 tiktoken + 字符估算兜底,支持热切换 | | `backend/app/schemas/automation_p2.py` | P2/P3 Pydantic Schema 定义(BatchCorrectRequest/Response、UndoCorrectRequest、VersionDiffRequest/Response、ContextCompressionResponse、SnapshotResponse 等) | | `backend/app/models/automation_p2.py` | P2/P3 SQLAlchemy 模型定义(ContextCompression、InformationSnapshot) | | `backend/migrations/versions/049_add_p2_p3_tables.py` | 数据库迁移脚本:新增 `auto_context_compressions`、`auto_information_snapshots` 表;`information_items` 表新增 `derived_from`、`correction_reason` 列 | ### 5.2 后端修改文件 | 文件路径 | 修改内容 | |----------|----------| | `backend/app/services/automation/auto_session_service.py` | `_run_background()` 方法中增加压缩触发逻辑:每次调用 LLM 前检查 token 数,超阈值执行压缩;注入压缩后上下文 | | `backend/app/services/automation/session_manager.py` | 确认无单次更正限制逻辑;增加更正结果和撤销结果的系统消息注入方法 | | `backend/app/services/automation/intent_router.py` | CORRECT 意图识别后调用 CorrectionService.batch_correct;增加 AI 自动标注 derived_from 依赖关系的 prompt | | `backend/app/models/automation.py` | InformationItem 模型新增 `derived_from`(JSON)和 `correction_reason`(String)两个映射字段 | | `backend/app/api/v1/automation.py` | 新增 API 端点:`POST /batch-correct`、`POST /undo-correction`、`GET /correction-history`、`GET /version-diff`、`GET /compression-logs` | | `backend/app/config.py` | 新增 P2/P3 配置项:`CONTEXT_COMPRESS_THRESHOLD`(压缩阈值)、`CONTEXT_COMPRESS_TIMEOUT`(摘要超时)、`MAX_UNDO_COUNT`(最大撤销次数)等 | | `backend/app/scenarios/it_helpdesk.py` | 场景配置中预定义 `derived_from` 依赖关系(如设备分配人 derived_from 工号) | ### 5.3 前端新增/修改文件 #### H5 端(员工侧) | 文件路径 | 职责 | |----------|------| | `frontend-h5/src/components/automation/CorrectionNotice.vue` | 更正结果通知组件:展示更正成功提示 + 依赖联动警告 | | `frontend-h5/src/components/automation/UndoButton.vue` | 撤销更正按钮组件:展示可撤销状态 + 撤销次数剩余提示 | #### 坐席端(Agent 侧) | 文件路径 | 职责 | |----------|------| | `frontend-agent/src/views/automation/CorrectionHistory.vue` | 更正历史页面:展示更正时间线、版本链、快照列表 | | `frontend-agent/src/views/automation/VersionDiff.vue` | 版本对比页面:选择两个版本对比信息项值差异,高亮变更项 | | `frontend-agent/src/views/automation/CompressionLogs.vue` | 上下文压缩日志页面:展示压缩记录、压缩率、耗时、摘要内容 | | `frontend-agent/src/components/automation/DependencyWarning.vue` | 依赖联动提示组件:更正某项时展示受影响的关联信息项列表 | | `frontend-agent/src/api/automation_p2.ts` | P2/P3 API 请求封装:batchCorrect、undoCorrection、getCorrectionHistory、getVersionDiff、getCompressionLogs | --- ## 6. 任务列表 ### 6.1 后端任务(按实现顺序) | 任务 ID | 描述 | 依赖 | 涉及文件 | 复杂度 | |---------|------|------|----------|--------| | T01 | 数据库迁移 + 模型层:新增 ContextCompression、InformationSnapshot 模型;InformationItem 新增 derived_from、correction_reason 列;编写迁移脚本 049 | 无 | `models/automation_p2.py`、`models/automation.py`、`migrations/versions/049_*.py` | 中 | | T02 | Token 计数工具类 + 上下文压缩引擎:实现 TokenCounter(tiktoken + 兜底)、ContextCompressor(压缩判断、关键信息提取、LLM 摘要、渐进式压缩、日志记录) | T01 | `utils/token_counter.py`、`services/automation/context_compressor.py` | 高 | | T03 | 快照服务 + 纠错服务:实现 SnapshotService(创建快照、撤销更正、历史查询、版本对比)、CorrectionService(批量更正、依赖检查、版本链、撤销委托) | T01 | `services/automation/snapshot_service.py`、`services/automation/correction_service.py` | 高 | | T04 | API 层 + Schema + 集成:定义 Pydantic Schema;新增 API 端点;修改 AutoSessionService 注入压缩逻辑;修改 IntentRouter 接入纠错服务;修改 SessionManager 注入系统消息;场景配置预定义依赖关系 | T02, T03 | `schemas/automation_p2.py`、`api/v1/automation.py`、`services/automation/auto_session_service.py`、`services/automation/intent_router.py`、`services/automation/session_manager.py`、`scenarios/it_helpdesk.py`、`config.py` | 中 | ### 6.2 前端任务 | 任务 ID | 描述 | 依赖 | 涉及文件 | 复杂度 | |---------|------|------|----------|--------| | T05 | H5 端组件 + 坐席端页面 + API 封装:实现更正通知、撤销按钮(H5 端);更正历史、版本对比、压缩日志、依赖提示页面(坐席端);API 请求封装 | T04 | `frontend-h5/src/components/automation/*.vue`、`frontend-agent/src/views/automation/*.vue`、`frontend-agent/src/components/automation/DependencyWarning.vue`、`frontend-agent/src/api/automation_p2.ts` | 中 | ### 任务依赖图 ```mermaid graph LR T01[T01: 数据库迁移 + 模型层] T02[T02: Token计数 + 压缩引擎] T03[T03: 快照服务 + 纠错服务] T04[T04: API层 + Schema + 集成] T05[T05: 前端组件 + 页面 + API封装] T01 --> T02 T01 --> T03 T02 --> T04 T03 --> T04 T04 --> T05 ``` --- ## 7. 依赖包列表 | 包名 | 版本要求 | 用途 | |------|----------|------| | `tiktoken` | `>= 0.5.0` | Token 计数(OpenAI cl100k_base 编码,与 Dify/GPT 模型一致) | > **说明**:除 `tiktoken` 外,P2/P3 其余功能均复用 Phase 1 已有技术栈(SQLAlchemy、FastAPI、Pydantic、DifyClient 等),无新增依赖。 --- ## 8. 共享知识 ### 8.1 命名约定 | 约定 | 规则 | 示例 | |------|------|------| | 压缩日志表 | `auto_context_compressions` | — | | 快照表 | `auto_information_snapshots` | — | | 服务类命名 | `XxxService` / `XxxCompressor` | `CorrectionService`、`ContextCompressor`、`SnapshotService` | | Schema 命名 | `XxxRequest` / `XxxResponse` | `BatchCorrectRequest`、`SnapshotResponse` | | API 路径前缀 | `/api/v1/automation/` | `/api/v1/automation/batch-correct` | | 系统消息注入 | 以 `[SYSTEM]` 前缀标识 | `[SYSTEM] 更正撤销:信息项 工号 已回滚到 v2` | ### 8.2 错误码扩展 | 错误码 | 含义 | HTTP 状态码 | 说明 | |--------|------|-------------|------| | 4013 | 上下文压缩失败 | 500 | LLM 摘要调用超时或异常,已降级为截断处理 | | 4014 | 撤销次数超限 | 400 | 已撤销次数达到上限(5次),无法继续撤销 | | 4015 | 批量更正部分失败 | 500 | 事务回滚,所有更正均未生效 | **错误响应格式**(与 Phase 1 统一): ```json { "code": 4014, "data": null, "message": "撤销次数超限,最多可撤销最近5次更正" } ``` ### 8.3 配置项 | 配置项 | 默认值 | 说明 | |--------|--------|------| | `CONTEXT_COMPRESS_THRESHOLD` | `6000` | 上下文压缩触发阈值(token 数),超过则执行压缩 | | `CONTEXT_COMPRESS_TIMEOUT` | `30` | LLM 摘要调用超时时间(秒),超时降级截断 | | `CONTEXT_MAX_COMPRESS_LEVEL` | `3` | 渐进式压缩最大级别,超出则降级截断 | | `CONTEXT_KEEP_RECENT_TURNS` | `4` | 压缩后保留的最近对话轮数(Level 1) | | `MAX_UNDO_COUNT` | `5` | 最大撤销更正次数 | | `CONTEXT_COMPRESS_RATIO_WARN` | `0.3` | 压缩率警告阈值(压缩后/压缩前 < 0.3 时告警) | --- ## 9. 数据库迁移方案 ### 迁移脚本:`049_add_p2_p3_tables.py` ```python """Add P2 context compression and P3 snapshot tables Revision ID: 049 Revises: 048 Create Date: 2025-07-11 """ from alembic import op import sqlalchemy as sa from sqlalchemy.dialects import postgresql revision = "049" down_revision = "048" def upgrade(): # 1. 新增 auto_context_compressions 表 op.create_table( "auto_context_compressions", sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), sa.Column("session_id", sa.String(36), nullable=False), sa.Column("tokens_before", sa.Integer(), nullable=False), sa.Column("tokens_after", sa.Integer(), nullable=False), sa.Column("compression_ratio", sa.Numeric(5, 2), nullable=False), sa.Column("task_node", sa.String(128), nullable=True), sa.Column("duration_ms", sa.Integer(), nullable=False), sa.Column("compression_level", sa.SmallInteger(), nullable=False, server_default="1"), sa.Column("summary", sa.Text(), nullable=True), sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()), sa.PrimaryKeyConstraint("id"), ) op.create_index("ix_auto_context_compressions_session_id", "auto_context_compressions", ["session_id"]) # 2. 新增 auto_information_snapshots 表 op.create_table( "auto_information_snapshots", sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), sa.Column("session_id", sa.String(36), nullable=False), sa.Column("trigger_item_key", sa.String(64), nullable=False), sa.Column("snapshot_data", postgresql.JSON(), nullable=False), sa.Column("correction_ids", postgresql.JSON(), nullable=False, server_default="[]"), sa.Column("is_undone", sa.Boolean(), nullable=False, server_default="false"), sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()), sa.PrimaryKeyConstraint("id"), ) op.create_index("ix_auto_information_snapshots_session_id", "auto_information_snapshots", ["session_id"]) # 3. information_items 表新增列 op.add_column("information_items", sa.Column("derived_from", postgresql.JSON(), nullable=True)) op.add_column("information_items", sa.Column("correction_reason", sa.String(200), nullable=True)) def downgrade(): op.drop_column("information_items", "correction_reason") op.drop_column("information_items", "derived_from") op.drop_index("ix_auto_information_snapshots_session_id", table_name="auto_information_snapshots") op.drop_table("auto_information_snapshots") op.drop_index("ix_auto_context_compressions_session_id", table_name="auto_context_compressions") op.drop_table("auto_context_compressions") ``` **迁移影响评估**: | 影响项 | 说明 | |--------|------| | 新增表 | 2 张(`auto_context_compressions`、`auto_information_snapshots`),均为追加表,不影响现有数据 | | 变更表 | 1 张(`information_items` 新增 2 列),均为 nullable 列,向后兼容 | | 数据迁移 | 无需迁移历史数据,新列默认 NULL | | 回滚风险 | 低,`downgrade()` 完整可逆 | --- ## 10. 待明确事项 | 序号 | 待明确事项 | 当前假设 | 影响范围 | |------|-----------|----------|----------| | 1 | 压缩阈值的精确值需根据实际模型上下文窗口确定 | 暂设 6000 tokens(GPT-3.5 4K 上下文留 2K 余量) | `ContextCompressor` 配置项 | | 2 | LLM 摘要使用的 Dify 应用是否独立配置专用摘要 prompt | 假设复用现有 Dify 应用,通过 system message 注入摘要 prompt | `ContextCompressor._summarize_history` | | 3 | `derived_from` 依赖关系的场景模板具体内容需与业务方确认 | 已在 `it_helpdesk.py` 中预定义常见依赖(如设备分配人 derived_from 工号),待业务方补充 | `scenarios/it_helpdesk.py` | | 4 | 撤销操作是否需要二次确认(防误操作) | 假设前端弹出确认框,后端直接执行 | 前端 `UndoButton.vue` | | 5 | 压缩摘要是否需要人工审核后才生效 | 假设自动生效,无需人工审核 | `ContextCompressor.compress` | | 6 | tiktoken 在企业内网环境(无外网)是否可正常安装和运行 | 假设可通过内网 PyPI 镜像安装;运行时编码数据文件(`~/.cache/tiktoken`)需预置 | `utils/token_counter.py` | | 7 | 批量更正的最大数量限制 | 假设无硬限制,由前端 UI 交互控制(单次通常 ≤10 项) | `CorrectionService.batch_correct` |