78 lines
2.1 KiB
Python
78 lines
2.1 KiB
Python
|
|
"""Memoir ingest scheduling boundary.
|
||
|
|
|
||
|
|
The real batching logic still lives in ``BackgroundTaskRunner``. This adapter
|
||
|
|
keeps conversation code from depending on that implementation directly.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from dataclasses import dataclass
|
||
|
|
from typing import Literal, Sequence
|
||
|
|
|
||
|
|
from app.features.memoir.background_runner import BackgroundTaskRunner
|
||
|
|
|
||
|
|
MemoirTrigger = Literal[
|
||
|
|
"turn",
|
||
|
|
"conversation_end",
|
||
|
|
"manual_flush",
|
||
|
|
"evaluation_replay",
|
||
|
|
]
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass(frozen=True)
|
||
|
|
class MemoirPhasePlan:
|
||
|
|
"""A visible plan for submitting segments into the memoir pipeline."""
|
||
|
|
|
||
|
|
user_id: str
|
||
|
|
segment_ids: tuple[str, ...]
|
||
|
|
trigger: MemoirTrigger
|
||
|
|
|
||
|
|
|
||
|
|
class MemoirIngestScheduler:
|
||
|
|
"""Small facade over debounce batching and Phase2 flush dispatch."""
|
||
|
|
|
||
|
|
def __init__(self, runner: BackgroundTaskRunner | None = None) -> None:
|
||
|
|
self._runner = runner or BackgroundTaskRunner()
|
||
|
|
|
||
|
|
@property
|
||
|
|
def runner(self) -> BackgroundTaskRunner:
|
||
|
|
"""Compatibility escape hatch for existing tests and eval utilities."""
|
||
|
|
|
||
|
|
return self._runner
|
||
|
|
|
||
|
|
async def queue_segment(
|
||
|
|
self,
|
||
|
|
user_id: str,
|
||
|
|
segment_id: str,
|
||
|
|
*,
|
||
|
|
text_char_count: int = 0,
|
||
|
|
trigger: MemoirTrigger = "turn",
|
||
|
|
) -> MemoirPhasePlan:
|
||
|
|
await self._runner.queue_message(
|
||
|
|
user_id,
|
||
|
|
segment_id,
|
||
|
|
text_char_count=text_char_count,
|
||
|
|
)
|
||
|
|
return MemoirPhasePlan(
|
||
|
|
user_id=user_id,
|
||
|
|
segment_ids=(segment_id,),
|
||
|
|
trigger=trigger,
|
||
|
|
)
|
||
|
|
|
||
|
|
async def flush_pending(
|
||
|
|
self,
|
||
|
|
user_id: str,
|
||
|
|
*,
|
||
|
|
extra_segment_ids: Sequence[str] | None = None,
|
||
|
|
trigger: MemoirTrigger = "manual_flush",
|
||
|
|
) -> tuple[MemoirPhasePlan, str | None]:
|
||
|
|
ids = tuple(str(x) for x in (extra_segment_ids or ()) if str(x).strip())
|
||
|
|
task_id = await self._runner.flush_pending(
|
||
|
|
user_id,
|
||
|
|
extra_segment_ids=list(ids),
|
||
|
|
)
|
||
|
|
return MemoirPhasePlan(user_id=user_id, segment_ids=ids, trigger=trigger), task_id
|
||
|
|
|
||
|
|
|
||
|
|
__all__ = ["MemoirIngestScheduler", "MemoirPhasePlan", "MemoirTrigger"]
|