Spaces:
Runtime error
Runtime error
| import os | |
| import json | |
| import requests | |
| import logging | |
| from typing import List, Dict, Optional, Union, Any | |
| from datetime import datetime | |
| import random | |
| from dotenv import load_dotenv | |
| # Import Google's Generative AI library | |
| try: | |
| import google.generativeai as genai | |
| except ImportError: | |
| genai = None | |
| # Load environment variables | |
| load_dotenv() | |
| # Set up logging | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| # Get API keys from environment | |
| ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY") | |
| OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY") | |
| DEEPSEEK_API_KEY = os.environ.get("DEEPSEEK_API_KEY") | |
| GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY") | |
| # Initialize Gemini if available | |
| if genai and GEMINI_API_KEY: | |
| genai.configure(api_key=GEMINI_API_KEY) | |
| class ConversationContext: | |
| """Maintains conversation history and context for more natural interactions""" | |
| def __init__(self, role: str = "speech_therapist", topic: str = "general"): | |
| self.role = role | |
| self.topic = topic | |
| self.history = [] | |
| self.turn_count = 0 | |
| self.user_interests = [] | |
| self.conversation_style = "supportive" | |
| def add_turn(self, speaker: str, message: str): | |
| self.history.append({ | |
| "speaker": speaker, | |
| "message": message, | |
| "timestamp": datetime.now().isoformat() | |
| }) | |
| if speaker == "user": | |
| self.turn_count += 1 | |
| def get_conversation_summary(self, last_n_turns: int = 5) -> str: | |
| """Get a summary of recent conversation for context""" | |
| recent = self.history[-last_n_turns:] if len(self.history) > last_n_turns else self.history | |
| summary = [] | |
| for turn in recent: | |
| summary.append(f"{turn['speaker'].capitalize()}: {turn['message']}") | |
| return "\n".join(summary) | |
| class AIContentGenerator: | |
| """ | |
| Class to handle AI-based content generation for speech therapy practice | |
| Uses Gemini, DeepSeek, Anthropic Claude, or OpenAI GPT depending on available API keys | |
| """ | |
| def __init__(self): | |
| # Determine which AI provider to use | |
| self.has_gemini = bool(genai and GEMINI_API_KEY) | |
| self.has_deepseek = bool(DEEPSEEK_API_KEY) | |
| self.has_anthropic = bool(ANTHROPIC_API_KEY) | |
| self.has_openai = bool(OPENAI_API_KEY) | |
| # Initialize conversation contexts | |
| self.conversation_contexts = {} | |
| print(f"Has Gemini API: {self.has_gemini}") | |
| print(f"Has DeepSeek API: {self.has_deepseek}") | |
| print(f"Has Anthropic API: {self.has_anthropic}") | |
| print(f"Has OpenAI API: {self.has_openai}") | |
| if not self.has_gemini and not self.has_deepseek and not self.has_anthropic and not self.has_openai: | |
| logger.warning("No API keys found. Using mock content generation instead.") | |
| def generate_content(self, prompt: str) -> str: | |
| """Generate content using available AI provider""" | |
| # Prioritize Gemini if available | |
| if self.has_gemini: | |
| return self._generate_with_gemini(prompt) | |
| elif self.has_deepseek: | |
| return self._generate_with_deepseek(prompt) | |
| elif self.has_anthropic: | |
| return self._generate_with_anthropic(prompt) | |
| elif self.has_openai: | |
| return self._generate_with_openai(prompt) | |
| else: | |
| return self._generate_mock_content(prompt) | |
| def _generate_with_gemini(self, prompt: str) -> str: | |
| """Generate content using Google's Gemini API""" | |
| try: | |
| # Configure the API | |
| genai.configure(api_key=GEMINI_API_KEY) | |
| # Use component-specific model for reading content generation | |
| model_name = os.environ.get('READING_CONTENT_MODEL', 'gemini-2.5-flash') | |
| model = genai.GenerativeModel(model_name) | |
| # Generate content | |
| response = model.generate_content( | |
| prompt, | |
| generation_config={ | |
| "temperature": 0.7, | |
| "top_p": 0.95, | |
| "top_k": 40, | |
| "max_output_tokens": 1000, | |
| } | |
| ) | |
| if response and hasattr(response, 'text'): | |
| # Log cost | |
| try: | |
| from utils.cost_tracker import log_gemini_call | |
| log_gemini_call(prompt, response.text, model_name, practice_type='content_generation') | |
| except Exception as e: | |
| logger.warning(f"Failed to log Gemini cost: {e}") | |
| return response.text | |
| elif response and hasattr(response, 'parts'): | |
| # Log cost | |
| try: | |
| from utils.cost_tracker import log_gemini_call | |
| log_gemini_call(prompt, response.parts[0].text, model_name, practice_type='content_generation') | |
| except Exception as e: | |
| logger.warning(f"Failed to log Gemini cost: {e}") | |
| return response.parts[0].text | |
| else: | |
| raise ValueError(f"Unexpected response format from Gemini API") | |
| except Exception as e: | |
| logger.error(f"Error generating with Gemini: {str(e)}") | |
| # Fall back to other providers | |
| if self.has_deepseek: | |
| return self._generate_with_deepseek(prompt) | |
| elif self.has_anthropic: | |
| return self._generate_with_anthropic(prompt) | |
| elif self.has_openai: | |
| return self._generate_with_openai(prompt) | |
| else: | |
| return self._generate_mock_content(prompt) | |
| def _generate_with_deepseek(self, prompt: str) -> str: | |
| """Generate content using DeepSeek API""" | |
| try: | |
| headers = { | |
| "Content-Type": "application/json", | |
| "Authorization": f"Bearer {DEEPSEEK_API_KEY}" | |
| } | |
| payload = { | |
| "model": "deepseek-chat", | |
| "messages": [ | |
| {"role": "system", "content": "You are a speech therapy assistant that creates reading practice materials."}, | |
| {"role": "user", "content": prompt} | |
| ], | |
| "temperature": 0.7, | |
| "max_tokens": 1000 | |
| } | |
| response = requests.post( | |
| "https://api.deepseek.com/v1/chat/completions", | |
| json=payload, | |
| headers=headers | |
| ) | |
| response.raise_for_status() | |
| result = response.json() | |
| return result["choices"][0]["message"]["content"].strip() | |
| except Exception as e: | |
| logger.error(f"Error generating content with DeepSeek: {str(e)}") | |
| if self.has_anthropic: | |
| return self._generate_with_anthropic(prompt) | |
| elif self.has_openai: | |
| return self._generate_with_openai(prompt) | |
| else: | |
| return self._generate_mock_content(prompt) | |
| def _generate_with_anthropic(self, prompt: str) -> str: | |
| """Generate content using Anthropic's Claude API""" | |
| try: | |
| headers = { | |
| "Content-Type": "application/json", | |
| "X-Api-Key": ANTHROPIC_API_KEY, | |
| "anthropic-version": "2023-06-01" | |
| } | |
| data = { | |
| "model": "claude-3-haiku-20240307", | |
| "messages": [ | |
| {"role": "user", "content": prompt} | |
| ], | |
| "max_tokens": 1000, | |
| "temperature": 0.7 | |
| } | |
| response = requests.post( | |
| "https://api.anthropic.com/v1/messages", | |
| headers=headers, | |
| json=data | |
| ) | |
| response.raise_for_status() | |
| result = response.json() | |
| return result["content"][0]["text"] | |
| except Exception as e: | |
| logger.error(f"Error generating content with Anthropic: {str(e)}") | |
| if self.has_openai: | |
| return self._generate_with_openai(prompt) | |
| else: | |
| return self._generate_mock_content(prompt) | |
| def _generate_with_openai(self, prompt: str) -> str: | |
| """Generate content using OpenAI's API""" | |
| try: | |
| headers = { | |
| "Authorization": f"Bearer {OPENAI_API_KEY}", | |
| "Content-Type": "application/json" | |
| } | |
| data = { | |
| "model": "gpt-3.5-turbo", | |
| "messages": [ | |
| {"role": "system", "content": "You are a speech therapy assistant that creates reading practice materials."}, | |
| {"role": "user", "content": prompt} | |
| ], | |
| "max_tokens": 1000, | |
| "temperature": 0.7 | |
| } | |
| response = requests.post( | |
| "https://api.openai.com/v1/chat/completions", | |
| headers=headers, | |
| json=data | |
| ) | |
| response.raise_for_status() | |
| result = response.json() | |
| return result["choices"][0]["message"]["content"].strip() | |
| except Exception as e: | |
| logger.error(f"Error generating content with OpenAI: {str(e)}") | |
| return self._generate_mock_content(prompt) | |
| def _generate_mock_content(self, prompt: str) -> str: | |
| """Fallback mock content when no AI provider is available""" | |
| # Extract topic if possible | |
| topic = "general topics" | |
| if "about " in prompt: | |
| parts = prompt.split("about ") | |
| if len(parts) > 1: | |
| topic_part = parts[1].split(".")[0] | |
| topic = topic_part.strip() | |
| if "articulation" in prompt.lower(): | |
| return json.dumps([ | |
| {"word": "rainbow", "position": "initial"}, | |
| {"word": "carrot", "position": "medial"}, | |
| {"word": "car", "position": "final"}, | |
| {"word": "read", "position": "initial"}, | |
| {"word": "horror", "position": "medial"} | |
| ]) | |
| elif "reading passage" in prompt.lower() or "paragraph" in prompt.lower(): | |
| return f"""Here's a reading passage about {topic}. This contains various words designed for speech therapy practice.""" | |
| elif "conversation" in prompt.lower(): | |
| return json.dumps({ | |
| "response": f"That's interesting! Tell me more about {topic}.", | |
| "technique_focus": "natural flow", | |
| "target_sounds": ["t", "m"], | |
| "follow_up_suggestions": [ | |
| "What aspects interest you most?", | |
| "How did you learn about this?" | |
| ] | |
| }) | |
| else: | |
| return f"Here's some content about {topic}." | |
| # ====== ENHANCED CONVERSATION GENERATORS ====== | |
| # Available conversation roles | |
| CONVERSATION_ROLES = { | |
| "friend": { | |
| "name": "Friendly Companion", | |
| "style": "casual, warm, and supportive", | |
| "topics": ["hobbies", "daily life", "interests", "weekend plans", "favorite things"], | |
| "personality": "enthusiastic, curious, and encouraging" | |
| }, | |
| "teacher": { | |
| "name": "Patient Teacher", | |
| "style": "educational, clear, and encouraging", | |
| "topics": ["learning", "subjects", "study tips", "school experiences", "knowledge"], | |
| "personality": "knowledgeable, patient, and supportive" | |
| }, | |
| "interviewer": { | |
| "name": "Professional Interviewer", | |
| "style": "professional, respectful, and interested", | |
| "topics": ["experience", "skills", "goals", "achievements", "background"], | |
| "personality": "attentive, thorough, and encouraging" | |
| }, | |
| "therapist": { | |
| "name": "Speech Therapist", | |
| "style": "supportive, understanding, and helpful", | |
| "topics": ["progress", "practice", "feelings", "challenges", "successes"], | |
| "personality": "compassionate, patient, and motivating" | |
| }, | |
| "shopkeeper": { | |
| "name": "Helpful Shop Assistant", | |
| "style": "helpful, friendly, and informative", | |
| "topics": ["products", "recommendations", "prices", "preferences", "needs"], | |
| "personality": "knowledgeable, patient, and service-oriented" | |
| }, | |
| "doctor": { | |
| "name": "Medical Professional", | |
| "style": "professional, caring, and clear", | |
| "topics": ["health", "symptoms", "lifestyle", "wellness", "concerns"], | |
| "personality": "attentive, thorough, and reassuring" | |
| }, | |
| "coach": { | |
| "name": "Motivational Coach", | |
| "style": "energetic, positive, and motivating", | |
| "topics": ["goals", "progress", "challenges", "strategies", "achievements"], | |
| "personality": "enthusiastic, supportive, and inspiring" | |
| }, | |
| "tourist_guide": { | |
| "name": "Tour Guide", | |
| "style": "informative, engaging, and friendly", | |
| "topics": ["places", "history", "culture", "recommendations", "experiences"], | |
| "personality": "knowledgeable, enthusiastic, and welcoming" | |
| } | |
| } | |
| def initialize_conversation( | |
| role: str = "friend", | |
| topic: str = "general", | |
| user_name: Optional[str] = None, | |
| session_id: Optional[str] = None | |
| ) -> Dict[str, Any]: | |
| """ | |
| Initialize a new conversation with specified role and topic | |
| Args: | |
| role: The AI's role in the conversation | |
| topic: Conversation topic | |
| user_name: Optional user name for personalization | |
| session_id: Unique session identifier | |
| Returns: | |
| Initial conversation setup with greeting and context | |
| """ | |
| generator = AIContentGenerator() | |
| # Get role details | |
| role_info = CONVERSATION_ROLES.get(role, CONVERSATION_ROLES["friend"]) | |
| # Create or retrieve conversation context | |
| if session_id: | |
| if session_id not in generator.conversation_contexts: | |
| generator.conversation_contexts[session_id] = ConversationContext(role, topic) | |
| context = generator.conversation_contexts[session_id] | |
| else: | |
| context = ConversationContext(role, topic) | |
| # Generate personalized greeting | |
| greeting_prompt = f""" | |
| You are playing the role of a {role_info['name']}. | |
| Your conversation style is {role_info['style']}. | |
| Your personality is {role_info['personality']}. | |
| Create a natural, welcoming greeting to start a conversation about {topic}. | |
| {f"The person's name is {user_name}." if user_name else ""} | |
| Make the greeting: | |
| - Warm and inviting | |
| - Appropriate for the role | |
| - Natural and conversational | |
| - Set up for easy continuation | |
| Format as JSON: | |
| {{ | |
| "greeting": "Your greeting message", | |
| "suggested_topics": ["topic1", "topic2", "topic3"], | |
| "conversation_starters": ["question1", "question2"] | |
| }} | |
| """ | |
| try: | |
| response = generator.generate_content(greeting_prompt) | |
| greeting_data = json.loads(response) | |
| # Add to conversation history | |
| context.add_turn("ai", greeting_data["greeting"]) | |
| return { | |
| "role": role, | |
| "role_info": role_info, | |
| "topic": topic, | |
| "greeting": greeting_data["greeting"], | |
| "suggested_topics": greeting_data.get("suggested_topics", role_info["topics"][:3]), | |
| "conversation_starters": greeting_data.get("conversation_starters", []), | |
| "session_id": session_id or f"session_{datetime.now().timestamp()}" | |
| } | |
| except Exception as e: | |
| logger.error(f"Error initializing conversation: {str(e)}") | |
| # Fallback greeting | |
| greeting = f"Hello{f' {user_name}' if user_name else ''}! I'm excited to chat with you about {topic}. What would you like to discuss?" | |
| context.add_turn("ai", greeting) | |
| return { | |
| "role": role, | |
| "role_info": role_info, | |
| "topic": topic, | |
| "greeting": greeting, | |
| "suggested_topics": role_info["topics"][:3], | |
| "conversation_starters": [ | |
| f"What interests you most about {topic}?", | |
| f"Tell me about your experience with {topic}." | |
| ], | |
| "session_id": session_id or f"session_{datetime.now().timestamp()}" | |
| } | |
| def generate_ai_conversation_response( | |
| user_input: str, | |
| role: str = "friend", | |
| topic: str = "general", | |
| technique: str = "normal", | |
| session_id: Optional[str] = None, | |
| conversation_history: Optional[List[Dict]] = None | |
| ) -> Dict[str, Any]: | |
| """ | |
| Generate a natural AI response based on role and conversation context | |
| Args: | |
| user_input: What the user said | |
| role: AI's conversation role | |
| topic: Current conversation topic | |
| technique: Speech technique being practiced | |
| session_id: Session identifier for context | |
| conversation_history: Optional conversation history | |
| Returns: | |
| AI response with practice suggestions and follow-ups | |
| """ | |
| generator = AIContentGenerator() | |
| # Get role information | |
| role_info = CONVERSATION_ROLES.get(role, CONVERSATION_ROLES["friend"]) | |
| # Get or create conversation context | |
| if session_id and session_id in generator.conversation_contexts: | |
| context = generator.conversation_contexts[session_id] | |
| else: | |
| context = ConversationContext(role, topic) | |
| if session_id: | |
| generator.conversation_contexts[session_id] = context | |
| # Add user input to history | |
| context.add_turn("user", user_input) | |
| # Build conversation history for context | |
| history_context = "" | |
| if conversation_history: | |
| history_context = "Recent conversation:\n" | |
| for turn in conversation_history[-5:]: # Last 5 turns | |
| history_context += f"{turn['speaker']}: {turn['message']}\n" | |
| elif context.history: | |
| history_context = f"Recent conversation:\n{context.get_conversation_summary()}\n" | |
| # Technique-specific guidance | |
| technique_guidance = { | |
| "normal": "natural speech patterns", | |
| "prolonged": "opportunities to extend vowel sounds", | |
| "gentle_onset": "words starting with soft sounds or vowels", | |
| "easy_onset": "words beginning with continuous sounds (m, n, f, s)", | |
| "rhythmic": "rhythmic speaking patterns and natural pauses" | |
| }.get(technique, "natural speech patterns") | |
| # Create the response prompt | |
| prompt = f""" | |
| You are playing the role of a {role_info['name']}. | |
| Your conversation style is {role_info['style']}. | |
| Your personality is {role_info['personality']}. | |
| The conversation topic is: {topic} | |
| {history_context} | |
| The user just said: "{user_input}" | |
| Generate a natural, engaging response that: | |
| 1. Directly addresses what the user said | |
| 2. Stays in character for your role | |
| 3. Keeps the conversation flowing naturally | |
| 4. Shows genuine interest and engagement | |
| 5. Provides opportunities for {technique_guidance} | |
| 6. Varies your responses to avoid repetition | |
| Important guidelines: | |
| - Make each response unique and contextual | |
| - React naturally to the user's emotions or experiences | |
| - Ask follow-up questions that build on what they said | |
| - Share relevant thoughts or experiences when appropriate for the role | |
| - Keep responses conversational, not clinical | |
| Format your response as JSON: | |
| {{ | |
| "response": "Your natural conversational response", | |
| "emotion": "The emotional tone (e.g., curious, excited, supportive)", | |
| "technique_focus": "Specific speech element to practice", | |
| "target_sounds": ["sound1", "sound2"], | |
| "follow_up_options": ["natural follow-up 1", "natural follow-up 2"], | |
| "conversation_tips": ["tip for continuing the conversation"] | |
| }} | |
| Provide only the JSON. | |
| """ | |
| try: | |
| response = generator.generate_content(prompt) | |
| # Parse JSON response | |
| json_start = response.find("{") | |
| json_end = response.rfind("}") + 1 | |
| if json_start >= 0 and json_end > json_start: | |
| json_str = response[json_start:json_end] | |
| response_data = json.loads(json_str) | |
| # Add AI response to history | |
| context.add_turn("ai", response_data["response"]) | |
| # Ensure all fields are present | |
| return { | |
| "response": response_data.get("response", "That's interesting! Tell me more."), | |
| "emotion": response_data.get("emotion", "interested"), | |
| "technique_focus": response_data.get("technique_focus", technique_guidance), | |
| "target_sounds": response_data.get("target_sounds", ["r", "s"]), | |
| "follow_up_options": response_data.get("follow_up_options", [ | |
| "What else would you like to share?", | |
| "How does that make you feel?" | |
| ]), | |
| "conversation_tips": response_data.get("conversation_tips", [ | |
| "Take your time to form your thoughts" | |
| ]), | |
| "turn_count": context.turn_count, | |
| "role": role, | |
| "topic": topic | |
| } | |
| except Exception as e: | |
| logger.error(f"Error generating conversation response: {str(e)}") | |
| # Generate fallback response based on role | |
| fallback_responses = { | |
| "friend": [ | |
| "That's really interesting! I'd love to hear more about that.", | |
| "Oh wow, I hadn't thought about it that way. What made you think of that?", | |
| "That sounds like quite an experience! How did it make you feel?" | |
| ], | |
| "teacher": [ | |
| "That's a thoughtful observation. Can you elaborate on that idea?", | |
| "Excellent point! What other connections can you make?", | |
| "I'm impressed by your thinking. What led you to that conclusion?" | |
| ], | |
| "interviewer": [ | |
| "That's valuable insight. Could you provide a specific example?", | |
| "Interesting perspective. How has that shaped your approach?", | |
| "I appreciate you sharing that. What was the outcome?" | |
| ], | |
| "therapist": [ | |
| "Thank you for sharing that with me. How are you feeling about it?", | |
| "That's an important observation. What would you like to explore further?", | |
| "I hear what you're saying. What matters most to you about this?" | |
| ] | |
| } | |
| responses = fallback_responses.get(role, fallback_responses["friend"]) | |
| selected_response = random.choice(responses) | |
| # Add to history | |
| context.add_turn("ai", selected_response) | |
| return { | |
| "response": selected_response, | |
| "emotion": "supportive", | |
| "technique_focus": technique_guidance, | |
| "target_sounds": ["r", "s", "m"], | |
| "follow_up_options": [ | |
| "Would you like to tell me more?", | |
| "What are your thoughts on this?" | |
| ], | |
| "conversation_tips": [ | |
| "Take your time to express yourself clearly" | |
| ], | |
| "turn_count": context.turn_count, | |
| "role": role, | |
| "topic": topic | |
| } | |
| def generate_dynamic_conversation_prompts( | |
| role: str = "friend", | |
| topic: str = "general", | |
| technique: str = "normal", | |
| difficulty: str = "medium", | |
| conversation_stage: str = "beginning" | |
| ) -> List[Dict[str, Any]]: | |
| """ | |
| Generate conversation prompts that adapt to role and conversation stage | |
| Args: | |
| role: AI's conversation role | |
| topic: Conversation subject | |
| technique: Speech technique being practiced | |
| difficulty: Complexity level | |
| conversation_stage: beginning, middle, or ending | |
| Returns: | |
| List of dynamic conversation prompts | |
| """ | |
| generator = AIContentGenerator() | |
| role_info = CONVERSATION_ROLES.get(role, CONVERSATION_ROLES["friend"]) | |
| # Stage-specific guidance | |
| stage_guidance = { | |
| "beginning": "opening questions to establish rapport and interest", | |
| "middle": "deeper questions that build on established topics", | |
| "ending": "reflective questions or future-oriented topics" | |
| }.get(conversation_stage, "engaging questions") | |
| prompt = f""" | |
| Create conversation prompts for a {role_info['name']} discussing {topic}. | |
| Role characteristics: | |
| - Style: {role_info['style']} | |
| - Personality: {role_info['personality']} | |
| - Typical topics: {', '.join(role_info['topics'])} | |
| Generate 5-7 {stage_guidance} that: | |
| 1. Feel natural for this role | |
| 2. Encourage {difficulty} level responses | |
| 3. Provide opportunities for {technique} speech practice | |
| 4. Vary in style and approach | |
| 5. Build engagement and rapport | |
| Each prompt should feel like something this character would naturally say. | |
| Format as JSON array: | |
| [ | |
| {{ | |
| "prompt": "The natural question or statement", | |
| "intent": "What the prompt aims to explore", | |
| "technique_focus": "Speech technique element", | |
| "target_sounds": ["sound1", "sound2"], | |
| "difficulty_notes": "Why this matches the difficulty level" | |
| }} | |
| ] | |
| Provide only the JSON array. | |
| """ | |
| try: | |
| response = generator.generate_content(prompt) | |
| # Extract and parse JSON | |
| json_start = response.find("[") | |
| json_end = response.rfind("]") + 1 | |
| if json_start >= 0 and json_end > json_start: | |
| json_str = response[json_start:json_end] | |
| prompts = json.loads(json_str) | |
| # Validate and enrich prompts | |
| validated_prompts = [] | |
| for p in prompts: | |
| if isinstance(p, dict) and "prompt" in p: | |
| validated_prompts.append({ | |
| "prompt": p["prompt"], | |
| "intent": p.get("intent", "Explore user's thoughts"), | |
| "technique_focus": p.get("technique_focus", technique), | |
| "target_sounds": p.get("target_sounds", ["r", "s"]), | |
| "difficulty_notes": p.get("difficulty_notes", "Appropriate for practice"), | |
| "role": role, | |
| "stage": conversation_stage | |
| }) | |
| return validated_prompts if validated_prompts else generate_fallback_dynamic_prompts(role, topic, conversation_stage) | |
| except Exception as e: | |
| logger.error(f"Error generating dynamic prompts: {str(e)}") | |
| return generate_fallback_dynamic_prompts(role, topic, conversation_stage) | |
| def generate_fallback_dynamic_prompts(role: str, topic: str, stage: str) -> List[Dict[str, Any]]: | |
| """Generate fallback prompts when AI generation fails""" | |
| role_prompts = { | |
| "friend": { | |
| "beginning": [ | |
| f"Hey! I've been really curious about {topic} lately. What got you interested in it?", | |
| f"So, what's your take on {topic}? I'd love to hear your thoughts!", | |
| f"You know, I was just thinking about {topic} the other day. Do you have any experience with it?" | |
| ], | |
| "middle": [ | |
| f"That's so cool! What's been the most surprising thing about {topic} for you?", | |
| f"I love how passionate you are about this! What would you like to try next with {topic}?", | |
| f"Based on what you've shared, what advice would you give someone just starting with {topic}?" | |
| ] | |
| }, | |
| "teacher": { | |
| "beginning": [ | |
| f"Welcome! Today we're exploring {topic}. What do you already know about this subject?", | |
| f"Let's discuss {topic}. What questions do you have that you'd like us to address?", | |
| f"Before we dive deeper into {topic}, what aspects interest you the most?" | |
| ], | |
| "middle": [ | |
| f"Excellent observations! How do you think this concept applies to real-world situations?", | |
| f"You're making great connections! What patterns have you noticed in {topic}?", | |
| f"That's a thoughtful analysis. Can you think of any examples that illustrate this principle?" | |
| ] | |
| }, | |
| "interviewer": { | |
| "beginning": [ | |
| f"Thank you for joining me today. Could you tell me about your background with {topic}?", | |
| f"I'd like to learn about your experience. How did you first become involved with {topic}?", | |
| f"Let's start with your journey. What initially attracted you to {topic}?" | |
| ], | |
| "middle": [ | |
| f"That's impressive. Could you walk me through a specific situation where you applied this knowledge?", | |
| f"Building on that, what challenges have you encountered and how did you overcome them?", | |
| f"Your approach is interesting. What results have you seen from implementing these strategies?" | |
| ] | |
| } | |
| } | |
| # Get prompts for the specific role and stage | |
| prompts_list = role_prompts.get(role, role_prompts["friend"]).get(stage, role_prompts["friend"]["beginning"]) | |
| # Format as proper response objects | |
| formatted_prompts = [] | |
| for i, prompt in enumerate(prompts_list): | |
| formatted_prompts.append({ | |
| "prompt": prompt, | |
| "intent": "Build rapport and explore topic", | |
| "technique_focus": "natural conversation flow", | |
| "target_sounds": ["r", "s", "t", "m"][i:i+2], | |
| "difficulty_notes": "Appropriate for speech practice", | |
| "role": role, | |
| "stage": stage | |
| }) | |
| return formatted_prompts | |
| # ====== ARTICULATION PRACTICE GENERATORS (keeping existing functions) ====== | |
| def generate_articulation_words( | |
| sound: str, | |
| difficulty: str = "medium", | |
| count: int = 5 | |
| ) -> List[Dict[str, str]]: | |
| """Generate words that target a specific sound for articulation practice""" | |
| generator = AIContentGenerator() | |
| difficulty_desc = { | |
| "easy": "simple words with the sound in initial position mostly, suitable for young children", | |
| "medium": "moderately complex words with the sound in various positions", | |
| "hard": "more complex and longer words, including consonant clusters with the target sound" | |
| }.get(difficulty, "moderately complex words") | |
| prompt = f""" | |
| Generate {count} words that include the '{sound}' sound for speech therapy articulation practice. | |
| The words should be {difficulty_desc}. | |
| For each word, specify whether the target sound appears in the: | |
| - "initial" position (beginning of word) | |
| - "medial" position (middle of word) | |
| - "final" position (end of word) | |
| Format your response as a JSON array of objects, each with 'word' and 'position' fields: | |
| [ | |
| {{"word": "example", "position": "initial"}}, | |
| ... | |
| ] | |
| Provide only the JSON array with no additional text or explanations. | |
| """ | |
| try: | |
| response = generator.generate_content(prompt) | |
| json_start = response.find("[") | |
| json_end = response.rfind("]") + 1 | |
| if json_start >= 0 and json_end > json_start: | |
| json_str = response[json_start:json_end] | |
| words_list = json.loads(json_str) | |
| result = [] | |
| for item in words_list[:count]: | |
| if isinstance(item, dict) and "word" in item and "position" in item: | |
| result.append({ | |
| "word": item["word"].lower().strip(), | |
| "position": item["position"].lower().strip() | |
| }) | |
| return result | |
| else: | |
| logger.error("Failed to extract JSON from AI response") | |
| return generate_fallback_articulation_words(sound, count) | |
| except Exception as e: | |
| logger.error(f"Error generating articulation words: {str(e)}") | |
| return generate_fallback_articulation_words(sound, count) | |
| def generate_fallback_articulation_words(sound: str, count: int = 5) -> List[Dict[str, str]]: | |
| """Generate fallback articulation words when AI generation fails""" | |
| sound_map = { | |
| "r": [ | |
| {"word": "red", "position": "initial"}, | |
| {"word": "car", "position": "final"}, | |
| {"word": "parent", "position": "medial"}, | |
| {"word": "train", "position": "initial"}, | |
| {"word": "borrow", "position": "medial"} | |
| ], | |
| "s": [ | |
| {"word": "sun", "position": "initial"}, | |
| {"word": "pass", "position": "final"}, | |
| {"word": "messy", "position": "medial"}, | |
| {"word": "snake", "position": "initial"}, | |
| {"word": "bus", "position": "final"} | |
| ], | |
| "l": [ | |
| {"word": "light", "position": "initial"}, | |
| {"word": "ball", "position": "final"}, | |
| {"word": "yellow", "position": "medial"}, | |
| {"word": "link", "position": "initial"}, | |
| {"word": "pillow", "position": "medial"} | |
| ], | |
| "sh": [ | |
| {"word": "ship", "position": "initial"}, | |
| {"word": "fish", "position": "final"}, | |
| {"word": "washing", "position": "medial"}, | |
| {"word": "shape", "position": "initial"}, | |
| {"word": "brush", "position": "final"} | |
| ] | |
| } | |
| if sound in sound_map: | |
| return sound_map[sound][:count] | |
| else: | |
| return [ | |
| {"word": f"{sound}ample", "position": "initial"}, | |
| {"word": f"e{sound}ample", "position": "medial"}, | |
| {"word": f"bas{sound}", "position": "final"}, | |
| {"word": f"{sound}onder", "position": "initial"}, | |
| {"word": f"ca{sound}e", "position": "medial"} | |
| ][:count] | |
| # ====== READING PRACTICE GENERATORS (keeping existing functions) ====== | |
| def generate_reading_passage( | |
| topic: str, | |
| difficulty: str = "medium", | |
| format_type: str = "paragraph", | |
| vowel_focus: Optional[List[str]] = None, | |
| consonant_focus: Optional[List[str]] = None, | |
| sentence_count: Optional[int] = None, | |
| technique: Optional[str] = None | |
| ) -> str: | |
| """Generate a reading passage for fluency practice""" | |
| generator = AIContentGenerator() | |
| if not sentence_count and format_type == "sentences": | |
| sentence_count = 5 | |
| reading_level = { | |
| "easy": "simple vocabulary and short sentences (1st-2nd grade level)", | |
| "medium": "moderate vocabulary and sentence structure (3rd-5th grade level)", | |
| "hard": "more complex vocabulary and varied sentence structure (6th-8th grade level)" | |
| }.get(difficulty, "moderate vocabulary and sentence structure") | |
| technique_guidance = "" | |
| if technique: | |
| if technique == "prolonged": | |
| technique_guidance = "Include words with long vowel sounds that can be stretched out for prolonged speech practice." | |
| elif technique == "gentle_onset" or technique == "easy_onset": | |
| technique_guidance = "Include words that begin with vowels or soft consonants for gentle/easy onset practice." | |
| elif technique == "rhythmic": | |
| technique_guidance = "Use a natural rhythm and include some repeated phrases for rhythmic speaking practice." | |
| vowel_instruction = "" | |
| if vowel_focus and len(vowel_focus) > 0: | |
| vowel_str = ", ".join([f"'{v}'" for v in vowel_focus]) | |
| vowel_instruction = f"Emphasize words containing these vowel sounds: {vowel_str}." | |
| consonant_instruction = "" | |
| if consonant_focus and len(consonant_focus) > 0: | |
| consonant_str = ", ".join([f"'{c}'" for c in consonant_focus]) | |
| consonant_instruction = f"Emphasize words containing these consonant sounds: {consonant_str}." | |
| format_instruction = { | |
| "paragraph": f"Create a cohesive paragraph about {topic} with 4-6 sentences.", | |
| "sentences": f"Create {sentence_count} individual, standalone sentences about {topic}.", | |
| "story": f"Create a short story about {topic} with a beginning, middle, and end." | |
| }.get(format_type, f"Write about {topic}") | |
| prompt = f""" | |
| {format_instruction} | |
| The text should use {reading_level}. | |
| {vowel_instruction} | |
| {consonant_instruction} | |
| {technique_guidance} | |
| Make the content engaging and appropriate for speech therapy practice. | |
| Provide only the text with no additional explanations or commentary. | |
| """ | |
| try: | |
| content = generator.generate_content(prompt) | |
| if not content or len(content.strip()) < 10: | |
| logger.error("AI returned empty or very short content") | |
| return generate_fallback_reading_passage(topic, format_type, sentence_count) | |
| return content | |
| except Exception as e: | |
| logger.error(f"Error generating reading passage: {str(e)}") | |
| return generate_fallback_reading_passage(topic, format_type, sentence_count) | |
| def generate_fallback_reading_passage( | |
| topic: str, | |
| format_type: str, | |
| sentence_count: Optional[int] = 5 | |
| ) -> str: | |
| """Generate fallback reading passage when AI generation fails""" | |
| topics = { | |
| "technology": [ | |
| "Smartphones have revolutionized how we communicate and access information.", | |
| "Artificial intelligence is being integrated into many everyday devices.", | |
| "Cloud computing allows us to store vast amounts of data remotely.", | |
| "Virtual reality creates immersive experiences for gaming and education.", | |
| "Robotics is advancing rapidly in manufacturing and healthcare." | |
| ], | |
| "travel": [ | |
| "Paris attracts millions of visitors to see the Eiffel Tower and Louvre Museum.", | |
| "Japan's bullet trains make traveling between cities incredibly efficient.", | |
| "The Great Barrier Reef in Australia is the world's largest coral reef system.", | |
| "Machu Picchu reveals the impressive engineering skills of the Inca civilization.", | |
| "Venice is famous for its canals, with boats as the main transportation method." | |
| ], | |
| "animals": [ | |
| "Elephants are highly intelligent and have complex social structures.", | |
| "Dolphins communicate using a series of clicks, whistles, and body movements.", | |
| "Chameleons can change color to match their surroundings and regulate temperature.", | |
| "Eagles have incredible eyesight and can spot prey from great distances.", | |
| "Octopuses have three hearts and can solve complex puzzles." | |
| ] | |
| } | |
| sentences = topics.get(topic.lower(), [ | |
| f"{topic} is a fascinating subject that continues to evolve over time.", | |
| f"Many people are interested in learning more about {topic} through books and online resources.", | |
| f"Experts in {topic} often share their knowledge through lectures and publications.", | |
| f"The history of {topic} reveals interesting patterns and developments.", | |
| f"Modern advances in {topic} have changed how we think about many aspects of life." | |
| ]) | |
| if format_type == "sentences": | |
| return " ".join(sentences[:sentence_count]) | |
| elif format_type == "story": | |
| return f"""Once upon a time, there was a curious student who became interested in {topic}. | |
| They discovered that {sentences[0].lower()} | |
| As they learned more, they found that {sentences[1].lower()} | |
| Their research showed that {sentences[2].lower()} | |
| The most surprising thing they learned was that {sentences[3].lower()} | |
| They shared their knowledge with friends, explaining how {sentences[4].lower()} | |
| Everyone was impressed by how much they had learned about {topic}.""" | |
| else: # paragraph | |
| return " ".join(sentences[:5]) | |
| # ====== UTILITY FUNCTIONS ====== | |
| def get_conversation_summary(session_id: str) -> Dict[str, Any]: | |
| """ | |
| Get a summary of the conversation session | |
| Args: | |
| session_id: Session identifier | |
| Returns: | |
| Summary with statistics and key points | |
| """ | |
| generator = AIContentGenerator() | |
| if session_id not in generator.conversation_contexts: | |
| return { | |
| "session_id": session_id, | |
| "status": "not_found", | |
| "message": "No conversation found with this session ID" | |
| } | |
| context = generator.conversation_contexts[session_id] | |
| # Calculate statistics | |
| user_messages = [turn for turn in context.history if turn["speaker"] == "user"] | |
| ai_messages = [turn for turn in context.history if turn["speaker"] == "ai"] | |
| # Extract topics discussed | |
| topics_discussed = [] | |
| for turn in user_messages: | |
| # Simple topic extraction (could be enhanced with NLP) | |
| words = turn["message"].lower().split() | |
| for word in words: | |
| if len(word) > 5 and word not in topics_discussed: | |
| topics_discussed.append(word) | |
| return { | |
| "session_id": session_id, | |
| "role": context.role, | |
| "main_topic": context.topic, | |
| "turn_count": context.turn_count, | |
| "total_exchanges": len(context.history), | |
| "user_messages": len(user_messages), | |
| "ai_messages": len(ai_messages), | |
| "topics_discussed": topics_discussed[:5], | |
| "conversation_duration": None, # Could calculate from timestamps | |
| "last_activity": context.history[-1]["timestamp"] if context.history else None | |
| } | |
| def reset_conversation(session_id: str) -> bool: | |
| """ | |
| Reset a conversation session | |
| Args: | |
| session_id: Session identifier | |
| Returns: | |
| Success status | |
| """ | |
| generator = AIContentGenerator() | |
| if session_id in generator.conversation_contexts: | |
| del generator.conversation_contexts[session_id] | |
| return True | |
| return False | |
| def list_available_roles() -> List[Dict[str, Any]]: | |
| """ | |
| Get list of available conversation roles | |
| Returns: | |
| List of role information | |
| """ | |
| roles = [] | |
| for role_id, role_info in CONVERSATION_ROLES.items(): | |
| roles.append({ | |
| "id": role_id, | |
| "name": role_info["name"], | |
| "description": role_info["style"], | |
| "suggested_topics": role_info["topics"], | |
| "personality": role_info["personality"] | |
| }) | |
| return roles | |
| # ====== TEST FUNCTIONS ====== | |
| def test_conversation_system(): | |
| """Test the conversation system with different roles""" | |
| print("Testing Enhanced Conversation System") | |
| print("=" * 50) | |
| # Test 1: Initialize conversations with different roles | |
| print("\n1. Testing role initialization:") | |
| for role in ["friend", "teacher", "interviewer"]: | |
| result = initialize_conversation( | |
| role=role, | |
| topic="hobbies", | |
| user_name="Sam" | |
| ) | |
| print(f"\n{role.upper()} Role:") | |
| print(f"Greeting: {result['greeting']}") | |
| print(f"Suggested topics: {result['suggested_topics']}") | |
| # Test 2: Generate responses | |
| print("\n\n2. Testing conversation responses:") | |
| session_id = "test_session_123" | |
| # Initialize a friend conversation | |
| init_result = initialize_conversation( | |
| role="friend", | |
| topic="cooking", | |
| session_id=session_id | |
| ) | |
| # Simulate user responses | |
| user_inputs = [ | |
| "I love trying new recipes, especially Italian food!", | |
| "My favorite dish to make is homemade pasta with fresh tomatoes.", | |
| "I learned from my grandmother who was an amazing cook." | |
| ] | |
| for user_input in user_inputs: | |
| print(f"\nUser: {user_input}") | |
| response = generate_ai_conversation_response( | |
| user_input=user_input, | |
| role="friend", | |
| topic="cooking", | |
| technique="normal", | |
| session_id=session_id | |
| ) | |
| print(f"AI: {response['response']}") | |
| print(f"Emotion: {response['emotion']}") | |
| print(f"Follow-up options: {response['follow_up_options']}") | |
| # Test 3: Get conversation summary | |
| print("\n\n3. Testing conversation summary:") | |
| summary = get_conversation_summary(session_id) | |
| print(f"Total turns: {summary['turn_count']}") | |
| print(f"Topics discussed: {summary['topics_discussed']}") | |
| print("\n" + "=" * 50) | |
| print("Testing complete!") | |
| if __name__ == "__main__": | |
| # Run tests | |
| test_conversation_system() |