tan-en-yao commited on
Commit
3372085
Β·
1 Parent(s): b7d7f2a

feat: parse real tool responses in agent execution summary

Browse files
Files changed (1) hide show
  1. app.py +139 -45
app.py CHANGED
@@ -112,56 +112,136 @@ def analyze_image(image_path: str) -> Optional[str]:
112
 
113
 
114
  def _extract_result_summary(observations: str) -> str:
115
- """Extract a short summary from tool observations."""
116
  import re
117
  if not observations:
118
  return ""
119
  obs_lower = observations.lower()
120
 
121
- # Address validation - extract formatted address or borough
122
- if "borough" in obs_lower or "manhattan" in obs_lower or "brooklyn" in obs_lower:
123
- # Try formatted address first
124
- match = re.search(r'"formatted_address"[:\s]*"([^"]+)"', observations)
125
- if match:
126
- addr = match.group(1)
127
- lat_match = re.search(r'"lat"[:\s]*(-?\d+\.?\d*)', observations)
128
- lon_match = re.search(r'"lon"[:\s]*(-?\d+\.?\d*)', observations)
129
- if lat_match and lon_match:
130
- return f"{addr[:30]} ({lat_match.group(1)[:6]}, {lon_match.group(1)[:7]})"
131
- return addr[:40]
132
- match = re.search(r'"borough"[:\s]*"([^"]+)"', observations)
133
- if match:
134
- return match.group(1)
135
-
136
- # Nearby reports
137
- if "nearby" in obs_lower or "similar" in obs_lower:
138
- match = re.search(r'(\d+)\s*(?:similar|nearby|reports)', obs_lower)
139
- if match:
140
- return f"{match.group(1)} similar nearby"
141
-
142
- # Weather
143
- if "temperature" in obs_lower or "weather" in obs_lower:
144
- temp_match = re.search(r'(\d+)[Β°]?[FCfc]', observations)
145
  if temp_match:
146
- return f"{temp_match.group(1)}Β°F"
147
-
148
- # Asset lookup
149
- if "asset_id" in obs_lower:
150
- match = re.search(r'"asset_id"[:\s]*"([^"]+)"', observations)
151
- if match:
152
- return f"Asset: {match.group(1)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
 
154
- # PDF report
155
- if "report_id" in obs_lower:
156
- match = re.search(r'"report_id"[:\s]*"([^"]+)"', observations)
157
- if match:
158
- return f"Report: {match.group(1)}"
159
-
160
- # Email
161
- if "email" in obs_lower and "sent" in obs_lower:
162
- return "Sent to department"
163
-
164
- return "Done"
165
 
166
 
167
  def _extract_tool_input_preview(tool_name: str, input_str: str) -> str:
@@ -253,7 +333,7 @@ def build_agent_summary(tool_history: list, total_duration: str = "") -> str:
253
  """
254
  Build a compact summary of agent execution for final display.
255
 
256
- Shows the agent hierarchy with completed tools (no timing indicator).
257
  """
258
  if not tool_history:
259
  return ""
@@ -263,11 +343,23 @@ def build_agent_summary(tool_history: list, total_duration: str = "") -> str:
263
  agent_icons = {"triage": "🎯", "research": "πŸ”", "report": "πŸ“‹"}
264
  agent_names = {"triage": "Triage Agent", "research": "Research Agent", "report": "Report Agent"}
265
 
 
 
 
266
  for tool in tool_history:
267
  agent = tool.get("agent")
268
  name = tool.get("display_name", tool.get("tool", "Unknown"))
269
  result = tool.get("result_summary", "")
270
 
 
 
 
 
 
 
 
 
 
271
  # Show agent header when agent changes
272
  if agent and agent != current_agent:
273
  current_agent = agent
@@ -275,7 +367,7 @@ def build_agent_summary(tool_history: list, total_duration: str = "") -> str:
275
  agent_name = agent_names.get(agent, agent.title())
276
  lines.append(f"● {icon} **{agent_name}**")
277
 
278
- # Tool with result (compact)
279
  if result:
280
  lines.append(f" └─ {name} β†’ {result}")
281
  else:
@@ -742,6 +834,8 @@ with gr.Blocks(title="FixMyNeighborhood - Multi-Agent Reporter") as demo:
742
  - **Reasoning Trace** - See the agent's thought process in real-time
743
  - **Completeness Check** - Smart follow-up questions when info is missing
744
  - **Quality Self-Check** - Reports scored before submission
 
 
