from abc import ABC, abstractmethod from typing import Any class BaseProvider(ABC): @abstractmethod async def chat_completion( self, messages: list[dict[str, str]], model: str, temperature: float = 0.0, max_tokens: int = 8000, reasoning_effort: str | None = None, response_format: dict[str, Any] | None = None, tools: list[dict[str, Any]] | None = None, tool_choice: str | dict[str, Any] | None = None, ) -> dict[str, Any]: """Call LLM and return {content, usage, finish_reason, tool_calls}. `tools`/`tool_choice` follow the OpenAI function-calling shape (`tools=[{"type": "function", "function": {...}}]`). When the model responds with tool calls instead of (or alongside) content, the returned dict's `tool_calls` key carries them in the same shape OpenAI/Upstage return them in (`[{"id", "type": "function", "function": {"name", "arguments"}}]`) — empty list when none. """ ... @abstractmethod async def chat_completion_json( self, messages: list[dict[str, str]], model: str, temperature: float = 0.0, max_tokens: int = 8000, ) -> dict[str, Any]: """Call LLM and parse JSON from response.""" ...