Skip to content

Commit 9117a8a

Browse files
NiallJoeMaherclaude
andcommitted
docs: update CHAPTER-1.md to match full weather implementation
- Show full geocodeCity implementation - Update route.ts example with stepCountIs, experimental_activeTools - Fix Weather component props to match API response - Update flow diagram to show city name lookup 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 5402b01 commit 9117a8a

File tree

1 file changed

+111
-27
lines changed

1 file changed

+111
-27
lines changed

CHAPTER-1.md

Lines changed: 111 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -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
5656
import { tool } from "ai";
5757
import { 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+
5988
export 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}&current=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

85149
Add 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";
91155
import { myProvider } from "@/lib/ai/providers";
92156
import { systemPrompt } from "@/lib/ai/prompts";
93157
import { getWeather } from "@/lib/ai/tools/get-weather";
94158

95159
export 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

141217
type 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

Comments
 (0)