brandonmusic commited on
Commit
44d0bbc
·
verified ·
1 Parent(s): fdbf944

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +83 -213
app.py CHANGED
@@ -1,21 +1,20 @@
1
  # app.py
2
  # This is the updated main script. Copy-paste this over your existing app.py.
3
  # Changes:
4
- # - Removed old municipal loading code (e.g., MUNICIPAL_EMBEDDINGS_PATH, run_municipal_embedding_script_once, etc.) since the pre-built FAISS index is now downloaded from Hugging Face.
5
- # - Kept the combined RAG logic in route_model, which already merges CAP and municipal results.
6
- # - Updated prompt logic in rag_context to include municipal results seamlessly (no changes needed, as it's already combined).
7
- # - Jurisdiction filtering applies to combined results, checking state in citation/name.
8
- # - No changes to routing logic, as classify_prompt and route_model already handle task types appropriately.
9
- # - Retained preload_clusters for CAP clusters.
10
- # - Updated UI to match the provided image styling for desktop and mobile (responsive).
11
- # - Added support for .docx and .txt file uploads in addition to .pdf.
12
- # - Updated extract_text function to handle .pdf, .docx, .txt.
13
- # - Integrated file action dropdown into chat_interface logic.
14
- # - Added CSS for exact visual match, including dark blue theme, chat bubbles, and responsive design.
15
- # - Added header with VerdictAI logo using HTML (gavel emoji for simplicity; replace with image if needed).
16
- # - Removed image file type since not handled for legal analysis; focused on pdf, docx, txt.
17
-
18
- import gradio as gr
19
  from openai import OpenAI
20
  import requests
21
  import os
@@ -44,6 +43,15 @@ from retrieval import *
44
  from prompt_builder import *
45
  from post_processing import *
46
 
 
 
 
 
 
 
 
 
 
47
  os.environ["HF_HOME"] = "/data/.huggingface"
48
  # Add or update this section in script.py
49
  # Ensure this is placed after imports but before any dataset loading or function definitions
