Spaces:
Sleeping
Sleeping
File size: 5,615 Bytes
2db8ee1 a529aa1 2db8ee1 7af9025 e296091 7af9025 e296091 7af9025 1d2f776 2db8ee1 a529aa1 e296091 a529aa1 2db8ee1 e296091 08292d0 2db8ee1 e296091 a529aa1 e296091 2db8ee1 e296091 2db8ee1 d8c0ee6 e296091 d8c0ee6 e296091 d8c0ee6 a529aa1 e296091 a529aa1 e296091 08292d0 d8c0ee6 e296091 d8c0ee6 a529aa1 e296091 a529aa1 e296091 d8c0ee6 e296091 d8c0ee6 e296091 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | import os
from dotenv import load_dotenv
import google.genai as genai
from PIL import Image
load_dotenv()
client = genai.Client(api_key=os.getenv("GEMINI_API_KEY"))
# --- Groq Fallback ---
_groq_client = None
def _get_groq_client():
global _groq_client
if _groq_client is None:
from groq import Groq
_groq_client = Groq(api_key=os.getenv("GROQ_API_KEY"))
return _groq_client
GROQ_MODEL = "llama-3.3-70b-versatile"
def _build_prompt(query, text_contexts, image_contexts):
"""Build the text-only prompt (shared by Gemini and Groq)."""
text_block = "\n\n".join(
[f"[Text Source {i+1}]\n{ctx['content']}"
for i, ctx in enumerate(text_contexts)]
)
return f"""You are VectorMind, a smart and conversational AI assistant. The user has uploaded documents, and the relevant excerpts are provided below as context.
Your job is to be genuinely helpful. You should:
- Answer questions using the provided context as your primary source of truth
- Analyze, interpret, summarize, compare, evaluate, or give opinions about the content when asked
- Be conversational and natural — not robotic or overly cautious
- If the user asks you to do something with the document content (evaluate, critique, improve, etc.), do your best using what you have
- Only say you don't have enough information if the context truly has nothing relevant
- Cite your sources naturally, like (Text Source 1) or (Image Source 2)
TEXT CONTEXT:
{text_block}
QUESTION:
{query}
"""
def _load_images(image_contexts):
images = []
for i, ctx in enumerate(image_contexts):
path = ctx.get("image_path", "")
if os.path.isfile(path):
try:
img = Image.open(path)
images.append((f"[Image Source {i+1}] from page {ctx.get('page', '?')}", img))
except Exception:
pass
return images
def generate_title(user_message):
"""Generate a short title for a conversation based on the first user message."""
prompt = f"Generate a concise 3-6 word title for a conversation that starts with this message. Return ONLY the title, no quotes or punctuation.\n\nMessage: {user_message}\n\nTitle:"
try:
response = client.models.generate_content(model="gemini-2.5-flash", contents=prompt)
return response.text.strip().strip('"').strip("'")[:50]
except Exception:
# Fallback to Groq for title generation
try:
groq = _get_groq_client()
response = groq.chat.completions.create(
model=GROQ_MODEL,
messages=[{"role": "user", "content": prompt}],
max_tokens=20,
)
return response.choices[0].message.content.strip().strip('"').strip("'")[:50]
except Exception:
return "New Chat"
def decompose_query(user_query):
prompt = f"""
Break the following user question into smaller, independent search queries.
Question:
{user_query}
Return 2–4 focused sub-queries related to the input documents, if the query can be broken down, else return a singular query.
Do NOT answer the question.
Only return the search queries.
"""
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=prompt
)
return [q.strip("- ").strip() for q in response.text.split("\n") if q.strip()]
def generate_answer(query, text_contexts, image_contexts):
prompt = _build_prompt(query, text_contexts, image_contexts)
contents = [prompt]
loaded_images = _load_images(image_contexts)
for label, img in loaded_images:
contents.append(img)
contents.append(label)
try:
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=contents
)
return response.text
except Exception as e:
if _is_rate_limit(e):
return _groq_generate(prompt)
raise
def generate_answer_stream(query, text_contexts, image_contexts):
"""Streaming version — tries Gemini first, falls back to Groq on rate limit."""
prompt = _build_prompt(query, text_contexts, image_contexts)
contents = [prompt]
loaded_images = _load_images(image_contexts)
for label, img in loaded_images:
contents.append(img)
contents.append(label)
try:
response = client.models.generate_content_stream(
model="gemini-2.5-flash",
contents=contents,
)
for chunk in response:
if chunk.text:
yield chunk.text
except Exception as e:
if _is_rate_limit(e):
yield from _groq_generate_stream(prompt)
else:
raise
def _is_rate_limit(error):
"""Check if the error is a rate limit / quota error."""
error_str = str(error).lower()
return any(kw in error_str for kw in ["429", "quota", "rate limit", "resource exhausted"])
def _groq_generate(prompt):
"""Non-streaming Groq fallback."""
groq = _get_groq_client()
response = groq.chat.completions.create(
model=GROQ_MODEL,
messages=[{"role": "user", "content": prompt}],
)
return response.choices[0].message.content
def _groq_generate_stream(prompt):
"""Streaming Groq fallback."""
groq = _get_groq_client()
stream = groq.chat.completions.create(
model=GROQ_MODEL,
messages=[{"role": "user", "content": prompt}],
stream=True,
)
for chunk in stream:
content = chunk.choices[0].delta.content
if content:
yield content |