defineTool creates a reusable, validated boundary between model intent and
deterministic code. Descriptions teach the model when and how to call a tool;
Valibot validates arguments before run; thrown errors become actionable tool
errors; and the abort signal cancels in-flight fetches with the turn.
+ 82: 'violent showers', 95: 'thunderstorm', 96: 'thunderstorm with hail', 99: 'severe thunderstorm with hail',
+};
+
+const isoDate = v.pipe(v.string(), v.isoDate());
+
+export const getForecast = defineTool({
+ name: 'get_forecast',
+ description:
+ 'Get the daily weather forecast (min/max °C, chance of rain, conditions) for a latitude/longitude between two dates (YYYY-MM-DD). Only works up to 16 days ahead. Get coordinates from geocode_city first.',
82: 'violent showers', 95: 'thunderstorm', 96: 'thunderstorm with hail', 99: 'severe thunderstorm with hail',
};
const isoDate = v.pipe(v.string(), v.isoDate());
export const getForecast = defineTool({
name: 'get_forecast',
description:
'Get the daily weather forecast (min/max °C, chance of rain, conditions) for a latitude/longitude between two dates (YYYY-MM-DD). Only works up to 16 days ahead. Get coordinates from geocode_city first.',
Update src/agents/field-trip.ts: import and register both weather tools,
then add the current date and the geocoding → forecast rule to the instructions:
import { useModel, usePersistentState, useTool } from '@flue/runtime';
import * as v from 'valibot';
+import { geocodeCity, getForecast } from '../tools/weather.ts';
// The trip brief the agent remembers for this conversation.
type TripBrief = {
@@ -42,16 +43,24 @@
},
});
+ // Tools that call an external API (Open-Meteo), defined in src/tools/weather.ts.
+ useTool(geocodeCity);
+ useTool(getForecast);
+
// The agent re-renders before every model call, so these instructions
// always reflect the latest saved brief.
const hasBrief = Object.keys(brief).length > 0;
+ const today = new Date().toISOString().slice(0, 10);
return `You are FieldTrip, a helpful team-offsite planner. You help groups plan memorable offsites by understanding their destination, dates, headcount, budget, and interests.
Rules:
1. If the user's message contains ANY trip detail (city, dates, headcount, budget, interests), your FIRST action is to call \`save_trip_brief\` with those fields. Do this before writing any reply.
2. Answer questions about the trip from the saved brief below. If a detail is missing, ask for it.
-3. Keep replies short: at most 120 words unless the user asks for more detail.
+3. For weather questions: call \`geocode_city\` for the city, then \`get_forecast\` with its latitude/longitude and the trip dates (use the saved brief). If there is no end date, use the start date. Summarise the forecast per day in plain words; if a tool returns an error, explain it to the user.
+4. Keep replies short: at most 120 words unless the user asks for more detail.
// Tools can write state. The write commits together with the tool call.
useTool({
name: 'save_trip_brief',
description:
'Save or update the offsite trip brief. Call this whenever the user states or changes the city, dates, headcount, budget, or interests. Only include fields the user mentioned; they are merged into the saved brief.',
return`You are FieldTrip, a helpful team-offsite planner. You help groups plan memorable offsites by understanding their destination, dates, headcount, budget, and interests.
Rules:
1. If the user's message contains ANY trip detail (city, dates, headcount, budget, interests), your FIRST action is to call \`save_trip_brief\` with those fields. Do this before writing any reply.
2. Answer questions about the trip from the saved brief below. If a detail is missing, ask for it.
3. For weather questions: call \`geocode_city\` for the city, then \`get_forecast\` with its latitude/longitude and the trip dates (use the saved brief). If there is no end date, use the start date. Summarise the forecast per day in plain words; if a tool returns an error, explain it to the user.
4. Keep replies short: at most 120 words unless the user asks for more detail.
npmrunsmoke--https://field-trip-agent.<subdomain>.workers.devcp3-live"Offsite in Lisbon from <START> to <END> for 14 people, budget 400 EUR each. We like food and hiking."
npmrunsmoke--https://field-trip-agent.<subdomain>.workers.devcp3-live"What's the weather forecast for our trip dates?"
The second turn should call geocode_city, then get_forecast with the returned
coordinates and dates from persistent state.
If both tool calls and ✔ completed appear without a prose summary, ask the
model to finish in the same conversation:
Terminal window
npmrunsmoke--https://field-trip-agent.<subdomain>.workers.devcp3-live"Please summarize that forecast for each day in plain language."
Optional depth check: exercise the error path in a fresh conversation:
Terminal window
npmrunsmoke--https://field-trip-agent.<subdomain>.workers.devcp3-live-err"What will the weather be in Lisbon on 2099-03-01?"
The forecast tool should fail clearly and the agent should explain the 16-day
limit instead of inventing weather.
This opens the same conversation as the smoke test. If you repeat this checkpoint, before copying its commands.
Start npm run dev in Terminal A and wait for VITE ... ready. Run in Terminal B:
Terminal window
npmrunsmoke--http://localhost:5173cp3-local"Offsite in Lisbon from <START> to <END> for 14 people, budget 400 EUR each. We like food and hiking."
npmrunsmoke--http://localhost:5173cp3-local"What's the weather forecast for our trip dates?"
The second turn should call geocode_city, then get_forecast with the returned
coordinates and dates from persistent state.
If the output shows both tool calls and ✔ completed but stops after the
get_forecast result, the tools worked and the model simply ended without prose.
Ask it to finish instead of rebuilding anything:
Terminal window
npmrunsmoke--http://localhost:5173cp3-local"Please summarize that forecast for each day in plain language."
Optional depth check: exercise the error path in a fresh conversation:
Terminal window
npmrunsmoke--http://localhost:5173cp3-local-err"What will the weather be in Lisbon on 2099-03-01?"
The forecast tool should fail clearly and the agent should explain the 16-day
limit instead of inventing weather.
This opens the same conversation as the smoke test. If you repeat this checkpoint, before copying its commands.
Verification gate
Prove it works
geocode_city runs before get_forecast.
The forecast uses coordinates from geocoding and dates from saved state.
The reply summarizes each supported day in plain language.
Optional: an out-of-range date produces a tool error and no fabricated forecast.
Optional mini quizCheck tool boundaries3 questions / instant feedback
Recovery lane
Get back on track in under a minute
If a dev server is running, stop it first. Uncommitted work is stashed and the current commit gets a backup branch before anything moves.
Option 01
Restart this exercise
Return to cp/2-hooks-state, rebuild this exercise, then use its Live deploy verification tab. Stop any running dev server with Ctrl+C before switching.