Rajan Sharma commited on
Commit
2f7a273
·
verified ·
1 Parent(s): 9998fe9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +160 -91
app.py CHANGED
@@ -9,87 +9,167 @@ import gradio as gr
9
  import pandas as pd
10
  from datetime import datetime
11
 
12
- # --- (All your previous backend imports remain the same) ---
13
  from langchain.agents.agent_types import AgentType
14
  from langchain_cohere import ChatCohere
15
  from langchain_experimental.agents.agent_toolkits import create_pandas_dataframe_agent
 
 
 
16
  from settings import (
17
  HEALTHCARE_SETTINGS, GENERAL_CONVERSATION_PROMPT, USE_SCENARIO_ENGINE, DEBUG_PLAN,
18
  COHERE_MODEL_PRIMARY, COHERE_TIMEOUT_S, USE_OPEN_FALLBACKS
19
  )
20
  from audit_log import log_event
21
  from privacy import safety_filter, refusal_reply
22
- # ... (and so on for the rest of your backend functions)
23
-
24
- # --- (The entire backend logic from the previous version should be pasted here) ---
25
- # This includes:
26
- # _sanitize_text, _create_enhanced_prompt, is_healthcare_scenario,
27
- # _append_msg, ping_cohere, and the main handle() function.
28
- # For brevity, I am omitting them here, but they are ESSENTIAL.
29
- # Please copy them from the previous version you have.
30
- # The ONLY CHANGE is that we will now call handle() from a new UI wrapper function.
31
-
32
- # (For demonstration, I will paste a minimal version of the backend functions here)
33
- def _sanitize_text(s: str) -> str: return s
34
- def _create_enhanced_prompt(s: str) -> str: return s
35
- def _append_msg(h, r, c): return (h or []) + [{"role": r, "content": c}]
36
-
37
- # This is your perfected backend engine. It does not need to be changed.
38
- def handle(user_msg: str, history_messages: List[Dict[str, str]], files: list) -> Tuple[List[Dict[str, str]], str]:
39
- # --- (Paste your entire, working handle() function here) ---
40
- # For now, I'll use a placeholder that returns a success message
41
- # In your real app, this will be your full LangChain agent logic.
42
- file_names = [os.path.basename(f) for f in files]
43
- response_text = f"### Analysis Complete\n**Prompt:** {user_msg}\n**Files Used:** {', '.join(file_names)}\n\nThis is where the structured output from the AI agent would appear."
44
- new_hist = _append_msg(history_messages, "user", user_msg)
45
- new_hist = _append_msg(new_hist, "assistant", response_text)
46
- return new_hist, ""
47
-
48
-
49
- # ---------------- THE NEW UI ----------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  with gr.Blocks(theme="soft", css="style.css") as demo:
51
  # State to store the history of all assessments in this session
52
  assessment_history = gr.State([])
53
 
54
- gr.Markdown("# Universal AI Data Analyst", elem_classes="h1")
55
 
56
  with gr.Row(variant="panel"):
57
  # --- LEFT COLUMN: CONTROLS ---
58
  with gr.Column(scale=1):
59
- gr.Markdown("## New Assessment", elem_classes="h2")
60
-
61
- files = gr.Files(
62
  label="Upload Data Files (CSV recommended)",
63
  file_count="multiple",
64
  type="filepath",
65
  file_types=[".csv"]
66
  )
67
- msg = gr.Textbox(
68
  label="Prompt",
69
  placeholder="Paste your scenario, tasks, and any specific instructions here.",
70
- lines=10
71
  )
72
  with gr.Row():
73
- send_btn = gr.Button("▶️ Run Analysis", variant="primary")
74
- clear_btn = gr.Button("🗑️ Clear", variant="secondary")
 
 
 
75
 
76
  # --- RIGHT COLUMN: RESULTS & HISTORY ---
77
  with gr.Column(scale=2):
78
  with gr.Tabs():
79
  # --- TAB 1: CURRENT ASSESSMENT ---
80
  with gr.TabItem("Current Assessment", id=0):
81
- chat_history = gr.Chatbot(
82
- label="Chat History",
83
  bubble_full_width=True,
84
- height=500
85
  )
86
- ping_btn = gr.Button("Ping Cohere")
87
- ping_out = gr.Markdown()
88
-
89
-
90
  # --- TAB 2: ASSESSMENT HISTORY ---
91
  with gr.TabItem("Assessment History", id=1):
92
- gr.Markdown("## Review Past Assessments", elem_classes="h2")
93
  history_dropdown = gr.Dropdown(
94
  label="Select an assessment to review",
95
  choices=[]
@@ -99,70 +179,60 @@ with gr.Blocks(theme="soft", css="style.css") as demo:
99
  )
100
 
101
  # --- UI LOGIC ---
