| """ |
| 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" |
| |
| |
| 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 |
| |
| |
| 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 |
| """ |
| |
| |
| 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}") |
| |
| |
| 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}") |
| |
| |
| 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) |
| |
| |
| 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}") |
| |
| 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") |
|
|
|
|
| |
| llm_service = LLMService() |