refactor(eval+memoir):精简内部评测路由与服务,composite/对话摘要与 judge 能力补强

- 访谈:新增 interview_state_hints,联动 orchestrator 与提示词
- 回忆录:story_pipeline_sync/state/memory/post_commit 与 Celery 任务调整
- 基建:开发用 celery broker、compose/development 脚本、依赖注入
- eval-web:移除数据集/实验/版本等页面与流式轮询,突出 Playground
- 文档与单测同步
This commit is contained in:
Kevin
2026-04-08 21:36:12 +08:00
parent 2a0c80987d
commit 064ad2161d
64 changed files with 3412 additions and 3068 deletions

View File

@@ -4,7 +4,7 @@
"""
import uuid
from typing import Dict, List
from typing import Dict, List, cast
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
@@ -14,10 +14,18 @@ from app.agents.stage_constants import (
chat_bucket,
normalize_chat_stage,
)
from app.agents.state_schema import MemoirStateSchema, SlotData, default_state
from app.agents.state_schema import (
KnownFact,
MemoirStateSchema,
PersonaThread,
SlotData,
default_state,
)
from app.core.config import settings
from app.features.memoir.models import MemoirState as MemoirStateModel
_INTERVIEW_STATE_META_KEY = "__interview_state__"
def _slots_snapshot_for_merge(raw: Dict[str, Dict] | None) -> Dict[str, Dict]:
"""浅拷贝 slots避免就地改 JSON 列同一 dict 引用导致 ORM 不标记 dirty。"""
@@ -26,15 +34,67 @@ def _slots_snapshot_for_merge(raw: Dict[str, Dict] | None) -> Dict[str, Dict]:
return {k: dict(v or {}) for k, v in raw.items()}
def _extract_interview_state_meta(
raw_slots: Dict[str, Dict] | None,
) -> tuple[list[KnownFact], list[PersonaThread], list[str]]:
if not raw_slots or not isinstance(raw_slots, dict):
return [], [], []
meta = raw_slots.get(_INTERVIEW_STATE_META_KEY)
if not isinstance(meta, dict):
return [], [], []
known = meta.get("known_facts") if isinstance(meta.get("known_facts"), list) else []
persona = (
meta.get("persona_threads")
if isinstance(meta.get("persona_threads"), list)
else []
)
recent = (
meta.get("recent_questions")
if isinstance(meta.get("recent_questions"), list)
else []
)
return (
[KnownFact.model_validate(x) for x in known if isinstance(x, dict)],
[PersonaThread.model_validate(x) for x in persona if isinstance(x, dict)],
[str(x).strip() for x in recent if str(x).strip()],
)
def _inject_interview_state_meta(
*,
slots: Dict[str, Dict],
known_facts: list[KnownFact],
persona_threads: list[PersonaThread],
recent_questions: list[str],
) -> Dict[str, Dict]:
out = dict(slots)
out[_INTERVIEW_STATE_META_KEY] = cast(
Dict,
{
"known_facts": [x.model_dump() for x in known_facts],
"persona_threads": [x.model_dump() for x in persona_threads],
"recent_questions": list(recent_questions),
},
)
return out
def coerce_memoir_state(model: MemoirStateModel) -> MemoirStateSchema:
raw_slots = model.slots if isinstance(model.slots, dict) else None
known_facts, persona_threads, recent_questions = _extract_interview_state_meta(
raw_slots
)
clean_slots = dict(raw_slots) if raw_slots else dict(default_state().slots)
clean_slots.pop(_INTERVIEW_STATE_META_KEY, None)
return MemoirStateSchema.model_validate(
{
"stage_order": model.stage_order or default_state().stage_order,
"current_stage": model.current_stage,
"covered_stages": model.covered_stages or [],
"slots": model.slots
if isinstance(model.slots, dict)
else default_state().slots,
"slots": clean_slots,
"known_facts": known_facts,
"persona_threads": persona_threads,
"recent_questions": recent_questions,
}
)
@@ -187,6 +247,40 @@ async def switch_stage(
return coerce_memoir_state(state)
async def save_interview_state_meta(
user_id: str,
*,
known_facts: list[KnownFact],
persona_threads: list[PersonaThread],
recent_questions: list[str],
db: AsyncSession,
) -> MemoirStateSchema:
stmt = (
select(MemoirStateModel)
.where(MemoirStateModel.user_id == user_id)
.with_for_update()
)
result = await db.execute(stmt)
state = result.scalar_one_or_none()
if not state:
await get_or_create_state(user_id, db)
result = await db.execute(stmt)
state = result.scalar_one()
slots = _slots_snapshot_for_merge(
state.slots if isinstance(state.slots, dict) else None
)
state.slots = _inject_interview_state_meta(
slots=slots,
known_facts=known_facts,
persona_threads=persona_threads,
recent_questions=recent_questions,
)
await db.commit()
await db.refresh(state)
return coerce_memoir_state(state)
def get_or_create_state_sync(user_id: str, db: Session) -> MemoirStateSchema:
stmt = select(MemoirStateModel).where(MemoirStateModel.user_id == user_id)
result = db.execute(stmt)