Spaces:
Paused
Paused
| """ | |
| 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() |