vachaspathi commited on
Commit
e6a09aa
·
verified ·
1 Parent(s): 35158be

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +35 -19
app.py CHANGED
@@ -14,7 +14,7 @@ import base64
14
  import asyncio
15
  import gc
16
 
17
- # --- NEW: Import OCR Engine & Prompts ---
18
  try:
19
  from ocr_engine import extract_text_from_file
20
  from prompts import get_ocr_extraction_prompt, get_agent_prompt
@@ -31,7 +31,7 @@ logger = logging.getLogger("mcp_server")
31
  # Attempt to import transformers
32
  TRANSFORMERS_AVAILABLE = False
33
  try:
34
- from transformers import pipeline, AutoTokenizer, AutoModelForCausalLM, AutoModelForSeq2SeqLM
35
  TRANSFORMERS_AVAILABLE = True
36
  except Exception as e:
37
  logger.warning("transformers not available: %s", e)
@@ -63,14 +63,10 @@ _init_analytics()
63
  # Regex JSON Extractor
64
  # ----------------------------
65
  def extract_json_safely(text: str) -> Optional[Any]:
66
- """
67
- Extracts JSON from text even if the model adds conversational filler.
68
- """
69
  try:
70
  return json.loads(text)
71
  except:
72
  pass
73
-
74
  try:
75
  match = re.search(r'(\{.*\}|\[.*\])', text, re.DOTALL)
76
  if match:
@@ -81,7 +77,7 @@ def extract_json_safely(text: str) -> Optional[Any]:
81
  return None
82
 
83
  # ----------------------------
84
- # Local LLM loader
85
  # ----------------------------
86
  LLM_PIPELINE = None
87
  TOKENIZER = None
@@ -89,8 +85,14 @@ LOADED_MODEL_NAME = None
89
 
90
  def init_local_model():
91
  global LLM_PIPELINE, TOKENIZER, LOADED_MODEL_NAME
 
 
 
 
 
92
  if not LOCAL_MODEL or not TRANSFORMERS_AVAILABLE:
93
  return
 
94
  try:
95
  logger.info(f"Loading model: {LOCAL_MODEL}...")
96
  TOKENIZER = AutoTokenizer.from_pretrained(LOCAL_MODEL, trust_remote_code=True)
@@ -98,23 +100,28 @@ def init_local_model():
98
 
99
  LLM_PIPELINE = pipeline("text-generation", model=model, tokenizer=TOKENIZER)
100
  LOADED_MODEL_NAME = LOCAL_MODEL
101
- logger.info("Model loaded.")
102
  except Exception as e:
103
  logger.error(f"Model load failed: {e}")
104
 
105
- init_local_model()
 
106
 
107
- # --- FIX APPLIED HERE ---
108
  def local_llm_generate(prompt: str, max_tokens: int = 512) -> Dict[str, Any]:
 
 
 
 
109
  if LLM_PIPELINE is None:
110
  return {"text": "LLM not loaded.", "raw": None}
 
111
  try:
112
- # FIX: Added `use_cache=False` to resolve 'DynamicCache' object has no attribute 'seen_tokens'
113
  out = LLM_PIPELINE(
114
  prompt,
115
  max_new_tokens=max_tokens,
116
  return_full_text=False,
117
- use_cache=False
118
  )
119
  text = out[0]["generated_text"] if out else ""
120
  return {"text": text, "raw": out}
@@ -144,15 +151,19 @@ def _get_valid_token_headers() -> dict:
144
  r = requests.post(token_url, params=params, timeout=20)
145
  if r.status_code == 200:
146
  return {"Authorization": f"Zoho-oauthtoken {r.json().get('access_token')}"}
147
- raise RuntimeError(f"Token refresh failed: {r.text}")
 
 
148
 
149
  @mcp.tool()
150
  def authenticate_zoho() -> str:
151
- _get_valid_token_headers(); return "Zoho token refreshed (ok)."
 
152
 
153
  @mcp.tool()
154
  def create_record(module_name: str, record_data: dict) -> str:
155
  headers = _get_valid_token_headers()
 
156
  url = f"{API_BASE}/{module_name}"
157
  r = requests.post(url, headers=headers, json={"data": [record_data]}, timeout=20)
158
  if r.status_code in (200, 201): return json.dumps(r.json(), ensure_ascii=False)
@@ -161,18 +172,21 @@ def create_record(module_name: str, record_data: dict) -> str:
161
  @mcp.tool()
162
  def get_records(module_name: str, page: int = 1, per_page: int = 200) -> list:
163
  headers = _get_valid_token_headers()
 
164
  r = requests.get(f"{API_BASE}/{module_name}", headers=headers, params={"page": page, "per_page": per_page})
165
  return r.json().get("data", []) if r.status_code == 200 else []
166
 
167
  @mcp.tool()
168
  def update_record(module_name: str, record_id: str, data: dict) -> str:
169
  headers = _get_valid_token_headers()
 
170
  r = requests.put(f"{API_BASE}/{module_name}/{record_id}", headers=headers, json={"data": [data]})
171
  return json.dumps(r.json()) if r.status_code == 200 else r.text
