Agentic Systems
Wired an LLM to 4 backend tools instead of RAG, over a real 9,095-record transit dataset
Ask it how to get across Islamabad by CDA bus, in plain language, misspelled stop names and all — the assistant calls the right tools in the right order, corrects your spelling, and streams back a real route with real departure times.
Stack
- Python
- OpenAI
- React
- JavaScript
9,095
route-stop records
extracted from CDA’s published PDF schedules
4
backend tools
called autonomously, chained when the first misses
460
lines of backend Python
5 files — FastAPI + pandas + OpenAI
The brief
Overview
"Capital Transit" is an agentic AI trip-planning assistant for Islamabad’s CDA (Capital Development Authority) bus network, built on top of a FAST-NUCES process-mining course project (SE4009) whose core assignment was extracting structured route data from the transit authority’s published PDF schedules into an event log.
The course project stopped at the dataset. I took it further, solo: a full conversational product on a real 9,095-record dataset of route-stop pairs, with an LLM that calls backend tools directly rather than reasoning over retrieved text.
The architecture choice
Why tool-calling, not RAG
A transit query isn’t a search problem — it’s sequencing, transfer math and timestamp comparison.
Retrieval-augmented generation is the default reach for "answer questions over a dataset." Why wire the model to explicit tools instead?
RAG’s semantic search can’t reliably do sequencing, transfer math, or timestamp comparison — the actual operations a route query needs, not a fuzzy match over route descriptions.
Wiring `gpt-4o-mini` to 4 backend tools (`get_all_routes`, `find_direct_route`, `find_transfer_routes`, `get_schedule_and_time`) that it calls autonomously means the model reasons about WHICH tool to call and WHEN to chain a second one, while the actual route-finding stays deterministic Python and pandas — not a language model guessing at arithmetic.
The alternative
Embed route descriptions and retrieve the closest match to the user’s question.
What it cost
Tool-calling requires hand-authoring every operation the assistant can perform — there is no "just add more documents" path the way RAG has; the 4-tool surface is deliberately small rather than exhaustive.
Engineering details
What makes the agent trustworthy on a real query
3-tier fuzzy stop resolution — Exact case-insensitive match → substring match → `difflib` fuzzy fallback, so a misspelled stop ("Khanapul") silently resolves to the real dataset value ("Khanna Pul") before any query runs — surfaced back to the user via the model’s own acknowledgement of the resolved name.
Bidirectional transfer-route discovery — Intersects the stop-sets of every route pair reachable from the origin and destination respectively, capped at 3 results per query to keep answers focused rather than exhaustive.
A real data-modelling bug, found and fixed — The extracted schedule data was sequence-numbered per-trip-per-day; naive index-difference math produced impossible travel times (thousands of minutes). Fixed by bounding the calculation to a single route’s own unique stop sequence.
Guardrails in the system prompt — The assistant must call `find_direct_route` before `find_transfer_routes`, must always follow a found route with `get_schedule_and_time`, must never answer outside the CDA transit domain, and must never fabricate a route no tool returned.
Two-phase streaming
A two-phase SSE architecture separates the model’s tool-use from its answer: an internal, non-streaming tool-execution loop emits `[AGENT_WORKING]` progress events to the frontend while tools run, followed by a genuinely streamed final completion once every tool call has resolved.
The UI shows "thinking" while the agent is actually calling tools, and a live typing effect only for the final, grounded answer — not a fake typing animation over an answer that was already fully generated.
Real departure times, estimated durations — said plainly
Outcome
Selected results
9,095
route-stop records
extracted from published PDF schedules
4
backend tools
autonomous chaining on a direct-route miss
460
lines of backend Python
5 files, FastAPI + pandas + OpenAI
5
REST/SSE endpoints
behind a 4-component React 19 frontend
Retrospective
What I would take to the next build
Pick tool-calling when the problem is operations, not documents — RAG is the reflexive choice for "ask questions over data" — but sequencing and arithmetic belong in deterministic code the model calls, not in a retrieved-context guess.
A resolver that fixes typos silently, then says so — Correcting "Khanapul" to "Khanna Pul" without failing the query, then having the model acknowledge the correction, is a small detail that’s most of what makes the assistant feel trustworthy on a real, messy query.
State an estimate’s limits in the product, not just the code comments — The travel-time heuristic is fine to ship — it just has to be labelled as one.
Built solo, extending a FAST-NUCES SE4009 (Process Mining and Simulation) course dataset into a full conversational product beyond what the course itself required.