Spaces:
Sleeping
Sleeping
Update main.py
Browse files
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=
|
| 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 |
-
|
| 256 |
-
|
| 257 |
"""
|
| 258 |
-
system_prompt = """You are a senior forensic legal
|
| 259 |
-
Analyze Party A's testimony. Extract 4 to 6
|
| 260 |
|
| 261 |
STRICT CRITERIA:
|
| 262 |
-
1.
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 266 |
{
|
| 267 |
"queries": [
|
| 268 |
{
|
| 269 |
"id": 0,
|
| 270 |
-
"question": "
|
| 271 |
-
"party_a_fact": "
|
| 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 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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
|
| 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
|
| 547 |
-
2. Fast Fallback: Local T5
|
| 548 |
3. Cross-examines Party B on identical queries using integer indexes.
|
| 549 |
-
4.
|
| 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)
|