Phoenix1410 commited on
Commit
f431ac5
·
verified ·
1 Parent(s): ab7a706

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +62 -37
main.py CHANGED
@@ -165,7 +165,7 @@ analyst = ChatGroq(
165
  temperature=0,
166
  model_name=DEFAULT_MODEL,
167
  groq_api_key=GROQ_API_KEY,
168
- request_timeout=30.0,
169
  max_retries=2
170
  )
171
  print(f"[OK] Analyst Model Configured: {DEFAULT_MODEL}", flush=True)
@@ -252,23 +252,29 @@ Task:
252
 
253
  async def generate_dynamic_questions_groq(text: str, max_items: int = 6) -> list[dict]:
254
  """
255
- PRIMARY TRACK: Uses Groq to generate single-subject factual questions.
256
- Returns: [{"id": 0, "question": "...", "party_a_fact": "..."}, ...]
257
  """
258
- system_prompt = """You are a senior forensic legal cross-examiner.
259
- Analyze Party A's testimony. Extract 4 to 6 concrete, single-subject factual assertions.
260
 
261
  STRICT CRITERIA:
262
- 1. Questions MUST focus on single, atomic facts (e.g., exact time/date, specific location, named companion, specific item or physical action).
263
- 2. NEVER ask broad, vague, or summary questions (e.g., do NOT ask "What happened?", "What is the accusation?", "Can you describe the event?").
264
- 3. For each question, extract the EXACT verbatim sentence from Party A's testimony that provides the answer as 'party_a_fact'.
265
- 4. Respond ONLY with a valid JSON object with a root key "queries" containing the list:
 
 
 
 
 
 
266
  {
267
  "queries": [
268
  {
269
  "id": 0,
270
- "question": "What specific time did Party A arrive at the store?",
271
- "party_a_fact": "On March 14, I arrived at the store around 6:15 PM to buy groceries."
272
  }
273
  ]
274
  }"""
@@ -282,22 +288,35 @@ STRICT CRITERIA:
282
  response = await chain.ainvoke({"text": text})
283
  raw_content = response.content.strip()
284
 
 
 
 
 
 
 
 
 
285
  json_match = re.search(r"\{.*\}", raw_content, re.DOTALL)
286
  if json_match:
287
- data = json.loads(json_match.group(0))
288
- items = data.get("queries", []) if isinstance(data, dict) else data
289
- valid_items = []
290
- for idx, item in enumerate(items[:max_items]):
291
- if "question" in item and "party_a_fact" in item:
292
- valid_items.append({
293
- "id": idx,
294
- "question": str(item["question"]).strip(),
295
- "party_a_fact": str(item["party_a_fact"]).strip()
296
- })
297
- if valid_items:
298
- return valid_items
299
-
300
- raise ValueError("Groq returned unparseable JSON for Party A questions.")
 
 
 
 
 
301
 
302
  def generate_dynamic_questions_local_t5(text: str, max_items: int = 5) -> list[dict]:
303
  """
@@ -371,15 +390,21 @@ RULES:
371
  })
372
 
373
  raw_content = response.content.strip()
 
 
 
 
374
  json_match = re.search(r"\{.*\}", raw_content, re.DOTALL)
375
-
376
  results_map = {}
377
  if json_match:
378
- data = json.loads(json_match.group(0))
379
- items = data.get("answers", []) if isinstance(data, dict) else data
380
- for entry in items:
381
- if "id" in entry and "answer" in entry:
382
- results_map[int(entry["id"])] = str(entry["answer"]).strip()
 
 
 
383
 
384
  for item in factual_pairs:
385
  idx = item["id"]
@@ -403,14 +428,14 @@ async def health_check():
403
  diagnostic["key_length"] = len(GROQ_API_KEY) if GROQ_API_KEY else 0
404
  diagnostic["key_format_valid"] = (GROQ_API_KEY.startswith("gsk_") and len(GROQ_API_KEY) > 20) if GROQ_API_KEY else False
405
 
406
- # 2. DNS Check (Can container resolve api.groq.com?)
407
  try:
408
  ip = socket.gethostbyname("api.groq.com")
409
  diagnostic["dns_resolution"] = f"SUCCESS ({ip})"
410
  except Exception as dns_err:
411
  diagnostic["dns_resolution"] = f"FAILED: {str(dns_err)}"
412
 
413
- # 3. Direct HTTPS Ping (Tests whether Groq accepts this exact key without LangChain)
414
  if GROQ_API_KEY:
415
  try:
416
  req = urllib.request.Request(
@@ -543,10 +568,10 @@ async def stream_compare_testimonies(
543
  ):
544
  """
