Files
wecom_it_smart_desk/docs/03-技术架构/复杂场景重构第二阶段-架构设计.md
T

764 lines
32 KiB
Markdown
Raw Normal View History

# 复杂场景重构第二阶段 — 架构设计
## 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: 提取已收集信息项当前值<br/>已执行动作、当前任务节点
CC->>LLM: _summarize_history(messages_to_compress)
Note over LLM: 摘要 prompt<br/>将历史压缩为结构化摘要<br/>保留关键决策点和信息项
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<br/>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 依赖关系<br/>(通过 Dify prompt
IR->>CS: batch_correct(session_id, corrections)
CS->>SS: create_snapshot(session_id, trigger_item_key, correction_ids)
Note over SS: 在更正前创建快照<br/>记录所有涉及信息项的当前值和版本
SS->>DB: INSERT auto_information_snapshots
SS-->>CS: 返回 snapshot_id
CS->>DB: BEGIN TRANSACTION
Note over CS: 单事务内批量更新信息项
loop 遍历 corrections 列表
CS->>DB: UPDATE InformationItem<br/>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 的信息项<br/>生成联动提示列表
CS-->>IR: 返回 BatchCorrectResult<br/>{corrected_items, snapshot_id, dependency_warnings}
IR->>AI: 注入更正结果系统消息
Note over AI: AI 感知信息项已更正<br/>后续对话基于新值继续
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<br/>{item_key: {value, version}}
Note over SS: 快照记录了更正前的信息项值和版本
loop 遍历 snapshot_data 中每个信息项
SS->>DB: UPDATE InformationItem<br/>SET value=快照前值, version=快照前版本
Note over DB: 回滚信息项到更正前状态
end
SS->>DB: UPDATE InformationSnapshot<br/>SET is_undone=True
Note over DB: 标记该快照为已撤销
SS->>AI: 注入"更正撤销"系统消息
Note over AI: 系统消息内容:<br/>"用户撤销了最近一次更正,<br/>信息项 {item_key} 已回滚到 v{version}"<br/>AI 后续对话基于回滚后的值
SS-->>API: 返回撤销结果<br/>{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 计数工具类 + 上下文压缩引擎:实现 TokenCountertiktoken + 兜底)、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 tokensGPT-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` |