VigneshVS2005 commited on
Commit
52002cf
·
1 Parent(s): fe06b42

Add Groq Llama 3.2 Vision implementation with strict formatting constraints

Browse files
Files changed (5) hide show
  1. ai_router.py +3 -0
  2. config.py +2 -1
  3. models/groq_vision.py +91 -0
  4. static/app.js +8 -7
  5. templates/index.html +1 -0
ai_router.py CHANGED
@@ -1,6 +1,7 @@
1
  from models.blip_model import blip_answer
2
  from models.reasoning_model import reasoning_answer
3
  from models.gemini_vision import gemini_vision_answer
 
4
 
5
  try:
6
  from deep_translator import GoogleTranslator
@@ -21,6 +22,8 @@ def route_model(model_choice, image, question, lang="en"):
21
  cap, ans, exp = reasoning_answer(image, question)
22
  elif model_choice == "gemini":
23
  cap, ans, exp = gemini_vision_answer(image, question, lang)
 
 
24
  else:
25
  cap, ans, exp = "Unknown", "Invalid", "Invalid"
26
 
 
1
  from models.blip_model import blip_answer
2
  from models.reasoning_model import reasoning_answer
3
  from models.gemini_vision import gemini_vision_answer
4
+ from models.groq_vision import groq_vision_answer
5
 
6
  try:
7
  from deep_translator import GoogleTranslator
 
22
  cap, ans, exp = reasoning_answer(image, question)
23
  elif model_choice == "gemini":
24
  cap, ans, exp = gemini_vision_answer(image, question, lang)
25
+ elif model_choice == "groq":
26
+ cap, ans, exp = groq_vision_answer(image, question, lang)
27
  else:
28
  cap, ans, exp = "Unknown", "Invalid", "Invalid"
29
 
config.py CHANGED
@@ -13,4 +13,5 @@ GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "AIzaSyB1fMfnnp2etuOVWiLrecdMp3_0Gb
13
  # ===== Security Limits =====
14
  MAX_IMAGE_SIZE_MB = 5
15
  RATE_LIMIT_PER_MINUTE = 30
16
- HF_TOKEN = os.getenv("HF_TOKEN", "")
 
 
13
  # ===== Security Limits =====
14
  MAX_IMAGE_SIZE_MB = 5
15
  RATE_LIMIT_PER_MINUTE = 30
