Ctrl K
ComunidadGuías…

butler/transport/base.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""Abstract base class for LLM transports and client protocol."""

from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Any, Callable, Dict, List, Optional, Protocol, runtime_checkable

from butler.transport.types import NormalizedResponse


class ProviderTransport(ABC):
    """Protocol adapter between Butler and a specific LLM API format."""

    @property
    @abstractmethod
    def api_mode(self) -> str: ...

    def convert_messages(
        self, messages: List[Dict[str, Any]], **kwargs: Any
    ) -> Any:
        return messages

    def convert_tools(self, tools: List[Dict[str, Any]]) -> Any:
        return tools

    @abstractmethod
    def build_kwargs(
        self,
        model: str,
        messages: Any,
        tools: Optional[Any] = None,
        **params: Any,
    ) -> Dict[str, Any]: ...

    @abstractmethod
    def normalize_response(
        self, response: Any, **kwargs: Any
    ) -> NormalizedResponse: ...

    def validate_response(self, response: Any) -> bool:
        return True

    def extract_cache_stats(self, response: Any) -> Optional[Dict[str, int]]:
        return None

    def map_finish_reason(self, raw_reason: str) -> str:
        return raw_reason


# ---------------------------------------------------------------------------
# LLM client protocol — structural interface for AgentLoop decoupling
# ---------------------------------------------------------------------------


@runtime_checkable
class LLMClientProtocol(Protocol):
    """Minimal interface that AgentLoop requires from an LLM client.

    ``LLMClient`` satisfies this protocol automatically via structural
    subtyping — no explicit inheritance needed.
    """

    provider_name: str
    model: str
    max_tokens: Optional[int]
    temperature: Optional[float]
    timeout: int

    def complete(
        self,
        messages: list[dict[str, Any]],
        tools: Optional[list[dict[str, Any]]] = None,
        check_interrupt: Optional[Callable[[], bool]] = None,
        stale_timeout: Optional[float] = None,
        **kwargs: Any,
    ) -> NormalizedResponse: ...

    def stream(
        self,
        messages: list[dict[str, Any]],
        tools: Optional[list[dict[str, Any]]] = None,
        on_delta: Optional[Callable[[str], None]] = None,
        on_tool_call_ready: Optional[Callable[[int, str, str, dict[str, Any]], None]] = None,
        check_interrupt: Optional[Callable[[], bool]] = None,
        stale_timeout: Optional[float] = None,
        **kwargs: Any,
    ) -> NormalizedResponse: ...

__all__ = [
    "LLMClientProtocol", "ProviderTransport", "api_mode", "build_kwargs", "complete", "convert_messages", "convert_tools", "extract_cache_stats", "map_finish_reason", "normalize_response", "stream", "validate_response",
]