AI generated first iteration
This commit is contained in:
1
app/llm/__init__.py
Normal file
1
app/llm/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""LLM module exports."""
|
||||
117
app/llm/client.py
Normal file
117
app/llm/client.py
Normal file
@@ -0,0 +1,117 @@
|
||||
"""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]}...")
|
||||
|
||||
# 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."
|
||||
60
app/llm/prompts.py
Normal file
60
app/llm/prompts.py
Normal file
@@ -0,0 +1,60 @@
|
||||
"""LLM prompt templates and generation utilities."""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class PromptTemplates:
|
||||
"""Collection of prompt templates for different modes."""
|
||||
|
||||
@staticmethod
|
||||
def gentle_prompt(current_theme: Optional[str] = None) -> str:
|
||||
"""Generate a gentle prompt when chat has been inactive."""
|
||||
if current_theme:
|
||||
return f"Gently prompt the chat about: {current_theme}"
|
||||
return "Generate a gentle, inviting prompt to encourage discussion in the stream."
|
||||
|
||||
@staticmethod
|
||||
def steward_response(message: str, context: Optional[str] = None) -> str:
|
||||
"""Generate a response as the Steward mode."""
|
||||
prompt = f"As a thoughtful steward of this stream, respond briefly and helpfully to: {message}"
|
||||
if context:
|
||||
prompt += f"\nContext: {context}"
|
||||
return prompt
|
||||
|
||||
@staticmethod
|
||||
def warden_analysis(message: str) -> str:
|
||||
"""Generate analysis for suspicious content detection."""
|
||||
return f"Analyze this message for suspicious patterns (spam, scams, manipulation): {message}"
|
||||
|
||||
@staticmethod
|
||||
def librarian_summary(messages: list[str]) -> str:
|
||||
"""Generate a summary of important discussion points."""
|
||||
messages_text = "\n".join(messages)
|
||||
return f"Summarize the key discussion points from this chat log:\n{messages_text}"
|
||||
|
||||
@staticmethod
|
||||
def scribe_ledger(
|
||||
theme: str,
|
||||
discussion: list[str],
|
||||
actions: list[str],
|
||||
clips: list[str],
|
||||
seeds: list[str],
|
||||
) -> str:
|
||||
"""Generate markdown ledger summary."""
|
||||
return f"""Generate a professional markdown ledger with these sections:
|
||||
- Theme: {theme}
|
||||
- Notable Discussion: {len(discussion)} key points
|
||||
- Agent Actions: {len(actions)} recorded
|
||||
- Clip Candidates: {len(clips)} identified
|
||||
- Blog Seeds: {len(seeds)} proposed"""
|
||||
|
||||
@staticmethod
|
||||
def clip_candidate_reason(message: str) -> str:
|
||||
"""Generate reasoning for marking a message as a clip candidate."""
|
||||
return f"Explain why this is a good clip candidate: {message}"
|
||||
|
||||
@staticmethod
|
||||
def blog_seed_topic(context: list[str]) -> str:
|
||||
"""Generate a blog post topic from discussion context."""
|
||||
context_text = "\n".join(context[:5]) # First 5 messages
|
||||
return f"Based on this discussion, suggest a blog post topic:\n{context_text}"
|
||||
Reference in New Issue
Block a user