78 lines
3.1 KiB
Python
78 lines
3.1 KiB
Python
"""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
|