74 lines
2.9 KiB
Python
74 lines
2.9 KiB
Python
"""客服转人工的最小必要上下文构造。
|
||
|
||
摘要完全由已持久化的会话消息和受控运行结果确定性生成,不调用模型,也不读取账户、画像或
|
||
长期记忆。其目的只是让管理员理解转接缘由,而不是向用户承诺已受理或已完成处理。
|
||
"""
|
||
|
||
from collections.abc import Sequence
|
||
from dataclasses import dataclass
|
||
from decimal import Decimal
|
||
from typing import Any
|
||
|
||
from app.core.contracts import SourceReference
|
||
from app.core.conversation_privacy import sanitize_customer_service_message
|
||
from app.model.conversation import ConversationMessage
|
||
|
||
MAX_SUMMARY_MESSAGES = 6
|
||
MAX_SUMMARY_MESSAGE_CHARACTERS = 280
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class CustomerServiceHandoverContext:
|
||
"""写入工单、审计与 Outbox 的统一、安全转接上下文。"""
|
||
|
||
reason_detail: str
|
||
conversation_summary: str
|
||
source_references: list[dict[str, Any]]
|
||
event_metadata: dict[str, Any]
|
||
|
||
|
||
def _safe_content(content: str) -> str:
|
||
"""二次脱敏和单条截断,确保历史会话也不会把凭据扩散到工单。"""
|
||
sanitized = sanitize_customer_service_message(content).strip()
|
||
if len(sanitized) > MAX_SUMMARY_MESSAGE_CHARACTERS:
|
||
return f"{sanitized[:MAX_SUMMARY_MESSAGE_CHARACTERS - 6]}[截断]"
|
||
return sanitized
|
||
|
||
|
||
def build_customer_service_handover_context(
|
||
*, reason_code: str, clarification_round: int, confidence: Decimal | None,
|
||
source_references: Sequence[SourceReference],
|
||
messages: Sequence[ConversationMessage],
|
||
) -> CustomerServiceHandoverContext:
|
||
"""构造管理员可读但不包含原始敏感凭据的转人工背景。"""
|
||
safe_references = [reference.model_dump(mode="json") for reference in source_references]
|
||
safe_messages = tuple(messages[-MAX_SUMMARY_MESSAGES:])
|
||
summary_lines = [
|
||
f"转接原因:{reason_code}",
|
||
f"已进行澄清轮次:{clarification_round}",
|
||
f"已核验知识来源数:{len(safe_references)}",
|
||
"最近会话(已脱敏):",
|
||
]
|
||
if confidence is not None:
|
||
summary_lines.insert(2, f"意图置信度:{confidence}")
|
||
for message in safe_messages:
|
||
role = "用户" if message.role == "user" else "助手"
|
||
summary_lines.append(f"{role}:{_safe_content(message.content)}")
|
||
conversation_summary = "\n".join(summary_lines)
|
||
reason_detail = (
|
||
f"系统自动转接;原因={reason_code};澄清轮次={clarification_round};"
|
||
f"知识来源数={len(safe_references)}"
|
||
)
|
||
return CustomerServiceHandoverContext(
|
||
reason_detail=reason_detail,
|
||
conversation_summary=conversation_summary,
|
||
source_references=safe_references,
|
||
event_metadata={
|
||
"reason_code": reason_code,
|
||
"clarification_round": clarification_round,
|
||
"confidence": str(confidence) if confidence is not None else None,
|
||
"source_references": safe_references,
|
||
"conversation_summary": conversation_summary,
|
||
},
|
||
)
|