""" 会话 transcript 与 Redis 历史条目的纯映射(无 I/O)。 仅由 ConversationService 使用:对齐 ChatOrchestrator 经 save_message 写入 Redis 的字段形状, 不属于 Agent 层 —— 多 Agent 模块只消费已就绪的 history,不负责从 DB 重建。 """ from __future__ import annotations from datetime import timezone from typing import Any, Dict, List from app.features.conversation.models import Segment def _voice_session_id_from_audio_url(audio_url: str | None) -> str | None: if not audio_url: return None prefix = "audio-segment:" if not audio_url.startswith(prefix): return None payload = audio_url[len(prefix) :] voice_session_id_raw, sep, _ = payload.rpartition(":") if sep and voice_session_id_raw: return voice_session_id_raw return None def _segment_timestamp_iso(seg: Segment) -> str | None: if not seg.created_at: return None dt = seg.created_at if dt.tzinfo is None: dt = dt.replace(tzinfo=timezone.utc) return dt.isoformat() def segments_to_redis_history(segments: List[Segment]) -> List[Dict[str, Any]]: """Segment 行 → Redis conversation history 项(与 ChatOrchestrator 写入格式一致)。""" history: List[Dict[str, Any]] = [] for seg in segments: ts = _segment_timestamp_iso(seg) is_voice = bool(seg.audio_url) human: Dict[str, Any] = { "role": "human", "content": seg.transcript_text or "", "messageType": "audio" if is_voice else "text", "timestamp": ts, } vsid = _voice_session_id_from_audio_url(seg.audio_url) if vsid: human["voiceSessionId"] = vsid history.append(human) if seg.agent_response and seg.agent_response.strip(): history.append( { "role": "ai", "content": seg.agent_response.strip(), "messageType": "text", "timestamp": ts, } ) return history