172
 
173
  @mcp.tool()
174
  def delete_record(module_name: str, record_id: str) -> str:
175
  headers = _get_valid_token_headers()
 
176
  r = requests.delete(f"{API_BASE}/{module_name}/{record_id}", headers=headers)
177
  return json.dumps(r.json()) if r.status_code == 200 else r.text
178
 
@@ -183,6 +197,7 @@ def _ensure_invoice_config():
183
  def create_invoice(data: dict) -> str:
184
  _ensure_invoice_config()
185
  headers = _get_valid_token_headers()
 
186
  params = {"organization_id": ORGANIZATION_ID}
187
  r = requests.post(f"{INVOICE_API_BASE}/invoices", headers=headers, params=params, json=data)
188
  if r.status_code in (200, 201): return json.dumps(r.json(), ensure_ascii=False)
@@ -191,6 +206,7 @@ def create_invoice(data: dict) -> str:
191
  def upload_invoice_attachment(invoice_id: str, file_path: str) -> str:
192
  if not os.path.exists(file_path): return "File not found"
193
  headers = _get_valid_token_headers()
 
194
  headers.pop("Content-Type", None)
195
  url = f"{INVOICE_API_BASE}/invoices/{invoice_id}/attachments"
196
  with open(file_path, "rb") as f:
@@ -200,9 +216,6 @@ def upload_invoice_attachment(invoice_id: str, file_path: str) -> str:
200
 
201
  @mcp.tool()
202
  def process_document(file_path: str, target_module: Optional[str] = "Contacts") -> dict:
203
- """
204
- Extracts data from file using OCR + LLM.
205
- """
206
  try:
207
  if not os.path.exists(file_path):
208
  return {"status": "error", "error": "file not found"}
@@ -215,7 +228,7 @@ def process_document(file_path: str, target_module: Optional[str] = "Contacts")
215
  # 2. Use Prompt Template
216
  prompt = get_ocr_extraction_prompt(raw_text)
217
 
218
- # 3. Generate (with cache fix applied in local_llm_generate)
219
  llm_out = local_llm_generate(prompt, max_tokens=300)
220
  extracted_text = llm_out.get("text", "")
221
 
