Abid Ali Awan commited on
Commit
17424a1
·
1 Parent(s): 9d073ea

refactor: Update Gradio application to enhance MLOps agent functionality with improved tool resolution, refined chat handling, and clearer user prompts. Streamlined file upload process and removed outdated todo list.

Browse files
Files changed (2) hide show
  1. app.py +289 -135
  2. todolist.md +0 -5
app.py CHANGED
@@ -1,5 +1,6 @@
1
  """
2
- Gradio + OpenAI MCP Connector Clean, Fast, Streaming, With File Upload
 
3
  """
4
 
5
  import os
@@ -8,210 +9,363 @@ import shutil
8
  import gradio as gr
9
  from openai import OpenAI
10
 
11
- # ---------------------
12
- # CONFIGURATION
13
- # ---------------------
14
- OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
15
- MCP_SERVER_URL = "https://mcp-1st-birthday-auto-deployer.hf.space/gradio_api/mcp/"
16
 
17
- MODEL_FAST = "gpt-5-mini" # for tool resolution / MCP calls
18
- MODEL_STREAM = "gpt-5" # for final streaming reply
 
19
 
20
  client = OpenAI(api_key=OPENAI_API_KEY)
21
 
22
- # ---------------------
23
- # SYSTEM PROMPT
24
- # ---------------------
25
- SYSTEM_PROMPT = """
26
- You are a fast MLOps automation assistant equipped with remote MCP tools
27
- for dataset analysis, model training, evaluation, and deployment.
28
-
29
- Rules:
30
- - Use MCP tools when they directly address the user's request.
31
- - Treat the uploaded CSV file URL as the source of truth. Never modify it.
32
- - Never hallucinate tool names, arguments, or fields.
33
- - Keep your internal reasoning hidden.
34
- - Keep responses short, direct, and practical.
35
-
36
- Workflow:
37
- 1) Decide if a tool is needed for the request.
38
- 2) If needed, call the correct MCP tool with the exact schema.
39
- 3) After tools complete, give a concise summary in plain language.
40
- 4) If no tool is needed, answer directly and briefly.
41
- """
42
-
43
- # ---------------------
44
- # NATIVE MCP CONNECTOR (HTTP STREAMING)
45
- # ---------------------
46
- TOOLS = [
47
  {
48
  "type": "mcp",
49
- "server_label": "deploy_tools",
50
- "server_url": MCP_SERVER_URL, # HTTP streaming MCP server
 
51
  }
52
  ]
53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
- # ---------------------
56
- # FILE UPLOAD HANDLER
57
- # ---------------------
58
- def handle_upload(file_obj, request: gr.Request):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  """
60
- - Persist uploaded file to a stable /tmp path
61
- - Return a public URL that the MCP tools can use directly
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  """
63
- if file_obj is None:
 
 
 
 
64
  return None
65
 
66
- local_path = file_obj.name
67
  stable_path = os.path.join("/tmp", os.path.basename(local_path))
68
-
69
  try:
70
  shutil.copy(local_path, stable_path)
71
  local_path = stable_path
72
  except Exception:
73
- # If copy fails, still try original path
74
  pass
75
 
76
- base = str(request.base_url).rstrip("/")
77
- return f"{base}/gradio_api/file={local_path}"
 
78
 
79
 
80
- # ---------------------
81
- # MAIN CHAT HANDLER (STREAMING)
82
- # ---------------------
83
- def chat_send_stream(user_msg, history, file_url):
84
  """
85
- 2-phase pipeline:
86
- PHASE 1: Non-streaming tool resolution using MODEL_FAST
87
- PHASE 2: Streaming final answer using MODEL_STREAM
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
 
89
- Gradio Chatbot (messages mode) expects:
90
- history: list[{"role": "...", "content": "..."}]
 
 
 
 
 
 
 
 
 
 
91
  """
92
 
93
- # Ensure history is list[dict(role, content)]
94
  if history is None:
95
  history = []
96
 
97
- # Append the user message to the UI history first
98
  history.append({"role": "user", "content": user_msg})
99
 
