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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user