745
  """)
746
 
747
  with gr.Row():
 
112
 
113
 
114
  def _extract_result_summary(observations: str) -> str:
115
+ """Extract a short, meaningful summary from tool observations."""
116
  import re
117
  if not observations:
118
  return ""
119
  obs_lower = observations.lower()
120
 
121
+ # Weather - extract temp + conditions + hazard level
122
+ if "temp_f" in obs_lower or ("conditions" in obs_lower and "hazard" in obs_lower):
123
+ temp_match = re.search(r"['\"]temp_f['\"]:\s*(\d+)", observations)
124
+ cond_match = re.search(r"['\"]conditions['\"]:\s*['\"]([^'\"]+)['\"]", observations)
125
+ hazard_match = re.search(r"['\"]hazard_level['\"]:\s*['\"]([^'\"]+)['\"]", observations)
126
+ parts = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  if temp_match:
128
+ parts.append(f"{temp_match.group(1)}Β°F")
129
+ if cond_match:
130
+ parts.append(cond_match.group(1)[:20])
131
+ if hazard_match and hazard_match.group(1) != "low":
132
+ parts.append(f"⚠️{hazard_match.group(1)}")
133
+ if parts:
134
+ return ", ".join(parts)
135
+
136
+ # Address validation - extract borough + formatted address
137
+ if "is_valid_nyc" in obs_lower or "formatted_address" in obs_lower:
138
+ borough_match = re.search(r"['\"]borough['\"]:\s*['\"]([^'\"]+)['\"]", observations)
139
+ addr_match = re.search(r"['\"]formatted_address['\"]:\s*['\"]([^'\"]+)['\"]", observations)
140
+ valid_match = re.search(r"['\"]is_valid_nyc['\"]:\s*(true|false)", obs_lower)
141
+
142
+ if valid_match and valid_match.group(1) == "false":
143
+ return "❌ Not NYC"
144
+ if addr_match:
145
+ addr = addr_match.group(1)
146
+ # Shorten long addresses
147
+ if len(addr) > 35:
148
+ addr = addr[:32] + "..."
149
+ return f"βœ“ {addr}"
150
+ if borough_match:
151
+ return f"βœ“ {borough_match.group(1)}"
152
+
153
+ # Nearby reports - extract count + pattern info
154
+ if "total_reports" in obs_lower or "nearby" in obs_lower:
155
+ total_match = re.search(r"['\"]total_reports['\"]:\s*(\d+)", observations)
156
+ open_match = re.search(r"['\"]open_reports['\"]:\s*(\d+)", observations)
157
+ pattern_match = re.search(r"['\"]pattern_detected['\"]:\s*(true|false)", obs_lower)
158
+
159
+ if total_match:
160
+ total = int(total_match.group(1))
161
+ open_count = int(open_match.group(1)) if open_match else 0
162
+ pattern = pattern_match and pattern_match.group(1) == "true"
163
+
164
+ if total == 0:
165
+ return "No similar reports"
166
+ result = f"{total} reports"
167
+ if open_count > 0:
168
+ result += f" ({open_count} open)"
169
+ if pattern:
170
+ result += " πŸ”„"
171
+ return result
172
+
173
+ # Asset lookup - extract asset_id + status + complaints
174
+ if "asset_id" in obs_lower or "asset_type" in obs_lower:
175
+ asset_match = re.search(r"['\"]asset_id['\"]:\s*['\"]([^'\"]+)['\"]", observations)
176
+ status_match = re.search(r"['\"]status['\"]:\s*['\"]([^'\"]+)['\"]", observations)
177
+ complaints_match = re.search(r"['\"]recent_complaints['\"]:\s*(\d+)", observations)
178
+
179
+ parts = []
180
+ if asset_match:
181
+ asset_id = asset_match.group(1)
182
+ if len(asset_id) > 20:
183
+ asset_id = asset_id[:17] + "..."
184
+ parts.append(asset_id)
185
+ if complaints_match:
186
+ parts.append(f"{complaints_match.group(1)} complaints")
187
+ elif status_match:
188
+ status = status_match.group(1).replace("_", " ")
189
+ parts.append(status[:15])
190
+
191
+ if parts:
192
+ return ", ".join(parts)
193
+
194
+ # PDF report - extract report_id + status
195
+ if "report_id" in obs_lower and ("generated" in obs_lower or "pdf" in obs_lower):
196
+ report_match = re.search(r"['\"]report_id['\"]:\s*['\"]([^'\"]+)['\"]", observations)
197
+ status_match = re.search(r"['\"]status['\"]:\s*['\"]([^'\"]+)['\"]", observations)
198
+ size_match = re.search(r"['\"]size_kb['\"]:\s*([\d.]+)", observations)
199
+
200
+ if report_match:
201
+ report_id = report_match.group(1)
202
+ if status_match and status_match.group(1) == "generated":
203
+ size_str = f" ({size_match.group(1)}KB)" if size_match else ""
204
+ return f"πŸ“„ {report_id}{size_str}"
205
+ return f"πŸ“„ {report_id}"
206
+
207
+ # Email sent - extract recipient info (demo mode - sink)
208
+ if "email" in obs_lower or "sendgrid" in obs_lower:
209
+ if "success" in obs_lower or "sent" in obs_lower or "queued" in obs_lower:
210
+ to_match = re.search(r"['\"]to['\"]:\s*['\"]([^'\"]+)['\"]", observations)
211
+ if to_match:
212
+ email = to_match.group(1)
213
+ # Show just domain for privacy
214
+ if "@" in email:
215
+ domain = email.split("@")[1]
216
+ return f"βœ‰οΈ β†’ @{domain} (demo sink)"
217
+ return "βœ‰οΈ Sent (demo sink)"
218
+ if "error" in obs_lower or "failed" in obs_lower:
219
+ return "❌ Email failed"
220
+
221
+ # Department info - extract name + response time
222
+ if "department" in obs_lower or "response_time" in obs_lower:
223
+ name_match = re.search(r"['\"]name['\"]:\s*['\"]([^'\"]+)['\"]", observations)
224
+ time_match = re.search(r"['\"]response_time['\"]:\s*['\"]([^'\"]+)['\"]", observations)
225
+
226
+ if name_match:
227
+ name = name_match.group(1)[:25]
228
+ if time_match:
229
+ return f"{name} ({time_match.group(1)})"
230
+ return name
231
+
232
+ # Generic borough detection (fallback)
233
+ for borough in ["Manhattan", "Brooklyn", "Queens", "Bronx", "Staten Island"]:
234
+ if borough.lower() in obs_lower:
235
+ return f"βœ“ {borough}"
236
+
237
+ # Error detection
238
+ if "error" in obs_lower:
239
+ error_match = re.search(r"['\"]error['\"]:\s*['\"]([^'\"]+)['\"]", observations)
240
+ if error_match:
241
+ err = error_match.group(1)[:30]
242
+ return f"⚠️ {err}"
243
 
244
+ return ""
 
 
 
 
 
 
 
 
 
 
245
 
246
 
247
  def _extract_tool_input_preview(tool_name: str, input_str: str) -> str:
 
333
  """