100
- # ---- Build messages for OpenAI (sanitize away metadata etc.) ----
101
- messages = [{"role": "system", "content": SYSTEM_PROMPT}]
102
- for msg in history:
103
- role = msg.get("role")
104
- content = msg.get("content", "")
105
- if role in ("user", "assistant"):
106
- messages.append({"role": role, "content": content})
107
-
108
- # Inject file context into *last* user message for the model
109
- if file_url:
110
- last_user = messages[-1]
111
- if last_user["role"] == "user":
112
- last_user["content"] = f"[Uploaded CSV file: {file_url}]\n\n{user_msg}"
113
-
114
- # -----------------------------
115
- # PHASE 1 — TOOL RESOLUTION
116
- # -----------------------------
117
- tool_phase = client.responses.create(
118
- model=MODEL_FAST,
119
- reasoning={"effort": "low"}, # minimal thinking for speed
120
- tools=TOOLS,
121
- instructions=SYSTEM_PROMPT,
122
- input=messages,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  )
124
 
125
- tool_feedback_lines = []
126
-
127
- # Collect MCP tool usage info if present
128
- if tool_phase.output:
129
- for item in tool_phase.output:
130
- # Types may vary; handle generically
131
- item_type = getattr(item, "type", None)
132
- if item_type == "tool_call":
133
- tool_feedback_lines.append(f"🛠️ Used tool `{item.name}`.")
134
- elif item_type == "tool_result":
135
- tool_feedback_lines.append(str(item.content))
136
-
137
- if not tool_feedback_lines:
138
- tool_feedback_lines.append("No MCP tools needed.")
139
-
140
- tool_feedback_text = "\n".join(tool_feedback_lines)
141
-
142
- # Add assistant message with tool feedback to both histories
143
- history.append({"role": "assistant", "content": tool_feedback_text})
144
- yield history # show tool feedback immediately in UI
145
 
146
- # Add that same feedback into messages for the final answer
147
- messages.append({"role": "assistant", "content": tool_feedback_text})
 
148
 
149
- # -----------------------------
150
- # PHASE 2 — STREAMING FINAL ANSWER
151
- # -----------------------------
152
- final_text = tool_feedback_text + "\n\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
 
154
  stream = client.responses.create(
155
- model=MODEL_STREAM,
 
 
156
  reasoning={"effort": "low"},
157
- instructions=SYSTEM_PROMPT,
158
- input=messages,
159
  stream=True,
160
  )
161
 
162
- # Stream tokens and update the last assistant message
163
- for ev in stream:
164
- if ev.type == "response.output_text.delta":
165
- final_text += ev.delta
166
  history[-1]["content"] = final_text
167
  yield history
168
- elif ev.type == "response.completed":
169
  break
170
 
171
- stream.close()
172
 
 
 
 
173
 
174
- # ---------------------
175
- # GRADIO UI
176
- # ---------------------
177
- with gr.Blocks(title="MCP + GPT-5 — Fast Streaming MLOps Agent") as demo:
178
  gr.Markdown(
179
  """
180
- # 🚀 AI-Driven MLOps Agent (MCP-Powered)
181
- - Upload a CSV file
182
- - Tools resolve quickly via MCP
183
- - Final answer streams smoothly
 
 
184
  """
185
  )
186
 
187
- file_state = gr.State() # stores uploaded CSV URL
188
 
189
  uploader = gr.File(
190
- label="Upload CSV file",
191
- type="filepath",
192
  file_count="single",
 
193
  file_types=[".csv"],
194
  )
195
 
196
  uploader.change(
197
  handle_upload,
198
  inputs=[uploader],
199
- outputs=[file_state],
200
  )
201
 
202
- chatbot = gr.Chatbot(label="Chat") # uses messages format (dicts)
203
- msg = gr.Textbox(label="Message")
204
- send = gr.Button("Send")
 
 
 
 
 
 
 
 
 
 
 
205
 
206
  send.click(
207
  chat_send_stream,
208
- inputs=[msg, chatbot, file_state],
209
  outputs=[chatbot],
210
  ).then(lambda: "", outputs=[msg])
211
 
212
  msg.submit(
213
  chat_send_stream,
214
- inputs=[msg, chatbot, file_state],
215
  outputs=[chatbot],
216
  ).then(lambda: "", outputs=[msg])
217
 
@@ -219,6 +373,6 @@ with gr.Blocks(title="MCP + GPT-5 — Fast Streaming MLOps Agent") as demo:
219
  if __name__ == "__main__":
220
  demo.queue().launch(
221
  allowed_paths=["/tmp"],
 
222
  show_error=True,
223
- quiet=True,
224
- )
 
