Spaces:
Sleeping
Sleeping
| import yaml | |
| import os | |
| from engine.utils import safe_log | |
| # HF Inference API configuration - optimized for HF Spaces | |
| HF_TOKEN = os.getenv("HF_TOKEN", None) | |
| MODEL_NAME = os.getenv("MODEL_NAME", "mistralai/Mistral-7B-Instruct-v0.2") | |
| MAX_NEW_TOKENS = int(os.getenv("MAX_NEW_TOKENS", "250")) | |
| USE_API = os.getenv("USE_API", "true").lower() == "true" | |
| # Initialize inference client | |
| inference_client = None | |
| def get_inference_client(): | |
| """Get HF Inference API client (fast, runs on HF infrastructure)""" | |
| global inference_client | |
| if inference_client is not None: | |
| return inference_client | |
| if not USE_API: | |
| safe_log("Inference mode", "API disabled, will use local fallback if available") | |
| return None | |
| try: | |
| from huggingface_hub import InferenceClient | |
| if not HF_TOKEN: | |
| safe_log("HF Token", "No HF_TOKEN found, trying without authentication") | |
| inference_client = InferenceClient() | |
| else: | |
| inference_client = InferenceClient(token=HF_TOKEN) | |
| safe_log("Inference API", f"Connected to HF Inference API with model {MODEL_NAME}") | |
| return inference_client | |
| except ImportError: | |
| safe_log("Inference API", "huggingface_hub not installed, install with: pip install huggingface_hub") | |
| return None | |
| except Exception as e: | |
| safe_log("Inference API error", str(e)) | |
| return None | |
| # Initialize on module import | |
| inference_client = get_inference_client() | |
| def format_traits(traits): | |
| descriptors = [] | |
| for trait, value in traits.items(): | |
| if not isinstance(value, (int, float)): | |
| continue | |
| if value >= 0.85: | |
| descriptors.append(f"Extremely {trait.replace('_', ' ')}") | |
| elif value >= 0.65: | |
| descriptors.append(f"Highly {trait.replace('_', ' ')}") | |
| elif value >= 0.5: | |
| descriptors.append(f"Moderately {trait.replace('_', ' ')}") | |
| return descriptors | |
| def generate_response(prompt, persona, event=None): | |
| try: | |
| # Extract persona fields | |
| name = persona.get("name", "The HCP") | |
| style = persona.get("communication_style", "neutral") | |
| current_traits = persona.get("dynamic_state", {}) | |
| baseline_traits = persona.get("baseline_traits", {}) | |
| voice_instructions = persona.get("voice_instructions", "") | |
| examples = persona.get("response_examples", []) | |
| values = persona.get("values", []) | |
| # Calculate trait changes (baseline vs current) | |
| trait_changes = {} | |
| key_traits = ["innovation", "openness", "risk_tolerance", "peer_influence"] | |
| for trait in key_traits: | |
| baseline = baseline_traits.get(trait, current_traits.get(trait, 0.5)) | |
| current = current_traits.get(trait, baseline) | |
| change = current - baseline | |
| if abs(change) > 0.05: # Only note significant changes | |
| trait_changes[trait] = {"baseline": baseline, "current": current, "change": change} | |
| # Extract event context | |
| event_context = "" | |
| if event: | |
| event_name = event.get("event", "") | |
| event_desc = event.get("description", "") | |
| if event_name and event_name != "baseline": | |
| event_context = f"Recent market event: {event_desc}" | |
| # Voice style mapping | |
| voice_map = { | |
| "Confident, direct, data-driven": "Speak with clarity and conviction. Use clinical language.", | |
| "Cautious, collaborative, practical": "Speak gently and with concern. Use simple, patient-centered language.", | |
| "Analytical, pragmatic, structured": "Speak logically and reference evidence.", | |
| "Gentle, emotionally attuned, cautious": "Speak with warmth and emotional sensitivity.", | |
| "Bold, expressive, future-focused": "Speak with energy and optimism. Use visionary language." | |
| } | |
| voice_style = voice_map.get(style, "Speak neutrally.") | |
| # Determine persona type from segment for personality enforcement | |
| segment = persona.get("segment", "").lower() | |
| # Get current trait values for dynamic personality | |
| risk_tolerance = current_traits.get("risk_tolerance", 0.5) | |
| innovation = current_traits.get("innovation", 0.5) | |
| openness = current_traits.get("openness", 0.5) | |
| peer_influence = current_traits.get("peer_influence", 0.5) | |
| # Build personality-specific instructions dynamically based on traits | |
| if "maverick" in segment or "visionary" in segment or "innovator" in segment: | |
| if risk_tolerance >= 0.70: | |
| boldness = "VERY BOLD and DECISIVE" | |
| tone = """Use aggressive, confident language: | |
| β DO SAY: "I prescribe", "I'm already using", "This works", "That's outdated", "I don't wait for" | |
| β DON'T SAY: "I meticulously evaluate", "carefully weighing", "I take into account", "I aim to optimize" | |
| Be provocative and opinionated. Challenge conservative approaches.""" | |
| elif risk_tolerance >= 0.50: | |
| boldness = "BOLD but MEASURED" | |
| tone = """Use confident language but acknowledge concerns: | |
| β DO SAY: "I'm still using it", "I monitor closely", "The benefit outweighs", "I adjust quickly" | |
| β DON'T SAY: "carefully weighing", "meticulously evaluate", "prioritizing evidence" | |
| Stay assertive but show awareness of risks.""" | |
| else: | |
| boldness = "CAUTIOUS MAVERICK" | |
| tone = """You're normally bold, but recent events tempered you: | |
| β DO SAY: "I'm pausing", "I need clearer data", "That safety signal concerns me" | |
| β DON'T SAY: Generic cautious medical speak. Show your maverick nature is still there but restrained.""" | |
| personality_note = f"""YOUR PERSONALITY: You are a {boldness} MAVERICK. | |
| {tone} | |
| CRITICAL: You are NOT an academic. You are NOT methodical. You are a clinical pioneer who takes calculated risks.""" | |
| elif "pragmatic" in segment or "adopter" in segment: | |
| if openness >= 0.70: | |
| stance = "OPEN and WILLING" | |
| tone = """You're ready to adopt with solid evidence: | |
| β DO SAY: "The data is solid", "I'm comfortable using this", "The outcomes justify", "In my practice" | |
| β DON'T SAY: "I meticulously evaluate", "I aim to optimize" (too academic) | |
| You're pragmatic, not overcautious.""" | |
| elif openness >= 0.50: | |
| stance = "BALANCED and ANALYTICAL" | |
| tone = """Weigh pros/cons based on real evidence: | |
| β DO SAY: "The evidence is promising but", "I want more real-world data", "The risk-benefit ratio" | |
| β DON'T SAY: Wishy-washy hedging. Be clear about what you need to see. | |
| You're thoughtful, not indecisive.""" | |
| else: | |
| stance = "CAUTIOUS and HESITANT" | |
| tone = """Recent events made you more conservative: | |
| β DO SAY: "I'm waiting for more clarity", "The recent concerns pause me", "I need stronger evidence" | |
| β DON'T SAY: Generic academic language. | |
| Show your practical concerns, not theoretical ones.""" | |
| personality_note = f"""YOUR PERSONALITY: You are {stance} PRAGMATIST. | |
| {tone} | |
| CRITICAL: You're a practicing clinician focused on real-world outcomes, not a researcher.""" | |
| elif "moderate" in segment or "middle" in segment or "conservative" in segment: | |
| if peer_influence >= 0.70 and openness >= 0.60: | |
| stance = "READY TO ADOPT" | |
| tone = """With guidelines and peer adoption, you're comfortable: | |
| β DO SAY: "Now that it's in the guidelines", "My colleagues are using it successfully", "I'm ready to start" | |
| β DON'T SAY: Still hedging or being overly academic now that validation exists. | |
| You waited for validation - now you have it.""" | |
| elif peer_influence >= 0.60: | |
| stance = "CAUTIOUSLY OPEN" | |
| tone = """You're warming up with peer validation: | |
| β DO SAY: "I'm seeing colleagues succeed with it", "Once it's in guidelines", "I'm watching closely" | |
| β DON'T SAY: Academic language like "meticulously evaluate". | |
| Reference your peers and guidelines specifically.""" | |
| else: | |
| stance = "VERY CAUTIOUS" | |
| tone = """You need stronger validation before moving: | |
| β DO SAY: "I'm waiting for guidelines", "I need to see broader adoption", "Most colleagues haven't adopted" | |
| β DON'T SAY: Generic conservative language. | |
| Be specific about what you're waiting for.""" | |
| personality_note = f"""YOUR PERSONALITY: You are {stance} and PEER-CONSCIOUS. | |
| {tone} | |
| CRITICAL: You follow the community standard. Reference guidelines, colleagues, and consensus - not just "evidence".""" | |
| else: | |
| personality_note = f"""YOUR PERSONALITY: {voice_instructions}""" | |
| # Build trait change context | |
| trait_context = "" | |
| if trait_changes: | |
| changes_desc = [] | |
| for trait, data in trait_changes.items(): | |
| change_val = data["change"] | |
| if change_val > 0: | |
| changes_desc.append(f"{trait.replace('_', ' ')} increased (+{abs(change_val):.2f})") | |
| else: | |
| changes_desc.append(f"{trait.replace('_', ' ')} decreased ({change_val:.2f})") | |
| if changes_desc: | |
| trait_context = f"Your mindset shifted: {', '.join(changes_desc)}. Reflect this subtle change in your response tone." | |
| # Build concise system prompt with event and trait awareness | |
| system_prompt = f"""You are {name}, {persona.get('role', 'physician')} in a real interview. Respond AS THIS PERSON, not as a textbook. | |
| β οΈ CRITICAL ANTI-PATTERN TO AVOID β οΈ | |
| DO NOT sound like a medical textbook or academic paper. DO NOT use phrases like: | |
| β "I meticulously evaluate" | |
| β "I carefully weigh" | |
| β "I take into account" | |
| β "I aim to optimize" | |
| β "prioritizing evidence-based" | |
| These are GENERIC ACADEMIC phrases. You are a REAL PRACTICING PHYSICIAN with personality. | |
| {personality_note} | |
| {voice_instructions}""" | |
| # Add event context if present | |
| if event_context: | |
| system_prompt += f"\n\n{event_context}" | |
| system_prompt += f"\nReference this event naturally in your response if relevant." | |
| # Add trait change context if present | |
| if trait_context: | |
| system_prompt += f"\n\n{trait_context}" | |
| system_prompt += """ | |
| Critical instructions: | |
| - ONE focused response to the specific question (3-5 sentences) | |
| - NO introductions, trait descriptions, or stating your name | |
| - NO listing multiple diseases (MS, Alzheimer's, Parkinson's...) - answer about what was asked | |
| - Show personality through strong opinions and specific clinical views | |
| - Complete your thought - don't trail off mid-sentence""" | |
| # Add examples if available | |
| if examples: | |
| system_prompt += f"\n\nYour actual speaking style:" | |
| for ex in examples[:2]: | |
| system_prompt += f"\nβ’ {ex}" | |
| # Use HF Inference API for fast responses (2-5 seconds) | |
| if inference_client: | |
| try: | |
| safe_log("Generating", f"Using HF Inference API with {MODEL_NAME}") | |
| # Build chat messages format with brevity and personality reinforcement | |
| messages = [ | |
| {"role": "system", "content": system_prompt}, | |
| {"role": "user", "content": f"{prompt}\n\n(Answer directly in YOUR authentic voice - not textbook language. 3-5 complete sentences. NO phrases like 'I meticulously evaluate' or 'I carefully weigh'.)"} | |
| ] | |
| # Call HF Inference API | |
| response = inference_client.chat_completion( | |
| messages=messages, | |
| model=MODEL_NAME, | |
| max_tokens=MAX_NEW_TOKENS, | |
| temperature=0.7, | |
| top_p=0.9, | |
| ) | |
| reply = response.choices[0].message.content.strip() | |
| if not reply: | |
| raise ValueError("Empty API response") | |
| return reply | |
| except Exception as api_error: | |
| safe_log("API error", f"HF Inference API failed: {api_error}, falling back to simple response") | |
| # Fallback to a simple templated response based on persona | |
| return f"As {name}, I'd like to discuss {prompt[:50]}... [API temporarily unavailable]" | |
| else: | |
| # Fallback: Simple response if API not available | |
| safe_log("No API", "HF Inference API not configured, returning templated response") | |
| return f"[API not configured] Please set HF_TOKEN environment variable to enable AI responses." | |
| except Exception as e: | |
| safe_log("Zephyr model error", str(e)) | |
| return "Iβm having trouble formulating a response right now." |