Spaces:
Running
Running
Ubuntu commited on
Commit ·
0d6e94b
1
Parent(s): f1ed485
Added changes to fix nemotron parse
Browse files- config.py +1 -1
- gemini_classification_prompt.txt +2 -6
- gemini_classifier.py +3 -0
- gemma_classifier.py +82 -40
- processing.py +1 -1
- redact.py +1 -1
config.py
CHANGED
|
@@ -7,7 +7,7 @@ class Config:
|
|
| 7 |
OUTPUT_FOLDER = 'output'
|
| 8 |
DATABASE = 'database.db'
|
| 9 |
NVIDIA_API_KEY = os.getenv("NVIDIA_API_KEY")
|
| 10 |
-
NIM_API_URL = "https://ai.api.nvidia.com/v1/cv/nvidia/
|
| 11 |
NIM_HEADERS = {
|
| 12 |
"Authorization": f"Bearer {NVIDIA_API_KEY}",
|
| 13 |
"Accept": "application/json",
|
|
|
|
| 7 |
OUTPUT_FOLDER = 'output'
|
| 8 |
DATABASE = 'database.db'
|
| 9 |
NVIDIA_API_KEY = os.getenv("NVIDIA_API_KEY")
|
| 10 |
+
NIM_API_URL = "https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-ocr-v1"
|
| 11 |
NIM_HEADERS = {
|
| 12 |
"Authorization": f"Bearer {NVIDIA_API_KEY}",
|
| 13 |
"Accept": "application/json",
|
gemini_classification_prompt.txt
CHANGED
|
@@ -199,10 +199,6 @@ Your task is to analyze each question, first classify it into the most relevant
|
|
| 199 |
|
| 200 |
Now classify the following question(s):
|
| 201 |
```
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
10. Given below are two statements: The interphase nucleus has highly extended and elaborate nucleoprotein fibres called chromatin which contains DNA and some basic proteins called histones, some non-histone proteins and also RNA. Statement I: Statement II: A haploid set of chromosomes in humans contains 3.3 X 109 bp which is approximately two metre long thread of DNA distributed among its twenty three chromosomes. In the light of the above statements, choose the most appropriate answer from the options given below: (A) Statement is correct but Statement Il is incorrect. (B) Statement is incorrect but Statement ll is correct. (C) Both Statement and Statement ll are correct. (D) Both Statement and Statement ll are incorrect.
|
| 205 |
-
11. A force F = - (yi + +x)) N acts on a particle moving in the xy plane. Starting from the origin, the particle is taken along the positive x-axis to the point (a, 0) m and then parallel to the y-axis to the point (a, a) m. The total work done (in joules) by the force is: (A) ka² (B) ka² (C) -2ka² (D) Zero
|
| 206 |
-
12. On electrolysis of dilute nitric acid using platinum electrodes, the product obtained at the anode is: (A) H2 gas (B) O2 gas (C) NO2 gas (D) N2 gas
|
| 207 |
-
13. Identify the incorrectly matched pair: (A) Petiole is modified for photosynthesis Australian acacia (B) Leaves modified into spines Cactus (C) Stem modified into a fleshy, cylindrical photosynthetic structure Opuntia (D) Stem modified into thorns Citrus
|
| 208 |
```
|
|
|
|
| 199 |
|
| 200 |
Now classify the following question(s):
|
| 201 |
```
|
| 202 |
+
1. 1. A parallel plate capacitor is charged by connecting it to a battery through a resistor. If I is the current in the circuit, then in the gap between the plates 1) There is no current 2) Displacement current of magnitude greater than flows but it can be any direction 3) Displacement current of magnitude equal to flows in a direction opposite to that of I. 4) Displacement current of magnitude equal to flows in the same direction as I. 2. For the following diagram [used to measure the length of a small metal piece by using vernier callipers], determine the length of the metal piece. Least count of the vernier callipers is 0.1 mm 10 15 20 mm Vernier scale Object 1) 18 mm 2) 15.7 mm 3) 12.6 mm 4) 10.2 mm
|
| 203 |
+
2. 3. Statement I: The speed of whirlwind in a tornado is alarmingly high. Statement II: If no external torque acts on a body, its angular velocity remains conserved 1) Statement I is true, Statement II is true; Statement II is not the correct explanation of Statement I 2) Statement I is true, Statement II is false 3) Statement I is false, Statement II is true 4) Statement I is true, Statement II is true; Statement II is the correct explanation of Statement I
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
```
|
gemini_classifier.py
CHANGED
|
@@ -229,7 +229,10 @@ Now classify the following question(s):
|
|
| 229 |
with open('gemini_classification_prompt.txt', 'w') as f:
|
| 230 |
f.write(prompt)
|
| 231 |
|
|
|
|
|
|
|
| 232 |
url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={api_key}"
|
|
|
|
| 233 |
headers = {'Content-Type': 'application/json'}
|
| 234 |
|
| 235 |
request_body = {
|
|
|
|
| 229 |
with open('gemini_classification_prompt.txt', 'w') as f:
|
| 230 |
f.write(prompt)
|
| 231 |
|
| 232 |
+
|
| 233 |
+
# gemma-4-26b-a4b-it
|
| 234 |
url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={api_key}"
|
| 235 |
+
|
| 236 |
headers = {'Content-Type': 'application/json'}
|
| 237 |
|
| 238 |
request_body = {
|
gemma_classifier.py
CHANGED
|
@@ -1,92 +1,134 @@
|
|
| 1 |
-
import os
|
| 2 |
import json
|
| 3 |
import requests
|
| 4 |
import sys
|
| 5 |
from typing import List, Dict, Any, Optional
|
| 6 |
from api_key_manager import get_api_key_manager
|
| 7 |
|
|
|
|
| 8 |
class GemmaClassifier:
|
|
|
|
|
|
|
|
|
|
| 9 |
def __init__(self):
|
| 10 |
# API key will be fetched dynamically via get_api_key_manager
|
| 11 |
pass
|
| 12 |
|
| 13 |
def classify(self, questions: List[str], start_index: int = 0) -> Optional[Dict[str, Any]]:
|
| 14 |
"""
|
| 15 |
-
Classifies a list of questions using the
|
| 16 |
`questions` should be a list of strings representing the questions to classify.
|
| 17 |
`start_index` is the overall starting index for this batch (e.g., 0, 7, 14...).
|
| 18 |
"""
|
| 19 |
manager = get_api_key_manager()
|
| 20 |
-
api_key, key_index = manager.get_key('
|
| 21 |
|
| 22 |
if not api_key:
|
| 23 |
-
raise ValueError("No available
|
| 24 |
|
| 25 |
full_prompt = self._generate_gemma_prompt(questions=questions, start_index=start_index)
|
| 26 |
|
| 27 |
-
url = "
|
| 28 |
headers = {
|
| 29 |
-
"Authorization": f"Bearer {api_key}",
|
| 30 |
"Content-Type": "application/json"
|
| 31 |
}
|
| 32 |
|
| 33 |
payload = {
|
| 34 |
-
"
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
}
|
| 40 |
|
| 41 |
-
print(f"Sending batch to
|
| 42 |
|
| 43 |
try:
|
| 44 |
response = requests.post(url, headers=headers, json=payload, timeout=300)
|
| 45 |
response.raise_for_status()
|
| 46 |
|
|
|
|
| 47 |
response_json = response.json()
|
| 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 |
-
manager.mark_failure('nvidia', key_index)
|
| 73 |
return None
|
| 74 |
|
| 75 |
except requests.exceptions.RequestException as e:
|
| 76 |
-
print(f"Error during
|
| 77 |
if e.response is not None:
|
| 78 |
print(f"Response status code: {e.response.status_code}", file=sys.stderr)
|
| 79 |
print(f"Response body: {e.response.text}", file=sys.stderr)
|
| 80 |
-
manager.mark_failure('
|
| 81 |
return None
|
| 82 |
except Exception as e:
|
| 83 |
print(f"An unexpected error occurred: {e}", file=sys.stderr)
|
| 84 |
-
manager.mark_failure('
|
| 85 |
return None
|
| 86 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
def _generate_gemma_prompt(self, questions: List[str], start_index: int) -> str:
|
| 88 |
"""
|
| 89 |
-
Generates the detailed prompt for the Gemma classifier
|
| 90 |
"""
|
| 91 |
input_text = "\n".join([f"{j + start_index + 1}. {q}" for j, q in enumerate(questions)])
|
| 92 |
|
|
|
|
|
|
|
| 1 |
import json
|
| 2 |
import requests
|
| 3 |
import sys
|
| 4 |
from typing import List, Dict, Any, Optional
|
| 5 |
from api_key_manager import get_api_key_manager
|
| 6 |
|
| 7 |
+
|
| 8 |
class GemmaClassifier:
|
| 9 |
+
MODEL_ID = "gemma-4-26b-a4b-it"
|
| 10 |
+
API_BASE = "https://generativelanguage.googleapis.com/v1beta/models"
|
| 11 |
+
|
| 12 |
def __init__(self):
|
| 13 |
# API key will be fetched dynamically via get_api_key_manager
|
| 14 |
pass
|
| 15 |
|
| 16 |
def classify(self, questions: List[str], start_index: int = 0) -> Optional[Dict[str, Any]]:
|
| 17 |
"""
|
| 18 |
+
Classifies a list of questions using the Gemini API (Gemma 4 model).
|
| 19 |
`questions` should be a list of strings representing the questions to classify.
|
| 20 |
`start_index` is the overall starting index for this batch (e.g., 0, 7, 14...).
|
| 21 |
"""
|
| 22 |
manager = get_api_key_manager()
|
| 23 |
+
api_key, key_index = manager.get_key('gemini')
|
| 24 |
|
| 25 |
if not api_key:
|
| 26 |
+
raise ValueError("No available Gemini API keys. Please set GEMINI_API_KEY environment variable.")
|
| 27 |
|
| 28 |
full_prompt = self._generate_gemma_prompt(questions=questions, start_index=start_index)
|
| 29 |
|
| 30 |
+
url = f"{self.API_BASE}/{self.MODEL_ID}:streamGenerateContent?key={api_key}"
|
| 31 |
headers = {
|
|
|
|
| 32 |
"Content-Type": "application/json"
|
| 33 |
}
|
| 34 |
|
| 35 |
payload = {
|
| 36 |
+
"contents": [
|
| 37 |
+
{
|
| 38 |
+
"role": "user",
|
| 39 |
+
"parts": [
|
| 40 |
+
{"text": full_prompt}
|
| 41 |
+
]
|
| 42 |
+
}
|
| 43 |
+
],
|
| 44 |
+
"generationConfig": {
|
| 45 |
+
"thinkingConfig": {
|
| 46 |
+
"thinkingLevel": "HIGH"
|
| 47 |
+
}
|
| 48 |
+
},
|
| 49 |
+
"tools": [
|
| 50 |
+
{
|
| 51 |
+
"googleSearch": {}
|
| 52 |
+
}
|
| 53 |
+
]
|
| 54 |
}
|
| 55 |
|
| 56 |
+
print(f"Sending batch to Gemini API (Gemma 4) with {len(questions)} questions.")
|
| 57 |
|
| 58 |
try:
|
| 59 |
response = requests.post(url, headers=headers, json=payload, timeout=300)
|
| 60 |
response.raise_for_status()
|
| 61 |
|
| 62 |
+
# streamGenerateContent returns a JSON array of chunks
|
| 63 |
response_json = response.json()
|
| 64 |
|
| 65 |
+
# Accumulate all text parts across all chunks
|
| 66 |
+
model_output_content = self._extract_streamed_text(response_json)
|
| 67 |
+
|
| 68 |
+
if model_output_content is None:
|
| 69 |
+
print("Error: No text content found in Gemini API response.", file=sys.stderr)
|
| 70 |
+
manager.mark_failure('gemini', key_index)
|
| 71 |
+
return None
|
| 72 |
+
|
| 73 |
+
# Strip optional ```json ... ``` fences
|
| 74 |
+
cleaned = model_output_content.strip()
|
| 75 |
+
if cleaned.startswith("```json") and cleaned.endswith("```"):
|
| 76 |
+
cleaned = cleaned[7:-3].strip()
|
| 77 |
+
elif cleaned.startswith("```") and cleaned.endswith("```"):
|
| 78 |
+
cleaned = cleaned[3:-3].strip()
|
| 79 |
+
|
| 80 |
+
try:
|
| 81 |
+
batch_result = json.loads(cleaned)
|
| 82 |
+
manager.mark_success('gemini', key_index)
|
| 83 |
+
return batch_result
|
| 84 |
+
except json.JSONDecodeError as e:
|
| 85 |
+
print(f"Error decoding JSON from model output: {e}", file=sys.stderr)
|
| 86 |
+
print(f"Model output content: {model_output_content}", file=sys.stderr)
|
| 87 |
+
manager.mark_failure('gemini', key_index)
|
|
|
|
| 88 |
return None
|
| 89 |
|
| 90 |
except requests.exceptions.RequestException as e:
|
| 91 |
+
print(f"Error during Gemini API call: {repr(e)}", file=sys.stderr)
|
| 92 |
if e.response is not None:
|
| 93 |
print(f"Response status code: {e.response.status_code}", file=sys.stderr)
|
| 94 |
print(f"Response body: {e.response.text}", file=sys.stderr)
|
| 95 |
+
manager.mark_failure('gemini', key_index)
|
| 96 |
return None
|
| 97 |
except Exception as e:
|
| 98 |
print(f"An unexpected error occurred: {e}", file=sys.stderr)
|
| 99 |
+
manager.mark_failure('gemini', key_index)
|
| 100 |
return None
|
| 101 |
|
| 102 |
+
# ------------------------------------------------------------------
|
| 103 |
+
# Private helpers
|
| 104 |
+
# ------------------------------------------------------------------
|
| 105 |
+
|
| 106 |
+
def _extract_streamed_text(self, response_json) -> Optional[str]:
|
| 107 |
+
"""
|
| 108 |
+
Gemini's streamGenerateContent returns a JSON array of candidate chunks.
|
| 109 |
+
Each chunk looks like:
|
| 110 |
+
{"candidates": [{"content": {"parts": [{"text": "..."}]}}]}
|
| 111 |
+
This method concatenates all text parts across all chunks.
|
| 112 |
+
"""
|
| 113 |
+
parts_text = []
|
| 114 |
+
|
| 115 |
+
# response_json may be a list of chunks or a single dict
|
| 116 |
+
chunks = response_json if isinstance(response_json, list) else [response_json]
|
| 117 |
+
|
| 118 |
+
for chunk in chunks:
|
| 119 |
+
candidates = chunk.get("candidates", [])
|
| 120 |
+
for candidate in candidates:
|
| 121 |
+
content = candidate.get("content", {})
|
| 122 |
+
for part in content.get("parts", []):
|
| 123 |
+
text = part.get("text")
|
| 124 |
+
if text:
|
| 125 |
+
parts_text.append(text)
|
| 126 |
+
|
| 127 |
+
return "".join(parts_text) if parts_text else None
|
| 128 |
+
|
| 129 |
def _generate_gemma_prompt(self, questions: List[str], start_index: int) -> str:
|
| 130 |
"""
|
| 131 |
+
Generates the detailed prompt for the Gemma classifier.
|
| 132 |
"""
|
| 133 |
input_text = "\n".join([f"{j + start_index + 1}. {q}" for j, q in enumerate(questions)])
|
| 134 |
|
processing.py
CHANGED
|
@@ -12,7 +12,7 @@ from flask import current_app
|
|
| 12 |
from api_key_manager import get_api_key_manager
|
| 13 |
|
| 14 |
# --- NVIDIA NIM Configuration ---
|
| 15 |
-
NIM_API_URL = "https://ai.api.nvidia.com/v1/cv/nvidia/
|
| 16 |
|
| 17 |
def resize_image_if_needed(image_path: str) -> bytes:
|
| 18 |
"""Resizes an image to a maximum of 500x500 pixels and returns bytes."""
|
|
|
|
| 12 |
from api_key_manager import get_api_key_manager
|
| 13 |
|
| 14 |
# --- NVIDIA NIM Configuration ---
|
| 15 |
+
NIM_API_URL = "https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-ocr-v1"
|
| 16 |
|
| 17 |
def resize_image_if_needed(image_path: str) -> bytes:
|
| 18 |
"""Resizes an image to a maximum of 500x500 pixels and returns bytes."""
|
redact.py
CHANGED
|
@@ -10,7 +10,7 @@ import json
|
|
| 10 |
|
| 11 |
# --- Configuration ---
|
| 12 |
# API endpoints should remain constant
|
| 13 |
-
INVOKE_URL_OCR = "https://ai.api.nvidia.com/v1/cv/nvidia/
|
| 14 |
INVOKE_URL_PARSER = "https://integrate.api.nvidia.com/v1/chat/completions"
|
| 15 |
|
| 16 |
# Define a max pixel count for the parser model to avoid sending overly large images.
|
|
|
|
| 10 |
|
| 11 |
# --- Configuration ---
|
| 12 |
# API endpoints should remain constant
|
| 13 |
+
INVOKE_URL_OCR = "https://ai.api.nvidia.com/v1/cv/nvidia/nemotron-ocr-v1"
|
| 14 |
INVOKE_URL_PARSER = "https://integrate.api.nvidia.com/v1/chat/completions"
|
| 15 |
|
| 16 |
# Define a max pixel count for the parser model to avoid sending overly large images.
|