124 lines
4.2 KiB
Python
124 lines
4.2 KiB
Python
"""LLM client abstraction for pluggable LLM providers."""
|
|
|
|
import logging
|
|
from typing import Optional
|
|
|
|
from app.config import settings
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class LLMClient:
|
|
"""
|
|
Abstraction layer for LLM providers.
|
|
|
|
Supports: OpenAI, Ollama, LM Studio, or offline/mock mode.
|
|
Can be extended to support other providers.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
provider: Optional[str] = None,
|
|
api_key: Optional[str] = None,
|
|
base_url: Optional[str] = None,
|
|
model: Optional[str] = None,
|
|
):
|
|
"""
|
|
Initialize LLM client.
|
|
|
|
Args:
|
|
provider: "openai", "ollama", "lm_studio", or None for mock
|
|
api_key: API key for provider
|
|
base_url: Base URL for API (for ollama/lm_studio)
|
|
model: Model identifier
|
|
"""
|
|
self.provider = provider or settings.LLM_PROVIDER
|
|
self.api_key = api_key or settings.LLM_API_KEY
|
|
self.base_url = base_url or settings.LLM_BASE_URL
|
|
self.model = model or settings.LLM_MODEL
|
|
|
|
logger.info(f"LLMClient initialized with provider: {self.provider}")
|
|
|
|
async def generate(self, prompt: str, max_tokens: int = 200) -> str:
|
|
"""
|
|
Generate text from a prompt.
|
|
|
|
Args:
|
|
prompt: Input prompt
|
|
max_tokens: Maximum tokens to generate
|
|
|
|
Returns:
|
|
Generated text
|
|
|
|
TODO: Implement OpenAI API integration
|
|
TODO: Implement Ollama API integration
|
|
TODO: Implement LM Studio API integration
|
|
"""
|
|
if self.provider == "openai":
|
|
return await self._generate_openai(prompt, max_tokens)
|
|
elif self.provider == "ollama":
|
|
return await self._generate_ollama(prompt, max_tokens)
|
|
elif self.provider == "lm_studio":
|
|
return await self._generate_lm_studio(prompt, max_tokens)
|
|
else:
|
|
return self._generate_mock(prompt)
|
|
|
|
async def _generate_openai(self, prompt: str, max_tokens: int) -> str:
|
|
"""
|
|
Generate using OpenAI API.
|
|
|
|
TODO: Implement using openai library
|
|
- Create client with api_key
|
|
- Call ChatCompletion
|
|
- Handle errors and retries
|
|
"""
|
|
logger.warning("OpenAI provider not yet implemented (stub)")
|
|
return self._generate_mock(prompt)
|
|
|
|
async def _generate_ollama(self, prompt: str, max_tokens: int) -> str:
|
|
"""
|
|
Generate using Ollama local API.
|
|
|
|
TODO: Implement using httpx or requests
|
|
- POST to base_url/api/generate
|
|
- Stream response and accumulate
|
|
- Handle model pulling if needed
|
|
"""
|
|
logger.warning("Ollama provider not yet implemented (stub)")
|
|
return self._generate_mock(prompt)
|
|
|
|
async def _generate_lm_studio(self, prompt: str, max_tokens: int) -> str:
|
|
"""
|
|
Generate using LM Studio local API.
|
|
|
|
TODO: Implement OpenAI-compatible API calls
|
|
- POST to base_url/v1/chat/completions
|
|
- Use same logic as OpenAI but with local endpoint
|
|
"""
|
|
logger.warning("LM Studio provider not yet implemented (stub)")
|
|
return self._generate_mock(prompt)
|
|
|
|
def _generate_mock(self, prompt: str) -> str:
|
|
"""
|
|
Generate deterministic mock response (no API needed).
|
|
|
|
Used when no provider is configured or for testing.
|
|
"""
|
|
logger.debug(f"Mock generation for prompt: {prompt[:50]}...")
|
|
|
|
if "content angle:" in prompt.lower():
|
|
for line in prompt.splitlines():
|
|
if line.lower().startswith("content angle:"):
|
|
angle = line.split(":", 1)[1].strip()
|
|
return f"The quiet here keeps circling back to {angle}."
|
|
|
|
# Simple deterministic responses for testing
|
|
if "hello" in prompt.lower():
|
|
return "Greetings, traveler! Welcome to The Sanctum."
|
|
elif "help" in prompt.lower():
|
|
return "I am here to guide your discourse through the streams."
|
|
elif "topic" in prompt.lower():
|
|
return "The archives speak of many topics worthy of discussion."
|
|
else:
|
|
return "An interesting observation. Tell me more."
|