545
  Symmetric Streaming Testimony Validator (SSE).
546
- 1. Primary: Groq isolates single-subject questions and pairs with Party A's facts.
547
- 2. Fast Fallback: Local T5 (greedy) executes in ~1.5s if Groq is unavailable.
548
  3. Cross-examines Party B on identical queries using integer indexes.
549
- 4. Streams real-time events without proxy buffering.
550
  """
551
  async def resolve_input(file: UploadFile, text: str) -> str:
552
  if file and file.filename:
@@ -572,7 +597,7 @@ async def stream_compare_testimonies(
572
  try:
573
  factual_pairs = await generate_dynamic_questions_groq(c_content, max_items=6)
574
  mode_label = "Groq LPU"
575
- print(f"[SUCCESS] Groq generated {len(factual_pairs)} questions.", flush=True)
576
  except Exception as qg_err:
577
  print(f"[NOTICE] Groq QG failed ({qg_err}). Engaging Fast T5 Fallback...", flush=True)
578
  factual_pairs = generate_dynamic_questions_local_t5(c_content, max_items=5)
 
165
  temperature=0,
166
  model_name=DEFAULT_MODEL,
167
  groq_api_key=GROQ_API_KEY,
168
+ request_timeout=35.0,
169
  max_retries=2
170
  )
171
  print(f"[OK] Analyst Model Configured: {DEFAULT_MODEL}", flush=True)
 
252
 
253
  async def generate_dynamic_questions_groq(text: str, max_items: int = 6) -> list[dict]:
254
  """
255
+ Extracts OBJECTIVE, THIRD-PERSON factual questions from Party A's text.
256
+ Focuses strictly on observable shared facts: Times, Doors, Aisles, Exits, Parking.
257
  """
258
+ system_prompt = """You are a senior forensic legal interrogator.
259
+ Analyze Party A's testimony. Extract 4 to 6 OBJECTIVE, THIRD-PERSON factual assertions.
260
 
261
  STRICT CRITERIA:
262
+ 1. NEVER write questions in the first person ("I", "me", "my bag", "my wallet").
263
+ Always refer to the parties as "the complainant", "Party A", or "the accused".
264
+ 2. Focus ONLY on observable, external facts that both parties or security could witness:
265
+ - Arrival time and entrance door used
266
+ - In-store location, movements, and observations of the other party (e.g., electronics aisle, pharmacy, registers)
267
+ - Departure time and exit door used
268
+ - Parking lot location
269
+ 3. NEVER ask about private internal actions (e.g., do NOT ask about checking phone prices or private thoughts).
270
+ 4. For each question, extract the EXACT verbatim sentence from Party A's testimony answering it as 'party_a_fact'.
271
+ 5. Respond ONLY with a valid JSON object matching this structure:
272
  {
273
  "queries": [
274
  {
275
  "id": 0,
276
+ "question": "At what time and through which door did the accused allegedly exit the store?",
277
+ "party_a_fact": "Security reviewed footage and pointed me toward the accused, who had exited through the side door near the pharmacy at approximately 6:35 PM."
278
  }
279
  ]
280
  }"""
 
288
  response = await chain.ainvoke({"text": text})
289
  raw_content = response.content.strip()
290
 
291
+ # 1. Clean reasoning tags (<think>...</think>) if present
292
+ raw_content = re.sub(r"<think>.*?</think>", "", raw_content, flags=re.DOTALL).strip()
293
+
294
+ # 2. Clean markdown code block markers
295
+ if "```" in raw_content:
296
+ raw_content = re.sub(r"```(?:json)?", "", raw_content).replace("```", "").strip()
297
+
298
+ # 3. Extract JSON object
299
  json_match = re.search(r"\{.*\}", raw_content, re.DOTALL)
300
  if json_match:
301
+ try:
302
+ data = json.loads(json_match.group(0))
303
+ items = data.get("queries", []) if isinstance(data, dict) else data
304
+ valid_items = []
305
+ for idx, item in enumerate(items[:max_items]):
306
+ q_text = str(item.get("question", "")).strip()
307
+ fact_text = str(item.get("party_a_fact", "")).strip()
308
+ if q_text and fact_text:
309
+ valid_items.append({
310
+ "id": idx,
311
+ "question": q_text,
312
+ "party_a_fact": fact_text
313
+ })
314
+ if valid_items:
315
+ return valid_items
316
+ except Exception as parse_err:
317
+ print(f"[PARSER ERROR] {parse_err} | Raw: {raw_content[:150]}", flush=True)
318
+
319
+ raise ValueError(f"Groq output could not be parsed: {raw_content[:150]}")
320
 
321
  def generate_dynamic_questions_local_t5(text: str, max_items: int = 5) -> list[dict]:
322
  """
 