1
  """
2
+ Gradio + OpenAI Responses API + Remote MCP Server (HTTP)
3
+ CSV-based MLOps Agent with streaming final answer & MCP tools
4
  """
5
 
6
  import os
 
9
  import gradio as gr
10
  from openai import OpenAI
11
 
12
+ # -------------------------
13
+ # Config
14
+ # -------------------------
 
 
15
 
16
+ MCP_SERVER_URL = "https://mcp-1st-birthday-auto-deployer.hf.space/gradio_api/mcp/"
17
+ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
18
+ MODEL = "gpt-5-mini" # you can swap to gpt-5 for final answers if you want
19
 
20
  client = OpenAI(api_key=OPENAI_API_KEY)
21
 
22
+ MCP_TOOLS = [
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  {
24
  "type": "mcp",
25
+ "server_label": "auto-deployer",
26
+ "server_url": MCP_SERVER_URL,
27
+ "require_approval": "never",
28
  }
29
  ]
30
 
31
+ # -------------------------
32
+ # Short prompts
33
+ # -------------------------
34
+
35
+ TOOL_SYSTEM_PROMPT = """
36
+ You are an MLOps assistant with MCP tools for CSV analysis, training,
37
+ evaluation, and deployment.
38
+
39
+ If the user asks about data, datasets, CSVs, models, training,
40
+ evaluation, or deployment, call MCP tools instead of guessing.
41
+
42
+ Use the CSV file URL exactly as given when tools need a file path.
43
+ Do not invent tool names or parameters.
44
+ Keep internal reasoning hidden and reply briefly with technical details.
45
+ """
46
+
47
+ FINAL_SYSTEM_PROMPT = """
48
+ You are a helpful MLOps explainer and general assistant.
49
+
50
+ You see the conversation plus a short technical summary of what tools did
51
+ (if any). Explain in simple language what was done and what the results
52
+ mean. Mention key metrics, model IDs, or endpoints if available.
53
+ Suggest next steps briefly. For normal chat (no tools), just respond
54
+ helpfully.
55
+
56
+ Do not mention tools or internal phases explicitly.
57
+ Keep the answer clear and concise.
58
+ """
59
+
60
 
61
+ # -------------------------
62
+ # Helpers
63
+ # -------------------------
64
+
65
+
66
+ def history_to_text(history) -> str:
67
+ """
68
+ Turn Gradio history (list of {role, content}) into a plain-text
69
+ conversation transcript for the model.
70
+ """
71
+ if not history:
72
+ return ""
73
+ lines = []
74
+ for msg in history:
75
+ role = msg.get("role")
76
+ content = msg.get("content", "")
77
+ if role == "user":
78
+ lines.append(f"User: {content}")
79
+ elif role == "assistant":
80
+ lines.append(f"Assistant: {content}")
81
+ return "\n".join(lines)
82
+
83
+
84
+ def extract_output_text(response) -> str:
85
  """
86
+ Extract plain text from a non-streaming Responses API call.
87
+ Fallback gracefully if the shape is unexpected.
88
+ """
89
+ try:
90
+ if response.output and len(response.output) > 0:
91
+ first = response.output[0]
92
+ if getattr(first, "content", None):
93
+ c0 = first.content[0]
94
+ text = getattr(c0, "text", None)
95
+ if text:
96
+ return text
97
+ # Fallback
98
+ return getattr(response, "output_text", None) or str(response)
99
+ except Exception:
100
+ return str(response)
101
+
102
+
103
+ def handle_upload(file_path, request: gr.Request):
104
  """
105
+ 1) Take uploaded file path (string)
106
+ 2) Copy to /tmp for a stable path
107
+ 3) Build a public Gradio file URL that the MCP server can fetch via HTTP
108
+ """
109
+ if not file_path:
110
  return None
111
 
112
+ local_path = file_path
113
  stable_path = os.path.join("/tmp", os.path.basename(local_path))
 
114
  try:
115
  shutil.copy(local_path, stable_path)
116
  local_path = stable_path
117
  except Exception:
118
+ # If copy fails, just use the original path
119
  pass
120
 
121
+ base_url = str(request.base_url).rstrip("/")
122
+ public_url = f"{base_url}/gradio_api/file={local_path}"
123
+ return public_url
124
 
125
 
126
+ def should_use_tools(user_msg: str) -> bool:
127
+ """
128
+ Simple heuristic to decide if this turn should trigger MCP tools.
129
+ Only fire tools if the user is clearly asking for data / model work.
130
  """
