feat: text streaming improvements and CMU Maps integration #1

Merged
Jc-965 merged 0 commits from main into main 2026-05-24 17:31:35 +00:00
Jc-965 commented 2026-05-24 07:40:52 +00:00 (Migrated from codeberg.org)

Summary

This PR adds CMU Maps support to the agent so the Surface can render a building or directions iframe alongside each response. It also includes small streaming and parser improvements to make tool-heavy answers feel more responsive.

Changes

CMU Maps integration

  • Add a CmuMaps Pydantic model on AgentResponse carrying url, mode ("location" / "directions"), and target/src/dest IDs + human labels. The Surface needs the matching cmu_maps payload handling to actually render the iframe (separate PR on cmugpt-surface).
  • The URL points at https://maps.scottylabs.org/{id} with either ?dst={id} (location mode) or ?src={src}&dst={dest} (directions mode); the Surface plugs that URL straight into an <iframe>. IDs and labels are kept as separate fields so the iframe header can show readable names ("Tepper School of Business") while the URL stays canonical.
  • Curated alias table (KNOWN_CMU_LOCATIONS) in agent_hub.py maps shorter building names ("ghc", "tepper", "uc", "mm", etc.) to canonical CMU Maps IDs. Matching is case-insensitive and word-boundary based, and the bare ID form ("GHC", "MM") is also accepted directly when it appears in the query or in a tool result. Aliases are ordered longest-first so "hillman center" resolves before "hillman" and "gates hillman centers" wins over "gates". Queries for buildings outside this list will return cmu_maps.url = null and fall back to plain text so no fabricated URL ever reaches the Surface.
  • _infer_cmu_maps derives the payload deterministically from the user query and any tool invocations (maps_search_buildings, maps_get_path, maps_distance_between, …) rather than trusting the model.
  • It walks a fixed priority order: (1) explicit directions tool calls win: maps_get_path / maps_distance_between's start_id and end_id are used directly; (2) otherwise, parse "from X to Y" / "between X and Y" / "X from Y" patterns out of the query; (3) otherwise, look at building-search tool results for a "Name (ID)" match; (4) finally, fall back to "where is X" / "show me X" / "directions to X" patterns in the query. Returns an empty CmuMaps (all fields null) if no rule fires.
  • _apply_cmu_maps_guard attaches the payload to responses and, when the model claims it couldn't find a location, OVERRIDES response_text with canned success text plus the working URL. Failure claims are matched by MAP_FAILURE_CLAIM_RE (catches phrasings like "couldn't find", "wasn't able", "unable to retrieve", "didn't find the location", …). The replacement text comes from _cmu_maps_success_text, a numbered step list for directions, or a one-liner pointing at the building for single locations so the user sees a usable answer even when the model itself gave up.
  • The model is not the source of truth for map IDs, so when it gives up we replace its prose with a canned answer pointing at the working map. The guard also clears the field entirely if the model fabricated a non-scottylabs.org URL.

Streaming

  • New SSE map event emitted before text starts streaming so the iframe can render immediately, before the first text delta.
  • event: map documented in the /agent/respond/stream docstring.

Parser robustness

  • _coerce_agent_response salvages the user-facing response_text when the model emits slightly malformed JSON (stray quotes, missing fields), so one bad token no longer kills a whole turn.

Tests

  • New test_cmu_maps_route_aliases exercises the alias-resolution path end-to-end.
  • Test helpers import _infer_cmu_maps and _cmu_maps_success_text so the maps inference logic is directly covered.
## Summary This PR adds CMU Maps support to the agent so the Surface can render a building or directions iframe alongside each response. It also includes small streaming and parser improvements to make tool-heavy answers feel more responsive. ## Changes **CMU Maps integration** - Add a `CmuMaps` Pydantic model on `AgentResponse` carrying `url`, `mode` (`"location"` / `"directions"`), and `target`/`src`/`dest` IDs + human labels. The Surface needs the matching `cmu_maps` payload handling to actually render the iframe (separate PR on `cmugpt-surface`). - The URL points at `https://maps.scottylabs.org/{id}` with either `?dst={id}` (location mode) or `?src={src}&dst={dest}` (directions mode); the Surface plugs that URL straight into an `<iframe>`. IDs and labels are kept as separate fields so the iframe header can show readable names ("Tepper School of Business") while the URL stays canonical. - Curated alias table (`KNOWN_CMU_LOCATIONS`) in `agent_hub.py` maps shorter building names (`"ghc"`, `"tepper"`, `"uc"`, `"mm"`, etc.) to canonical CMU Maps IDs. Matching is case-insensitive and word-boundary based, and the bare ID form (`"GHC"`, `"MM"`) is also accepted directly when it appears in the query or in a tool result. Aliases are ordered longest-first so `"hillman center"` resolves before `"hillman"` and `"gates hillman centers"` wins over `"gates"`. Queries for buildings outside this list will return `cmu_maps.url = null` and fall back to plain text so no fabricated URL ever reaches the Surface. - `_infer_cmu_maps` derives the payload deterministically from the user query and any tool invocations (`maps_search_buildings`, `maps_get_path`, `maps_distance_between`, …) rather than trusting the model. - It walks a fixed priority order: (1) explicit directions tool calls win: `maps_get_path` / `maps_distance_between`'s `start_id` and `end_id` are used directly; (2) otherwise, parse `"from X to Y"` / `"between X and Y"` / `"X from Y"` patterns out of the query; (3) otherwise, look at building-search tool results for a `"Name (ID)"` match; (4) finally, fall back to `"where is X"` / `"show me X"` / `"directions to X"` patterns in the query. Returns an empty `CmuMaps` (all fields null) if no rule fires. - `_apply_cmu_maps_guard` attaches the payload to responses and, when the model claims it couldn't find a location, OVERRIDES `response_text` with canned success text plus the working URL. Failure claims are matched by `MAP_FAILURE_CLAIM_RE` (catches phrasings like "couldn't find", "wasn't able", "unable to retrieve", "didn't find the location", …). The replacement text comes from `_cmu_maps_success_text`, a numbered step list for directions, or a one-liner pointing at the building for single locations so the user sees a usable answer even when the model itself gave up. - The model is not the source of truth for map IDs, so when it gives up we replace its prose with a canned answer pointing at the working map. The guard also clears the field entirely if the model fabricated a non-`scottylabs.org` URL. **Streaming** - New SSE `map` event emitted before text starts streaming so the iframe can render immediately, before the first text delta. - `event: map` documented in the `/agent/respond/stream` docstring. **Parser robustness** - `_coerce_agent_response` salvages the user-facing `response_text` when the model emits slightly malformed JSON (stray quotes, missing fields), so one bad token no longer kills a whole turn. **Tests** - New `test_cmu_maps_route_aliases` exercises the alias-resolution path end-to-end. - Test helpers import `_infer_cmu_maps` and `_cmu_maps_success_text` so the maps inference logic is directly covered.
Sign in to join this conversation.
No description provided.