voiceCal / core /llm_provider.py
Peter Michael Gits
feat: Deploy complete VoiceCal application with all files v0.5.6
5e8a657
"""
LLM Provider - Handles different LLM services for ChatCal Voice.
Implements the same fallback chain as the original ChatCal:
Groq (primary) -> Anthropic (fallback) -> Mock (development)
"""
import logging
from typing import Optional
from llama_index.core.llms import LLM
from llama_index.llms.groq import Groq
from llama_index.llms.anthropic import Anthropic
from .config import config
logger = logging.getLogger(__name__)
class MockLLM:
"""Mock LLM for development and testing."""
async def achat(self, messages):
"""Mock async chat method."""
last_message = messages[-1].content if messages else "Hello"
# Simple rule-based responses for development
if any(word in last_message.lower() for word in ["book", "schedule", "appointment"]):
response = "I'd be happy to help you book an appointment! Please provide your name, preferred date and time."
elif any(word in last_message.lower() for word in ["cancel", "delete"]):
response = "I can help you cancel an appointment. Could you tell me which meeting you'd like to cancel?"
elif any(word in last_message.lower() for word in ["available", "availability", "free"]):
response = "Let me check Peter's availability for you. What dates are you considering?"
else:
response = "Hello! I'm ChatCal, your voice-enabled scheduling assistant. I can help you book appointments with Peter. What would you like to schedule?"
class MockResponse:
def __init__(self, content):
self.message = self
self.content = content
return MockResponse(response)
async def acomplete(self, prompt):
"""Mock async completion method."""
class MockCompletion:
def __init__(self, content):
self.text = content
# Mock JSON response for booking parsing
if "Parse this booking request" in prompt:
return MockCompletion('{"date_time": "2024-01-01 14:00", "duration": 30, "meeting_type": "google_meet", "topic": "Meeting", "needs_clarification": true, "clarification_message": "Could you please specify the exact date and time?"}')
return MockCompletion("Mock response for development")
def get_llm() -> LLM:
"""
Get the appropriate LLM based on available configuration.
Implements fallback chain: Groq -> Anthropic -> Mock
"""
# Try Groq first (primary)
if config.groq_api_key:
try:
logger.info("πŸš€ Using Groq LLM (primary)")
return Groq(
model="llama-3.1-8b-instant",
api_key=config.groq_api_key,
temperature=0.1
)
except Exception as e:
logger.warning(f"❌ Groq LLM failed to initialize: {e}")
# Fallback to Anthropic
if config.anthropic_api_key:
try:
logger.info("🧠 Using Anthropic Claude (fallback)")
return Anthropic(
model="claude-3-sonnet-20240229",
api_key=config.anthropic_api_key,
temperature=0.1
)
except Exception as e:
logger.warning(f"❌ Anthropic LLM failed to initialize: {e}")
# Final fallback to Mock LLM
logger.warning("⚠️ Using Mock LLM (development/fallback)")
return MockLLM()
class LLMService:
"""Service wrapper for LLM operations."""
def __init__(self):
self.llm = get_llm()
self.is_mock = isinstance(self.llm, MockLLM)
async def chat(self, messages, temperature: float = 0.1):
"""Send chat messages to LLM."""
if self.is_mock:
return await self.llm.achat(messages)
# For real LLMs, set temperature if supported
try:
if hasattr(self.llm, 'temperature'):
original_temp = self.llm.temperature
self.llm.temperature = temperature
result = await self.llm.achat(messages)
self.llm.temperature = original_temp
return result
else:
return await self.llm.achat(messages)
except Exception as e:
logger.error(f"LLM chat error: {e}")
# Return a graceful error response
class ErrorResponse:
def __init__(self, content):
self.message = self
self.content = content
return ErrorResponse("I apologize, but I'm having trouble processing your request right now. Please try again.")
async def complete(self, prompt: str, temperature: float = 0.1):
"""Send completion prompt to LLM."""
if self.is_mock:
return await self.llm.acomplete(prompt)
try:
if hasattr(self.llm, 'temperature'):
original_temp = self.llm.temperature
self.llm.temperature = temperature
result = await self.llm.acomplete(prompt)
self.llm.temperature = original_temp
return result
else:
return await self.llm.acomplete(prompt)
except Exception as e:
logger.error(f"LLM completion error: {e}")
class ErrorCompletion:
def __init__(self, content):
self.text = content
return ErrorCompletion("Error processing request")
# Global LLM service instance
llm_service = LLMService()