131
+ text = user_msg.lower()
132
+ keywords = [
133
+ "data",
134
+ "dataset",
135
+ "csv",
136
+ "train",
137
+ "training",
138
+ "model",
139
+ "deploy",
140
+ "deployment",
141
+ "predict",
142
+ "prediction",
143
+ "inference",
144
+ "evaluate",
145
+ "evaluation",
146
+ "analyze",
147
+ "analysis",
148
+ ]
149
+ return any(k in text for k in keywords)
150
+
151
+
152
+ # -------------------------
153
+ # Main chat handler (streaming)
154
+ # -------------------------
155
 
156
+
157
+ def chat_send_stream(user_msg, history, file_url):
158
+ """
159
+ Main Gradio streaming handler.
160
+
161
+ - If the user is just chatting (e.g., "hey"), respond directly
162
+ with a streaming answer (no tools, no CSV required).
163
+ - If the user clearly asks for data/model operations, run:
164
+ Phase 1: non-stream tool phase via Responses API + MCP tools
165
+ Phase 2: streaming final answer via Responses API (no tools)
166
+ - Keeps full chat history so follow-ups work.
167
+ - Shows status/progress messages in the UI when tools are used.
168
  """
169
 
170
+ # UI history (what Gradio displays)
171
  if history is None:
172
  history = []
173
 
174
+ # Append the user message to the UI history
175
  history.append({"role": "user", "content": user_msg})
176
 
177
+ # Conversation before this turn (for context)
178
+ convo_before = history_to_text(history[:-1])
179
+
180
+ # Decide if this message should trigger tools
181
+ use_tools = should_use_tools(user_msg)
182
+
183
+ # -------------------------
184
+ # BRANCH 1: No tools (normal chat, e.g. "hey")
185
+ # -------------------------
186
+ if not use_tools:
187
+ # Add a small status bubble then stream
188
+ history.append({"role": "assistant", "content": "✏️ Generating answer..."})
189
+ yield history
190
+
191
+ # Build input text for Responses API
192
+ input_text = (
193
+ (f"Conversation so far:\n{convo_before}\n\n" if convo_before else "")
194
+ + "Latest user message:\n"
195
+ + user_msg
196
+ )
197
+
198
+ stream = client.responses.create(
199
+ model=MODEL,
200
+ instructions=FINAL_SYSTEM_PROMPT,
201
+ input=input_text,
202
+ reasoning={"effort": "low"},
203
+ stream=True,
204
+ )
205
+
206
+ final_text = ""
207
+ for event in stream:
208
+ if event.type == "response.output_text.delta":
209
+ final_text += event.delta
210
+ history[-1]["content"] = final_text
211
+ yield history
212
+ elif event.type == "response.completed":
213
+ break
214
+
215
+ return
216
+
217
+ # -------------------------
218
+ # BRANCH 2: Tools needed (data / model operations)
219
+ # -------------------------
220
+
221
+ # If tools are needed but no file URL, ask for CSV
222
+ if not file_url:
223
+ history.append(
224
+ {
225
+ "role": "assistant",
226
+ "content": (
227
+ "To analyze, train, or deploy, please upload a CSV file first "
228
+ "using the file upload control."
229
+ ),
230
+ }
231
+ )
232
+ yield history
233
+ return
234
+
235
+ # User message for the model includes the CSV URL
236
+ user_with_file = f"[Uploaded CSV file URL: {file_url}]\n\n{user_msg}"
237
+
238
+ # -------------------------
239
+ # Phase 1: Tool + technical summary (non-streaming)
240
+ # -------------------------
241
+
242
+ # Show a status message in UI
243
+ history.append(
244
+ {
245
+ "role": "assistant",
246
+ "content": "⏳ Analyzing your request and selecting MCP tools...",
247
+ }
248
+ )
249
+ yield history
250
+
251
+ # Build a single string input for the tool phase
252
+ tool_phase_input = (
253
+ (f"Conversation so far:\n{convo_before}\n\n" if convo_before else "")
254
+ + "Latest user request (with file URL):\n"
255
+ + user_with_file
256
+ + "\n\nYour task: decide which MCP tools to call and run them. "
257
+ "Then return a short technical summary of what you did and what the tools returned."
258
  )
259
 
