103 lines
2.5 KiB
Python
103 lines
2.5 KiB
Python
"""Wire schemas for the discovery API: requests and streamed events."""
|
|
|
|
from typing import Literal
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
NDJSON_CONTENT_TYPE = "application/x-ndjson"
|
|
|
|
|
|
class HistoryTurn(BaseModel):
|
|
"""One prior chat turn; conversation state lives client-side."""
|
|
|
|
role: Literal["user", "assistant"]
|
|
content: str = Field(min_length=1, max_length=2000)
|
|
|
|
|
|
class PriorRecommendation(BaseModel):
|
|
"""A track from an earlier response that follow-up turns can refer to."""
|
|
|
|
rank: int = Field(ge=1, le=50)
|
|
track_id: str
|
|
title: str
|
|
artists: list[str] = Field(max_length=10)
|
|
|
|
|
|
class RecommendationRequest(BaseModel):
|
|
"""A discovery query with bounded client-side conversation state."""
|
|
|
|
schema_version: Literal[1] = 1
|
|
query: str = Field(min_length=1, max_length=1000)
|
|
history: list[HistoryTurn] = Field(default_factory=list, max_length=12)
|
|
prior_recommendations: list[PriorRecommendation] = Field(default_factory=list, max_length=50)
|
|
|
|
|
|
class TrackCard(BaseModel):
|
|
"""The wire shape of one recommended track."""
|
|
|
|
id: str
|
|
uri: str
|
|
title: str
|
|
artists: list[str]
|
|
album_name: str
|
|
album_art_url: str | None
|
|
external_url: str | None
|
|
|
|
|
|
class MetadataEvent(BaseModel):
|
|
"""First stream event: how the query was understood."""
|
|
|
|
type: Literal["metadata"] = "metadata"
|
|
request_id: str
|
|
intent_summary: str
|
|
candidate_count: int
|
|
|
|
|
|
class TrackEvent(BaseModel):
|
|
"""One recommended track, streamed as soon as it validates."""
|
|
|
|
type: Literal["track"] = "track"
|
|
rank: int
|
|
track: TrackCard
|
|
justification: str
|
|
|
|
|
|
class WarningEvent(BaseModel):
|
|
"""A non-fatal degradation, surfaced honestly instead of hidden."""
|
|
|
|
type: Literal["warning"] = "warning"
|
|
code: str
|
|
message: str
|
|
|
|
|
|
class ErrorEvent(BaseModel):
|
|
"""A terminal failure; no further events follow it."""
|
|
|
|
type: Literal["error"] = "error"
|
|
code: str
|
|
message: str
|
|
|
|
|
|
class DoneEvent(BaseModel):
|
|
"""Final stream event with response-level counters."""
|
|
|
|
type: Literal["done"] = "done"
|
|
track_count: int
|
|
total_ms: int
|
|
|
|
|
|
StreamEvent = MetadataEvent | TrackEvent | WarningEvent | ErrorEvent | DoneEvent
|
|
|
|
|
|
class PlaylistCreateRequest(BaseModel):
|
|
"""A request to save recommended tracks as a real Spotify playlist."""
|
|
|
|
schema_version: Literal[1] = 1
|
|
name: str = Field(min_length=1, max_length=100)
|
|
track_uris: list[str] = Field(min_length=1, max_length=50)
|
|
|
|
|
|
class PlaylistCreateResponse(BaseModel):
|
|
"""The created playlist's public location."""
|
|
|
|
url: str
|