Rich Results
Render tool results as interactive maps, cards, and structured UI instead of raw JSON.
Most AI APIs return plain text. Galadri returns structured data your UI can render as interactive maps, scrollable cards, and visual components. The AI gets compact summaries to stay efficient while your client gets the full picture.
Chat surfaces only
Rich results are a chat-surface feature. Email, SMS, and voice channels do not expose display directives, maps, cards, suggested prompts, or mutation cards to the model. Those channels should receive only the finished delivered content.
Overview
When an agent calls tools like vehicle search, gas price lookup, or Google Maps, the API returns full structured data in tool_result events. Instead of displaying raw JSON, you can render this data as interactive maps, scrollable cards, and other visual components.
The system works in three layers:
- Stable IDs — Every result item has a unique
_idfield (e.g.,listing_0,station_3) that the agent uses to reference specific results in conversation. - Display capabilities — Per-agent configuration that tells the AI what your UI can render, so it adapts its text accordingly.
- Client-side rendering — Your app receives the full result data via SSE and renders it using your own components.
No extra API calls needed
Rich results use data you already receive. The tool_result SSE event contains the full structured data. You just need to parse it and render the appropriate UI.
Stable IDs
Every array item in a tool result includes an _id field. The agent uses these IDs when referencing results in its response text (e.g., "Station 3 has the best price" corresponds to station_2 in the data).
ID prefixes by tool
| Field | Type | Description |
|---|---|---|
listing_N | vehicle-search | Vehicle sales listings |
place_N | google-maps-search | Places from Google Maps |
station_N | gas-price-lookup | Gas stations with fuel prices |
station_N | ev-charger-lookup / ev-charger-advanced | EV charging stations |
shop_N | repair-booking (find_shops) | Repair shops |
incident_N | traffic (nearby_incidents) | Traffic incidents |
{"type": "tool_result", "id": "tc_1", "results": [
{
"tool": "gas-price-lookup",
"status": "success",
"data": {
"stations": [
{
"_id": "station_0",
"brand": "Shell",
"address": "1234 E Camelback Rd, Phoenix, AZ",
"latitude": 33.5092,
"longitude": -112.0148,
"distance_miles": 0.8,
"prices": [
{"fuel_type": "regular", "price_per_gallon": 3.29},
{"fuel_type": "premium", "price_per_gallon": 3.89}
]
},
{
"_id": "station_1",
"brand": "Chevron",
"address": "5678 N 7th St, Phoenix, AZ",
"latitude": 33.5187,
"longitude": -112.0651,
"distance_miles": 1.4,
"prices": [
{"fuel_type": "regular", "price_per_gallon": 3.35}
]
}
]
}
}
]}Display Capabilities
Display capabilities tell the agent what visual components your UI supports. The agent adjusts its text responses accordingly. For example, if your UI renders maps, the agent might say "See the locations on the map below" instead of listing every address. If maps are disabled, it will describe locations in full text.
Within a chat-capable surface, rich rendering is explicit. If the assistant does not emit a display directive, clients should not invent fallback maps or cards from tool results on their own.
Configure display capabilities per agent in the Galadri Console under the "Display Capabilities" section. They are stored as part of the agent configuration and loaded automatically on each chat request.
Available capabilities
| Field | Type | Description |
|---|---|---|
maps | boolean | Interactive maps with location markers, route polylines, and traffic visualization. Affects google-maps-search, gas-price-lookup, ev-charger-lookup, ev-charger-advanced, traffic, and fleet-routing results. |
vehicle_cards | boolean | Vehicle listing cards with photos, price, specs, and dealer info. Affects vehicle-search results. |
station_cards | boolean | Station cards for gas prices and EV charger availability. Affects gas-price-lookup, ev-charger-lookup, and ev-charger-advanced results. |
shop_cards | boolean | Repair shop and place cards with ratings, address, and contact info. Affects repair-booking (find_shops) and google-maps-search results. |
booking_confirm | boolean | Booking confirmation cards. When enabled, repair bookings show a confirmation UI before executing. When disabled, bookings execute immediately. |
data_cards | object | Data mutation cards showing create/update/delete confirmations. Update cards should focus on only the fields the agent actually changed. |
data_cards.enabled | boolean | Show data mutation confirmation cards. |
All capabilities default to true. If you do not set display capabilities, the agent assumes your UI supports everything. Set capabilities to false for types your UI does not render, so the agent provides richer text descriptions instead.
Text-only mode
If your UI is text-only (no maps, no cards), set all capabilities to false. The agent will describe all results in full text, including addresses, prices, ratings, and directions.
Mutation card exception
Data mutation cards are the one system-generated exception. In chat UIs, Galadri may show create, update, or delete confirmations even when the assistant did not explicitly place a display directive for them. Do not mirror those cards into email, SMS, or voice outputs.
Rendering Tool Results
When you receive a tool_result event, check the tool slug and render the appropriate UI. Here is a minimal example of dispatching tool results to different renderers:
function renderToolResult(tool: string, data: any) {
switch (tool) {
case "vehicle-search":
return <VehicleCards listings={data.listings} />;
case "google-maps-search":
return <PlaceCards places={data.results} />;
case "gas-price-lookup":
return <GasStationCards stations={data.stations} />;
case "ev-charger-lookup":
case "ev-charger-advanced":
return <EvChargerCards stations={data.stations} />;
case "repair-booking":
if (data.shops) return <RepairShopCards shops={data.shops} />;
return null;
case "traffic":
if (data.incidents) return <IncidentCards incidents={data.incidents} />;
return null;
default:
return null; // Fall back to text
}
}Maps
Most location-based tools return latitude and longitude on each result item. You can plot these on an interactive map with numbered markers. The following tools include coordinates:
Maps only render when the assistant explicitly emits a display directive that requests a map layout. There is no automatic fallback map at the bottom of the message.
When available, route cards can also own the external navigation handoff. If the inline route map already renders an "Open directions in Google Maps" control, the assistant should reference that card instead of printing a second Markdown navigation link in the text. Larger optimized routes may render the inline map without that control.
google-maps-search— place markersgas-price-lookup— station markersev-charger-lookup/ev-charger-advanced— charger markersrepair-booking(find_shops) — shop markerstraffic(nearby_incidents) — incident markerstraffic(route_traffic) — route polyline viaroute_pointsfleet-routing(optimize_route) — optimized stop markers plus a Google-rendered route polyline
function extractMarkers(tool: string, data: any) {
const items =
data.stations || data.results || data.shops ||
data.incidents || data.optimized_order || [];
return items
.filter((item: any) => item.latitude != null && item.longitude != null)
.map((item: any, i: number) => ({
id: item._id || `marker_${i}`,
lat: item.latitude,
lng: item.longitude,
label: item.name || item.brand || item.category || `Result ${i + 1}`,
}));
}Result Schemas by Tool
Each tool returns data in a consistent structure. Below are the key fields available for rendering. All array items include an _id field for referencing.
vehicle-search
Returns data.listings[]
| Field | Type | Description |
|---|---|---|
_id | string | Stable ID (e.g., "listing_0") |
year | integer | Model year |
make | string | Vehicle make |
model | string | Vehicle model |
trim | string | Trim level |
price_usd | number | Listing price |
miles | number | Odometer reading |
car_type | string | Body type (sedan, SUV, etc.) |
photo_url | string | Primary photo URL |
listing_url | string | Link to dealer listing |
dealer_name | string | Dealer name |
dealer_city | string | Dealer city |
dealer_state | string | Dealer state |
google-maps-search
Returns data.results[]
| Field | Type | Description |
|---|---|---|
_id | string | Stable ID (e.g., "place_0") |
name | string | Place name |
address | string | Formatted address |
rating | number | Google rating (1-5) |
latitude | number | Latitude |
longitude | number | Longitude |
open_now | boolean | Currently open |
phone | string | Phone number |
website | string | Website URL |
gas-price-lookup
Returns data.stations[]
| Field | Type | Description |
|---|---|---|
_id | string | Stable ID (e.g., "station_0") |
brand | string | Gas station brand |
name | string | Station name |
latitude | number | Latitude |
longitude | number | Longitude |
distance_miles | number | Distance from search location |
prices | array | Fuel prices: [{fuel_type, price_per_gallon}] |
ev-charger-lookup / ev-charger-advanced
Returns data.stations[]
| Field | Type | Description |
|---|---|---|
_id | string | Stable ID (e.g., "station_0") |
name | string | Station name |
network | string | Charging network (ChargePoint, Tesla, etc.) |
latitude | number | Latitude |
longitude | number | Longitude |
distance_miles | number | Distance from search location |
ports | object | Port counts: {dc_fast, level_2} (basic lookup) |
available_points | integer | Currently available ports (advanced only) |
total_charging_points | integer | Total ports (advanced only) |
connectors | array | Connector details: [{type, max_power_kw}] (advanced only) |
repair-booking (find_shops)
Returns data.shops[]
| Field | Type | Description |
|---|---|---|
_id | string | Stable ID (e.g., "shop_0") |
name | string | Shop name |
address | string | Full address |
rating | number | Rating |
distance | string | Distance from search location |
latitude | number | Latitude (when available) |
longitude | number | Longitude (when available) |
phone | string | Phone number |
pricing | string | Estimated pricing |
traffic
The nearby_incidents action returns data.incidents[]. The route_traffic action returns data.route_points[] for polyline rendering.
Incident fields
| Field | Type | Description |
|---|---|---|
_id | string | Stable ID (e.g., "incident_0") |
category | string | Incident type (Accident, Construction, etc.) |
description | string | Incident description |
severity | integer | Severity (1=minor, 2=moderate, 3=major, 4=severe) |
latitude | number | Latitude |
longitude | number | Longitude |
delay_seconds | number | Estimated delay in seconds |
from | string | Road/location name |
Data Flow
Tool results flow through two paths simultaneously:
- To your client — Full structured data with all fields, coordinates, photos, and URLs via the
tool_resultSSE event. This is what you render. - To the AI agent — Compact one-line summaries (e.g.,
"station_0: Shell, regular $3.29/gal, 0.8 mi"). This keeps the agent's context window efficient while it still has enough information to discuss results by ID.
This split means the agent will reference results by their _id or index number in its response text, while your UI can show the full rich data alongside that text.
Recommended rendering order
For each assistant message, render in this order: (1) assistant text, (2) interactive map (if location data present), (3) result cards in a horizontal scroll strip, (4) data mutation cards (if data was saved).
See also: Chat API tool_result events and Tools reference