kendrickfff commited on
Commit
5e1b949
·
verified ·
1 Parent(s): 0246fd3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +91 -53
app.py CHANGED
@@ -1,5 +1,5 @@
1
  """
2
- Agent Ken — Data-Informed PM + Social Impact Copilot
3
  Powered by Azure AI Foundry + Microsoft Fabric
4
  Hugging Face Space (Gradio) — with file upload support
5
  """
@@ -11,14 +11,14 @@ from azure.ai.projects import AIProjectClient
11
  from azure.identity import ClientSecretCredential
12
  from azure.ai.agents.models import ListSortOrder, FilePurpose, MessageAttachment, CodeInterpreterTool
13
 
14
- # Azure config
15
  PROJECT_ENDPOINT = os.environ["PROJECT_ENDPOINT"]
16
  AGENT_ID = os.environ["AGENT_ID"]
17
  AZURE_TENANT_ID = os.environ["AZURE_TENANT_ID"]
18
  AZURE_CLIENT_ID = os.environ["AZURE_CLIENT_ID"]
19
  AZURE_CLIENT_SECRET = os.environ["AZURE_CLIENT_SECRET"]
20
 
21
- # Azure client
22
  credential = ClientSecretCredential(
23
  tenant_id=AZURE_TENANT_ID,
24
  client_id=AZURE_CLIENT_ID,
@@ -36,7 +36,7 @@ def get_agent():
36
  return _agent
37
 
38
 
39
- # Supported file types
40
  SUPPORTED_EXTENSIONS = {
41
  ".csv", ".xlsx", ".xls", ".json",
42
  ".txt", ".md", ".pdf",
@@ -54,9 +54,16 @@ def is_supported_file(filename: str) -> bool:
54
 
55
  # Chat logic
56
  def respond(user_message: str, uploaded_files, history: list, thread_state: dict | None):
57
- if not user_message.strip() and not uploaded_files:
 
 
 
 
 
 
58
  return history, thread_state
59
 
 
60
  if thread_state is None or "thread_id" not in thread_state:
61
  thread = project.agents.threads.create()
62
  thread_state = {"thread_id": thread.id}
@@ -66,28 +73,42 @@ def respond(user_message: str, uploaded_files, history: list, thread_state: dict
66
  try:
67
  agent = get_agent()
68
 
69
- # Handle file uploads
70
  attachments = []
71
  file_names = []
72
 
73
- if uploaded_files:
74
- for file_path in uploaded_files:
75
- filename = os.path.basename(file_path)
76
-
77
- if not is_supported_file(filename):
78
- history.append({"role": "user", "content": user_message})
79
- history.append({
80
- "role": "assistant",
81
- "content": f"⚠️ File `{filename}` tidak didukung. Format yang didukung: CSV, Excel, PDF, Word, TXT, PNG, JPG, JSON, dan lainnya."
82
- })
83
- return history, thread_state
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
- # Upload file to Azure
 
86
  uploaded = project.agents.files.upload_and_poll(
87
- file_path=file_path,
88
  purpose=FilePurpose.AGENTS,
89
  )
90
-
91
  attachments.append(
92
  MessageAttachment(
93
  file_id=uploaded.id,
@@ -95,29 +116,45 @@ def respond(user_message: str, uploaded_files, history: list, thread_state: dict
95
  )
96
  )
97
  file_names.append(filename)
 
 
 
 
 
 
 
98
 
99
  # Build message content
100
- if file_names and user_message.strip():
101
- content = f"{user_message}\n\n📎 Files uploaded: {', '.join(file_names)}"
 
 
 
 
 
 
 
102
  elif file_names:
103
- content = f"Analisa file berikut: {', '.join(file_names)}"
 
 
 
 
 
 
104
  else:
105
  content = user_message
106
 
107
- # Send message with attachments
 
 
 
 
 
108
  if attachments:
109
- project.agents.messages.create(
110
- thread_id=thread_id,
111
- role="user",
112
- content=content,
113
- attachments=attachments,
114
- )
115
- else:
116
- project.agents.messages.create(
117
- thread_id=thread_id,
118
- role="user",
119
- content=content,
120
- )
121
 
122
  # Run agent
123
  run = project.agents.runs.create_and_process(
@@ -134,7 +171,7 @@ def respond(user_message: str, uploaded_files, history: list, thread_state: dict
134
  messages = project.agents.messages.list(
135
  thread_id=thread_id, order=ListSortOrder.DESCENDING
136
  )
137
- assistant_reply = "🤔 No response received."
138
  for msg in messages:
139
  if msg.role == "assistant" and msg.text_messages:
140
  assistant_reply = msg.text_messages[-1].text.value
@@ -142,13 +179,13 @@ def respond(user_message: str, uploaded_files, history: list, thread_state: dict
142
 
143
  except Exception as e:
144
  assistant_reply = (
145
- f"❌ Error: {str(e)}\n\n"
146
- "Please try again. If this persists, the Azure endpoint may be unavailable."
147
  )
148
 
149
- # Display with file indicator
150
  if file_names:
151
- display_msg = f"{user_message}\n\n📎 {', '.join(file_names)}" if user_message.strip() else f"📎 {', '.join(file_names)}"
152
  else:
153
  display_msg = user_message
154
 
@@ -161,7 +198,7 @@ def new_conversation():
161
  return [], None
162
 
163
 
164
- # CSS
165
  CSS = """
166
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
167
 
@@ -348,14 +385,14 @@ EXAMPLES = [
348
  ["Were there any anomalies in our metrics recently?"],
349
  ]
350
 
351
- # UI/UX
352
  with gr.Blocks(title="Agent Ken — Data-Informed PM Copilot") as demo:
353
 
354
  # Header
355
  gr.HTML("""
356
  <div class="header-main">
357
  <h1>🤖 Agent Ken</h1>
358
- <p class="tagline">Data-Informed PM + Social Impact Copilot</p>
359
  </div>
360
  """)
361
 
@@ -373,8 +410,10 @@ with gr.Blocks(title="Agent Ken — Data-Informed PM Copilot") as demo:
373
  gr.HTML("""
374
  <div class="welcome-card">
375
  <span class="wave">👋</span>
376
- <h2>Hi, I'm Agent Ken — your Data-Informed Product Manager Copilot</h2>
377
- <p>I help you learn product management from strategy and frameworks to real-world execution. I also share practical insights on AI technology and my own findings from building data pipelines and ML models in Microsoft Fabric.</p>
 
 
378
  <div class="welcome-tags">
379
  <span>📊 Live Product Data</span>
380
  <span>🔮 Churn Prediction</span>
@@ -382,7 +421,7 @@ with gr.Blocks(title="Agent Ken — Data-Informed PM Copilot") as demo:
382
  <span>⚠️ Anomaly Detection</span>
383
  <span>🧪 A/B Test Results</span>
384
  <span>📝 PRDs & Strategy</span>
385
- <span> Inclusive Design</span>
386
  </div>
387
  </div>
388
  """)
@@ -425,7 +464,6 @@ with gr.Blocks(title="Agent Ken — Data-Informed PM Copilot") as demo:
425
  <div class="upload-hint">
426
  📎 <strong>You can also upload your files!</strong> (Supported: CSV, Excel, PDF, Word, Images, TXT, JSON.)
427
  I'll analyze them and give you insights — and if you want, I can compare your data against our Fabric benchmark.
428
-
429
  </div>
430
  """)
431
 
@@ -437,10 +475,10 @@ with gr.Blocks(title="Agent Ken — Data-Informed PM Copilot") as demo:
437
 
438
  thread_state = gr.State(value=None)
439
 
440
- # Input row (text + send only)
441
  with gr.Row():
442
  msg_input = gr.Textbox(
443
- placeholder="Ask about metrics, AI, tech, my Fabric finding or any other related PM question...",
444
  label="",
445
  show_label=False,
446
  scale=5,
@@ -454,7 +492,7 @@ with gr.Blocks(title="Agent Ken — Data-Informed PM Copilot") as demo:
454
  with gr.Row():
455
  clear_btn = gr.Button("🗑️ New Conversation", variant="secondary", size="sm", scale=1)
456
 
457
- # File upload (hidden inside accordion — clean)
458
  with gr.Accordion("📎 Upload a file (CSV, Excel, PDF, Word, Images, JSON)", open=False):
459
  file_input = gr.File(
460
  label="",
@@ -484,7 +522,7 @@ with gr.Blocks(title="Agent Ken — Data-Informed PM Copilot") as demo:
484
  </div>
485
  """)
486
 
487
- # Events
488
  send_btn.click(
489
  fn=respond,
490
  inputs=[msg_input, file_input, chatbot, thread_state],
@@ -503,4 +541,4 @@ with gr.Blocks(title="Agent Ken — Data-Informed PM Copilot") as demo:
503
  )
504
 
505
  if __name__ == "__main__":
506
- demo.launch(server_name="0.0.0.0", server_port=7860, css=CSS, theme=gr.themes.Soft())
 
1
  """
2
+ Agent Ken — Data-Informed PM + AI Copilot
3
  Powered by Azure AI Foundry + Microsoft Fabric
4
  Hugging Face Space (Gradio) — with file upload support
5
  """
 
11
  from azure.identity import ClientSecretCredential
12
  from azure.ai.agents.models import ListSortOrder, FilePurpose, MessageAttachment, CodeInterpreterTool
13
 
14
+ # Azure config
15
  PROJECT_ENDPOINT = os.environ["PROJECT_ENDPOINT"]
16
  AGENT_ID = os.environ["AGENT_ID"]
17
  AZURE_TENANT_ID = os.environ["AZURE_TENANT_ID"]
18
  AZURE_CLIENT_ID = os.environ["AZURE_CLIENT_ID"]
19
  AZURE_CLIENT_SECRET = os.environ["AZURE_CLIENT_SECRET"]
20
 
21
+ # Azure client
22
  credential = ClientSecretCredential(
23
  tenant_id=AZURE_TENANT_ID,
24
  client_id=AZURE_CLIENT_ID,
 
36
  return _agent
37
 
38
 
39
+ # Supported file types
40
  SUPPORTED_EXTENSIONS = {
41
  ".csv", ".xlsx", ".xls", ".json",
42
  ".txt", ".md", ".pdf",
 
54
 
55
  # Chat logic
56
  def respond(user_message: str, uploaded_files, history: list, thread_state: dict | None):
57
+ # Normalize inputs
58
+ user_message = (user_message or "").strip()
59
+ if not uploaded_files:
60
+ uploaded_files = []
61
+
62
+ # Nothing to send
63
+ if not user_message and not uploaded_files:
64
  return history, thread_state
65
 
66
+ # Create thread if needed
67
  if thread_state is None or "thread_id" not in thread_state:
68
  thread = project.agents.threads.create()
69
  thread_state = {"thread_id": thread.id}
 
73
  try:
74
  agent = get_agent()
75
 
76
+ # Handle file uploads
77
  attachments = []
78
  file_names = []
79
 
80
+ for file_path in uploaded_files:
81
+ # Gradio returns NamedString or str path
82
+ path_str = str(file_path)
83
+ filename = os.path.basename(path_str)
84
+
85
+ # Check file exists on disk
86
+ if not os.path.exists(path_str):
87
+ history.append({"role": "user", "content": user_message or f"📎 {filename}"})
88
+ history.append({
89
+ "role": "assistant",
90
+ "content": f"⚠️ File `{filename}` could not be read. Please re-upload and try again."
91
+ })
92
+ return history, thread_state
93
+
94
+ # Check supported type
95
+ if not is_supported_file(filename):
96
+ history.append({"role": "user", "content": user_message or f"📎 {filename}"})
97
+ history.append({
98
+ "role": "assistant",
99
+ "content": (
100
+ f"⚠️ File `{filename}` is not supported.\n\n"
101
+ f"**Supported formats:** CSV, Excel, PDF, Word, TXT, PNG, JPG, JSON, PowerPoint, Python, HTML, XML."
102
+ )
103
+ })
104
+ return history, thread_state
105
 
106
+ # Upload to Azure
107
+ try:
108
  uploaded = project.agents.files.upload_and_poll(
109
+ file_path=path_str,
110
  purpose=FilePurpose.AGENTS,
111
  )
 
112
  attachments.append(
113
  MessageAttachment(
114
  file_id=uploaded.id,
 
116
  )
117
  )
118
  file_names.append(filename)
119
+ except Exception as upload_err:
120
+ history.append({"role": "user", "content": user_message or f"📎 {filename}"})
121
+ history.append({
122
+ "role": "assistant",
123
+ "content": f"⚠️ Failed to upload `{filename}`: {str(upload_err)}\n\nPlease try again."
124
+ })
125
+ return history, thread_state
126
 
127
  # Build message content
128
+ if file_names and user_message:
129
+ # User typed something + uploaded files
130
+ content = (
131
+ f"{user_message}\n\n"
132
+ f"📎 Uploaded files: {', '.join(file_names)}\n\n"
133
+ f"Instructions: Read and analyze the uploaded file(s) immediately using code interpreter. "
134
+ f"Show the data structure, key findings, and actionable insights. "
135
+ f"Do NOT ask the user to re-upload or confirm — the file is already attached and accessible."
136
+ )
137
  elif file_names:
138
+ # Only files, no message
139
+ content = (
140
+ f"📎 Uploaded files: {', '.join(file_names)}\n\n"
141
+ f"Instructions: Read and analyze the uploaded file(s) immediately using code interpreter. "
142
+ f"Show a summary of the data (columns, rows, data types), then provide key findings, "
143
+ f"trends, and actionable insights. Start analyzing right away."
144
+ )
145
  else:
146
  content = user_message
147
 
148
+ # Send message to Azure
149
+ msg_kwargs = {
150
+ "thread_id": thread_id,
151
+ "role": "user",
152
+ "content": content,
153
+ }
154
  if attachments:
155
+ msg_kwargs["attachments"] = attachments
156
+
157
+ project.agents.messages.create(**msg_kwargs)
 
 
 
 
 
 
 
 
 
158
 
159
  # Run agent
160
  run = project.agents.runs.create_and_process(
 
171
  messages = project.agents.messages.list(
172
  thread_id=thread_id, order=ListSortOrder.DESCENDING
173
  )
174
+ assistant_reply = "🤔 No response received. Please try again."
175
  for msg in messages:
176
  if msg.role == "assistant" and msg.text_messages:
177
  assistant_reply = msg.text_messages[-1].text.value
 
179
 
180
  except Exception as e:
181
  assistant_reply = (
182
+ f"❌ Connection error: {str(e)}\n\n"
183
+ "Please try again. If this persists, the Azure endpoint may be temporarily unavailable."
184
  )
185
 
186
+ # Display in chat
187
  if file_names:
188
+ display_msg = f"{user_message}\n\n📎 {', '.join(file_names)}" if user_message else f"📎 {', '.join(file_names)}"
189
  else:
190
  display_msg = user_message
191
 
 
198
  return [], None
199
 
200
 
201
+ # CSS
202
  CSS = """
203
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
204
 
 
385
  ["Were there any anomalies in our metrics recently?"],
386
  ]
387
 
388
+ # UI
389
  with gr.Blocks(title="Agent Ken — Data-Informed PM Copilot") as demo:
390
 
391
  # Header
392
  gr.HTML("""
393
  <div class="header-main">
394
  <h1>🤖 Agent Ken</h1>
395
+ <p class="tagline">Your AI Companion for Product Management & Data</p>
396
  </div>
397
  """)
398
 
 
410
  gr.HTML("""
411
  <div class="welcome-card">
412
  <span class="wave">👋</span>
413
+ <h2>Hi, I'm Agent Ken — your AI Companion for Product Management & Data</h2>
414
+ <p>I help you learn product management, explore AI technology, and discover insights from
415
+ my hands-on experience with Microsoft Fabric — including data pipelines, ML models,
416
+ and turning raw data into real product decisions.</p>
417
  <div class="welcome-tags">
418
  <span>📊 Live Product Data</span>
419
  <span>🔮 Churn Prediction</span>
 
421
  <span>⚠️ Anomaly Detection</span>
422
  <span>🧪 A/B Test Results</span>
423
  <span>📝 PRDs & Strategy</span>
424
+ <span>🤖 AI & Tech</span>
425
  </div>
426
  </div>
427
  """)
 
464
  <div class="upload-hint">
465
  📎 <strong>You can also upload your files!</strong> (Supported: CSV, Excel, PDF, Word, Images, TXT, JSON.)
466
  I'll analyze them and give you insights — and if you want, I can compare your data against our Fabric benchmark.
 
467
  </div>
468
  """)
469
 
 
475
 
476
  thread_state = gr.State(value=None)
477
 
478
+ # Input row (text + send)
479
  with gr.Row():
480
  msg_input = gr.Textbox(
481
+ placeholder="Ask about metrics, upload a file, or any PM/tech question...",
482
  label="",
483
  show_label=False,
484
  scale=5,
 
492
  with gr.Row():
493
  clear_btn = gr.Button("🗑️ New Conversation", variant="secondary", size="sm", scale=1)
494
 
495
+ # File upload (inside accordion — clean)
496
  with gr.Accordion("📎 Upload a file (CSV, Excel, PDF, Word, Images, JSON)", open=False):
497
  file_input = gr.File(
498
  label="",
 
522
  </div>
523
  """)
524
 
525
+ # Events
526
  send_btn.click(
527
  fn=respond,
528
  inputs=[msg_input, file_input, chatbot, thread_state],
 
541
  )
542
 
543
  if __name__ == "__main__":
544
+ demo.launch(server_name="0.0.0.0", server_port=7860, css=CSS, theme=gr.themes.Soft())