"""Backend-neutral chat contract.""" from collections.abc import AsyncIterator from dataclasses import dataclass, field from typing import Any, Protocol @dataclass(frozen=True) class ChatInput: """Input shared by all chat backend implementations.""" session_id: str text: str need_form_update: bool = False @dataclass(frozen=True) class TextDelta: """A piece of raw model text. The text may contain a partial ``...`` prefix. Parsing that public protocol remains the responsibility of the FastAPI layer. """ text: str @dataclass(frozen=True) class FormUpdate: """A structured form update produced alongside model text.""" data: Any ChatStreamEvent = TextDelta | FormUpdate @dataclass(frozen=True) class ChatResult: """Backend-neutral result for a non-streaming chat request.""" content: str status_code: str | None = None form_update: Any = field(default_factory=dict) class ChatBackend(Protocol): """Contract implemented by FastGPT today and LangGraph later.""" def stream(self, chat_input: ChatInput) -> AsyncIterator[ChatStreamEvent]: """Stream raw text and structured side-channel events.""" ... async def complete(self, chat_input: ChatInput) -> ChatResult: """Return one complete backend-neutral chat result.""" ... class ChatBackendError(Exception): """Base error raised by a chat backend adapter.""" class ChatBackendAuthenticationError(ChatBackendError): """The backend rejected its configured credentials.""" class ChatBackendRateLimitError(ChatBackendError): """The backend rejected the request because of rate limiting.""" class ChatBackendAPIError(ChatBackendError): """The backend API failed or returned an invalid response."""