“Gikan SM Ecoland pa-Mintal Gym, unsa sakyan?” is a useful test for the assistant. The question is short, mixes Cebuano with place names, and asks for a trip without using the English words “origin” or “destination.” The assistant does not answer that question from memory. It extracts the two place strings and the trip intent, then lets the deterministic place resolver and planner decide what the data can support.
That boundary is the feature. The model is a language front door, not a second trip planner. It can recognize that “spmc po to uyanguren?” is a trip request and preserve both abbreviations. It cannot create a route, choose a transfer, quote a fare, or claim that a vehicle is nearby.
One message, one validated action
The normal path uses one model call. The API sends the message with a versioned extraction prompt and a strict JSON schema, compiles the returned fields against the original message, validates the intent against the request context, and returns the result to the web client. The client dispatches the accepted intent to deterministic transit functionality.
The model therefore sits between the rider's wording and the application's contracts. If the model returns invalid JSON, the API returns an unknown intent. If the request needs the rider's location and no location was supplied, validation returns needs_location. If the place is unfamiliar, the original text remains available for deterministic resolution. These states are more useful than a fluent guess.
Action chips take an even shorter path. Known actions such as nearest stop, route lookup for a selected place, live-tracking help, and service-area help use knownActionIntent and make zero model calls. A normal free-text message makes one inference call. There is no model-to-model debate and no agent loop.
The contract the model is allowed to fill
The current schema has eight intents:
| Intent | What the deterministic side does next |
|---|---|
plan_trip | Resolve one or two place strings, then call trip planning |
nearest_stop | Use the supplied location to find a nearby stop |
route_lookup | Find routes associated with a place or route label |
fare_lookup | Resolve trip endpoints or inspect a prior trip/fare result |
live_status | Look up live status for a named route or prior route result |
faq | Select a curated answer for live tracking, service area, no-trip, or data limits |
out_of_scope | Decline requests that are not about Davao public transport |
unknown | Abstain when the request is incomplete or cannot be supported |
The useful fields are deliberately small. The model can provide originText, destinationText, placeText, routeText, an explicit modeHint, a current-location flag, a follow-up flag, and an FAQ topic. It cannot provide route IDs, stop IDs, coordinates, fares, schedules, ETAs, or vehicle positions.
That difference matters for a message such as “unsay muagi gikan spmc paingon uyanguren.” The word muagi makes this a route lookup, not automatically a trip plan. The current evaluation case expects placeText to be “uyanguren,” while the origin phrase remains context in the message. The route resolver, not the model, decides which services pass that place.
The examples that shaped the boundary
The evaluation fixtures contain compressed messages rather than polished prompts. They include “DGT - ADDU, unsa sakyan?”, “How many rides from Lots for Less Sasa to UPMin?”, “Pila?”, “Asa na ang R603?”, and “Unsaon pag-adto?” The expected behavior is different in each case:
| Message | Extracted meaning |
|---|---|
DGT - ADDU, unsa sakyan? | Cebuano trip request; preserve DGT and ADDU |
How many rides from Lots for Less Sasa to UPMin? | Trip request; ride count is a question about the trip, not a new intent |
Pila? after a trip result | Fare follow-up on the previous result |
Asa na ang R603? | Live status for route R603 |
Unsaon pag-adto? | Unknown; there is no place to resolve |
The compiler checks that extracted place and route values actually occur in the user's message. It also removes placeholders such as “diri,” “didto,” and “my location” when the model has put them into a text field. This keeps an extraction error from becoming a fake place lookup.
Follow-ups keep the subject of the previous question. “Pabalik,” “2 rides ba siya?”, and “Asa ko munaog?” stay in the trip-planning family. “Magkano?” and “Pila?” stay in fare lookup. A client-supplied last result is only a type hint; it does not give the model permission to invent IDs or endpoints.
Language without a fake language wall
Davao passenger language is mixed in the fixtures and in the public examples that informed them. English supplies place names and question frames, Filipino contributes forms such as “paano” and “po,” and Cebuano contributes forms such as gikan, padulong, sakyan, muagi, and munaog. The extractor records one language label, but the slot rules still work across a mixed message.
The assistant preserves wording instead of normalizing it into a cleaner sentence. “front of KFC,” “kanto Gmall,” “spmc,” and “North Star Moon Base” remain the supplied text. The deterministic resolver can then say that a place is ambiguous, missing, or outside the data without pretending the model knew what the rider meant.
The contract does not yet represent every useful passenger detail. Road side, crossing instructions, alighting landmarks, luggage, crowding, time windows, and transfer tolerance appear in the research corpus, but they are not independent fields in assistant-intent-v1. The system must not imply that extraction support exists just because the prompt can understand the words.
What is current and what is not
The prompt is assembled from seven Markdown sections. The current source version is ai-intent-v11; the structured contract is assistant-intent-v1; and the configured default model is @cf/meta/llama-3.3-70b-instruct-fp8-fast. Those identifiers describe the checked-in configuration, not a permanent product promise.
The assistant can be disabled. Missing provider or gateway configuration, an unavailable rate limiter, a timeout, or invalid model output produces an unavailable or unknown path rather than a fabricated answer. Requests are capped at 16,384 bytes, inference has a 10-second deadline, and the outer assistant request has a 12-second deadline.
The current browser planner also sends liveBusArrivals: []. The assistant's language boundary therefore does not make static route planning live. Live status and ETA responses have their own data and freshness rules, described in the bus tracker and telemetry and transit intelligence.
The checked-in regression artifact is not a success story. It records 21 cases and 42 runs against prompt version ai-intent-v5: schema validity was 1, intent accuracy was 0.8571, outcome accuracy was 0.5952, and invented-slot rate was 0.381. The language boundary held better than the action boundary, but the run did not meet the configured intent or outcome gates. The details are in AI reliability and model evaluation.
The assistant's current job is therefore modest and testable: understand a rider's wording, preserve what was actually said, and hand a deterministic system enough information to act or ask for the missing piece. That is narrower than a chatbot. It is also the part that can be held accountable.