390
  })
391
 
392
  raw_content = response.content.strip()
393
+ raw_content = re.sub(r"<think>.*?</think>", "", raw_content, flags=re.DOTALL).strip()
394
+ if "```" in raw_content:
395
+ raw_content = re.sub(r"```(?:json)?", "", raw_content).replace("```", "").strip()
396
+
397
  json_match = re.search(r"\{.*\}", raw_content, re.DOTALL)
 
398
  results_map = {}
399
  if json_match:
400
+ try:
401
+ data = json.loads(json_match.group(0))
402
+ items = data.get("answers", []) if isinstance(data, dict) else data
403
+ for entry in items:
404
+ if "id" in entry and "answer" in entry:
405
+ results_map[int(entry["id"])] = str(entry["answer"]).strip()
406
+ except Exception as e:
407
+ print(f"[PARTY B PARSE WARNING] {e}", flush=True)
408
 
409
  for item in factual_pairs:
410
  idx = item["id"]
 
428
  diagnostic["key_length"] = len(GROQ_API_KEY) if GROQ_API_KEY else 0
429
  diagnostic["key_format_valid"] = (GROQ_API_KEY.startswith("gsk_") and len(GROQ_API_KEY) > 20) if GROQ_API_KEY else False
430
 
431
+ # 2. DNS Check
432
  try:
433
  ip = socket.gethostbyname("api.groq.com")
434
  diagnostic["dns_resolution"] = f"SUCCESS ({ip})"
435
  except Exception as dns_err:
436
  diagnostic["dns_resolution"] = f"FAILED: {str(dns_err)}"
437
 
438
+ # 3. Direct HTTPS Ping
439
  if GROQ_API_KEY:
440
  try:
441
  req = urllib.request.Request(
 
568
  ):
569
  """
570
  Symmetric Streaming Testimony Validator (SSE).
571
+ 1. Primary: Groq dynamically isolates single-subject, third-person questions.
572
+ 2. Fast Fallback: Local T5 takes over in ~1.5s if Groq is unavailable.
573
  3. Cross-examines Party B on identical queries using integer indexes.
574
+ 4. Anti-buffering headers flush SSE chunks to Vercel instantly.
575
  """
576
  async def resolve_input(file: UploadFile, text: str) -> str:
577
  if file and file.filename:
 
597
  try:
598
  factual_pairs = await generate_dynamic_questions_groq(c_content, max_items=6)
599
  mode_label = "Groq LPU"
600
+ print(f"[SUCCESS] Groq generated {len(factual_pairs)} objective questions.", flush=True)
601
  except Exception as qg_err:
602
  print(f"[NOTICE] Groq QG failed ({qg_err}). Engaging Fast T5 Fallback...", flush=True)
603
  factual_pairs = generate_dynamic_questions_local_t5(c_content, max_items=5)