102
-
103
- # Function to run when "Run Analysis" is clicked
104
- def run_analysis(prompt, files, chat, history_state):
105
  if not prompt or not files:
106
  gr.Warning("Please provide both a prompt and at least one data file.")
107
- return chat, history_state, gr.update()
 
 
 
 
 
 
108
 
109
- # Call your powerful backend function
110
- final_chat, _ = handle(prompt, chat, files)
111
 
112
- # Save the completed assessment to our history state
113
  timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
114
  file_names = [os.path.basename(f) for f in files]
115
 
116
  new_assessment = {
117
- "id": timestamp,
118
- "prompt": prompt,
119
- "files": file_names,
120
- "response": final_chat[-1]['content'] # Get the AI's final response
121
  }
 
122
 
123
- updated_history = history_state + [new_assessment]
124
-
125
- # Create user-friendly labels for the dropdown
126
  history_labels = [f"{item['id']} - {item['prompt'][:40]}..." for item in updated_history]
127
 
128
  return final_chat, updated_history, gr.update(choices=history_labels)
129
 
130
- # Function to run when a history item is selected from the dropdown
131
- def view_history(selection, history_state):
132
- if not selection or not history_state:
133
- return ""
134
-
135
- # Find the selected assessment
136
- # The selection string is "TIMESTAMP - PROMPT...", so we match by the timestamp
137
  selected_id = selection.split(" - ")[0]
138
- selected_assessment = next((item for item in history_state if item["id"] == selected_id), None)
139
 
140
  if selected_assessment:
141
- # Format the past assessment for beautiful display in Markdown
142
- display_text = f"""
143
- ### Assessment from: {selected_assessment['id']}
144
-
145
- **Files Used:**
146
- - {'- '.join(selected_assessment['files'])}
147
-
148
- ---
149
-
150
- **Original Prompt:**
151
- > {selected_assessment['prompt']}
152
-
153
- ---
154
-
155
- **AI Generated Response:**
156
- {selected_assessment['response']}
157
- """
158
- return display_text
159
  return "Could not find the selected assessment."
160
 
161
  # Wire up the components
162
  send_btn.click(
163
- run_analysis,
164
- inputs=[msg, files, chat_history, assessment_history],
165
- outputs=[chat_history, assessment_history, history_dropdown]
166
  )
167
 
168
  history_dropdown.change(
@@ -171,12 +241,11 @@ with gr.Blocks(theme="soft", css="style.css") as demo:
171
  outputs=[history_display]
172
  )
173
 
174
- clear_btn.click(lambda: (None, None, None), outputs=[msg, files, chat_history])
175
  ping_btn.click(lambda: ping_cohere(), outputs=[ping_out])
176
 
177
-
178
  if __name__ == "__main__":
179
- # --- (Your startup logic remains the same) ---
180
  if not os.getenv("COHERE_API_KEY"):
181
  print("🔴 COHERE_API_KEY environment variable not set. Application may not function correctly.")
 
182
  demo.launch(server_name="0.0.0.0", server_port=int(os.getenv("PORT", "7860")))
 
9
  import pandas as pd
10
  from datetime import datetime
11
 
12
+ # --- BACKEND IMPORTS ---
13
  from langchain.agents.agent_types import AgentType
14
  from langchain_cohere import ChatCohere
15
  from langchain_experimental.agents.agent_toolkits import create_pandas_dataframe_agent
16
+
17
+ # --- LOCAL MODULE IMPORTS ---
18
+ # (Assuming these files exist in your project)
19
  from settings import (
20
  HEALTHCARE_SETTINGS, GENERAL_CONVERSATION_PROMPT, USE_SCENARIO_ENGINE, DEBUG_PLAN,
21
  COHERE_MODEL_PRIMARY, COHERE_TIMEOUT_S, USE_OPEN_FALLBACKS
22
  )
23
  from audit_log import log_event
24
  from privacy import safety_filter, refusal_reply