16
+ HF_TOKEN = os.getenv("HF_TOKEN", "")
17
+ GROQ_API_KEY = os.getenv("GROQ_API_KEY", "")
models/groq_vision.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import io
3
+ import base64
4
+ import requests
5
+ import re
6
+ from config import GROQ_API_KEY
7
+
8
+ def groq_vision_answer(image, question, lang="en"):
9
+ token = GROQ_API_KEY or os.getenv("GROQ_API_KEY")
10
+ if not token:
11
+ return "Setup Required", "Groq API Key is missing.", "Please set the GROQ_API_KEY in config.py or environment."
12
+
13
+ try:
14
+ model_id = "llama-3.2-11b-vision-preview"
15
+ api_url = "https://api.groq.com/openai/v1/chat/completions"
16
+
17
+ headers = {
18
+ "Authorization": f"Bearer {token}",
19
+ "Content-Type": "application/json"
20
+ }
21
+
22
+ if image.mode != 'RGB':
23
+ image = image.convert('RGB')
24
+
25
+ # Max resolution of 600x600 for reliable API processing
26
+ image.thumbnail((600, 600))
27
+
28
+ buffered = io.BytesIO()
29
+ image.save(buffered, format="JPEG", quality=85)
30
+ img_b64 = base64.b64encode(buffered.getvalue()).decode("utf-8")
31
+ img_data_url = f"data:image/jpeg;base64,{img_b64}"
32
+
33
+ prompt = f"""Analyze this image carefully.
34
+
35
+ Question:
36
+ {question}
37
+
38
+ CRITICAL RULES FOR YOUR RESPONSE:
39
+ 1. You MUST respond strictly in the language code: {lang}. All text in your output must be translated to '{lang}'.
40
+ 2. You MUST use EXACTLY the format below with these English labels.
41
+ 3. The Caption MUST be exactly 1 line only.
42
+ 4. The Final Answer MUST be exactly 1 line only.
43
+ 5. The Explanation MUST be strictly 2 to 3 lines maximum. Be very concise and do not exceed this limit!
44
+
45
+ Respond strictly following this exact structure without any deviations or markdown blocks.
46
+
47
+ Caption: <one-line caption>
48
+ Final Answer: <one-line answer>
49
+ Explanation: <2-3 lines max explanation>"""
50
+
51
+ payload = {
52
+ "model": model_id,
53
+ "messages": [
54
+ {
55
+ "role": "user",
56
+ "content": [
57
+ {"type": "text", "text": prompt},
58
+ {"type": "image_url", "image_url": {"url": img_data_url}}
59
+ ]
60
+ }
61
+ ],
62
+ "max_tokens": 512,
63
+ "temperature": 0.2
64
+ }
65
+
66
+ response = requests.post(api_url, headers=headers, json=payload, timeout=60)
67
+
68
+ if response.status_code != 200:
69
+ return "Groq Server Error", f"HTTP {response.status_code}", response.text
70
+
71
+ data = response.json()
72
+ raw_text = data["choices"][0]["message"]["content"]
73
+
74
+ # Parse response using regex
75
+ caption_match = re.search(r'Caption:\s*(.*?)(?=Final Answer:|$)', raw_text, re.IGNORECASE | re.DOTALL)
76
+ answer_match = re.search(r'Final Answer:\s*(.*?)(?=Explanation:|$)', raw_text, re.IGNORECASE | re.DOTALL)
77
+ explanation_match = re.search(r'Explanation:\s*(.*)', raw_text, re.IGNORECASE | re.DOTALL)
78
+
79
+ caption = caption_match.group(1).strip() if caption_match else "Caption not generated correctly."
80
+ answer = answer_match.group(1).strip() if answer_match else "Answer not generated correctly."
81
+ explanation = explanation_match.group(1).strip() if explanation_match else "Explanation not generated correctly."
82
+
83
+ # Filter out markdown bolds if they leaked and force one-line where needed
84
+ caption = caption.replace("**", "").replace("\n", " ")
85
+ answer = answer.replace("**", "").replace("\n", " ")
86
+ explanation = explanation.replace("**", "")
87
+
88
+ return caption, answer, explanation
89
+
90
+ except Exception as e:
91
+ return "Groq API Error", "The request crashed.", repr(e)
static/app.js CHANGED
@@ -2,7 +2,7 @@
2
  let appState = {
3
  username: '',
4
  logs: [],
5
- stats: { total: 0, local: 0, gemini: 0 },
6
  geminiEnabled: true
7
  };
8
 
