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

resolve dynamic cache

Browse files
Files changed (1) hide show
  1. app.py +23 -39
app.py CHANGED
@@ -51,31 +51,26 @@ except Exception as e:
51
  mcp = FastMCP("ZohoCRMAgent")
52
 
53
  # ----------------------------
54
- # Analytics (Kept intact)
55
  # ----------------------------
56
  ANALYTICS_PATH = "mcp_analytics.json"
57
  def _init_analytics():
58
  if not os.path.exists(ANALYTICS_PATH):
59
  with open(ANALYTICS_PATH, "w") as f: json.dump({}, f)
60
- def _log_tool_call(t, s): pass
61
- def _log_llm_call(c): pass
62
  _init_analytics()
63
 
64
  # ----------------------------
65
- # FIX: Regex JSON Extractor
66
  # ----------------------------
67
  def extract_json_safely(text: str) -> Optional[Any]:
68
  """
69
  Extracts JSON from text even if the model adds conversational filler.
70
- Fixes the '(Parse) Model output was not valid JSON' error.
71
  """
72
  try:
73
- # 1. Try direct parse
74
  return json.loads(text)
75
  except:
76
  pass
77
 
78
- # 2. Regex search for { ... } or [ ... ]
79
  try:
80
  match = re.search(r'(\{.*\}|\[.*\])', text, re.DOTALL)
81
  if match:
@@ -99,10 +94,8 @@ def init_local_model():
99
  try:
100
  logger.info(f"Loading model: {LOCAL_MODEL}...")
101
  TOKENIZER = AutoTokenizer.from_pretrained(LOCAL_MODEL, trust_remote_code=True)
102
- # Use CPU if needed, or remove device_map="auto" if causing issues
103
  model = AutoModelForCausalLM.from_pretrained(LOCAL_MODEL, trust_remote_code=True, device_map="auto")
104
 
105
- # FIX: Lower max_new_tokens to prevent 400s generation loops
106
  LLM_PIPELINE = pipeline("text-generation", model=model, tokenizer=TOKENIZER)
107
  LOADED_MODEL_NAME = LOCAL_MODEL
108
  logger.info("Model loaded.")
@@ -111,19 +104,26 @@ def init_local_model():
111
 
112
  init_local_model()
113
 
 
114
  def local_llm_generate(prompt: str, max_tokens: int = 512) -> Dict[str, Any]:
115
  if LLM_PIPELINE is None:
116
  return {"text": "LLM not loaded.", "raw": None}
117
  try:
118
- # FIX: return_full_text=False ensures we don't re-parse the prompt
119
- out = LLM_PIPELINE(prompt, max_new_tokens=max_tokens, return_full_text=False)
 
 
 
 
 
120
  text = out[0]["generated_text"] if out else ""
121
  return {"text": text, "raw": out}
122
  except Exception as e:
 
123
  return {"text": f"Error: {e}", "raw": None}
124
 
125
  # ----------------------------
126
- # Helper: normalize local file_path args (Kept intact)
127
  # ----------------------------
128
  def _normalize_local_path_args(args: Any) -> Any:
129
  if not isinstance(args, dict): return args
@@ -133,7 +133,7 @@ def _normalize_local_path_args(args: Any) -> Any:
133
  return args
134
 
135
  # ----------------------------
136
- # Zoho Auth & Tools (Kept intact)
137
  # ----------------------------
138
  def _get_valid_token_headers() -> dict:
139
  token_url = "https://accounts.zoho.in/oauth/v2/token"
@@ -212,18 +212,17 @@ def process_document(file_path: str, target_module: Optional[str] = "Contacts")
212
  if not raw_text or len(raw_text) < 5:
213
  return {"status": "error", "error": "OCR failed to extract text."}
214
 
215
- # 2. Use Prompt Template (Strict Mode)
216
- # FIX: Use prompts.py template + reduce max_tokens for speed
217
  prompt = get_ocr_extraction_prompt(raw_text)
218
 
219
- llm_out = local_llm_generate(prompt, max_tokens=300) # 300 tokens is plenty for JSON
 
220
  extracted_text = llm_out.get("text", "")
221
 
222
- # FIX: Use Regex Safe Extraction
223
  extracted_data = extract_json_safely(extracted_text)
224
 
225
  if not extracted_data:
226
- # Fallback for debugging
227
  extracted_data = {"raw_llm_text": extracted_text}
228
 
