@@ -48,69 +48,142 @@ const myTool = tool({
4848
4949## The Weather Tool
5050
51- Here's the complete weather tool implementation:
51+ Here's the complete weather tool implementation with city name geocoding :
5252
5353### File: ` lib/ai/tools/get-weather.ts `
5454
5555``` typescript
5656import { tool } from " ai" ;
5757import { z } from " zod" ;
5858
59+ // Helper function to convert city names to coordinates
60+ async function geocodeCity(
61+ city : string
62+ ): Promise <{ latitude: number ; longitude: number } | null > {
63+ try {
64+ const response = await fetch (
65+ ` https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent (city )}&count=1&language=en&format=json `
66+ );
67+
68+ if (! response .ok ) {
69+ return null ;
70+ }
71+
72+ const data = await response .json ();
73+
74+ if (! data .results || data .results .length === 0 ) {
75+ return null ;
76+ }
77+
78+ const result = data .results [0 ];
79+ return {
80+ latitude: result .latitude ,
81+ longitude: result .longitude ,
82+ };
83+ } catch {
84+ return null ;
85+ }
86+ }
87+
5988export const getWeather = tool ({
6089 description:
61- " Get the current weather at a location. Use this when users ask about weather ." ,
90+ " Get the current weather at a location. You can provide either coordinates or a city name ." ,
6291 inputSchema: z .object ({
63- latitude: z .number ().describe (" Latitude coordinate" ),
64- longitude: z .number ().describe (" Longitude coordinate" ),
92+ latitude: z .number ().optional (),
93+ longitude: z .number ().optional (),
94+ city: z
95+ .string ()
96+ .describe (" City name (e.g., 'San Francisco', 'New York', 'London')" )
97+ .optional (),
6598 }),
66- execute : async ({ latitude , longitude }) => {
67- // In production, you'd call a real weather API
68- // For demo, we return mock data based on coordinates
99+ execute : async (input ) => {
100+ let latitude: number ;
101+ let longitude: number ;
102+
103+ // If city name provided, geocode it to coordinates
104+ if (input .city ) {
105+ const coords = await geocodeCity (input .city );
106+ if (! coords ) {
107+ return {
108+ error: ` Could not find coordinates for "${input .city }". Please check the city name. ` ,
109+ };
110+ }
111+ latitude = coords .latitude ;
112+ longitude = coords .longitude ;
113+ } else if (input .latitude !== undefined && input .longitude !== undefined ) {
114+ latitude = input .latitude ;
115+ longitude = input .longitude ;
116+ } else {
117+ return {
118+ error:
119+ " Please provide either a city name or both latitude and longitude coordinates." ,
120+ };
121+ }
122+
123+ // Fetch weather data from Open-Meteo API
69124 const response = await fetch (
70125 ` https://api.open-meteo.com/v1/forecast?latitude=${latitude }&longitude=${longitude }¤t=temperature_2m&hourly=temperature_2m&daily=sunrise,sunset&timezone=auto `
71126 );
72127
73- const data = await response .json ();
128+ const weatherData = await response .json ();
74129
75- return {
76- temperature: data .current .temperature_2m ,
77- unit: data .current_units .temperature_2m ,
78- };
130+ // Include city name in response if provided
131+ if (" city" in input ) {
132+ weatherData .cityName = input .city ;
133+ }
134+
135+ return weatherData ;
79136 },
80137});
81138```
82139
140+ ### Key Features
141+
142+ 1 . ** Geocoding** : The ` geocodeCity ` helper converts city names to coordinates using the free Open-Meteo Geocoding API
143+ 2 . ** Flexible Input** : Accepts either a city name OR latitude/longitude coordinates
144+ 3 . ** Error Handling** : Returns helpful error messages if geocoding fails
145+ 4 . ** Real Data** : Uses the Open-Meteo Weather API for actual weather data
146+
83147## Wiring Tools into the Chat Route
84148
85149Add the tool to your chat route:
86150
87151### File: ` app/(chat)/api/chat/route.ts `
88152
89153``` typescript
90- import { streamText } from " ai" ;
154+ import { stepCountIs , streamText } from " ai" ;
91155import { myProvider } from " @/lib/ai/providers" ;
92156import { systemPrompt } from " @/lib/ai/prompts" ;
93157import { getWeather } from " @/lib/ai/tools/get-weather" ;
94158
95159export async function POST(request : Request ) {
96- const { messages } = await request .json ();
160+ const { messages, selectedChatModel } = await request .json ();
97161
98162 const result = streamText ({
99- model: myProvider .languageModel (" chat-model " ),
100- system: systemPrompt (),
163+ model: myProvider .languageModel (selectedChatModel ),
164+ system: systemPrompt ({ selectedChatModel } ),
101165 messages ,
166+ // Stop after 5 tool call steps
167+ stopWhen: stepCountIs (5 ),
168+ // Disable tools for reasoning models
169+ experimental_activeTools:
170+ selectedChatModel === " chat-model-reasoning" ? [] : [" getWeather" ],
102171 // Add tools here!
103172 tools: {
104173 getWeather ,
105174 },
106- // Let the AI call multiple tools if needed
107- maxSteps: 5 ,
108175 });
109176
110177 return result .toDataStreamResponse ();
111178}
112179```
113180
181+ ### Key Configuration
182+
183+ - ** ` stopWhen: stepCountIs(5) ` ** : Limits tool call chains to 5 steps
184+ - ** ` experimental_activeTools ` ** : Conditionally enables/disables tools (disabled for reasoning model)
185+ - ** ` tools ` ** : Object containing all available tools
186+
114187## How Tool Calling Works
115188
116189```
@@ -120,9 +193,12 @@ export async function POST(request: Request) {
120193│ AI thinks: "I should use the getWeather tool" │
121194│ ↓ │
122195│ AI generates tool call: │
123- │ { name: "getWeather", args: { lat: 48.8, lon: 2.3 }} │
196+ │ { name: "getWeather", args: { city: "Paris" }} │
124197│ ↓ │
125- │ Tool executes and returns: { temperature: 18, unit: "°C"}│
198+ │ Tool executes: │
199+ │ 1. geocodeCity("Paris") → { lat: 48.8, lon: 2.3 } │
200+ │ 2. fetch weather data from Open-Meteo API │
201+ │ 3. returns: { temperature: 18, cityName: "Paris" } │
126202│ ↓ │
127203│ AI receives result and generates response: │
128204│ "The current temperature in Paris is 18°C" │
@@ -139,19 +215,26 @@ Tool calls and results appear as special message parts. Here's how to render the
139215" use client" ;
140216
141217type WeatherProps = {
142- temperature: number ;
143- unit: string ;
218+ current: {
219+ temperature_2m: number ;
220+ };
221+ current_units: {
222+ temperature_2m: string ;
223+ };
224+ cityName? : string ;
144225};
145226
146- export function Weather({ temperature , unit }: WeatherProps ) {
227+ export function Weather({ current , current_units , cityName }: WeatherProps ) {
147228 return (
148229 < div className = " flex items-center gap-2 rounded-lg border p-4" >
149230 < span className = " text-2xl" > 🌡️< / span >
150231 <div >
151- < p className = " font-semibold" > Current Temperature < / p >
232+ < p className = " font-semibold" >
233+ {cityName ? ` Weather in ${cityName } ` : " Current Weather" }
234+ < / p >
152235 < p className = " text-3xl" >
153- {temperature }
154- {unit }
236+ {current . temperature_2m }
237+ {current_units . temperature_2m }
155238 < / p >
156239 < / div >
157240 < / div >
@@ -214,7 +297,8 @@ Should I bring an umbrella to Seattle?
214297
215298** Troubleshooting:**
216299- If you see "I don't have access to real-time weather", check that the tool is properly added to the ` tools ` object in your chat route
217- - If coordinates seem wrong, the AI is inferring lat/long from city names - this is expected behavior
300+ - If the city isn't found, try using a more common spelling or a larger nearby city
301+ - Check the browser console for any API errors
218302
219303---
220304
0 commit comments