334
  Build a compact summary of agent execution for final display.
335
 
336
+ Shows the agent hierarchy with completed tools and meaningful results.
337
  """
338
  if not tool_history:
339
  return ""
 
343
  agent_icons = {"triage": "🎯", "research": "πŸ”", "report": "πŸ“‹"}
344
  agent_names = {"triage": "Triage Agent", "research": "Research Agent", "report": "Report Agent"}
345
 
346
+ # Skip internal/meta tools that don't add value to summary
347
+ skip_tools = {"Finalizing Response", "πŸ“ Map updated"}
348
+
349
  for tool in tool_history:
350
  agent = tool.get("agent")
351
  name = tool.get("display_name", tool.get("tool", "Unknown"))
352
  result = tool.get("result_summary", "")
353
 
354
+ # Skip meta tools unless they have meaningful results
355
+ if name in skip_tools and not result:
356
+ continue
357
+ # Keep map update if it has location info
358
+ if name == "πŸ“ Map updated":
359
+ if result:
360
+ lines.append(f" └─ πŸ“ {result}")
361
+ continue
362
+
363
  # Show agent header when agent changes
364
  if agent and agent != current_agent:
365
  current_agent = agent
 
367
  agent_name = agent_names.get(agent, agent.title())
368
  lines.append(f"● {icon} **{agent_name}**")
369
 
370
+ # Tool with result (compact, meaningful)
371
  if result:
372
  lines.append(f" └─ {name} β†’ {result}")
373
  else:
 
834
  - **Reasoning Trace** - See the agent's thought process in real-time
835
  - **Completeness Check** - Smart follow-up questions when info is missing
836
  - **Quality Self-Check** - Reports scored before submission
837
+
838
+ > ⚠️ **Demo Mode:** Emails are sent to a sink (not delivered to real recipients)
839
  """)
840
 
841
  with gr.Row():