Files

1 line
9.0 KiB
JSON

{"repo": "ajsinha/sajhamcpserver", "files": {"sajha/ai/providers/__init__.py": {"5d819c78a8a4791f5fac666d9736dc3af1a9a354": {"t": 1785674495.876661, "n": 8304, "h": "0641ea32e822a730", "c": "\"\"\"\nSAJHA MCP Server — LLM Provider Abstraction\nCopyright All rights Reserved 2025-2030, Ashutosh Sinha\n\nAbstract base for all LLM providers. Concrete implementations:\n- AnthropicProvider (Claude via API)\n- OpenAIProvider (GPT via API)\n- BedrockProvider (AWS Bedrock — Claude, Titan, Llama, Mistral)\n- TogetherProvider (Together.ai — open-source models)\n- OllamaProvider (Local models via Ollama)\n- AzureOpenAIProvider (Azure-hosted OpenAI models)\n\nUsage:\n from sajha.ai.providers import create_provider\n provider = create_provider('anthropic', api_key='sk-...')\n response = provider.complete([{\"role\": \"user\", \"content\": \"Hello\"}], model=\"claude-sonnet-4-20250514\")\n\"\"\"\n\nimport logging\nfrom abc import ABC, abstractmethod\nfrom dataclasses import dataclass, field\nfrom enum import Enum\nfrom typing import Any, Dict, Iterator, List, Optional\n\nlogger = logging.getLogger(__name__)\n\n\n# ── Data Classes ─────────────────────────────────────────────\n\nclass ProviderType(str, Enum):\n ANTHROPIC = 'anthropic'\n OPENAI = 'openai'\n BEDROCK = 'bedrock'\n TOGETHER = 'together'\n OLLAMA = 'ollama'\n AZURE_OPENAI = 'azure_openai'\n\n\n@dataclass\nclass ModelInfo:\n \"\"\"Describes an available model.\"\"\"\n id: str # e.g. \"claude-sonnet-4-20250514\"\n name: str # e.g. \"Claude Sonnet 4\"\n provider: str # e.g. \"anthropic\"\n context_window: int = 0 # max tokens\n input_cost_per_1k: float = 0.0 # USD per 1K input tokens\n output_cost_per_1k: float = 0.0 # USD per 1K output tokens\n supports_tools: bool = True\n supports_vision: bool = False\n supports_streaming: bool = True\n max_output_tokens: int = 4096\n tags: List[str] = field(default_factory=list) # [\"fast\", \"reasoning\", \"code\"]\n\n\n@dataclass\nclass LLMMessage:\n \"\"\"A single message in a conversation.\"\"\"\n role: str # \"system\", \"user\", \"assistant\"\n content: str\n\n\n@dataclass\nclass LLMResponse:\n \"\"\"Response from an LLM completion.\"\"\"\n content: str\n model: str\n provider: str\n input_tokens: int = 0\n output_tokens: int = 0\n total_tokens: int = 0\n finish_reason: str = 'stop'\n latency_ms: int = 0\n cost_usd: float = 0.0\n raw: Optional[Dict[str, Any]] = None # full provider response\n\n\n@dataclass\nclass EmbeddingResponse:\n \"\"\"Response from an embedding request.\"\"\"\n embeddings: List[List[float]]\n model: str\n provider: str\n total_tokens: int = 0\n dimensions: int = 0\n\n\n# ── Abstract Base ────────────────────────────────────────────\n\nclass LLMProvider(ABC):\n \"\"\"\n Abstract LLM provider. All providers implement the same interface.\n SAJHA's LLMGateway delegates to these providers based on config.\n \"\"\"\n\n provider_type: str = 'unknown'\n\n @abstractmethod\n def complete(\n self,\n messages: List[Dict[str, str]],\n model: str,\n temperature: float = 0.7,\n max_tokens: int = 1024,\n system: str = '',\n tools: Optional[List[Dict]] = None,\n **kwargs,\n ) -> LLMResponse:\n \"\"\"Synchronous completion. Returns full response.\"\"\"\n ...\n\n @abstractmethod\n def stream(\n self,\n messages: List[Dict[str, str]],\n model: str,\n temperature: float = 0.7,\n max_tokens: int = 1024,\n system: str = '',\n **kwargs,\n ) -> Iterator[str]:\n \"\"\"Streaming completion. Yields content chunks.\"\"\"\n ...\n\n def embed(\n self,\n texts: List[str],\n model: str = '',\n ) -> EmbeddingResponse:\n \"\"\"Generate embeddings. Not all providers support this.\"\"\"\n raise NotImplementedError(f\"{self.provider_type} does not support embeddings\")\n\n @abstractmethod\n def list_models(self) -> List[ModelInfo]:\n \"\"\"List available models for this provider.\"\"\"\n ...\n\n @abstractmethod\n def health_check(self) -> bool:\n \"\"\"Test provider connectivity. Returns True if healthy.\"\"\"\n ...\n\n def get_default_model(self) -> str:\n \"\"\"Return the recommended default model for this provider.\"\"\"\n models = self.list_models()\n return models[0].id if models else ''\n\n def get_default_embedding_model(self) -> str:\n \"\"\"Return the default embedding model.\"\"\"\n return ''\n\n\n# ── Provider Registry (Factory Pattern) ──────────────────────\n#\n# Register provider classes by type. Adding a new provider\n# (including custom/local ones) requires only:\n#\n# from sajha.ai.providers import register_provider_class\n# register_provider_class('my_local', MyLocalProvider)\n#\n# The factory instantiates from the registry — no if/elif chain.\n\n_registry: Dict[str, type] = {} # provider_type → LLMProvider subclass\n_instances: Dict[str, LLMProvider] = {} # cache_key → instantiated provider\n\n\ndef register_provider_class(provider_type: str, cls: type) -> None:\n \"\"\"Register a provider class in the factory.\n\n Args:\n provider_type: Unique identifier (e.g. 'anthropic', 'my_local_llm')\n cls: Class that extends LLMProvider\n\n Usage:\n # In your custom provider file:\n class MyLocalProvider(LLMProvider):\n provider_type = 'my_local'\n ...\n\n register_provider_class('my_local', MyLocalProvider)\n\n # Then configure in DB (llm_providers table) or application.yml\n \"\"\"\n if not issubclass(cls, LLMProvider):\n raise TypeError(f\"{cls.__name__} must extend LLMProvider\")\n _registry[provider_type] = cls\n logger.info(f\"LLM provider class registered: {provider_type} → {cls.__name__}\")\n\n\ndef get_registered_types() -> Dict[str, str]:\n \"\"\"Return all registered provider types → class names.\"\"\"\n return {k: v.__name__ for k, v in _registry.items()}\n\n\ndef create_provider(provider_type: str, **config) -> LLMProvider:\n \"\"\"Create (or return cached) provider instance from the registry.\n\n Resolution order:\n 1. Check instance cache (same type + config → same instance)\n 2. Look up class in _registry\n 3. Instantiate with **config\n 4. Cache and return\n\n Raises ValueError if provider_type is not registered.\n \"\"\"\n cache_key = f\"{provider_type}:{hash(frozenset(config.items()))}\"\n if cache_key in _instances:\n return _instances[cache_key]\n\n cls = _registry.get(provider_type)\n if cls is None:\n available = ', '.join(sorted(_registry.keys())) or '(none)'\n raise ValueError(\n f\"Unknown LLM provider: '{provider_type}'. \"\n f\"Registered: [{available}]. \"\n f\"Use register_provider_class('{provider_type}', YourProviderClass) to add it.\"\n )\n\n provider = cls(**config)\n _instances[cache_key] = provider\n logger.info(f\"LLM provider created: {provider_type} ({cls.__name__})\")\n return provider\n\n\ndef get_provider(provider_type: str) -> Optional[LLMProvider]:\n \"\"\"Get a cached provider instance by type.\"\"\"\n for key, p in _instances.items():\n if key.startswith(provider_type + ':'):\n return p\n return None\n\n\n# ── Auto-Register Built-in Providers ─────────────────────────\n# Each built-in provider registers itself on import.\n# Custom providers register via register_provider_class().\n\ndef _auto_register_builtins():\n \"\"\"Register all built-in provider classes. Called once at module load.\"\"\"\n from sajha.ai.providers.anthropic_provider import AnthropicProvider\n from sajha.ai.providers.openai_provider import OpenAIProvider\n from sajha.ai.providers.bedrock_provider import BedrockProvider\n from sajha.ai.providers.together_provider import TogetherProvider\n from sajha.ai.providers.ollama_provider import OllamaProvider\n from sajha.ai.providers.azure_openai_provider import AzureOpenAIProvider\n\n register_provider_class(ProviderType.ANTHROPIC, AnthropicProvider)\n register_provider_class(ProviderType.OPENAI, OpenAIProvider)\n register_provider_class(ProviderType.BEDROCK, BedrockProvider)\n register_provider_class(ProviderType.TOGETHER, TogetherProvider)\n register_provider_class(ProviderType.OLLAMA, OllamaProvider)\n register_provider_class(ProviderType.AZURE_OPENAI, AzureOpenAIProvider)\n\n\ntry:\n _auto_register_builtins()\nexcept ImportError as e:\n logger.warning(f\"Some built-in LLM providers failed to register: {e}\", exc_info=True)\n"}}}}