Ubuntu commited on
Commit
0d6e94b
·
1 Parent(s): f1ed485

Added changes to fix nemotron parse

Browse files
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/nemoretriever-ocr-v1"
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
- 8. first-order reaction has half-life 200 s. Time required for the amount of reactant to become one- eighth of its initial value is: (A) 200 s (B) 400 s (C) 600 s (D) 800 s
203
- 9. Which hormone helps in internode/petiole elongation in deep water rice plants to keep leaves/ upper parts of the shoot above water? (A) Gibberellins (B) Zeatin (C) ABA (D) Ethylene
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 NVIDIA NIM Gemma API.
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('nvidia')
21
 
22
  if not api_key:
23
- raise ValueError("No available NVIDIA API keys. Please set NVIDIA_API_KEY environment variable.")
24
 
25
  full_prompt = self._generate_gemma_prompt(questions=questions, start_index=start_index)
26
 
27
- url = "https://integrate.api.nvidia.com/v1/chat/completions"
28
  headers = {
29
- "Authorization": f"Bearer {api_key}",
30
  "Content-Type": "application/json"
31
  }
32
 
33
  payload = {
34
- "model": "google/gemma-3n-e4b-it",
35
- "messages": [{"role": "user", "content": full_prompt}],
36
- "temperature": 0.2,
37
- "max_tokens": 2048,
38
- "stream": False
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  }
40
 
41
- print(f"Sending batch to NVIDIA NIM Gemma API with {len(questions)} questions.")
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
- if 'choices' in response_json and len(response_json['choices']) > 0:
50
- first_choice = response_json['choices'][0]
51
- if 'message' in first_choice and 'content' in first_choice['message']:
52
- model_output_content = first_choice['message']['content']
53
-
54
- if model_output_content.startswith("```json") and model_output_content.endswith("```"):
55
- model_output_content = model_output_content[7:-3].strip()
56
-
57
- try:
58
- batch_result = json.loads(model_output_content)
59
- manager.mark_success('nvidia', key_index)
60
- return batch_result
61
- except json.JSONDecodeError as e:
62
- print(f"Error decoding JSON from model output: {e}", file=sys.stderr)
63
- print(f"Model output content: {model_output_content}", file=sys.stderr)
64
- manager.mark_failure('nvidia', key_index)
65
- return None
66
- else:
67
- print("Error: 'message' or 'content' not found in NVIDIA NIM Gemma response choice.", file=sys.stderr)
68
- manager.mark_failure('nvidia', key_index)
69
- return None
70
- else:
71
- print("Error: 'choices' not found or empty in NVIDIA NIM Gemma response.", file=sys.stderr)
72
- manager.mark_failure('nvidia', key_index)
73
  return None
74
 
75
  except requests.exceptions.RequestException as e:
76
- print(f"Error during NVIDIA NIM Gemma API call: {repr(e)}", file=sys.stderr)
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('nvidia', key_index)
81
  return None
82
  except Exception as e:
83
  print(f"An unexpected error occurred: {e}", file=sys.stderr)
84
- manager.mark_failure('nvidia', key_index)
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, similar to gemini_classifier.py.
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/nemoretriever-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."""
 
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/nemoretriever-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.
 
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.