Spaces:
Running
Running
| """ | |
| Spatial Atlas: Unified LLM Interface | |
| Wraps litellm for multi-provider model access with cost tracking. | |
| Supports text generation, JSON mode, and vision analysis. | |
| """ | |
| import base64 | |
| import logging | |
| import litellm | |
| from config import Config | |
| from cost.tracker import CostTracker | |
| logger = logging.getLogger("spatial-atlas.llm") | |
| # Suppress litellm's verbose logging | |
| litellm.suppress_debug_info = True | |
| class LLMClient: | |
| def __init__(self, config: Config): | |
| self.config = config | |
| self.cost_tracker = CostTracker() | |
| def _get_model(self, model_tier: str) -> str: | |
| return self.config.model_tiers.get(model_tier, self.config.standard_model) | |
| def _apply_chat_template_options(self, kwargs: dict) -> None: | |
| """Apply optional vLLM chat-template controls to one request.""" | |
| enable_thinking = self.config.llm_enable_thinking | |
| if enable_thinking is not None: | |
| kwargs["extra_body"] = {"chat_template_kwargs": {"enable_thinking": enable_thinking}} | |
| async def generate( | |
| self, | |
| prompt: str, | |
| *, | |
| model_tier: str = "standard", | |
| system_prompt: str | None = None, | |
| json_mode: bool = False, | |
| temperature: float = 0.0, | |
| max_tokens: int = 4096, | |
| ) -> str: | |
| """Generate text completion using specified model tier.""" | |
| model = self._get_model(model_tier) | |
| messages: list[dict] = [] | |
| if system_prompt: | |
| messages.append({"role": "system", "content": system_prompt}) | |
| messages.append({"role": "user", "content": prompt}) | |
| kwargs: dict = { | |
| "model": model, | |
| "messages": messages, | |
| "temperature": temperature, | |
| "max_tokens": max_tokens, | |
| } | |
| if json_mode: | |
| kwargs["response_format"] = {"type": "json_object"} | |
| self._apply_chat_template_options(kwargs) | |
| try: | |
| response = await litellm.acompletion(**kwargs) | |
| self.cost_tracker.track( | |
| response, | |
| role=model_tier, | |
| model_tier=model_tier, | |
| call_kind="generate", | |
| configured_max_tokens=max_tokens, | |
| ) | |
| content = response.choices[0].message.content or "" | |
| logger.debug(f"LLM [{model_tier}] generated {len(content)} chars") | |
| return content | |
| except Exception as e: | |
| logger.error(f"LLM generation failed [{model_tier}]: {e}") | |
| raise | |
| async def vision_analyze( | |
| self, | |
| image_bytes: bytes, | |
| prompt: str, | |
| *, | |
| model_tier: str = "vision", | |
| temperature: float = 0.0, | |
| max_tokens: int = 4096, | |
| ) -> str: | |
| """Analyze an image using a multimodal model.""" | |
| model = self._get_model(model_tier) | |
| b64 = base64.b64encode(image_bytes).decode("ascii") | |
| messages = [ | |
| { | |
| "role": "user", | |
| "content": [ | |
| {"type": "text", "text": prompt}, | |
| { | |
| "type": "image_url", | |
| "image_url": {"url": f"data:image/jpeg;base64,{b64}"}, | |
| }, | |
| ], | |
| } | |
| ] | |
| try: | |
| kwargs: dict = { | |
| "model": model, | |
| "messages": messages, | |
| "temperature": temperature, | |
| "max_tokens": max_tokens, | |
| } | |
| self._apply_chat_template_options(kwargs) | |
| response = await litellm.acompletion(**kwargs) | |
| self.cost_tracker.track( | |
| response, | |
| role=model_tier, | |
| model_tier=model_tier, | |
| call_kind="vision_analyze", | |
| configured_max_tokens=max_tokens, | |
| ) | |
| content = response.choices[0].message.content or "" | |
| logger.debug(f"Vision analysis generated {len(content)} chars") | |
| return content | |
| except Exception as e: | |
| logger.error(f"Vision analysis failed: {e}") | |
| raise | |
| async def generate_with_messages( | |
| self, | |
| messages: list[dict], | |
| *, | |
| model_tier: str = "standard", | |
| json_mode: bool = False, | |
| temperature: float = 0.0, | |
| max_tokens: int = 4096, | |
| ) -> str: | |
| """Generate with full message control (for multi-turn).""" | |
| model = self._get_model(model_tier) | |
| kwargs: dict = { | |
| "model": model, | |
| "messages": messages, | |
| "temperature": temperature, | |
| "max_tokens": max_tokens, | |
| } | |
| if json_mode: | |
| kwargs["response_format"] = {"type": "json_object"} | |
| self._apply_chat_template_options(kwargs) | |
| try: | |
| response = await litellm.acompletion(**kwargs) | |
| self.cost_tracker.track( | |
| response, | |
| role=model_tier, | |
| model_tier=model_tier, | |
| call_kind="generate_with_messages", | |
| configured_max_tokens=max_tokens, | |
| ) | |
| return response.choices[0].message.content or "" | |
| except Exception as e: | |
| logger.error(f"LLM generation failed: {e}") | |
| raise | |