Commit Β·
3372085
1
Parent(s): b7d7f2a
feat: parse real tool responses in agent execution summary
Browse files
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 |
-
#
|
| 122 |
-
if "
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 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 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
if
|
| 152 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
|
| 154 |
-
|
| 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
|
| 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():
|