How the Agent Toolkit works
The Agent Toolkit sits between your application (chat UI and model provider) and the TomTom Maps SDK. Each user message goes through two phases, intent classification and then a tool loop. Shared state carries context across turns.
1. Intent classification
Before the model sees any tool, one lightweight LLM call (two if the first answer fails validation) picks the tools this turn needs. Fewer tools in the request means fewer wrong choices and fewer tokens.
- It reads each tool’s
classificationPrompt, a one-line “when to use me”. A custom tool that has one takes part automatically. - It sees the last user message plus up to eight earlier messages, each cut to 300 characters. It never sees the system prompt, tool results or state.
- Tools with
alwaysActive: true(clarifyIntentby default) are added every step, whatever the classifier picked. - For scopable tools (
analyseData,processData,clusterIncidents, and custom tools with ascopeSchema), it also names the data kinds the turn touches, and the tool’s schema shrinks to those kinds. See Scope-aware data tools. - If classification fails, the toolkit offers every tool (fail-open).
The default classifier reuses your main model. Give it a smaller one, or change how much history it sees:
import { createMapAgent, createDefaultClassifier } from '@tomtom-org/maps-sdk-plugin-agent-toolkit';import { openai } from '@ai-sdk/openai';
const agent = createMapAgent(map, { model: openai('gpt-4o'), classifier: createDefaultClassifier({ model: openai('gpt-4o-mini'), maxHistoryMessages: 8, // earlier messages sent (default 8) maxHistoryMessageLength: 300, // characters kept per earlier message (default 300) }),});Or turn it off. Every tool is then offered every turn, and scopable tools keep their full, unscoped schema:
const agent = createMapAgent(map, { model: openai('gpt-4o'), classifier: false,});classifier also accepts your own function, (context) => Promise<ClassificationResult | null>. It receives the
messages and every tool’s metadata. Returning null offers every tool.
2. Tool loop
The model calls tools, such as geocode, search, route and show on map, in sequence or in parallel until it can answer.
- Tools return compact summaries (ids, counts, names, status). The geodata stays in state.
- The toolkit stores results as entries with stable ids (
places-3,routes-1), and the model reads them back withrecallState. That is why “add a stop after the first one” or “what were those restaurants again?” work turns later. - The loop stops after
maxStepssteps (default 10), or as soon as the model calls a tool withendsTurnOnCall.
The tool registry lists every tool.
Per-turn sequence
Hooking into the loop
| Option | Use it to |
|---|---|
prepareStep | Run your own per-step logic. Any activeTools it returns are intersected with the classifier’s pick |
maxSteps | Cap the steps per turn (default 10) |
providerOptions | Pass provider settings such as reasoning effort to every step |
stepProviderOptions | Return per-step provider settings from { activeTools, stepNumber }, merged over providerOptions |
outputSchemas | Set false for providers that reject structured tool outputs |
onToolExecute | Observe each call’s toolName, durationMs, isError and errorMessage |
const agent = createMapAgent(map, { model: openai('gpt-5'), providerOptions: { openai: { reasoningEffort: 'low' } }, // Think harder only on turns that write code. stepProviderOptions: ({ activeTools }) => activeTools?.some((name) => name === 'analyseData' || name === 'processData') ? { openai: { reasoningEffort: 'medium' } } : undefined, onToolExecute: ({ toolName, durationMs, isError }) => console.log(toolName, durationMs, isError),});Run one turn at a time per agent, because scoping rewrites the agent’s shared tool objects each turn, and state belongs to the agent too. Create one agent per concurrent conversation.
Per-tool classification prompts
If a custom tool is offered too often, or not when it should be, tune its classificationPrompt:
// Too broad — activates for any location questionclassificationPrompt: 'Get fleet vehicle data.'
// Precise — activates only for explicit vehicle ID referencesclassificationPrompt: 'Locate a fleet vehicle by its ID (e.g. "TT-001"); not for general location queries.'The system prompt never reaches the classifier, so an instruction there cannot make a tool available. The system prompt guide explains what to do instead.
Observing classification
onClassify receives each turn’s pick and per-tool scopes, or null if the classifier failed open:
const agent = createMapAgent(map, { model: openai('gpt-4o'), onClassify: (result) => { if (result) { console.log('Selected tools:', result.activeToolNames); console.log('Per-tool scopes:', result.toolScopes); // e.g. { analyseData: { kinds: ['places', 'routes'] } } console.log('Classification took:', result.timeMs, 'ms', result.usage.totalTokens, 'tokens'); } },});Related
- Tool registry — the tools the classifier selects from.
- Customizing the system prompt — the prompt the tool loop runs with.
- Securing your agent — what bounds a turn, and why everything in this flow runs where the user can edit it.