Rajan Sharma commited on
Commit
c04b182
·
verified ·
1 Parent(s): ddf056f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +231 -0
app.py CHANGED
@@ -11,6 +11,237 @@ import pandas as pd
11
  from datetime import datetime
12
  import regex as re2
13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  # --- BACKEND IMPORTS ---
15
  from langchain_cohere import ChatCohere
16
 
 
11
  from datetime import datetime
12
  import regex as re2
13
 
14
+ # --- THE FINAL FIX (PART 1): Make sure 're' is imported ---
15
+ import re
16
+
17
+ # --- BACKEND IMPORTS ---
18
+ from langchain_cohere import ChatCohere
19
+
20
+ # --- LOCAL MODULE IMPORTS ---
21
+ from settings import (
22
+ HEALTHCARE_SETTINGS, GENERAL_CONVERSATION_PROMPT,
23
+ COHERE_MODEL_PRIMARY, COHERE_TIMEOUT_S, USE_OPEN_FALLBACKS
24
+ )
25
+ from audit_log import log_event
26
+ from privacy import safety_filter, refusal_reply
27
+ from llm_router import cohere_chat, _co_client, cohere_embed
28
+
29
+ # --- UTILITY FUNCTIONS ---
30
+
31
+ def load_markdown_text(filepath: str) -> str:
32
+ """Safely loads text content from a markdown file."""
33
+ try:
34
+ with open(filepath, 'r', encoding='utf-8') as f:
35
+ return f.read()
36
+ except FileNotFoundError:
37
+ return f"**Error:** The document `{os.path.basename(filepath)}` was not found."
38
+
39
+ def _sanitize_text(s: str) -> str:
40
+ if not isinstance(s, str): return s
41
+ return re2.sub(r'[\p{C}--[\n\t]]+', '', s)
42
+
43
+ def _create_python_script(user_scenario: str, schema_context: str) -> str:
44
+ """Uses an LLM to act as an "AI Coder", writing a complete Python script."""
45
+ prompt_for_coder = f"""
46
+ You are an expert Python data scientist. Your sole job is to write a single, complete, and executable Python script to answer the user's request.
47
+ You have access to a list of pandas dataframes loaded into a variable named `dfs`.
48
+
49
+ --- DATA SCHEMA ---
50
+ {schema_context}
51
+ --- END SCHEMA ---
52
+
53
+ CRITICAL RULES FOR YOUR SCRIPT:
54
+ 1. **ROBUST STRING CLEANING:** When you extract a SINGLE string value from a dataframe (e.g., using `.loc` or `.iloc`), you MUST clean it using the standard `re.sub()` function before converting it to a number. DO NOT use pandas' `.str` accessor on single strings, as it will cause a fatal `AttributeError`. For example: `my_string = health_indicators.loc[0, 'Value']` -> `cleaned_string = re.sub(r'[^0-9.-]', '', my_string)` -> `my_float = float(cleaned_string)`.
55
+ 2. **CHECK COLUMN NAMES:** You MUST use the exact, case-sensitive column names provided in the DATA SCHEMA. A `KeyError` will cause a failure.
56
+ 3. **PRINT FINDINGS:** Use the `print()` function at each step to output your results as a formatted report.
57
+
58
+ --- USER'S SCENARIO ---
59
+ {user_scenario}
60
+
61
+ --- PYTHON SCRIPT ---
62
+ Now, write the complete Python script to be executed. The script MUST start with `import pandas as pd` and `import re`.
63
+ ```python
64
+ """
65
+ generated_text = cohere_chat(prompt_for_coder)
66
+ match = re2.search(r"```python\n(.*?)```", generated_text, re2.DOTALL)
67
+ if match:
68
+ return match.group(1).strip()
69
+ else:
70
+ return "print('Error: The AI failed to generate a valid Python script.')"
71
+
72
+ def _append_msg(history_messages: List[Dict[str, str]], role: str, content: str) -> List[Dict[str, str]]:
73
+ return (history_messages or []) + [{"role": role, "content": content}]
74
+
75
+ def ping_cohere() -> str:
76
+ """Lightweight health check against Cohere."""
77
+ try:
78
+ cli = _co_client()
79
+ if not cli: return "Cohere client not initialized."
80
+ vecs = cohere_embed(["hello", "world"])
81
+ return f"Cohere OK ✅ (model={COHERE_MODEL_PRIMARY})" if vecs else "Cohere reachable."
82
+ except Exception as e:
83
+ return f"Cohere ping failed: {e}"
84
+
85
+ # --- THE CORE ANALYSIS ENGINE ---
86
+
87
+ def handle(user_msg: str, files: list) -> str:
88
+ """This is the powerful backend engine using the "Coder" pattern."""
89
+ try:
90
+ safe_in, blocked_in, reason_in = safety_filter(user_msg, mode="input")
91
+ if blocked_in: return refusal_reply(reason_in)
92
+
93
+ file_paths: List[str] = [getattr(f, "name", None) or f for f in (files or [])]
94
+
95
+ if file_paths:
96
+ dataframes = []
97
+ schema_parts = []
98
+ for i, p in enumerate(file_paths):
99
+ if p.endswith('.csv'):
100
+ try: df = pd.read_csv(p)
101
+ except UnicodeDecodeError: df = pd.read_csv(p, encoding='latin1')
102
+ dataframes.append(df)
103
+ schema_parts.append(f"DataFrame `dfs[{i}]` (from `{os.path.basename(p)}`):\n{df.head().to_markdown()}\n")
104
+
105
+ if not dataframes: return "Please upload at least one CSV file."
106
+
107
+ schema_context = "\n".join(schema_parts)
108
+ analysis_script = _create_python_script(safe_in, schema_context)
109
+
110
+ # --- THE FINAL FIX (PART 2): Give the script access to the 're' module ---
111
+ execution_namespace = {
112
+ "dfs": dataframes,
113
+ "pd": pd,
114
+ "re": re # <-- This line gives the script the tool it needs
115
+ }
116
+ output_buffer = io.StringIO()
117
+
118
+ try:
119
+ with redirect_stdout(output_buffer):
120
+ exec(analysis_script, execution_namespace)
121
+
122
+ result = output_buffer.getvalue()
123
+ return _sanitize_text(result or "(The analysis script ran but produced no output.)")
124
+ except Exception as e:
125
+ return f"An error occurred executing the script: {e}\n\nGenerated Script:\n```python\n{analysis_script}\n```"
126
+ else:
127
+ prompt = f"{GENERAL_CONVERSATION_PROMPT}\n\nUser: {safe_in}\nAssistant:"
128
+ return _sanitize_text(cohere_chat(prompt) or "How can I help further?")
129
+
130
+ except Exception as e:
131
+ tb = traceback.format_exc()
132
+ log_event("app_error", None, {"err": str(e), "tb": tb})
133
+ return f"A critical error occurred: {e}"
134
+
135
+ # --- PRE-LOAD LEGAL DOCUMENTS ---
136
+ PRIVACY_POLICY_TEXT = load_markdown_text("privacy_policy.md")
137
+ TERMS_OF_SERVICE_TEXT = load_markdown_text("terms_of_service.md")
138
+
139
+ # ---------------- THE PROFESSIONAL UI WITH INTEGRATED LEGAL DOCS ----------------
140
+ with gr.Blocks(theme="soft", css="style.css") as demo:
141
+ assessment_history = gr.State([])
142
+
143
+ with gr.Group(visible=False) as privacy_modal:
144
+ with gr.Blocks():
145
+ gr.Markdown(PRIVACY_POLICY_TEXT)
146
+ close_privacy_btn = gr.Button("Close")
147
+
148
+ with gr.Group(visible=False) as terms_modal:
149
+ with gr.Blocks():
150
+ gr.Markdown(TERMS_OF_SERVICE_TEXT)
151
+ close_terms_btn = gr.Button("Close")
152
+
153
+ gr.Markdown("# Universal AI Data Analyst")
154
+ with gr.Row(variant="panel"):
155
+ with gr.Column(scale=1):
156
+ gr.Markdown("## New Assessment")
157
+ gr.Markdown("<p style='font-size:0.9rem; color: #6C757D;'>Upload CSV files for data analysis, or just enter a prompt to chat with the AI.</p>")
158
+ files_input = gr.Files(label="Upload Data Files (.csv)", file_count="multiple", type="filepath", file_types=[".csv"])
159
+ prompt_input = gr.Textbox(label="Prompt", placeholder="Paste your scenario or question here.", lines=15)
160
+ with gr.Row():
161
+ send_btn = gr.Button("▶️ Send / Run Analysis", variant="primary", scale=2)
162
+ clear_btn = gr.Button("🗑️ Clear")
163
+ ping_btn = gr.Button("Ping Cohere")
164
+ ping_out = gr.Markdown()
165
+
166
+ with gr.Column(scale=2):
167
+ with gr.Tabs():
168
+ with gr.TabItem("Current Assessment", id=0):
169
+ chat_history_output = gr.Chatbot(label="Analysis Output", type="messages", height=600)
170
+ with gr.TabItem("Assessment History", id=1):
171
+ gr.Markdown("## Review Past Assessments")
172
+ history_dropdown = gr.Dropdown(label="Select an assessment to review", choices=[])
173
+ history_display = gr.Markdown(label="Selected Assessment Details")
174
+ with gr.Row(): gr.Markdown("---")
175
+ with gr.Row():
176
+ privacy_link = gr.Button("Privacy Policy", variant="link")
177
+ terms_link = gr.Button("Terms of Service", variant="link")
178
+
179
+ def run_analysis_wrapper(prompt, files, chat_history_list, history_state_list):
180
+ if not prompt:
181
+ gr.Warning("Please enter a prompt.")
182
+ yield chat_history_list, history_state_list, gr.update()
183
+ return
184
+
185
+ chat_with_user_msg = _append_msg(chat_history_list, "user", prompt)
186
+ thinking_message = _append_msg(chat_with_user_msg, "assistant", "```\n🧠 Generating and executing analysis script... This may take a moment.\n```")
187
+ yield thinking_message, history_state_list, gr.update()
188
+
189
+ ai_response_text = handle(prompt, files)
190
+
191
+ final_chat = _append_msg(chat_with_user_msg, "assistant", ai_response_text)
192
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
193
+
194
+ if files:
195
+ file_names = [os.path.basename(f.name if hasattr(f, 'name') else f) for f in files]
196
+ new_assessment = {"id": timestamp, "prompt": prompt, "files": file_names, "response": ai_response_text}
197
+ updated_history = history_state_list + [new_assessment]
198
+ history_labels = [f"{item['id']} - {item['prompt'][:40]}..." for item in updated_history]
199
+ yield final_chat, updated_history, gr.update(choices=history_labels)
200
+ else:
201
+ yield final_chat, history_state_list, gr.update()
202
+
203
+ def view_history(selection, history_state_list):
204
+ if not selection or not history_state_list: return ""
205
+ selected_id = selection.split(" - ")
206
+ selected_assessment = next((item for item in history_state_list if item["id"] == selected_id), None)
207
+ if selected_assessment:
208
+ file_list_md = "\n- ".join(selected_assessment['files'])
209
+ return f"""### Assessment from: {selected_assessment['id']}\n**Files Used:**\n- {file_list_md}\n---\n**Original Prompt:**\n> {selected_assessment['prompt']}\n---\n**AI Generated Response:**\n{selected_assessment['response']}"""
210
+ return "Could not find the selected assessment."
211
+
212
+ send_btn.click(
213
+ run_analysis_wrapper,
214
+ inputs=[prompt_input, files_input, chat_history_output, assessment_history],
215
+ outputs=[chat_history_output, assessment_history, history_dropdown]
216
+ )
217
+ history_dropdown.change(
218
+ view_history,
219
+ inputs=[history_dropdown, assessment_history],
220
+ outputs=[history_display]
221
+ )
222
+ clear_btn.click(lambda: (None, None, [], []), outputs=[prompt_input, files_input, chat_history_output, assessment_history])
223
+ ping_btn.click(ping_cohere, outputs=[ping_out])
224
+ privacy_link.click(lambda: gr.update(visible=True), outputs=[privacy_modal])
225
+ close_privacy_btn.click(lambda: gr.update(visible=False), outputs=[privacy_modal])
226
+ terms_link.click(lambda: gr.update(visible=True), outputs=[terms_modal])
227
+ close_terms_btn.click(lambda: gr.update(visible=False), outputs=[terms_modal])
228
+
229
+ if __name__ == "__main__":
230
+ if not os.getenv("COHERE_API_KEY"):
231
+ print("🔴 COHERE_API_KEY environment variable not set. Application may not function correctly.")
232
+ demo.launch(server_name="0.0.0.0", server_port=int(os.getenv("PORT", "7860")))# app.py
233
+ from __future__ import annotations
234
+ import os
235
+ import io
236
+ import traceback
237
+ from contextlib import redirect_stdout
238
+ from typing import List, Dict, Any
239
+
240
+ import gradio as gr
241
+ import pandas as pd
242
+ from datetime import datetime
243
+ import regex as re2
244
+
245
  # --- BACKEND IMPORTS ---
246
  from langchain_cohere import ChatCohere
247