25
+ from llm_router import cohere_chat, _co_client, cohere_embed
26
+
27
+ # --- BACKEND UTILITY FUNCTIONS ---
28
+
29
+ def _sanitize_text(s: str) -> str:
30
+ if not isinstance(s, str):
31
+ return s
32
+ return re2.sub(r'[\p{C}--[\n\t]]+', '', s)
33
+
34
+ def _create_enhanced_prompt(user_scenario: str) -> str:
35
+ """Uses an LLM to pre-process the user's messy prompt into a structured brief."""
36
+ prompt_for_planner = f"""
37
+ You are an expert data analysis project manager. Your task is to read the user's unstructured scenario below and create a clear, structured brief for a data analysis AI.
38
+
39
+ From the user's text, extract the following:
40
+ 1. **Primary Objective:** A one-sentence summary of the user's main goal.
41
+ 2. **Key Tasks:** A numbered list of ALL the specific questions the user wants answered.
42
+ 3. **Expert Guidelines & Assumptions:** A bulleted list of any specific numbers, metrics, or calculation methods mentioned.
43
+ 4. **Required Output Format:** A description of how the user wants the final answer structured.
44
+
45
+ CRITICAL INSTRUCTION: Tell the data analyst that it MUST answer ALL of the key tasks before providing its final answer.
46
+
47
+ --- USER'S SCENARIO ---
48
+ {user_scenario}
49
+ """
50
+ structured_brief = cohere_chat(prompt_for_planner)
51
+ return structured_brief if structured_brief else user_scenario
52
+
53
+ def _append_msg(history_messages: List[Dict[str, str]], role: str, content: str) -> List[Dict[str, str]]:
54
+ return (history_messages or []) + [{"role": role, "content": content}]
55
+
56
+ def ping_cohere() -> str:
57
+ """Lightweight health check against Cohere."""
58
+ try:
59
+ cli = _co_client()
60
+ if not cli:
61
+ return "Cohere client not initialized. Is COHERE_API_KEY set?"
62
+ vecs = cohere_embed(["hello", "world"])
63
+ if vecs and len(vecs) == 2:
64
+ return f"Cohere OK ✅ (model={COHERE_MODEL_PRIMARY}, timeout={COHERE_TIMEOUT_S}s)"
65
+ return "Cohere reachable, but embeddings returned no vectors."
66
+ except Exception as e:
67
+ return f"Cohere ping failed: {e}"
68
+
69
+ # --- THE CORE ANALYSIS ENGINE ---
70
+
71
+ def handle(user_msg: str, files: list) -> str:
72
+ """
73
+ This is the powerful backend engine. It takes the user's query and files
74
+ and returns only the final AI-generated text response.
75
+ """
76
+ try:
77
+ safe_in, blocked_in, reason_in = safety_filter(user_msg, mode="input")
78
+ if blocked_in:
79
+ return refusal_reply(reason_in)
80
+
81
+ file_paths: List[str] = [getattr(f, "name", None) or f for f in (files or [])]
82
+
83
+ if file_paths:
84
+ dataframes = [pd.read_csv(p) for p in file_paths if p.endswith('.csv')]
85
+ if not dataframes:
86
+ return "Please upload at least one CSV file."
87
+
88
+ llm = ChatCohere(model=COHERE_MODEL_PRIMARY, temperature=0)
89
+ enhanced_prompt = _create_enhanced_prompt(safe_in)
90
+
91
+ AGENT_PREFIX = """
92
+ You are a data analysis agent. You have access to one or more pandas dataframes.
93
+ You MUST respond in one of two formats.
94
+
95
+ FORMAT 1: To perform a task. Your response must be a single block of text with ONLY these three sections:
96
+ Thought: Your step-by-step reasoning.
97
+ Action: python_repl_ast
98
+ Action Input: The Python code to run.
99
+
100
+ FORMAT 2: To give the final answer. Your response must be a single block of text with ONLY these two sections:
101
+ Thought: I have now answered all the user's questions and can provide the final report.
102
+ Final Answer: The complete answer, structured as the user requested.
103
+
104
+ CRITICAL RULE: NEVER combine `Action` and `Final Answer` in the same response. Choose one format.
105
+ """
106
+
107
+ agent = create_pandas_dataframe_agent(
108
+ llm,
109
+ dataframes,
110
+ agent_type=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
111
+ verbose=True,
112
+ allow_dangerous_code=True,
113
+ prefix=AGENT_PREFIX,
114
+ max_iterations=50
115
+ )
116
+
117
+ result = agent.invoke({"input": enhanced_prompt})
118
+ reply = _sanitize_text(result.get("output", "No output generated."))
119
+ return reply
120
+ else:
121
+ # General conversation mode if no files are uploaded
122
+ prompt = f"{GENERAL_CONVERSATION_PROMPT}\n\nUser: {safe_in}\nAssistant:"
123
+ reply = cohere_chat(prompt) or "How can I help further?"
124
+ return _sanitize_text(reply)
125
+
126
+ except Exception as e:
127
+ tb = traceback.format_exc()
128
+ log_event("app_error", None, {"err": str(e), "tb": tb})
129
+ return f"A critical error occurred: {e}"
130
+
131
+ # ---------------- THE NEW PROFESSIONAL UI ----------------
132
  with gr.Blocks(theme="soft", css="style.css") as demo:
133
  # State to store the history of all assessments in this session
134
  assessment_history = gr.State([])
135
 
136
+ gr.Markdown("# Universal AI Data Analyst")
137
 
138
  with gr.Row(variant="panel"):
