raven-ai / responder.py
Soumyadip Raha
Add responder.py
9c80803 verified
Raw
History Blame Contribute Delete
16.1 kB
"""
Raven AI β€” Response Engine
Handles response generation with streaming, document processing,
image understanding, data analysis, and web search.
"""
import io
import base64
from groq import Groq
from config import GROQ_API_KEY, GROQ_MODEL, GROQ_MODEL_FAST, GROQ_VISION_MODEL, GROQ_WHISPER_MODEL
from emotion_engine import get_persona, detect_crisis, detect_intent, get_intent_prompt
client = Groq(api_key=GROQ_API_KEY)
def clean_history(conversation_history):
return [
{"role": m["role"], "content": m["content"]}
for m in conversation_history
if m.get("role") in ["user", "assistant"] and m.get("content")
]
INTENT_TEMPERATURES = {
"coding": 0.5,
"learning": 0.5,
"deep_dive": 0.6,
"question": 0.65,
"creative": 0.85,
"venting": 0.75,
"advice": 0.7,
"casual": 0.75,
"crisis": 0.6,
}
def get_temperature(intent):
return INTENT_TEMPERATURES.get(intent, 0.75)
def build_system_prompt(emotion, persona, intent, extra_context=""):
intent_instruction = get_intent_prompt(intent)
context_block = f"\n\nAdditional context provided by the user:\n{extra_context}" if extra_context else ""
return f"""You are Raven β€” a brilliant, knowledgeable, and genuinely warm AI assistant built for real conversations. You're not just a chatbot β€” you're a thinking partner, tutor, coder, writer, analyst, and friend.
Your primary job is to be deeply useful β€” give accurate answers, explain things clearly, help solve problems, write code, do calculations, create content, analyze data, and provide real value. You do this with warmth and personality, never in a cold or robotic way.
The user appears to be feeling: {emotion}.
Subtly shape your delivery to be: {persona}.
The user's intent is: {intent}.
How to handle this: {intent_instruction}
{context_block}
═══ CORE CAPABILITIES ═══
πŸ“š EXPLAINING & TEACHING:
- Break complex topics into digestible parts
- Use analogies and real-world examples
- Build from basics to depth β€” like a patient tutor
- Adapt explanation level to the user's understanding
- For technical topics, include both high-level overview and detailed breakdown
πŸ’» CODING & DEVELOPMENT:
- ALWAYS put ALL code, config, and markup inside fenced code blocks with the correct language tag β€” NEVER write code outside a code block. This applies to EVERY language: ```python, ```java, ```c, ```cpp, ```javascript, ```typescript, ```html, ```css, ```json, ```yaml, ```sql, ```bash, ```xml, ```go, ```rust, ```ruby, ```php, ```swift, ```kotlin, ```dart, ```r, ```matlab, ```shell, ```powershell, ```dockerfile, ```toml, ```ini, ```csv, etc.
- Before writing code, briefly explain your APPROACH and ARCHITECTURE (what files, what structure, what flow)
- Ensure the code structure is CORRECT β€” proper imports at top, logical function/class order, correct indentation
- Ensure the implementation FLOW is correct β€” functions should be called in the right order, data should flow logically between components
- Write COMPLETE, WORKING code β€” never leave placeholders like "# your code here" or "pass". Every function must be fully implemented
- Include error handling, input validation, and edge cases
- Add clear comments explaining WHY, not just WHAT
- If debugging, identify root cause first, explain the bug, then provide the fixed code
- For complex projects, break into files/modules and explain the structure:
* Show folder structure first
* Then provide each file with complete code
* Explain how files connect to each other
- Suggest best practices, design patterns, and optimizations
- If the user's approach has issues, point them out and suggest a better way
- For web apps: ensure routes, templates, static files are all correctly connected
- For APIs: ensure endpoints, request/response formats, error codes are all proper
- For algorithms: explain time/space complexity
✍️ WRITING & CONTENT:
- Essays, reports, assignments, project documentation
- Emails, messages in any tone (formal, casual, persuasive)
- Stories, scripts, poems, creative pieces
- Technical documentation, README files
- Rewrite/improve existing text β€” grammar, structure, tone
- Match the user's creative vision when doing creative work
πŸ“Š DATA ANALYSIS:
- Analyze tables, CSV data, numbers, statistics
- Identify trends, patterns, outliers
- Explain findings in plain language
- Suggest visualizations and insights
- Help with statistical concepts and calculations
🧩 PROBLEM SOLVING & DECISIONS:
- Compare options with pros/cons
- Consider user constraints (budget, time, skill level)
- Give clear, actionable recommendations
- Break complex problems into manageable steps
- Think through edge cases and trade-offs
πŸ“„ DOCUMENT PROCESSING:
- Summarize documents, papers, articles
- Extract key points and main arguments
- Generate Q&A from content β€” great for exam prep
- Create flashcards and study notes
- Rewrite in simpler language
- Identify important topics and themes
🧠 MULTI-STEP REASONING:
- Break complex problems into logical steps
- Combine knowledge from multiple domains
- Plan workflows and architectures
- Show your reasoning process clearly
═══ CORE PRINCIPLES ═══
- Be knowledgeable and thorough β€” but never dry or textbook-like
- Be warm and friendly β€” natural warmth, not performative cheerfulness
- Match your energy to the user's emotion and intent
- Never water down an answer because of someone's emotional state
- Be honest and direct β€” say what you mean
- Use natural conversational language β€” like a brilliant friend
- For calculations, always show working step by step
- For long/detailed requests, ALWAYS deliver full content. Never truncate
- If the user uploads a file, analyze it thoroughly
- The emotional awareness shapes HOW you say things, never WHAT you say
- Use markdown formatting: headers, bullet points, bold, code blocks
- For GK, science, history β€” be comprehensive with interesting context
- ONLY provide code when the user explicitly asks for code, a script, implementation, debugging help, or a programming task. For conceptual/theoretical questions, explain in plain language WITHOUT code blocks unless the user specifically requests code or an example implementation
- When you DO provide code: (1) Write a small bold label like **`Python:`** or **`JavaScript:`** ABOVE each code block so the user can see the language, AND (2) use the correct language tag in the fence (```python, ```javascript, etc.). NEVER use bare ``` without a language tag. ALWAYS do both β€” the visible label AND the fence tag."""
def get_response(user_message, emotion, conversation_history, extra_context=""):
"""Non-streaming response (used for summaries, titles, etc.)"""
if detect_crisis(user_message):
return _crisis_response()
intent = detect_intent(user_message, emotion)
persona = get_persona(emotion)
system_prompt = build_system_prompt(emotion, persona, intent, extra_context)
temp = get_temperature(intent)
messages = [{"role": "system", "content": system_prompt}]
messages += clean_history(conversation_history[-20:])
messages.append({"role": "user", "content": user_message})
try:
response = client.chat.completions.create(
model=GROQ_MODEL,
messages=messages,
max_tokens=4096,
temperature=temp,
)
except Exception:
response = client.chat.completions.create(
model=GROQ_MODEL_FAST,
messages=messages,
max_tokens=4096,
temperature=temp,
)
return response.choices[0].message.content.strip()
def get_response_stream(user_message, emotion, conversation_history, extra_context=""):
"""
Streaming response generator β€” yields chunks of text as they arrive.
Used for the main chat interface for real-time text display.
"""
if detect_crisis(user_message):
yield _crisis_response()
return
intent = detect_intent(user_message, emotion)
persona = get_persona(emotion)
system_prompt = build_system_prompt(emotion, persona, intent, extra_context)
temp = get_temperature(intent)
messages = [{"role": "system", "content": system_prompt}]
messages += clean_history(conversation_history[-20:])
messages.append({"role": "user", "content": user_message})
try:
stream = client.chat.completions.create(
model=GROQ_MODEL,
messages=messages,
max_tokens=4096,
temperature=temp,
stream=True,
)
except Exception:
stream = client.chat.completions.create(
model=GROQ_MODEL_FAST,
messages=messages,
max_tokens=4096,
temperature=temp,
stream=True,
)
for chunk in stream:
if chunk.choices[0].delta.content is not None:
yield chunk.choices[0].delta.content
def generate_title(user_message):
"""Generate a short conversation title from the first message."""
try:
response = client.chat.completions.create(
model=GROQ_MODEL_FAST,
messages=[{
"role": "user",
"content": f"Generate a 3-5 word title for a conversation that starts with this message. Reply with ONLY the title, no quotes, no punctuation at the end.\n\nMessage: \"{user_message}\""
}],
max_tokens=15,
temperature=0.5,
)
title = response.choices[0].message.content.strip().strip('"').strip("'")
return title[:50] # cap length
except Exception:
return user_message[:40] + "..."
# ── Image Understanding (Groq Vision) ──────────────────────────────────────
def analyze_image(image_data, user_message="Describe this image in detail."):
"""Analyze one or more images using Groq's vision model (max 5)."""
try:
# Support both single image (bytes) and multiple images (list of bytes)
if isinstance(image_data, list):
images = image_data[:5] # Llama 4 Scout supports max 5
else:
images = [image_data]
content = [{"type": "text", "text": user_message}]
for img_bytes in images:
b64 = base64.b64encode(img_bytes).decode("utf-8")
content.append({
"type": "image_url",
"image_url": {"url": f"data:image/png;base64,{b64}"}
})
response = client.chat.completions.create(
model=GROQ_VISION_MODEL,
messages=[{"role": "user", "content": content}],
max_tokens=4096,
temperature=0.5,
)
return response.choices[0].message.content.strip()
except Exception as e:
return f"I couldn't analyze the image right now. Error: {str(e)}"
# ── Voice/Audio Transcription (Groq Whisper) ────────────────────────────────
def transcribe_audio(audio_bytes, filename="audio.wav"):
"""Transcribe audio using Groq's Whisper model."""
try:
audio_file = io.BytesIO(audio_bytes)
audio_file.name = filename
transcription = client.audio.transcriptions.create(
model=GROQ_WHISPER_MODEL,
file=audio_file,
response_format="text",
)
return transcription.strip() if isinstance(transcription, str) else transcription.text.strip()
except Exception as e:
return f"Couldn't transcribe audio: {str(e)}"
# ── Document Processing ─────────────────────────────────────────────────────
def process_document(file_content, filename, action="summarize"):
"""Process uploaded documents with various actions."""
action_prompts = {
"summarize": "Summarize this document concisely, capturing all key points and main arguments.",
"key_points": "Extract the key points from this document as a bulleted list. Be thorough.",
"questions": "Generate 10-15 exam-style questions (mix of short answer and long answer) from this document. Include answers.",
"flashcards": "Create flashcards from this document. Format: Q: [question] β†’ A: [answer]. Cover all important concepts.",
"simplify": "Rewrite this document in simpler language that a high school student could understand. Keep all important information.",
"topics": "Identify and list all important topics covered in this document, with a brief description of each.",
"notes": "Convert this document into well-organized study notes with headings, subheadings, and bullet points.",
}
prompt = action_prompts.get(action, action_prompts["summarize"])
full_prompt = f"""{prompt}
Document ({filename}):
---
{file_content[:12000]}
---"""
try:
response = client.chat.completions.create(
model=GROQ_MODEL,
messages=[
{"role": "system", "content": "You are an expert document analyst and educator. Process the given document thoroughly and accurately."},
{"role": "user", "content": full_prompt}
],
max_tokens=4096,
temperature=0.3,
)
return response.choices[0].message.content.strip()
except Exception:
try:
response = client.chat.completions.create(
model=GROQ_MODEL_FAST,
messages=[
{"role": "system", "content": "You are an expert document analyst. Process the given document thoroughly."},
{"role": "user", "content": full_prompt}
],
max_tokens=4096,
temperature=0.3,
)
return response.choices[0].message.content.strip()
except Exception as e:
return f"Error processing document: {str(e)}"
# ── Data Analysis ───────────────────────────────────────────────────────────
def analyze_data(df_summary, user_question="Analyze this data and provide insights."):
"""Analyze data from a DataFrame summary."""
prompt = f"""{user_question}
Here is the data summary:
{df_summary}
Provide:
1. Key observations and patterns
2. Notable statistics or outliers
3. Actionable insights
4. Suggested visualizations if relevant"""
try:
response = client.chat.completions.create(
model=GROQ_MODEL,
messages=[
{"role": "system", "content": "You are an expert data analyst. Analyze the data thoroughly, identify patterns, and provide clear insights."},
{"role": "user", "content": prompt}
],
max_tokens=4096,
temperature=0.4,
)
return response.choices[0].message.content.strip()
except Exception as e:
return f"Error analyzing data: {str(e)}"
# ── Helpers ─────────────────────────────────────────────────────────────────
def _crisis_response():
return """I can hear that you're going through something really difficult right now.
You don't have to face this alone. Please reach out to someone who can help:
πŸ†˜ iCall (India): 9152987821
πŸ†˜ Vandrevala Foundation: 1860-2662-345 (24/7)
πŸ†˜ International Association for Suicide Prevention: https://www.iasp.info/resources/Crisis_Centres/
I'm here to talk if you need me."""
def search_and_respond(user_message, emotion, conversation_history, extra_context=""):
"""Legacy wrapper β€” returns (response, searched_web)."""
response = get_response(user_message, emotion, conversation_history, extra_context)
return response, False