bea288e414
== 已部署上线 (9项) == - 代办事项真实数据源集成 (企微审批API 8bug修复链) - H5/坐席端 Logo样式统一+绿色背景 - 视频引导页修复 (localStorage key v2) - 坐席端 v9 Vue版本修复 (ElMessage._context) - 截图按钮 v10 修复 (getDisplayMedia user gesture) - 扫码样式恢复+H5扫码登录跳转修复 - H5截图快捷键提示 == 代码完成待部署 (3项) == - 知识迭代3Bug修复 (#8 POST端点/#7 MERGE幂等/#6 过期检查) - 会议室预定-小鱼易联终端 (40文件, 40/40测试通过) - IT资产升级审批推送 (asset_service.py) == 需求文档 (2项) == - 坐席端AI辅助消息框-PRD (4项新功能确认) - 坐席端布局优化建议 v2.0 (7天计划) == 新增文档 == - 日报-2026-07-11.md - 知识迭代Bug修复报告-20260711.md - 会议室预定-部署指南.md - CHANGELOG.md 更新 == 测试 == - test_todo_integration.py: 40/40 - test_meetingroom.py: 40/40 - test_bugfix_ki_suggestions.py: 21/21
471 lines
14 KiB
Python
471 lines
14 KiB
Python
# =============================================================================
|
||
# 企微IT智能服务台 — 阶段5 自动化闭环 Schema
|
||
# =============================================================================
|
||
# 说明:定义自动化会话相关接口的 Pydantic 请求/响应模型,以及 ORM → dict
|
||
# 序列化辅助函数。所有响应沿用项目 {code, data, message} 约定,
|
||
# 此处只描述 data 结构。
|
||
# =============================================================================
|
||
|
||
from __future__ import annotations
|
||
|
||
from datetime import datetime
|
||
from typing import Any, Dict, List, Optional
|
||
|
||
from pydantic import BaseModel, Field
|
||
|
||
|
||
# --------------------------------------------------------------------------
|
||
# 请求模型
|
||
# --------------------------------------------------------------------------
|
||
class CreateSessionRequest(BaseModel):
|
||
"""创建自动化会话请求。"""
|
||
|
||
conversation_id: Optional[str] = Field(None, description="关联工单ID(弱关联)")
|
||
employee_id: str = Field(..., description="发起员工企微 UserID")
|
||
description: str = Field(..., description="员工诉求/原始消息")
|
||
mode: str = Field("real_exec", description="执行模式:plan_only / real_exec")
|
||
|
||
|
||
class ApprovalDecisionRequest(BaseModel):
|
||
"""坐席审批决策请求。"""
|
||
|
||
decision: str = Field(..., description="approve / reject")
|
||
note: Optional[str] = Field(None, description="审批意见")
|
||
|
||
|
||
class ConfirmRequest(BaseModel):
|
||
"""员工 H5 二次确认请求。"""
|
||
|
||
confirmed: bool = Field(..., description="是否确认执行")
|
||
note: Optional[str] = Field(None, description="备注")
|
||
|
||
|
||
class TakeoverRequest(BaseModel):
|
||
"""转人工接管请求。"""
|
||
|
||
agent_id: str = Field(..., description="接管坐席ID")
|
||
note: Optional[str] = Field(None, description="接管说明")
|
||
|
||
|
||
class ResolveFeedbackRequest(BaseModel):
|
||
"""处置结果反馈(员工是否满意)。"""
|
||
|
||
satisfied: bool = Field(True, description="是否满意")
|
||
note: Optional[str] = Field(None, description="反馈备注")
|
||
|
||
|
||
class ScenarioConfigUpdate(BaseModel):
|
||
"""场景配置更新请求(管理端)。"""
|
||
|
||
name: Optional[str] = None
|
||
description: Optional[str] = None
|
||
enabled: Optional[bool] = None
|
||
trigger_conditions: Optional[dict] = None
|
||
actions: Optional[list] = None
|
||
approval_strategy: Optional[dict] = None
|
||
|
||
|
||
# --------------------------------------------------------------------------
|
||
# 响应模型
|
||
# --------------------------------------------------------------------------
|
||
class IntentResult(BaseModel):
|
||
"""意图识别结果。"""
|
||
|
||
scenario_key: Optional[str] = None
|
||
confidence: float = 0.0
|
||
raw: str = ""
|
||
error: str = ""
|
||
|
||
|
||
class ActionResponse(BaseModel):
|
||
"""处置动作响应。"""
|
||
|
||
id: str
|
||
session_id: str
|
||
action_index: int
|
||
action_type: str
|
||
adapter: str
|
||
risk_level: str
|
||
title: str
|
||
description: str
|
||
status: str
|
||
payload: Optional[dict] = None
|
||
result: Optional[dict] = None
|
||
error: Optional[str] = None
|
||
approved_by: Optional[str] = None
|
||
approved_at: Optional[str] = None
|
||
|
||
|
||
class ApprovalTicketResponse(BaseModel):
|
||
"""审批单响应。"""
|
||
|
||
id: str
|
||
action_id: str
|
||
session_id: str
|
||
approver_id: Optional[str] = None
|
||
channel: str
|
||
status: str
|
||
reason: Optional[str] = None
|
||
decision_note: Optional[str] = None
|
||
decided_at: Optional[str] = None
|
||
|
||
|
||
class SessionResponse(BaseModel):
|
||
"""自动化会话响应。"""
|
||
|
||
id: str
|
||
conversation_id: Optional[str] = None
|
||
employee_id: str
|
||
agent_id: Optional[str] = None
|
||
scenario_key: Optional[str] = None
|
||
status: str
|
||
mode: str
|
||
confidence: float
|
||
title: str
|
||
intent: Optional[dict] = None
|
||
current_action_id: Optional[str] = None
|
||
auto_close_at: Optional[str] = None
|
||
resolved_at: Optional[str] = None
|
||
closed_by: Optional[str] = None
|
||
meta: Optional[dict] = None
|
||
# 复杂场景重构:暂停时间戳
|
||
paused_at: Optional[str] = None
|
||
actions: List[ActionResponse] = Field(default_factory=list)
|
||
approval: Optional[ApprovalTicketResponse] = None
|
||
created_at: Optional[str] = None
|
||
updated_at: Optional[str] = None
|
||
|
||
|
||
class ScenarioConfigResponse(BaseModel):
|
||
"""场景配置响应。"""
|
||
|
||
id: str
|
||
scenario_key: str
|
||
name: str
|
||
description: str
|
||
enabled: bool
|
||
trigger_conditions: Optional[dict] = None
|
||
actions: Optional[list] = None
|
||
approval_strategy: Optional[dict] = None
|
||
current_version_id: Optional[str] = None
|
||
|
||
|
||
class RuleVersionResponse(BaseModel):
|
||
"""规则版本响应。"""
|
||
|
||
id: str
|
||
scenario_key: str
|
||
version: int
|
||
content: Optional[dict] = None
|
||
status: str
|
||
canary_percent: int
|
||
created_by: Optional[str] = None
|
||
remark: str
|
||
created_at: Optional[str] = None
|
||
|
||
|
||
class AutoMetricsResponse(BaseModel):
|
||
"""自动化看板指标响应。"""
|
||
|
||
total_sessions: int = 0
|
||
resolved_sessions: int = 0
|
||
handoff_sessions: int = 0
|
||
error_sessions: int = 0
|
||
auto_executed_actions: int = 0
|
||
approval_required_actions: int = 0
|
||
by_scenario: Dict[str, int] = Field(default_factory=dict)
|
||
|
||
|
||
# --------------------------------------------------------------------------
|
||
# 序列化辅助
|
||
# --------------------------------------------------------------------------
|
||
def _iso(dt: Optional[datetime]) -> Optional[str]:
|
||
"""将 datetime 转为 ISO 字符串(None 透传)。"""
|
||
return dt.isoformat() if dt else None
|
||
|
||
|
||
def serialize_action(action: Any) -> ActionResponse:
|
||
"""将 AutoAction ORM 对象序列化为响应模型。"""
|
||
return ActionResponse(
|
||
id=action.id,
|
||
session_id=action.session_id,
|
||
action_index=action.action_index,
|
||
action_type=action.action_type,
|
||
adapter=action.adapter,
|
||
risk_level=action.risk_level,
|
||
title=action.title,
|
||
description=action.description,
|
||
status=action.status,
|
||
payload=action.payload,
|
||
result=action.result,
|
||
error=action.error,
|
||
approved_by=action.approved_by,
|
||
approved_at=_iso(action.approved_at),
|
||
)
|
||
|
||
|
||
def serialize_approval(ticket: Any) -> ApprovalTicketResponse:
|
||
"""将 ApprovalTicket ORM 对象序列化为响应模型。"""
|
||
return ApprovalTicketResponse(
|
||
id=ticket.id,
|
||
action_id=ticket.action_id,
|
||
session_id=ticket.session_id,
|
||
approver_id=ticket.approver_id,
|
||
channel=ticket.channel,
|
||
status=ticket.status,
|
||
reason=ticket.reason,
|
||
decision_note=ticket.decision_note,
|
||
decided_at=_iso(ticket.decided_at),
|
||
)
|
||
|
||
|
||
def serialize_session(
|
||
session: Any,
|
||
actions: Optional[List[Any]] = None,
|
||
ticket: Optional[Any] = None,
|
||
) -> SessionResponse:
|
||
"""将 AutoSession ORM 对象序列化为响应模型。"""
|
||
return SessionResponse(
|
||
id=session.id,
|
||
conversation_id=session.conversation_id,
|
||
employee_id=session.employee_id,
|
||
agent_id=session.agent_id,
|
||
scenario_key=session.scenario_key,
|
||
status=session.status,
|
||
mode=session.mode,
|
||
confidence=session.confidence,
|
||
title=session.title,
|
||
intent=session.intent,
|
||
current_action_id=session.current_action_id,
|
||
auto_close_at=_iso(session.auto_close_at),
|
||
resolved_at=_iso(session.resolved_at),
|
||
closed_by=session.closed_by,
|
||
meta=session.meta,
|
||
paused_at=_iso(session.paused_at),
|
||
actions=[serialize_action(a) for a in (actions or [])],
|
||
approval=serialize_approval(ticket) if ticket else None,
|
||
created_at=_iso(session.created_at),
|
||
updated_at=_iso(session.updated_at),
|
||
)
|
||
|
||
|
||
# ===========================================================================
|
||
# 复杂场景重构第一阶段 — 新增 Schema
|
||
# ===========================================================================
|
||
|
||
# --------------------------------------------------------------------------
|
||
# 全局意图识别结果(扩展 IntentResult)
|
||
# --------------------------------------------------------------------------
|
||
class GlobalIntentResult(BaseModel):
|
||
"""全局意图识别结果。"""
|
||
|
||
global_intent: Optional[str] = None # pause / resume_task / correct / supplement / null
|
||
scenario_key: Optional[str] = None # 未命中全局意图时走场景识别
|
||
confidence: float = 0.0
|
||
corrected_field: Optional[str] = None # CORRECT: 更正的字段名
|
||
old_value: Optional[str] = None # CORRECT: 旧值
|
||
new_value: Optional[str] = None # CORRECT: 新值
|
||
supplement_field: Optional[str] = None # SUPPLEMENT: 补充的字段名
|
||
supplement_value: Optional[str] = None # SUPPLEMENT: 补充的值
|
||
raw: str = ""
|
||
error: str = ""
|
||
|
||
|
||
# --------------------------------------------------------------------------
|
||
# 请求模型(复杂场景重构)
|
||
# --------------------------------------------------------------------------
|
||
class PauseRequest(BaseModel):
|
||
"""暂停会话请求(通常由意图识别触发,也可直接调用)。"""
|
||
|
||
reason: Optional[str] = None
|
||
|
||
|
||
class ResumeRequest(BaseModel):
|
||
"""恢复会话请求。"""
|
||
|
||
session_id: Optional[str] = None # 多任务时指定恢复哪个;None 则自动选择唯一的
|
||
|
||
|
||
class CorrectRequest(BaseModel):
|
||
"""信息更正请求。"""
|
||
|
||
field: str = Field(..., description="更正的字段名")
|
||
new_value: str = Field(..., description="新值")
|
||
old_value: Optional[str] = None
|
||
|
||
|
||
class SupplementRequest(BaseModel):
|
||
"""信息补充请求。"""
|
||
|
||
field: str = Field(..., description="补充的字段名")
|
||
value: str = Field(..., description="补充的值")
|
||
|
||
|
||
class AgentResumeRequest(BaseModel):
|
||
"""坐席代恢复请求。"""
|
||
|
||
note: Optional[str] = None
|
||
|
||
|
||
class AgentCloseRequest(BaseModel):
|
||
"""坐席关闭会话请求。"""
|
||
|
||
note: Optional[str] = None
|
||
|
||
|
||
# --------------------------------------------------------------------------
|
||
# 响应模型(复杂场景重构)
|
||
# --------------------------------------------------------------------------
|
||
class InformationItemResponse(BaseModel):
|
||
"""信息项响应。"""
|
||
|
||
id: str
|
||
session_id: str
|
||
name: str
|
||
value: str
|
||
modifiers: List[str] = Field(default_factory=list)
|
||
is_filled: bool
|
||
is_locked: bool
|
||
version: int
|
||
update_history: List[dict] = Field(default_factory=list)
|
||
updated_at: Optional[str] = None
|
||
|
||
|
||
class ResumePointResponse(BaseModel):
|
||
"""恢复点响应。"""
|
||
|
||
session_id: str
|
||
title: str
|
||
scenario_key: Optional[str] = None
|
||
current_step: str
|
||
paused_at: Optional[str] = None
|
||
pending_items: List[str] = Field(default_factory=list)
|
||
info_items: List[InformationItemResponse] = Field(default_factory=list)
|
||
|
||
|
||
class PausedSessionItem(BaseModel):
|
||
"""暂停会话列表项。"""
|
||
|
||
session_id: str
|
||
title: str
|
||
scenario_key: Optional[str] = None
|
||
paused_at: Optional[str] = None
|
||
paused_duration: str = "" # 如 "2h 15min"
|
||
|
||
|
||
# --------------------------------------------------------------------------
|
||
# 序列化辅助(复杂场景重构)
|
||
# --------------------------------------------------------------------------
|
||
def serialize_information_item(item: Any) -> InformationItemResponse:
|
||
"""将 InformationItem ORM 对象序列化为响应模型。"""
|
||
return InformationItemResponse(
|
||
id=item.id,
|
||
session_id=item.session_id,
|
||
name=item.name,
|
||
value=item.value,
|
||
modifiers=item.modifiers or [],
|
||
is_filled=item.is_filled,
|
||
is_locked=item.is_locked,
|
||
version=item.version,
|
||
update_history=item.update_history or [],
|
||
updated_at=_iso(item.updated_at),
|
||
)
|
||
|
||
|
||
# ===========================================================================
|
||
# 复杂场景重构第二阶段 — P2/P3 新增 Schema
|
||
# ===========================================================================
|
||
|
||
# --------------------------------------------------------------------------
|
||
# P3 请求模型
|
||
# --------------------------------------------------------------------------
|
||
class BatchCorrectRequest(BaseModel):
|
||
"""批量更正请求。"""
|
||
|
||
corrections: List[dict] = Field(
|
||
..., description="更正列表 [{field, new_value, old_value?}, ...]"
|
||
)
|
||
reason: Optional[str] = Field(None, description="更正备注")
|
||
|
||
|
||
class UndoCorrectionRequest(BaseModel):
|
||
"""撤销更正请求。"""
|
||
|
||
pass # 无参数,撤销最近一次更正
|
||
|
||
|
||
class VersionDiffRequest(BaseModel):
|
||
"""版本对比请求。"""
|
||
|
||
v1: int = Field(..., description="版本号1")
|
||
v2: int = Field(..., description="版本号2")
|
||
|
||
|
||
# --------------------------------------------------------------------------
|
||
# P3 响应模型
|
||
# --------------------------------------------------------------------------
|
||
class BatchCorrectResponse(BaseModel):
|
||
"""批量更正响应。"""
|
||
|
||
corrected_items: List[dict] = Field(default_factory=list)
|
||
snapshot_id: int
|
||
dependency_warnings: List[dict] = Field(default_factory=list)
|
||
|
||
|
||
class UndoCorrectionResponse(BaseModel):
|
||
"""撤销更正响应。"""
|
||
|
||
undone_items: List[str] = Field(default_factory=list)
|
||
restored_values: Dict[str, str] = Field(default_factory=dict)
|
||
snapshot_id: int
|
||
remaining_undo_count: int = 5
|
||
|
||
|
||
class CorrectionHistoryResponse(BaseModel):
|
||
"""更正历史响应。"""
|
||
|
||
history: List[dict] = Field(default_factory=list)
|
||
|
||
|
||
class VersionChainResponse(BaseModel):
|
||
"""版本链响应。"""
|
||
|
||
item_key: str
|
||
chain: List[dict] = Field(default_factory=list)
|
||
|
||
|
||
class VersionDiffResponse(BaseModel):
|
||
"""版本对比响应。"""
|
||
|
||
item_key: str
|
||
v1: int
|
||
v1_value: str
|
||
v2: int
|
||
v2_value: str
|
||
changed: bool
|
||
|
||
|
||
# --------------------------------------------------------------------------
|
||
# P2 响应模型
|
||
# --------------------------------------------------------------------------
|
||
class CompressionLogItem(BaseModel):
|
||
"""压缩日志项。"""
|
||
|
||
id: int
|
||
session_id: str
|
||
tokens_before: int
|
||
tokens_after: int
|
||
compression_ratio: float
|
||
task_node: Optional[str] = None
|
||
duration_ms: int
|
||
compression_level: int
|
||
summary: Optional[str] = None
|
||
created_at: Optional[str] = None
|
||
|
||
|
||
class CompressionLogListResponse(BaseModel):
|
||
"""压缩日志列表响应。"""
|
||
|
||
logs: List[CompressionLogItem] = Field(default_factory=list)
|
||
total: int = 0
|