139
  # --- LEFT COLUMN: CONTROLS ---
140
  with gr.Column(scale=1):
141
+ gr.Markdown("## New Assessment")
142
+ files_input = gr.Files(
 
143
  label="Upload Data Files (CSV recommended)",
144
  file_count="multiple",
145
  type="filepath",
146
  file_types=[".csv"]
147
  )
148
+ prompt_input = gr.Textbox(
149
  label="Prompt",
150
  placeholder="Paste your scenario, tasks, and any specific instructions here.",
151
+ lines=15
152
  )
153
  with gr.Row():
154
+ send_btn = gr.Button("▶️ Run Analysis", variant="primary", scale=2)
155
+ clear_btn = gr.Button("🗑️ Clear")
156
+
157
+ ping_btn = gr.Button("Ping Cohere")
158
+ ping_out = gr.Markdown()
159
 
160
  # --- RIGHT COLUMN: RESULTS & HISTORY ---
161
  with gr.Column(scale=2):
162
  with gr.Tabs():
163
  # --- TAB 1: CURRENT ASSESSMENT ---
164
  with gr.TabItem("Current Assessment", id=0):
165
+ chat_history_output = gr.Chatbot(
166
+ label="Analysis Output",
167
  bubble_full_width=True,
168
+ height=600
169
  )
 
 
 
 
170
  # --- TAB 2: ASSESSMENT HISTORY ---
171
  with gr.TabItem("Assessment History", id=1):
172
+ gr.Markdown("## Review Past Assessments")
173
  history_dropdown = gr.Dropdown(
174
  label="Select an assessment to review",
175
  choices=[]
 
179
  )
180
 
181
  # --- UI LOGIC ---
182
+ def run_analysis_wrapper(prompt, files, chat_history_list, history_state_list):
 
 
183
  if not prompt or not files:
184
  gr.Warning("Please provide both a prompt and at least one data file.")
185
+ return chat_history_list, history_state_list, gr.update()
186
+
187
+ # 1. Append the user's message to the chat
188
+ chat_with_user_msg = _append_msg(chat_history_list, "user", prompt)
189
+
190
+ # 2. Call the powerful backend engine to get the AI response
191
+ ai_response_text = handle(prompt, files)
192
 
193
+ # 3. Append the AI's response to the chat
194
+ final_chat = _append_msg(chat_with_user_msg, "assistant", ai_response_text)
195
 
196
+ # 4. Save the completed assessment to our history state
197
  timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
198
  file_names = [os.path.basename(f) for f in files]
199
 
200
  new_assessment = {
201
+ "id": timestamp, "prompt": prompt, "files": file_names,
202
+ "response": ai_response_text
 
 
203
  }
204
+ updated_history = history_state_list + [new_assessment]
205
 
206
+ # 5. Create user-friendly labels for the history dropdown
 
 
207
  history_labels = [f"{item['id']} - {item['prompt'][:40]}..." for item in updated_history]
208
 
209
  return final_chat, updated_history, gr.update(choices=history_labels)
210
 
211
+ def view_history(selection, history_state_list):
212
+ if not selection or not history_state_list: return ""
 
 
 
 
 
213
  selected_id = selection.split(" - ")[0]
214
+ selected_assessment = next((item for item in history_state_list if item["id"] == selected_id), None)
215
 
216
  if selected_assessment:
217
+ file_list_md = "\n- ".join(selected_assessment['files'])
218
+ return f"""
219
+ ### Assessment from: {selected_assessment['id']}
220
+ **Files Used:**
221
+ - {file_list_md}
222
+ ---
223
+ **Original Prompt:**
224
+ > {selected_assessment['prompt']}
225
+ ---
226
+ **AI Generated Response:**
227
+ {selected_assessment['response']}
228
+ """
 
 
 
 
 
 
229
  return "Could not find the selected assessment."
230
 
231
  # Wire up the components
232
  send_btn.click(
233
+ run_analysis_wrapper,
234
+ inputs=[prompt_input, files_input, chat_history_output, assessment_history],
235
+ outputs=[chat_history_output, assessment_history, history_dropdown]
236
  )
237
 
238
  history_dropdown.change(
 
241
  outputs=[history_display]
242
  )
243
 
244
+ clear_btn.click(lambda: (None, None, [], []), outputs=[prompt_input, files_input, chat_history_output, assessment_history])
245
  ping_btn.click(lambda: ping_cohere(), outputs=[ping_out])
246
 
 
247
  if __name__ == "__main__":
 
248
  if not os.getenv("COHERE_API_KEY"):
249
  print("🔴 COHERE_API_KEY environment variable not set. Application may not function correctly.")
250
+
251
  demo.launch(server_name="0.0.0.0", server_port=int(os.getenv("PORT", "7860")))