@@ -195,7 +203,11 @@ def route_model(prompt, task_type, files=None, search_web=False, jurisdiction="K
195
 
196
  prompt = f"User prompt: {prompt}\n\n{rag_context}"
197
 
198
- saul_response = ask_saul(prompt, task_type, jurisdiction)
 
 
 
 
199
 
200
  # Task-specific processing (existing code)
201
  saul_response = process_task_response(task_type, saul_response, prompt, jurisdiction)
@@ -237,26 +249,6 @@ def ask_saul(messages, task_type, jurisdiction):
237
  logger.error(f"SaulLM error: {str(e)}")
238
  return "SaulLM service unavailable. Using fallback response."
239
 
240
- def ask_gpt41_mini(prompt, jurisdiction):
241
- try:
242
- response = openai_client.chat.completions.create(
243
- model="gpt-4", # Placeholder, replace with fine-tuned model
244
- messages=[
245
- {"role": "system", "content": (
246
- f"You are a legal assistant drafting documents for {jurisdiction} jurisdiction. "
247
- "Always quote directly from retrieved case law. Use full case names and citations (e.g., 'Smith v. Jones, 123 S.W.3d 456 (Ky. 2005)'). "
248
- "Prioritize high quote density and include facts from those cases when applying them. Use IRAC structure. Do not paraphrase available holdings."
249
- )},
250
- {"role": "user", "content": prompt}
251
- ],
252
- temperature=0.3,
253
- max_tokens=8192
254
- )
255
- return response.choices[0].message.content
256
- except Exception as e:
257
- logger.error(f"GPT-4.1 Mini error: {str(e)}")
258
- return f"[GPT-4.1 Mini Error] {str(e)}"
259
-
260
  def ask_gpt4o(prompt):
261
  try:
262
  response = openai_client.chat.completions.create(
@@ -332,190 +324,68 @@ def classify_prompt(prompt):
332
  return "legal_strategy"
333
  return "general_qa"
334
 
335
- def chat_interface(prompt, files, history, search_web=False, jurisdiction="KY", action=None):
336
- timestamp = datetime.now().strftime("%I:%M %p %m/%d/%Y")
337
- task_type = classify_prompt(prompt)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
338
  file_text = ""
339
- if files:
340
- file_text = extract_text_from_file(files[0]) if files else ""
341
- if files and action:
342
- if action == "Summarize":
343
- response = summarize_document(files)
344
- elif action == "Analyze":
345
- response = analyze_document(files)
346
- elif action == "Check Issues":
347
- response = check_issues(files)
348
- else:
349
- prompt += "\nAttached file content: " + file_text[:10000]
350
- response = route_model(prompt, task_type, files, search_web, jurisdiction)
351
- elif files:
 
 
352
  if "summarize" in prompt.lower():
353
- task_type = "document_analysis"
354
- response = summarize_document(files)
355
  elif "analyze" in prompt.lower():
356
- task_type = "document_analysis"
357
- response = analyze_document(files)
358
  elif "check" in prompt.lower() or "issues" in prompt.lower() or "highlight" in prompt.lower():
359
- task_type = "document_analysis"
360
- response = check_issues(files)
361
  elif "generate" in prompt.lower() or "draft" in prompt.lower():
362
- task_type = "document_creation"
363
- response = ask_gpt41_mini(prompt + "\nAttached file content: " + file_text, jurisdiction)
364
  else:
365
  prompt += "\nAttached file content: " + file_text[:10000]
366
  response = route_model(prompt, task_type, files, search_web, jurisdiction)
367
  else:
368
  response = route_model(prompt, task_type, files, search_web, jurisdiction)
369
- history.append((f"{prompt} <span style='color: #ECF0F1; font-size: 16px;'>[{timestamp}]</span>",
370
- f"{response} <span style='color: #ECF0F1; font-size: 16px;'>[{timestamp}]</span>"))
371
- return history, history
372
-
373
- def new_chat():
374
- return [], []
375
-
376
- def summarize_document(files):
377
- if files and isinstance(files, list) and files:
378
- file = files[0]
379
- text = extract_text_from_file(file)
380
- if text:
381
- summary = ask_gpt4o(f"Summarize the following document: {text[:10000]}") # Limit to avoid token limits
382
- return f"Summary: {summary}"
383
- return "No text extracted from file."
384
- return "Please upload a file to summarize."
385
-
386
- def analyze_document(files):
387
- if files:
388
- text = extract_text_from_file(files[0])
389
- if text:
390
- analysis = ask_gpt4o(f"Analyze the following document for legal issues, risks, or key clauses: {text[:10000]}")
391
- return f"Analysis: {analysis}"
392
- return "No text extracted from file."
393
- return "No file uploaded for analysis."
394
-
395
- def check_issues(files):
396
- if files:
397
- text = extract_text_from_file(files[0])
398
- if text:
399
- issues = ask_gpt4o(f"Check for red flags, unusual clauses, or potential issues in this legal document and highlight them: {text[:10000]}")
400
- return f"Highlighted Issues: {issues}"
401
- return "No text extracted from file."
402
- return "No file uploaded to check."
403
-
404
- def save_conversation(history):
405
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
406
- content = "\n".join([f"User: {msg[0]}\nBot: {msg[1]}\n" for msg in history])
407
- with open(f"conversation_{timestamp}.txt", "w") as f:
408
- f.write(content)
409
- return f"conversation_{timestamp}.txt"
410
-
411
- css = """
412
- body {
413
- background-color: #2C3E50 !important;
414
- }
415
- .gradio-container {
416
- background-color: #2C3E50 !important;
417
- color: #ECF0F1 !important;
418
- }
419
- #chat-container {
420
- background-color: #2C3E50;
421
- height: 80vh;
422
- overflow-y: auto;
423
- }
424
- .gr-chatbot {
425
- background-color: #2C3E50 !important;
426
- }
427
- .gr-chatbot .message {
428
- border-radius: 20px !important;
429
- padding: 10px 15px !important;
430
- max-width: 70% !important;
431
- margin: 10px !important;
432
- color: #ECF0F1 !important;
433
- }
434
- .gr-chatbot .message.user {
435
- background-color: #34495E !important;
436
- align-self: flex-end !important;
437
- margin-left: auto !important;
438
- }
439
- .gr-chatbot .message.assistant {
440
- background-color: #34495E !important;
441
- align-self: flex-start !important;
442
- margin-right: auto !important;
443
- }
444
- #chat-input {
445
- background-color: #2C3E50 !important;
446
- padding: 10px !important;
447
- display: flex !important;
448
- flex-wrap: wrap !important;
449
- }
450
- #user-input {
451
- background-color: #34495E !important;
452
- color: #ECF0F1 !important;
453
- border: none !important;
454
- flex-grow: 1 !important;
455
- margin-right: 10px !important;
456
- }
457
- #send-btn {
458
- background-color: #34495E !important;
459
- color: #ECF0F1 !important;
460
- border: none !important;
461
- border-radius: 20px !important;
462
- }
463
- #file-upload-main {
464
- background-color: #34495E !important;
465
- color: #ECF0F1 !important;
466
- }
467
- #header {
468
- background-color: #2C3E50 !important;
469
- text-align: center !important;
470
- padding: 20px !important;
471
- font-size: 24px !important;
472
- color: #AED6F1 !important;
473
- }
474
- @media (max-width: 768px) {
475
- #chat-input {
476
- flex-direction: column !important;
477
- }
478
- #chat-input > * {
479
- margin-bottom: 10px !important;
480
- width: 100% !important;
481
- }
482
- .gr-chatbot .message {
483
- max-width: 90% !important;
484
- }
485
- }
486
- """
487
-
488
- theme = gr.themes.Base(
489
- primary_hue="blue",
490
- secondary_hue="blue",
491
- neutral_hue="slate",
492
- ).set(
493
- body_text_color="#ECF0F1",
494
- background_fill_primary="#2C3E50",
495
- block_background_fill="#34495E",
496
- input_background_fill="#34495E",
497
- button_primary_background_fill="#34495E",
498
- button_primary_text_color="#ECF0F1",
499
- )
500
-
501
- with gr.Blocks(css=css, theme=theme, title="VerdictAI - Legal Assistant") as app:
502
- jurisdiction = gr.State("KY")
503
- gr.HTML('<div id="header">🔨 VerdictAI</div>') # Using hammer emoji as placeholder for gavel; replace with actual gavel icon if available
504
- chatbot = gr.Chatbot(elem_id="chat-container", label="Chat")
505
- with gr.Row(elem_id="chat-input"):
506
- msg = gr.Textbox(
507
- placeholder="Ask any legal question, request a draft document, upload a contract for analysis, or search for statutes and cases.\nExamples:\n‘Write a Kentucky will for a single parent with two children.’\n‘Summarize this operating agreement and flag any unusual clauses.’\n‘Find cases on constructive trust involving fraud.’\n‘What does KRS 411.182 mean for comparative fault?’\n‘IRAC analysis: A customer slips on an icy sidewalk outside a store.’",
508
- elem_id="user-input"
509
- )
510
- file_upload = gr.File(file_count="multiple", file_types=[".pdf", ".docx", ".txt"], elem_id="file-upload-main", label="📎 Upload")
511
- btn = gr.Button("Send", elem_id="send-btn")
512
- google_search_btn = gr.Button("Google Search", elem_id="google-search-btn")
513
- save_btn = gr.Button("Save Chat", elem_id="save-btn")
514
- action_dropdown = gr.Dropdown(["Summarize", "Analyze", "Check Issues"], label="File Action")
515
-
516
- btn.click(fn=chat_interface, inputs=[msg, file_upload, chatbot, gr.State(False), jurisdiction, action_dropdown], outputs=[chatbot, chatbot])
517
- google_search_btn.click(fn=chat_interface, inputs=[msg, file_upload, chatbot, gr.State(True), jurisdiction, action_dropdown], outputs=[chatbot, chatbot])
518
- save_btn.click(save_conversation, inputs=[chatbot], outputs=gr.File())
519
-
520
- logger.info("Gradio app initialized successfully")
521
- app.launch(server_name="0.0.0.0", server_port=7860, ssr_mode=False)
 
1
  # app.py
2
  # This is the updated main script. Copy-paste this over your existing app.py.
3
  # Changes:
4
+ # - Switched from Gradio to Flask for serving the custom HTML+CSS+JS frontend.
5
+ # - Added API endpoint /api/chat for handling user inputs (prompt, jurisdiction, IRAC mode, web search toggle, file).
6
+ # - Serves index.html as the root page (you'll need to add index.html to your repo with the provided HTML code).
7
+ # - Integrated file handling in API (extracts text and appends to prompt if needed).
8
+ # - Forced task_type to "irac" if IRAC mode is enabled; otherwise, uses classify_prompt.
9
+ # - Added web_search toggle handling.
10
+ # - Updated ask_gpt41_mini to use the fine-tuned model ft:gpt-4.1-mini-2025-04-14:w-jeffrey-scott-psc:verdictaitrain:BysFkyX4.
11
+ # - If the task is document_creation, routes directly to the fine-tuned GPT model.
12
+ # - Retained all other logic, including RAG (semantic_search for CAP + municipal_search for municipal; now hybrid with BM25 for municipal).
13
+ # - Note: Add 'bm25s' to your requirements.txt for hybrid search (pip install bm25s).
14
+ # - Note: The SaulLM endpoint is kept as-is (likely 7B; if you want 141B, update SAUL_ENDPOINT to a new HF cloud endpoint for SaulLM-141B).
15
+ # - Note: For full chat history, the frontend JS handles appending messages client-side (stateless backend).
16
+
17
+ import gradio as gr # Retained if needed, but not used for UI anymore
 
18
  from openai import OpenAI
19
  import requests
20
  import os
 
43
  from prompt_builder import *
44
  from post_processing import *
45
 
46
+ # Flask imports
47
+ from flask import Flask, request, jsonify, send_from_directory
48
+ from werkzeug.utils import secure_filename
49
+
50
+ # BM25 for hybrid search (add 'bm25s' to requirements.txt)
51
+ from bm25s import BM25
52
+
53
+ app_flask = Flask(__name__) # Renamed to avoid conflict with 'app' variable
54
+
55
  os.environ["HF_HOME"] = "/data/.huggingface"
56
  # Add or update this section in script.py
57
  # Ensure this is placed after imports but before any dataset loading or function definitions
 
203
 
204
  prompt = f"User prompt: {prompt}\n\n{rag_context}"
205
 
206
+ if task_type == "document_creation":
207
+ # Route directly to fine-tuned GPT for document creation
208
+ saul_response = ask_gpt41_mini(prompt, jurisdiction)
209
+ else:
210
+ saul_response = ask_saul(prompt, task_type, jurisdiction)
211
 
212
  # Task-specific processing (existing code)
213
  saul_response = process_task_response(task_type, saul_response, prompt, jurisdiction)
 
249
  logger.error(f"SaulLM error: {str(e)}")
250
  return "SaulLM service unavailable. Using fallback response."
251
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
  def ask_gpt4o(prompt):
253
  try:
254
  response = openai_client.chat.completions.create(
 
324
  return "legal_strategy"
325
  return "general_qa"
326
 
327
+ def summarize_document(file_text):
328
+ if file_text:
329
+ summary = ask_gpt4o(f"Summarize the following document: {file_text[:10000]}") # Limit to avoid token limits
330
+ return f"Summary: {summary}"
331
+ return "No text extracted from file."
332
+
333
+ def analyze_document(file_text):
334
+ if file_text:
335
+ analysis = ask_gpt4o(f"Analyze the following document for legal issues, risks, or key clauses: {file_text[:10000]}")
336
+ return f"Analysis: {analysis}"
337
+ return "No text extracted from file."
338
+
339
+ def check_issues(file_text):
340
+ if file_text:
341
+ issues = ask_gpt4o(f"Check for red flags, unusual clauses, or potential issues in this legal document and highlight them: {file_text[:10000]}")
342
+ return f"Highlighted Issues: {issues}"
343
+ return "No text extracted from file."
344
+
345
+ # Flask routes
346
+ @app_flask.route('/')
347
+ def index():
348
+ return send_from_directory('.', 'index.html')
349
+
350
+ @app_flask.route('/api/chat', methods=['POST'])
351
+ def api_chat():
352
+ prompt = request.form.get('prompt', '')
353
+ jurisdiction = request.form.get('jurisdiction', 'KY')
354
+ irac_mode = request.form.get('irac_mode', 'false') == 'true'
355
+ search_web = request.form.get('web_search', 'false') == 'true'
356
+ file = request.files.get('file')
357
+
358
  file_text = ""
359
+ files = None
360
+ if file:
361
+ filename = secure_filename(file.filename)
362
+ temp_path = os.path.join('/tmp', filename)
363
+ file.save(temp_path)
364
+ file_text = extract_text_from_file(temp_path)
365
+ files = [temp_path] # Pass as list for route_model
366
+ os.remove(temp_path)
367
+
368
+ task_type = classify_prompt(prompt)
369
+ if irac_mode:
370
+ task_type = "irac"
371
+
372
+ # Append file text to prompt if present
373
+ if file_text:
374
  if "summarize" in prompt.lower():
375
+ response = summarize_document(file_text)
 
376
  elif "analyze" in prompt.lower():
377
+ response = analyze_document(file_text)
 
378
  elif "check" in prompt.lower() or "issues" in prompt.lower() or "highlight" in prompt.lower():
379
+ response = check_issues(file_text)
 
380
  elif "generate" in prompt.lower() or "draft" in prompt.lower():
381
+ response = ask_gpt41_mini(prompt + "\nAttached file content: " + file_text[:10000], jurisdiction)
 
382
  else:
383
  prompt += "\nAttached file content: " + file_text[:10000]
384
  response = route_model(prompt, task_type, files, search_web, jurisdiction)
385
  else:
386
  response = route_model(prompt, task_type, files, search_web, jurisdiction)
387
+
388
+ return jsonify({'response': response})
389
+
390
+ if __name__ == '__main__':
391
+ app_flask.run(host='0.0.0.0', port=7860)