""" Yutori adapter for litellm - routes yutori/ prefixed models to the Yutori API. """ import os from typing import Any, AsyncIterator, Iterator from litellm import acompletion, completion from litellm.llms.custom_llm import CustomLLM from litellm.types.utils import GenericStreamingChunk, ModelResponse YUTORI_API_BASE = "https://api.yutori.com/v1" class YutoriAdapter(CustomLLM): def __init__(self, base_url: str | None = None, api_key: str | None = None, **_: Any): super().__init__() self.base_url = base_url or os.environ.get("YUTORI_API_BASE") or YUTORI_API_BASE self.api_key = api_key or os.environ.get("YUTORI_API_KEY") def _normalize_model(self, model: str) -> str: """Strip the yutori/ prefix to get the bare model name.""" if model.startswith("yutori/"): return model[len("yutori/") :] return model def _resolve_api_key(self, kwargs: dict | None = None) -> str: """Resolve the Yutori API key, raising a clear error if missing.""" resolved = (kwargs.get("api_key") if kwargs else None) or self.api_key if not resolved: raise ValueError( "No Yutori API key provided. " "Please either set the YUTORI_API_KEY environment variable " "or pass api_key to ComputerAgent()." ) return resolved def _build_params(self, kwargs: dict) -> dict: """Build parameters for the inner litellm call.""" model = self._normalize_model(kwargs.get("model", "")) api_key = self._resolve_api_key(kwargs) extra_headers = {} if "extra_headers" in kwargs: extra_headers.update(kwargs.pop("extra_headers")) extra_headers["Authorization"] = f"Bearer {api_key}" params = { "model": f"openai/{model}", "messages": kwargs.get("messages", []), "api_base": self.base_url, "api_key": api_key, "extra_headers": extra_headers, "stream": False, } # Forward tools if provided if "tools" in kwargs: params["tools"] = kwargs["tools"] if "tool_choice" in kwargs: params["tool_choice"] = kwargs["tool_choice"] # Forward optional generation params for key in ( "temperature", "top_p", "max_completion_tokens", "max_tokens", "response_format", ): if key in kwargs: params[key] = kwargs[key] if "optional_params" in kwargs: protected_keys = {"api_key", "extra_headers", "model", "api_base", "stream"} filtered = { k: v for k, v in kwargs["optional_params"].items() if k not in protected_keys } params.update(filtered) if "headers" in kwargs: params["headers"] = kwargs["headers"] return params def completion(self, *args, **kwargs) -> ModelResponse: params = self._build_params(kwargs) return completion(**params) # type: ignore async def acompletion(self, *args, **kwargs) -> ModelResponse: params = self._build_params(kwargs) response = await acompletion(**params) # type: ignore return response def streaming(self, *args, **kwargs) -> Iterator[GenericStreamingChunk]: raise NotImplementedError("Yutori n1 does not support streaming.") async def astreaming(self, *args, **kwargs) -> AsyncIterator[GenericStreamingChunk]: raise NotImplementedError("Yutori n1 does not support streaming.")