@@ -334,7 +347,7 @@ def deepseek_response(message: str, file_path: Optional[str] = None, history: li
334
  history_text = "\n".join([f"User: {h[0]}\nBot: {h[1]}" for h in history])
335
  prompt = get_agent_prompt(history_text, ocr_context, message)
336
 
337
- # 3. Generate (Cache Fix applies here too)
338
  gen = local_llm_generate(prompt, max_tokens=256)
339
  response_text = gen["text"]
340
 
@@ -384,6 +397,9 @@ def cleanup_event_loop():
384
  if __name__ == "__main__":
385
  cleanup_event_loop()
386
 
 
 
 
387
  demo = gr.ChatInterface(
388
  fn=chat_handler,
389
  multimodal=True,
 
14
  import asyncio
15
  import gc
16
 
17
+ # --- Import OCR Engine & Prompts ---
18
  try:
19
  from ocr_engine import extract_text_from_file
20
  from prompts import get_ocr_extraction_prompt, get_agent_prompt
 
31
  # Attempt to import transformers
32
  TRANSFORMERS_AVAILABLE = False
33
  try:
34
+ from transformers import pipeline, AutoTokenizer, AutoModelForCausalLM
35
  TRANSFORMERS_AVAILABLE = True
36
  except Exception as e:
37
  logger.warning("transformers not available: %s", e)
 
63
  # Regex JSON Extractor
64
  # ----------------------------
65
  def extract_json_safely(text: str) -> Optional[Any]:
 
 
 
66
  try:
67
  return json.loads(text)
68
  except:
69
  pass
 
70
  try:
71
  match = re.search(r'(\{.*\}|\[.*\])', text, re.DOTALL)
72
  if match:
 
77
  return None
78
 
79
  # ----------------------------
80
+ # Local LLM loader (Lazy Loading Fix)
81
  # ----------------------------
82
  LLM_PIPELINE = None
83
  TOKENIZER = None
 
85
 
86
  def init_local_model():
87
  global LLM_PIPELINE, TOKENIZER, LOADED_MODEL_NAME
88
+
89
+ # FIX 1: Check if already loaded to prevent double-memory usage
90
+ if LLM_PIPELINE is not None:
91
+ return
92
+
93
  if not LOCAL_MODEL or not TRANSFORMERS_AVAILABLE:
94
  return
95
+
96
  try:
97
  logger.info(f"Loading model: {LOCAL_MODEL}...")
98
  TOKENIZER = AutoTokenizer.from_pretrained(LOCAL_MODEL, trust_remote_code=True)
 
100
 
101
  LLM_PIPELINE = pipeline("text-generation", model=model, tokenizer=TOKENIZER)
102
  LOADED_MODEL_NAME = LOCAL_MODEL
103
+ logger.info("Model loaded successfully.")
104
  except Exception as e:
105
  logger.error(f"Model load failed: {e}")
106
 
107
+ # FIX 2: Removed global call to init_local_model() here.
108
+ # It will now be called only in __main__ or lazily.
109
 
 
110
  def local_llm_generate(prompt: str, max_tokens: int = 512) -> Dict[str, Any]:
111
+ # FIX 3: Lazy load if accessed before main (safety net)
112
+ if LLM_PIPELINE is None:
113
+ init_local_model()
114
+
115
  if LLM_PIPELINE is None:
116
  return {"text": "LLM not loaded.", "raw": None}
117
+
118
  try:
119
+ # Includes Cache Fix from previous step
120
  out = LLM_PIPELINE(
121
  prompt,
122
  max_new_tokens=max_tokens,
123
  return_full_text=False,
124
+ use_cache=False
125
  )
126
  text = out[0]["generated_text"] if out else ""
127
  return {"text": text, "raw": out}
 
151
  r = requests.post(token_url, params=params, timeout=20)
152
  if r.status_code == 200:
153
  return {"Authorization": f"Zoho-oauthtoken {r.json().get('access_token')}"}
154
+ # Don't crash entire app on token fail, just return None so tool can report it
155
+ logger.error(f"Token Refresh Failed: {r.text}")
156
+ return {}
157
 
158
  @mcp.tool()
159
  def authenticate_zoho() -> str:
160
+ h = _get_valid_token_headers()
161
+ return "Zoho token refreshed." if h else "Failed to refresh token."
162
 
163
  @mcp.tool()
164
  def create_record(module_name: str, record_data: dict) -> str:
165
  headers = _get_valid_token_headers()
166
+ if not headers: return "Auth Error"
167
  url = f"{API_BASE}/{module_name}"
168
  r = requests.post(url, headers=headers, json={"data": [record_data]}, timeout=20)
169
  if r.status_code in (200, 201): return json.dumps(r.json(), ensure_ascii=False)
 
172
  @mcp.tool()
173
  def get_records(module_name: str, page: int = 1, per_page: int = 200) -> list:
174
  headers = _get_valid_token_headers()
175
+ if not headers: return []
176
  r = requests.get(f"{API_BASE}/{module_name}", headers=headers, params={"page": page, "per_page": per_page})
177
  return r.json().get("data", []) if r.status_code == 200 else []
178
 
179
  @mcp.tool()
180
  def update_record(module_name: str, record_id: str, data: dict) -> str:
181
  headers = _get_valid_token_headers()
182
+ if not headers: return "Auth Error"
183
  r = requests.put(f"{API_BASE}/{module_name}/{record_id}", headers=headers, json={"data": [data]})
184
  return json.dumps(r.json()) if r.status_code == 200 else r.text
185
 
186
  @mcp.tool()
187
  def delete_record(module_name: str, record_id: str) -> str:
188
  headers = _get_valid_token_headers()
189
+ if not headers: return "Auth Error"
190
  r = requests.delete(f"{API_BASE}/{module_name}/{record_id}", headers=headers)
191
  return json.dumps(r.json()) if r.status_code == 200 else r.text
192
 
 
197
  def create_invoice(data: dict) -> str:
198
  _ensure_invoice_config()
199
  headers = _get_valid_token_headers()
200
+ if not headers: return "Auth Error"
201
  params = {"organization_id": ORGANIZATION_ID}
202
  r = requests.post(f"{INVOICE_API_BASE}/invoices", headers=headers, params=params, json=data)
203
  if r.status_code in (200, 201): return json.dumps(r.json(), ensure_ascii=False)
 
206
  def upload_invoice_attachment(invoice_id: str, file_path: str) -> str:
207
  if not os.path.exists(file_path): return "File not found"
208
  headers = _get_valid_token_headers()
209
+ if not headers: return "Auth Error"
210
  headers.pop("Content-Type", None)
211
  url = f"{INVOICE_API_BASE}/invoices/{invoice_id}/attachments"
212
  with open(file_path, "rb") as f:
 
216
 
217
  @mcp.tool()
218
  def process_document(file_path: str, target_module: Optional[str] = "Contacts") -> dict:
 
 
 
219
  try:
220
  if not os.path.exists(file_path):
221
  return {"status": "error", "error": "file not found"}
 
228
  # 2. Use Prompt Template
229
  prompt = get_ocr_extraction_prompt(raw_text)
230
 
231
+ # 3. Generate
232
  llm_out = local_llm_generate(prompt, max_tokens=300)
233
  extracted_text = llm_out.get("text", "")
234
 
 
347
  history_text = "\n".join([f"User: {h[0]}\nBot: {h[1]}" for h in history])
348
  prompt = get_agent_prompt(history_text, ocr_context, message)
349
 
350
+ # 3. Generate
351
  gen = local_llm_generate(prompt, max_tokens=256)
352
  response_text = gen["text"]
353
 
 
397
  if __name__ == "__main__":
398
  cleanup_event_loop()
399
 
400
+ # FIX: Explicitly load model once here to prevent concurrent load attempts by Gradio
401
+ init_local_model()
402
+
403
  demo = gr.ChatInterface(
404
  fn=chat_handler,
405
  multimodal=True,