260
+ tool_phase = client.responses.create(
261
+ model=MODEL,
262
+ instructions=TOOL_SYSTEM_PROMPT,
263
+ input=tool_phase_input,
264
+ tools=MCP_TOOLS,
265
+ reasoning={"effort": "low"},
266
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
267
 
268
+ scratchpad = extract_output_text(tool_phase).strip()
269
+ if not scratchpad:
270
+ scratchpad = "No MCP tool output was returned."
271
 
272
+ # Update status message to show tools finished
273
+ history[-1] = {
274
+ "role": "assistant",
275
+ "content": "✅ MCP tools finished. Preparing explanation...",
276
+ }
277
+ yield history
278
+
279
+ # -------------------------
280
+ # Phase 2: Final streaming explanation
281
+ # -------------------------
282
+
283
+ # Replace last assistant message with streaming answer
284
+ history[-1] = {"role": "assistant", "content": ""}
285
+
286
+ # Build a single string input for the final explanation phase
287
+ final_input = (
288
+ (f"Conversation so far:\n{convo_before}\n\n" if convo_before else "")
289
+ + "Latest user request (with file URL):\n"
290
+ + user_with_file
291
+ + "\n\nTechnical summary of tool actions and results:\n"
292
+ + scratchpad
293
+ + "\n\nNow explain this clearly to the user."
294
+ )
295
 
296
  stream = client.responses.create(
297
+ model=MODEL,
298
+ instructions=FINAL_SYSTEM_PROMPT,
299
+ input=final_input,
300
  reasoning={"effort": "low"},
 
 
301
  stream=True,
302
  )
303
 
304
+ final_text = ""
305
+ for event in stream:
306
+ if event.type == "response.output_text.delta":
307
+ final_text += event.delta
308
  history[-1]["content"] = final_text
309
  yield history
310
+ elif event.type == "response.completed":
311
  break
312
 
 
313
 
314
+ # -------------------------
315
+ # Gradio UI
316
+ # -------------------------
317
 
318
+ with gr.Blocks(title="MCP + GPT-5 mini - Streaming MLOps Agent") as demo:
 
 
 
319
  gr.Markdown(
320
  """
321
+ # AI-Driven MLOps Agent 🤖
322
+
323
+ - You can just chat (e.g. "hey") — no tools needed.
324
+ - For data work, **upload a CSV file** and ask to analyze, train, or deploy.
325
+ - When tools are used, you’ll see status updates.
326
+ - Final answers stream token by token.
327
  """
328
  )
329
 
330
+ file_url_state = gr.State(value=None)
331
 
332
  uploader = gr.File(
333
+ label="Optional CSV file upload (required for data/model operations)",
 
334
  file_count="single",
335
+ type="filepath",
336
  file_types=[".csv"],
337
  )
338
 
339
  uploader.change(
340
  handle_upload,
341
  inputs=[uploader],
342
+ outputs=[file_url_state],
343
  )
344
 
345
+ chatbot = gr.Chatbot(
346
+ label="Chat",
347
+ avatar_images=(
348
+ None,
349
+ "https://huggingface.co/datasets/huggingface/brand-assets/resolve/main/hf-logo.png",
350
+ ),
351
+ )
352
+
353
+ msg = gr.Textbox(
354
+ label="Message",
355
+ interactive=True,
356
+ placeholder="Say hi, or ask me to analyze / train / deploy on your dataset...",
357
+ )
358
+ send = gr.Button("Send", interactive=True)
359
 
360
  send.click(
361
  chat_send_stream,
362
+ inputs=[msg, chatbot, file_url_state],
363
  outputs=[chatbot],
364
  ).then(lambda: "", outputs=[msg])
365
 
366
  msg.submit(
367
  chat_send_stream,
368
+ inputs=[msg, chatbot, file_url_state],
369
  outputs=[chatbot],
370
  ).then(lambda: "", outputs=[msg])
371
 
 
373
  if __name__ == "__main__":
374
  demo.queue().launch(
375
  allowed_paths=["/tmp"],
376
+ ssr_mode=False,
377
  show_error=True,
378
+ )
 
todolist.md DELETED
@@ -1,5 +0,0 @@
1
- - [] Diable message and send button while the final reosne is not recived.
2
- - [] reduce the thinking preces in tools secaltiona dn then analysing the dataset.
3
- - [] clear the message when user end the message.
4
- - [] model is not taking an acocunt of previous ocnverations.
5
- - [] add more infor to the readme