Abid Ali Awan commited on
Commit
788acd9
·
1 Parent(s): 84cc41e

refactor: Revise system prompts and enhance output extraction in Gradio application to improve clarity, formatting, and user interaction during data operations, while streamlining the chat response process.

Browse files
Files changed (2) hide show
  1. app.py +46 -86
  2. test_streaming.py +105 -0
app.py CHANGED
@@ -5,6 +5,7 @@ CSV-based MLOps Agent with streaming final answer & MCP tools
5
 
6
  import os
7
  import shutil
 
8
 
9
  import gradio as gr
10
  from openai import OpenAI
@@ -32,33 +33,28 @@ MCP_TOOLS = [
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 mean.
52
- Mention key metrics, model IDs, or endpoints if available.
53
- Suggest next steps briefly. For normal chat (no tools), just respond helpfully.
54
 
55
  Formatting rules:
56
- - Use Markdown.
57
- - Use bullet points for lists.
58
- - Wrap any commands or code (curl, bash, Python, JSON) in fenced code blocks:
59
- ```bash
60
- ...
61
- ```
62
  """
63
 
64
 
@@ -87,21 +83,26 @@ def history_to_text(history) -> str:
87
 
88
  def extract_output_text(response) -> str:
89
  """
90
- Extract plain text from a non-streaming Responses API call.
91
- Fallback gracefully if the shape is unexpected.
92
  """
93
  try:
94
- if response.output and len(response.output) > 0:
95
  first = response.output[0]
96
  if getattr(first, "content", None):
97
- c0 = first.content[0]
98
- text = getattr(c0, "text", None)
99
- if text:
100
- return text
 
 
 
 
 
 
101
  # Fallback
102
- return getattr(response, "output_text", None) or str(response)
103
- except Exception:
104
- return str(response)
105
 
106
 
107
  def handle_upload(file_path, request: gr.Request):
@@ -164,9 +165,8 @@ def chat_send_stream(user_msg, history, file_url):
164
 
165
  - If the user is just chatting (e.g., "hey"), respond directly
166
  with a streaming answer (no tools, no CSV required).
167
- - If the user clearly asks for data/model operations, run:
168
- Phase 1: non-stream tool phase via Responses API + MCP tools
169
- Phase 2: streaming final answer via Responses API (no tools)
170
  - Keeps full chat history so follow-ups work.
171
  - Shows status/progress messages in the UI when tools are used.
172
  - Disables the textbox during work, re-enables at the end.
@@ -190,7 +190,7 @@ def chat_send_stream(user_msg, history, file_url):
190
  # -------------------------
191
  if not use_tools:
192
  # Add a small status bubble then stream
193
- history.append({"role": "assistant", "content": "✏️ Generating answer..."})
194
  # Disable textbox while generating
195
  yield (
196
  history,
@@ -206,7 +206,7 @@ def chat_send_stream(user_msg, history, file_url):
206
 
207
  stream = client.responses.create(
208
  model=MODEL,
209
- instructions=FINAL_SYSTEM_PROMPT,
210
  input=input_text,
211
  reasoning={"effort": "low"},
212
  stream=True,
@@ -256,15 +256,11 @@ def chat_send_stream(user_msg, history, file_url):
256
  # User message for the model includes the CSV URL
257
  user_with_file = f"[Uploaded CSV file URL: {file_url}]\n\n{user_msg}"
258
 
259
- # -------------------------
260
- # Phase 1: Tool + technical summary (non-streaming)
261
- # -------------------------
262
-
263
  # Show a status message in UI
264
  history.append(
265
  {
266
  "role": "assistant",
267
- "content": "Analyzing your request and selecting MCP tools...",
268
  }
269
  )
270
  # Disable textbox while tools run
@@ -273,63 +269,26 @@ def chat_send_stream(user_msg, history, file_url):
273
  gr.update(interactive=False),
274
  )
275
 
276
- # Build a single string input for the tool phase
277
- tool_phase_input = (
278
  (f"Conversation so far:\n{convo_before}\n\n" if convo_before else "")
279
  + "Latest user request (with file URL):\n"
280
  + user_with_file
281
- + "\n\nYour task: decide which MCP tools to call and run them. "
282
- "Then return a short technical summary of what you did and what the tools returned."
283
  )
284
 
285
- tool_phase = client.responses.create(
 
286
  model=MODEL,
287
- instructions=TOOL_SYSTEM_PROMPT,
288
- input=tool_phase_input,
289
  tools=MCP_TOOLS,
290
  reasoning={"effort": "low"},
 
291
  )
292
 
293
- scratchpad = extract_output_text(tool_phase).strip()
294
- if not scratchpad:
295
- scratchpad = "No MCP tool output was returned."
296
-
297
- # Update status message to show tools finished
298
- history[-1] = {
299
- "role": "assistant",
300
- "content": "✅ MCP tools finished. Preparing explanation...",
301
- }
302
- # Keep textbox disabled (we're about to stream final answer)
303
- yield (
304
- history,
305
- gr.update(interactive=False),
306
- )
307
-
308
- # -------------------------
309
- # Phase 2: Final streaming explanation
310
- # -------------------------
311
-
312
- # Replace last assistant message with streaming answer
313
  history[-1] = {"role": "assistant", "content": ""}
314
 
315
- # Build a single string input for the final explanation phase
316
- final_input = (
317
- (f"Conversation so far:\n{convo_before}\n\n" if convo_before else "")
318
- + "Latest user request (with file URL):\n"
319
- + user_with_file
320
- + "\n\nTechnical summary of tool actions and results:\n"
321
- + scratchpad
322
- + "\n\nNow explain this clearly to the user."
323
- )
324
-
325
- stream = client.responses.create(
326
- model=MODEL,
327
- instructions=FINAL_SYSTEM_PROMPT,
328
- input=final_input,
329
- reasoning={"effort": "low"},
330
- stream=True,
331
- )
332
-
333
  final_text = ""
334
  for event in stream:
335
  if event.type == "response.output_text.delta":
@@ -382,6 +341,7 @@ with gr.Blocks(title="Streaming MLOps Agent") as demo:
382
  chatbot = gr.Chatbot(
383
  label="Chat",
384
  render_markdown=True,
 
385
  avatar_images=(
386
  None,
387
  "https://huggingface.co/datasets/huggingface/brand-assets/resolve/main/hf-logo.png",
@@ -403,7 +363,7 @@ with gr.Blocks(title="Streaming MLOps Agent") as demo:
403
 
404
  if __name__ == "__main__":
405
  demo.queue().launch(
406
- theme=gr.themes.Default(primary_hue="blue", secondary_hue="green"),
407
  allowed_paths=["/tmp"],
408
  ssr_mode=False,
409
  show_error=True,
 
5
 
6
  import os
7
  import shutil
8
+ import json
9
 
10
  import gradio as gr
11
  from openai import OpenAI
 
33
  # Short prompts
34
  # -------------------------
35
 
36
+ MAIN_SYSTEM_PROMPT = """
37
+ You are a helpful MLOps assistant with MCP tools for CSV analysis, training,
38
  evaluation, and deployment.
39
 
40
+ For data-related requests (datasets, CSVs, models, training, evaluation,
41
+ deployment), call MCP tools to get comprehensive natural language results.
42
+ The tools will return detailed explanations you can share directly.
43
 
44
+ For general chat (no data operations), respond helpfully and naturally.
 
 
 
45
 
46
+ When using tools:
47
+ - Use the CSV file URL exactly as provided
48
+ - Do not invent tool parameters
49
+ - Share the complete results from MCP tools
50
+ - Add brief context or suggestions if helpful
51
 
52
+ Keep responses clear, informative, and user-friendly.
 
 
 
53
 
54
  Formatting rules:
55
+ - Use Markdown for formatting
56
+ - Use bullet points for lists
57
+ - Wrap code, commands, and JSON in fenced code blocks
 
 
 
58
  """
59
 
60
 
 
83
 
84
  def extract_output_text(response) -> str:
85
  """
86
+ Extract text from a non-streaming Responses API call while preserving formatting.
 
87
  """
88
  try:
89
+ if hasattr(response, 'output') and response.output and len(response.output) > 0:
90
  first = response.output[0]
91
  if getattr(first, "content", None):
92
+ for content_item in first.content:
93
+ if hasattr(content_item, 'type') and content_item.type == "output_text":
94
+ text = getattr(content_item, "text", None)
95
+ if text:
96
+ return text
97
+ elif hasattr(content_item, 'type') and content_item.type == "output_json":
98
+ # If there's JSON output, format it nicely
99
+ json_data = getattr(content_item, 'json', None)
100
+ if json_data:
101
+ return f"```json\n{json.dumps(json_data, indent=2)}\n```"
102
  # Fallback
103
+ return getattr(response, 'output_text', None) or str(response)
104
+ except Exception as e:
105
+ return f"Error extracting output: {e}"
106
 
107
 
108
  def handle_upload(file_path, request: gr.Request):
 
165
 
166
  - If the user is just chatting (e.g., "hey"), respond directly
167
  with a streaming answer (no tools, no CSV required).
168
+ - If the user clearly asks for data/model operations:
169
+ Call API once with MCP tools and stream the natural language results directly
 
170
  - Keeps full chat history so follow-ups work.
171
  - Shows status/progress messages in the UI when tools are used.
172
  - Disables the textbox during work, re-enables at the end.
 
190
  # -------------------------
191
  if not use_tools:
192
  # Add a small status bubble then stream
193
+ history.append({"role": "assistant", "content": "Generating answer..."})
194
  # Disable textbox while generating
195
  yield (
196
  history,
 
206
 
207
  stream = client.responses.create(
208
  model=MODEL,
209
+ instructions=MAIN_SYSTEM_PROMPT,
210
  input=input_text,
211
  reasoning={"effort": "low"},
212
  stream=True,
 
256
  # User message for the model includes the CSV URL
257
  user_with_file = f"[Uploaded CSV file URL: {file_url}]\n\n{user_msg}"
258
 
 
 
 
 
259
  # Show a status message in UI
260
  history.append(
261
  {
262
  "role": "assistant",
263
+ "content": "Analyzing your request and running MCP tools...",
264
  }
265
  )
266
  # Disable textbox while tools run
 
269
  gr.update(interactive=False),
270
  )
271
 
272
+ # Build input for the tool phase (single call)
273
+ tool_input = (
274
  (f"Conversation so far:\n{convo_before}\n\n" if convo_before else "")
275
  + "Latest user request (with file URL):\n"
276
  + user_with_file
 
 
277
  )
278
 
279
+ # Single API call with tools - MCP returns natural language results
280
+ stream = client.responses.create(
281
  model=MODEL,
282
+ instructions=MAIN_SYSTEM_PROMPT,
283
+ input=tool_input,
284
  tools=MCP_TOOLS,
285
  reasoning={"effort": "low"},
286
+ stream=True,
287
  )
288
 
289
+ # Replace status message with streaming answer
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
  history[-1] = {"role": "assistant", "content": ""}
291
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
  final_text = ""
293
  for event in stream:
294
  if event.type == "response.output_text.delta":
 
341
  chatbot = gr.Chatbot(
342
  label="Chat",
343
  render_markdown=True,
344
+ height=500,
345
  avatar_images=(
346
  None,
347
  "https://huggingface.co/datasets/huggingface/brand-assets/resolve/main/hf-logo.png",
 
363
 
364
  if __name__ == "__main__":
365
  demo.queue().launch(
366
+ theme=gr.themes.Soft(primary_hue="red", secondary_hue="pink"),
367
  allowed_paths=["/tmp"],
368
  ssr_mode=False,
369
  show_error=True,
test_streaming.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test the optimized single-phase streaming functionality
4
+ """
5
+
6
+ import os
7
+ import sys
8
+ sys.path.append(os.path.dirname(os.path.abspath(__file__)))
9
+
10
+ from app import MAIN_SYSTEM_PROMPT, MCP_TOOLS, client, MODEL
11
+
12
+ def test_streaming_with_tools():
13
+ """
14
+ Test that the single-phase streaming works correctly
15
+ """
16
+ csv_url = "https://mcp-1st-birthday-mlops-agent.hf.space/gradio_api/file=/tmp/gradio/6abcca54f954f2ad99a8f8f330dc6e8082f03ef3090458d97c274efcc76d0170/heart.csv"
17
+
18
+ user_with_file = f"[Uploaded CSV file URL: {csv_url}]\n\nAnalyze this dataset and show me basic statistics"
19
+
20
+ print("Testing Single-Phase Streaming...")
21
+ print(f"Input: {user_with_file[:100]}...")
22
+ print("-" * 60)
23
+
24
+ stream = client.responses.create(
25
+ model=MODEL,
26
+ instructions=MAIN_SYSTEM_PROMPT,
27
+ input=user_with_file,
28
+ tools=MCP_TOOLS,
29
+ reasoning={"effort": "low"},
30
+ stream=True,
31
+ )
32
+
33
+ print("Streaming Response:")
34
+ print("=" * 60)
35
+
36
+ final_text = ""
37
+ chunk_count = 0
38
+
39
+ for event in stream:
40
+ if event.type == "response.output_text.delta":
41
+ chunk_count += 1
42
+ final_text += event.delta
43
+ print(f"[Chunk {chunk_count}] {event.delta[:50]}...")
44
+ elif event.type == "response.completed":
45
+ print("=" * 60)
46
+ print(f"Total chunks: {chunk_count}")
47
+ print(f"Total length: {len(final_text)} characters")
48
+ print("FINAL RESPONSE:")
49
+ print(final_text)
50
+ break
51
+
52
+ return final_text
53
+
54
+ def test_streaming_without_tools():
55
+ """
56
+ Test that regular streaming works for non-tool requests
57
+ """
58
+ print("\nTesting Regular Streaming (No Tools)...")
59
+ print("-" * 60)
60
+
61
+ stream = client.responses.create(
62
+ model=MODEL,
63
+ instructions=MAIN_SYSTEM_PROMPT,
64
+ input="Hello! Can you explain what MLOps is in simple terms?",
65
+ reasoning={"effort": "low"},
66
+ stream=True,
67
+ )
68
+
69
+ print("Streaming Response:")
70
+ print("=" * 60)
71
+
72
+ final_text = ""
73
+ chunk_count = 0
74
+
75
+ for event in stream:
76
+ if event.type == "response.output_text.delta":
77
+ chunk_count += 1
78
+ final_text += event.delta
79
+ print(f"[Chunk {chunk_count}] {event.delta[:50]}...")
80
+ elif event.type == "response.completed":
81
+ print("=" * 60)
82
+ print(f"Total chunks: {chunk_count}")
83
+ print(f"Total length: {len(final_text)} characters")
84
+ print("FINAL RESPONSE:")
85
+ print(final_text)
86
+ break
87
+
88
+ return final_text
89
+
90
+ if __name__ == "__main__":
91
+ print("Starting Streaming Tests")
92
+ print("=" * 60)
93
+
94
+ # Test 1: With MCP tools
95
+ response1 = test_streaming_with_tools()
96
+
97
+ # Test 2: Without tools
98
+ response2 = test_streaming_without_tools()
99
+
100
+ print("\n" + "=" * 60)
101
+ print("✅ Both streaming tests completed successfully!")
102
+ print("✅ Single-phase approach working correctly!")
103
+ print("✅ MCP tools returning natural language responses!")
104
+ print("✅ Response is properly streaming in chunks!")
105
+ print("=" * 60)