"""HTTP / OpenAPI 模型。""" from __future__ import annotations from datetime import datetime from typing import Any, Literal from pydantic import BaseModel, ConfigDict, Field EvalJudgeProviderLiteral = Literal["zhipu", "deepseek"] class SessionDialogueMessageOut(BaseModel): model_config = ConfigDict(from_attributes=True) role: str content: str created_at: datetime | None = None class SessionDialogueOut(BaseModel): conversation_id: str messages: list[SessionDialogueMessageOut] class SessionListItem(BaseModel): id: str user_id: str user_phone: str | None = Field(default=None, description="users.phone,列表展示用") started_at: datetime | None last_message_at: datetime | None = None conversation_stage: str | None current_topic: str | None status: str | None class SessionListResponse(BaseModel): items: list[SessionListItem] total: int class SessionTranscriptOut(BaseModel): conversation_id: str user_id: str user_utterances_from_segments: list[str] user_utterances_from_messages: list[str] class UserExportFixtureTurnOut(BaseModel): user: str ai: str class UserExportFixtureListOut(BaseModel): items: list[str] class MemoirSectionBaselineOut(BaseModel): title: str body: str class UserExportFixtureDetailOut(BaseModel): filename: str turns: list[UserExportFixtureTurnOut] source_user_id: str | None = None memoir_sections: list[MemoirSectionBaselineOut] = Field(default_factory=list) class ReplayBootstrapBody(BaseModel): user_id: str class ReplayBootstrapOut(BaseModel): conversation_id: str class EvalSandboxOut(BaseModel): """内部评测专用:一次性临时账号 + 空白会话,不落真实手机号业务。""" user_id: str conversation_id: str phone: str nickname: str class ReplayConversationBody(BaseModel): conversation_id: str fixture_filename: str | None = None user_utterances: list[str] | None = None flush_memoir_after: bool = True """为 True 且 skip_memoir 为 False 时,本批结束后 flush 回忆录队列。""" skip_memoir: bool = False """为 True 时不向回忆录防抖队列入队、不 flush(供 Playground 先只测对话)。""" skip_tts: bool = True class ReplayConversationOut(BaseModel): conversation_id: str turns_replayed: int utterances_echo: list[str] = Field(default_factory=list) segment_ids: list[str] = Field( default_factory=list, description="本批请求创建并已走 orchestrator 的用户 segment id(顺序与落库一致)", ) #: 服务端计量:本 HTTP 请求内回放逻辑耗时(与浏览器轮询间隔无关) started_at_utc: datetime | None = None finished_at_utc: datetime | None = None elapsed_ms: int | None = Field( default=None, description="服务端 wall 耗时(本请求内 replay_utterances / replay_fixture)", ) class MemoirPhase1ReadyOut(BaseModel): ready: bool checked_segment_ids: list[str] = Field(default_factory=list) pending_segment_ids: list[str] = Field(default_factory=list) #: 最近一次 Playground memoir-submit 写入 Redis 的提交时间(无记录时为 None) job_submitted_at_utc: datetime | None = None #: 自 job_submitted_at_utc 至本响应生成时服务端经过的毫秒数 elapsed_ms_since_submit: int | None = Field(default=None, ge=0) #: 可选分步耗时(毫秒),键由服务端定义 durations_ms: dict[str, int] = Field(default_factory=dict) class MemoirSubmitOut(BaseModel): conversation_id: str user_id: str segment_ids: list[str] = Field(default_factory=list) celery_task_id: str | None = None submitted_at_utc: datetime | None = None #: 提交接口瞬间耗时,通常为 0;与 Phase1 Celery 执行时间无关 elapsed_ms: int | None = Field(default=None, ge=0) class MemoirPipelineRunOut(BaseModel): """Redis 流水线快照(memoir_pipeline_run:*);字段随迭代扩展。""" model_config = ConfigDict(extra="allow") memoir_correlation_id: str user_id: str | None = None started_at_utc: str | None = None phase1: dict[str, Any] | None = None phase2: list[Any] = Field(default_factory=list) fanout: dict[str, Any] = Field(default_factory=dict) class ManualJudgeConversationBody(BaseModel): conversation_id: str """与当前评测台选中的 MD 一致,供基准 transcript / 整体打分。""" fixture_filename: str | None = None judge_provider: EvalJudgeProviderLiteral = "zhipu" judge_model: str | None = None """空则用该供应商默认模型(智谱:eval_judge_model;DeepSeek:eval_judge_deepseek_model)。""" class ManualJudgeConversationStreamBody(BaseModel): conversation_id: str fixture_filename: str | None = None include_turn_judges: bool = False """对当前会话逐轮调用评审 LLM(在整体分之后)。""" include_baseline_turn_judges: bool = False """对导出基线逐轮调用评审 LLM(需 fixture + 整体基线分成功)。""" judge_provider: EvalJudgeProviderLiteral = "zhipu" judge_model: str | None = None class RetryBaselineJudgeBody(BaseModel): conversation_id: str fixture_filename: str | None = None include_baseline_turn_judges: bool = False """与流式评分一致:成功重试基准整体分后是否补跑基线逐轮。""" judge_provider: EvalJudgeProviderLiteral = "zhipu" judge_model: str | None = None class RetryBaselineJudgeOut(BaseModel): ok: bool error: str | None = None message: str | None = None baseline_judge: dict[str, Any] | None = None replay_judge: dict[str, Any] | None = None compare_summary: dict[str, Any] | None = None compare_markdown: str = "" baseline_turn_judges: dict[str, Any] = Field(default_factory=dict) errors: list[str] = Field(default_factory=list) class ManualJudgeConversationOut(BaseModel): conversation_id: str fixture_filename: str | None = None baseline_transcript: str = "" replay_transcript: str baseline_judge: dict[str, Any] | None = None replay_judge: dict[str, Any] | None = None compare_summary: dict[str, Any] | None = None errors: list[str] = Field(default_factory=list) class PlaygroundConversationJudgeOut(BaseModel): """`conversations.playground_conversation_judge_json` 的只读视图。""" conversation_id: str judge: dict[str, Any] | None = None class ManualJudgeMemoirBody(BaseModel): user_id: str baseline_sections: list[MemoirSectionBaselineOut] | None = None judge_provider: EvalJudgeProviderLiteral = "zhipu" judge_model: str | None = None class ManualJudgeMemoirOut(BaseModel): user_id: str judge_provider: EvalJudgeProviderLiteral = "zhipu" judge_model: str = "" """本次请求实际解析后的模型 id(与 `build_eval_judge_llm_spec` 一致)。""" chapter_results: list[dict[str, Any]] = Field(default_factory=list) story_results: list[dict[str, Any]] = Field(default_factory=list) errors: list[str] = Field(default_factory=list) """单条章节/故事评审或列表加载失败时的可读原因(HTTP 仍为 200)。""" warnings: list[str] = Field(default_factory=list) """无失败但未评到任何条目时的提示(例如成稿均为空)。""" class MemoirChapterSnapOut(BaseModel): id: str title: str category: str | None = None order_index: int | None = None canonical_markdown: str | None = None class MemoirStorySnapOut(BaseModel): id: str title: str stage: str | None = None canonical_markdown: str | None = None class UserMemoirSnapshotOut(BaseModel): user_id: str chapters: list[MemoirChapterSnapOut] stories: list[MemoirStorySnapOut]