feat: add key-free demo mode replaying recorded fixtures
This commit is contained in:
parent
0664cc2d27
commit
401a0ddea7
21 changed files with 1040 additions and 31 deletions
78
backend/app/adapters/demo/pipeline.py
Normal file
78
backend/app/adapters/demo/pipeline.py
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
"""Demo-only pipeline decorator for honest fuzzy replay disclosure."""
|
||||
|
||||
import time
|
||||
from collections.abc import AsyncGenerator
|
||||
from contextlib import aclosing
|
||||
|
||||
from app.adapters.demo.catalog import DemoCatalog
|
||||
from app.adapters.demo.recommender import DemoRecommender, parse_recorded_intent
|
||||
from app.adapters.demo.scenario import select_replay_scenario
|
||||
from app.config import Settings
|
||||
from app.domain.models import ConversationTurn, PreviousRecommendation, Track
|
||||
from app.pipeline.event import PipelineEvent, PipelineMetadataEvent, PipelineWarningEvent
|
||||
from app.pipeline.grounding import Grounder
|
||||
from app.pipeline.orchestrator import RecommendationPipeline
|
||||
from app.ports.protocols import MusicCatalog
|
||||
|
||||
DEMO_REPLAY_CODE = "demo_replay"
|
||||
|
||||
|
||||
class DemoReplayPipeline:
|
||||
"""Decorate the real pipeline with a fuzzy-replay warning event."""
|
||||
|
||||
def __init__(self, settings: Settings) -> None:
|
||||
"""Create one cache-preserving pipeline with a replay recommender."""
|
||||
self.settings = settings
|
||||
self.pipeline = RecommendationPipeline(DemoRecommender(settings), settings)
|
||||
|
||||
async def stream(
|
||||
self,
|
||||
session_id: str,
|
||||
request_id: str,
|
||||
catalog: MusicCatalog,
|
||||
query: str,
|
||||
history: tuple[ConversationTurn, ...],
|
||||
previous_recommendations: tuple[PreviousRecommendation, ...],
|
||||
) -> AsyncGenerator[PipelineEvent]:
|
||||
"""Stream the selected fixture and disclose non-exact selection."""
|
||||
match = select_replay_scenario(query, bool(previous_recommendations))
|
||||
seeded_pool = await self._prepare_refinement_pool(catalog)
|
||||
event_stream = self.pipeline.stream(
|
||||
session_id,
|
||||
request_id,
|
||||
catalog,
|
||||
query,
|
||||
history,
|
||||
previous_recommendations,
|
||||
seeded_pool=seeded_pool,
|
||||
)
|
||||
async with aclosing(event_stream) as events:
|
||||
async for event in events:
|
||||
yield event
|
||||
if isinstance(event, PipelineMetadataEvent) and not match.is_exact:
|
||||
yield PipelineWarningEvent(
|
||||
code=DEMO_REPLAY_CODE,
|
||||
message=f'Demo replay is showing the recorded "{match.chip}" scenario.',
|
||||
)
|
||||
|
||||
async def _prepare_refinement_pool(
|
||||
self,
|
||||
catalog: MusicCatalog,
|
||||
) -> tuple[Track, ...] | None:
|
||||
if not isinstance(catalog, DemoCatalog):
|
||||
return None
|
||||
intent_bodies = catalog.cassette.intent_response_bodies
|
||||
if len(intent_bodies) < 2:
|
||||
return None
|
||||
# Refinement cassettes record the parent intent first and refinement intent last.
|
||||
parent_intent = parse_recorded_intent(intent_bodies[0])
|
||||
result = await Grounder(self.settings).ground(
|
||||
catalog,
|
||||
parent_intent.candidates,
|
||||
# The recorded parent pool is listener-neutral, so replay has no known-track exclusions.
|
||||
frozenset(),
|
||||
parent_intent.familiarity,
|
||||
self.settings.rerank_count + self.settings.rerank_pool_buffer,
|
||||
time.monotonic() + self.settings.request_deadline_seconds,
|
||||
)
|
||||
return result.tracks
|
||||
Loading…
Add table
Add a link
Reference in a new issue