@@ -355,6 +355,7 @@ async function fetchLogs() {
355
  appState.stats.total = appState.logs.length;
356
  appState.stats.local = appState.logs.filter(l => ['local', 'blip', 'reasoning'].includes(l.model.toLowerCase())).length;
357
  appState.stats.gemini = appState.logs.filter(l => ['gemini', 'external'].includes(l.model.toLowerCase())).length;
 
358
 
359
  updateDashboardView();
360
  } catch(err) {
@@ -364,7 +365,7 @@ async function fetchLogs() {
364
 
365
  function updateDashboardView() {
366
  els.statTotal.innerText = appState.stats.total;
367
- els.statModels.innerText = `${appState.stats.local} Local / ${appState.stats.gemini} Cloud`;
368
  renderLogs();
369
  updateChart();
370
  }
@@ -378,7 +379,7 @@ function renderLogs(filter = '') {
378
  tr.innerHTML = `
379
  <td>${log.timestamp}</td>
380
  <td><span class="user-badge" style="color: var(--text-secondary);"><i class="fa-solid fa-user"></i> ${log.user}</span></td>
381
- <td><span style="color: ${['gemini'].some(m => log.model.includes(m)) ? 'var(--accent)' : 'var(--success)'}">${log.model.toUpperCase().replace('_', ' ')}</span></td>
382
  <td>${log.question.length > 50 ? log.question.substring(0, 50) + '...' : log.question}</td>
383
  `;
384
  els.logsBody.appendChild(tr);
@@ -408,12 +409,12 @@ function initChart() {
408
 
409
  function getChartData() {
410
  return {
411
- labels: ['BLIP + FLAN (Local)', 'Gemini AI (Cloud)'],
412
  datasets: [{
413
  label: 'Queries',
414
- data: [appState.stats.local, appState.stats.gemini],
415
- backgroundColor: ['rgba(16, 185, 129, 0.6)', 'rgba(99, 102, 241, 0.6)'],
416
- borderColor: ['rgba(16, 185, 129, 1)', 'rgba(99, 102, 241, 1)'],
417
  borderWidth: 1, borderRadius: 6
418
  }]
419
  };
 
2
  let appState = {
3
  username: '',
4
  logs: [],
5
+ stats: { total: 0, local: 0, gemini: 0, groq: 0 },
6
  geminiEnabled: true
7
  };
8
 
 
355
  appState.stats.total = appState.logs.length;
356
  appState.stats.local = appState.logs.filter(l => ['local', 'blip', 'reasoning'].includes(l.model.toLowerCase())).length;
357
  appState.stats.gemini = appState.logs.filter(l => ['gemini', 'external'].includes(l.model.toLowerCase())).length;
358
+ appState.stats.groq = appState.logs.filter(l => ['groq'].includes(l.model.toLowerCase())).length;
359
 
360
  updateDashboardView();
361
  } catch(err) {
 
365
 
366
  function updateDashboardView() {
367
  els.statTotal.innerText = appState.stats.total;
368
+ els.statModels.innerText = `${appState.stats.local} Local / ${appState.stats.gemini + appState.stats.groq} Cloud`;
369
  renderLogs();
370
  updateChart();
371
  }
 
379
  tr.innerHTML = `
380
  <td>${log.timestamp}</td>
381
  <td><span class="user-badge" style="color: var(--text-secondary);"><i class="fa-solid fa-user"></i> ${log.user}</span></td>
382
+ <td><span style="color: ${['gemini', 'groq'].some(m => log.model.includes(m)) ? 'var(--accent)' : 'var(--success)'}">${log.model.toUpperCase().replace('_', ' ')}</span></td>
383
  <td>${log.question.length > 50 ? log.question.substring(0, 50) + '...' : log.question}</td>
384
  `;
385
  els.logsBody.appendChild(tr);
 
409
 
410
  function getChartData() {
411
  return {
412
+ labels: ['BLIP + FLAN (Local)', 'Gemini AI (Cloud)', 'Groq Vision (Cloud)'],
413
  datasets: [{
414
  label: 'Queries',
415
+ data: [appState.stats.local, appState.stats.gemini, appState.stats.groq],
416
+ backgroundColor: ['rgba(16, 185, 129, 0.6)', 'rgba(99, 102, 241, 0.6)', 'rgba(245, 158, 11, 0.6)'],
417
+ borderColor: ['rgba(16, 185, 129, 1)', 'rgba(99, 102, 241, 1)', 'rgba(245, 158, 11, 1)'],
418
  borderWidth: 1, borderRadius: 6
419
  }]
420
  };
templates/index.html CHANGED
@@ -97,6 +97,7 @@
97
  <select id="model-selector">
98
  <option value="local">BLIP + FLAN (Local)</option>
99
  <option value="gemini" id="gemini-option">Gemini 3.1 Flash Vision (High Quota)</option>
 
100
  </select>
101
  </div>
102
  <div class="control-group" style="flex: 1;">
 
97
  <select id="model-selector">
98
  <option value="local">BLIP + FLAN (Local)</option>
99
  <option value="gemini" id="gemini-option">Gemini 3.1 Flash Vision (High Quota)</option>
100
+ <option value="groq" id="groq-option">Groq Llama 3.2 Vision (Strict Constraints)</option>
101
  </select>
102
  </div>
103
  <div class="control-group" style="flex: 1;">