Lesson 4: Multi-Call Capture (WeatherStep)
Goal
Capture two city names, but only accept NYC and LA. Persist each success, loop until both are collected, then branch into logic.
Prompt and Tool Surface
public getPrompt(): string {
return `
${DemoPrompt.TravelRole}
Ask user to enter 2 city names to compare their current day temperature.
Capture the names of the two cities and call tool 'get_weather' for each city
If user prefer to exit, call tool 'end_chat'.
`;
}
public defineTool(): ToolType[] {
return [{
name: 'get_weather',
description: 'capture the weather of one city',
schema: z.object({ cityName: z.string() }),
}];
}
public getTool(): string[] {
return ['get_weather', 'end_chat'];
}
Tip
Restrict tools per step to minimize hallucinated actions and keep the LLM on the rail.
Handler Logic
protected async get_weather(tool: ToolCall): Promise<ToolResponseType> {
if (tool.args?.cityName === 'NYC' || tool.args?.cityName === 'LA') {
this.saveState({ [`cityName_${tool.args.cityName}`]: true });
const done = this.getState('cityName_LA') && this.getState('cityName_NYC');
return done ? FooLogicStep : WeatherStep;
}
return { step: WeatherStep, tool: 'Only LA and NYC cities are allowed' };
}
- Valid city → set a boolean state flag.
- Both flags present → transition to
FooLogicStep. - Invalid city → retry in place, surfacing the error as a tool result.
Patterns Illustrated
- Multi-call capture: stay in the same step until N items are collected.
- Retry-in-place:
{ step: SameStep, tool: 'error' }keeps the LLM focused. - Stateful guardrails: simple flags in step state drive deterministic branching.
Next we’ll capture the user’s name, inject runtime context, and run an InContext sub-agent.