229
  return {
@@ -236,10 +235,9 @@ def process_document(file_path: str, target_module: Optional[str] = "Contacts")
236
  return {"status": "error", "error": str(e)}
237
 
238
  # ----------------------------
239
- # Helpers: map LLM args -> Zoho payloads (Kept intact)
240
  # ----------------------------
241
  def _extract_created_id_from_zoho_response(resp_json) -> Optional[str]:
242
- # (Same implementation as before)
243
  try:
244
  if isinstance(resp_json, str): resp_json = json.loads(resp_json)
245
  data = resp_json.get("data") or resp_json.get("result")
@@ -251,17 +249,14 @@ def _extract_created_id_from_zoho_response(resp_json) -> Optional[str]:
251
  return None
252
 
253
  def _map_contact_args_to_zoho_payload(args: dict) -> dict:
254
- # (Same implementation as before - abbreviated for strict structure compliance)
255
  p = {}
256
  if "contact" in args: p["Last_Name"] = args["contact"]
257
  if "email" in args: p["Email"] = args["email"]
258
- # ... map other fields ...
259
  for k,v in args.items():
260
  if k not in ["contact", "email", "items"]: p[k] = v
261
  return p
262
 
263
  def _build_invoice_payload_for_zoho(contact_id: str, invoice_items: List[dict], currency: str = None, vat_pct: float = 0.0) -> dict:
264
- # (Same implementation as before)
265
  line_items = []
266
  for it in invoice_items:
267
  qty = int(it.get("quantity", 1))
@@ -272,16 +267,14 @@ def _build_invoice_payload_for_zoho(contact_id: str, invoice_items: List[dict],
272
  return payload
273
 
274
  # ----------------------------
275
- # Parse & Execute (Kept intact)
276
  # ----------------------------
277
  def parse_and_execute_model_tool_output(model_text: str, history: Optional[List] = None) -> str:
278
- # FIX: Use Safe Extraction first
279
  payload = extract_json_safely(model_text)
280
 
281
  if not payload:
282
  return "(Parse) Model output was not valid JSON tool instruction."
283
 
284
- # Normalize to list
285
  instructions = [payload] if isinstance(payload, dict) else payload
286
  results = []
287
  contact_id = None
@@ -293,13 +286,11 @@ def parse_and_execute_model_tool_output(model_text: str, history: Optional[List]
293
  args = _normalize_local_path_args(args)
294
 
295
  if tool == "create_record":
296
- # ... (logic same as before)
297
  res = create_record(args.get("module", "Contacts"), _map_contact_args_to_zoho_payload(args))
298
  results.append(f"create_record -> {res}")
299
  contact_id = _extract_created_id_from_zoho_response(res)
300
 
301
  elif tool == "create_invoice":
302
- # ... (logic same as before)
303
  if not contact_id: contact_id = args.get("customer_id")
304
  if contact_id:
305
  inv_payload = _build_invoice_payload_for_zoho(contact_id, args.get("line_items", []))
@@ -318,7 +309,6 @@ def parse_and_execute_model_tool_output(model_text: str, history: Optional[List]
318
  # Command Parser (Debug)
319
  # ----------------------------
320
  def try_parse_and_invoke_command(text: str):
321
- # (Same implementation)
322
  if text.startswith("/mnt/data/"): return str(process_document(text))
323
  return None
324
 
@@ -340,24 +330,19 @@ def deepseek_response(message: str, file_path: Optional[str] = None, history: li
340
  else:
341
  return f"Error processing file: {doc_result.get('error')}"
342
 
343
- # 2. Build Prompt (FIX: Use prompts.py)
344
- # Flatten history for the prompt
345
  history_text = "\n".join([f"User: {h[0]}\nBot: {h[1]}" for h in history])
346
  prompt = get_agent_prompt(history_text, ocr_context, message)
347
 
348
- # 3. Generate
349
  gen = local_llm_generate(prompt, max_tokens=256)
350
  response_text = gen["text"]
351
 
352
- # 4. Check for JSON Tool Call (FIX: Use Safe Extraction)
353
  tool_json = extract_json_safely(response_text)
354
 
355
  if tool_json and isinstance(tool_json, (dict, list)):
356
  try:
357
- # We must pass the RAW text or the JSON object?
358
- # Your existing function `parse_and_execute...` expects a string or valid json structure.
359
- # Let's pass the JSON stringified to be safe, or modify the caller.
360
- # The safest way given your strict structure requirement is:
361
  return parse_and_execute_model_tool_output(json.dumps(tool_json), history)
362
  except Exception as e:
363
  return f"(Execute) Error: {e}"
@@ -378,7 +363,6 @@ def chat_handler(message, history):
378
  else:
379
  user_text = str(message)
380
 
381
- # Debug command bypass
382
  if not uploaded_file_path:
383
  cmd = try_parse_and_invoke_command(user_text)
384
  if cmd: return cmd
@@ -386,7 +370,7 @@ def chat_handler(message, history):
386
  return deepseek_response(user_text, uploaded_file_path, history)
387
 
388
  # ----------------------------
389
- # FIX: Cleanup for fd -1 error
390
  # ----------------------------
391
  def cleanup_event_loop():
392
  gc.collect()
 
51
  mcp = FastMCP("ZohoCRMAgent")
52
 
53
  # ----------------------------
54
+ # Analytics
55
  # ----------------------------
56
  ANALYTICS_PATH = "mcp_analytics.json"
57
  def _init_analytics():
58
  if not os.path.exists(ANALYTICS_PATH):
59
  with open(ANALYTICS_PATH, "w") as f: json.dump({}, f)
 
 
60
  _init_analytics()
61
 
62
  # ----------------------------
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:
 
94
  try:
95
  logger.info(f"Loading model: {LOCAL_MODEL}...")
96
  TOKENIZER = AutoTokenizer.from_pretrained(LOCAL_MODEL, trust_remote_code=True)
 
97
  model = AutoModelForCausalLM.from_pretrained(LOCAL_MODEL, trust_remote_code=True, device_map="auto")
98
 
 
99
  LLM_PIPELINE = pipeline("text-generation", model=model, tokenizer=TOKENIZER)
100
  LOADED_MODEL_NAME = LOCAL_MODEL
101
  logger.info("Model loaded.")
 
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}
121
  except Exception as e:
122
+ logger.error(f"Generation Error: {e}")
123
  return {"text": f"Error: {e}", "raw": None}
124
 
125
  # ----------------------------
126
+ # Helper: normalize local file_path args
127
  # ----------------------------
128
  def _normalize_local_path_args(args: Any) -> Any:
129
  if not isinstance(args, dict): return args
 
133
  return args
134
 
135
  # ----------------------------
136
+ # Zoho Auth & Tools
137
  # ----------------------------
138
  def _get_valid_token_headers() -> dict:
139
  token_url = "https://accounts.zoho.in/oauth/v2/token"
 
212
  if not raw_text or len(raw_text) < 5:
213
  return {"status": "error", "error": "OCR failed to extract text."}
214
 
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
 
222
+ # 4. Extract JSON
223
  extracted_data = extract_json_safely(extracted_text)
224
 
225
  if not extracted_data:
 
226
  extracted_data = {"raw_llm_text": extracted_text}
227
 
228
  return {
 
235
  return {"status": "error", "error": str(e)}
236
 
237
  # ----------------------------
238
+ # Helpers: map LLM args -> Zoho payloads
239
  # ----------------------------
240
  def _extract_created_id_from_zoho_response(resp_json) -> Optional[str]:
 
241
  try:
242
  if isinstance(resp_json, str): resp_json = json.loads(resp_json)
243
  data = resp_json.get("data") or resp_json.get("result")
 
249
  return None
250
 
251
  def _map_contact_args_to_zoho_payload(args: dict) -> dict:
 
252
  p = {}
253
  if "contact" in args: p["Last_Name"] = args["contact"]
254
  if "email" in args: p["Email"] = args["email"]
 
255
  for k,v in args.items():
256
  if k not in ["contact", "email", "items"]: p[k] = v
257
  return p
258
 
259
  def _build_invoice_payload_for_zoho(contact_id: str, invoice_items: List[dict], currency: str = None, vat_pct: float = 0.0) -> dict:
 
260
  line_items = []
261
  for it in invoice_items:
262
  qty = int(it.get("quantity", 1))
 
267
  return payload
268
 
269
  # ----------------------------
270
+ # Parse & Execute
271
  # ----------------------------
272
  def parse_and_execute_model_tool_output(model_text: str, history: Optional[List] = None) -> str:
 
273
  payload = extract_json_safely(model_text)
274
 
275
  if not payload:
276
  return "(Parse) Model output was not valid JSON tool instruction."
277
 
 
278
  instructions = [payload] if isinstance(payload, dict) else payload
279
  results = []
280
  contact_id = None
 
286
  args = _normalize_local_path_args(args)
287
 
288
  if tool == "create_record":
 
289
  res = create_record(args.get("module", "Contacts"), _map_contact_args_to_zoho_payload(args))
290
  results.append(f"create_record -> {res}")
291
  contact_id = _extract_created_id_from_zoho_response(res)
292
 
293
  elif tool == "create_invoice":
 
294
  if not contact_id: contact_id = args.get("customer_id")
295
  if contact_id:
296
  inv_payload = _build_invoice_payload_for_zoho(contact_id, args.get("line_items", []))
 
309
  # Command Parser (Debug)
310
  # ----------------------------
311
  def try_parse_and_invoke_command(text: str):
 
312
  if text.startswith("/mnt/data/"): return str(process_document(text))
313
  return None
314
 
 
330
  else:
331
  return f"Error processing file: {doc_result.get('error')}"
332
 
333
+ # 2. Build Prompt
 
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
 
341
+ # 4. Check for JSON Tool Call
342
  tool_json = extract_json_safely(response_text)
343
 
344
  if tool_json and isinstance(tool_json, (dict, list)):
345
  try:
 
 
 
 
346
  return parse_and_execute_model_tool_output(json.dumps(tool_json), history)
347
  except Exception as e:
348
  return f"(Execute) Error: {e}"
 
363
  else:
364
  user_text = str(message)
365
 
 
366
  if not uploaded_file_path:
367
  cmd = try_parse_and_invoke_command(user_text)
368
  if cmd: return cmd
 
370
  return deepseek_response(user_text, uploaded_file_path, history)
371
 
372
  # ----------------------------
373
+ # Cleanup
374
  # ----------------------------
375
  def cleanup_event_loop():
376
  gc.collect()