Skip to content

Commit f990ce0

Browse files
committed
Chapter 1: Add weather tool (first tool)
- Add lib/ai/tools/get-weather.ts with geocoding support - Update route.ts to import and use weather tool - Add stepCountIs for multi-step tool conversations - Add experimental_activeTools for reasoning model compatibility - Update types.ts to use InferUITool for weather tool
1 parent dcdd374 commit f990ce0

File tree

3 files changed

+95
-8
lines changed

3 files changed

+95
-8
lines changed

app/(chat)/api/chat/route.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import {
44
createUIMessageStream,
55
JsonToSseTransformStream,
66
smoothStream,
7+
stepCountIs,
78
streamText,
89
} from "ai";
10+
import { getWeather } from "@/lib/ai/tools/get-weather";
911
import { unstable_cache as cache } from "next/cache";
1012
import type { ModelCatalog } from "tokenlens/core";
1113

@@ -175,7 +177,15 @@ export async function POST(request: Request) {
175177
model: myProvider.languageModel(selectedChatModel),
176178
system: systemPrompt({ selectedChatModel, requestHints }),
177179
messages: convertToModelMessages(uiMessages),
180+
stopWhen: stepCountIs(5),
181+
experimental_activeTools:
182+
selectedChatModel === "chat-model-reasoning"
183+
? []
184+
: ["getWeather"],
178185
experimental_transform: smoothStream({ chunking: "word" }),
186+
tools: {
187+
getWeather,
188+
},
179189
experimental_telemetry: {
180190
isEnabled: isProductionEnvironment,
181191
functionId: "stream-text",

lib/ai/tools/get-weather.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { tool } from "ai";
2+
import { z } from "zod";
3+
4+
async function geocodeCity(
5+
city: string
6+
): Promise<{ latitude: number; longitude: number } | null> {
7+
try {
8+
const response = await fetch(
9+
`https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(city)}&count=1&language=en&format=json`
10+
);
11+
12+
if (!response.ok) {
13+
return null;
14+
}
15+
16+
const data = await response.json();
17+
18+
if (!data.results || data.results.length === 0) {
19+
return null;
20+
}
21+
22+
const result = data.results[0];
23+
return {
24+
latitude: result.latitude,
25+
longitude: result.longitude,
26+
};
27+
} catch {
28+
return null;
29+
}
30+
}
31+
32+
export const getWeather = tool({
33+
description:
34+
"Get the current weather at a location. You can provide either coordinates or a city name.",
35+
inputSchema: z.object({
36+
latitude: z.number().optional(),
37+
longitude: z.number().optional(),
38+
city: z
39+
.string()
40+
.describe("City name (e.g., 'San Francisco', 'New York', 'London')")
41+
.optional(),
42+
}),
43+
execute: async (input) => {
44+
let latitude: number;
45+
let longitude: number;
46+
47+
if (input.city) {
48+
const coords = await geocodeCity(input.city);
49+
if (!coords) {
50+
return {
51+
error: `Could not find coordinates for "${input.city}". Please check the city name.`,
52+
};
53+
}
54+
latitude = coords.latitude;
55+
longitude = coords.longitude;
56+
} else if (input.latitude !== undefined && input.longitude !== undefined) {
57+
latitude = input.latitude;
58+
longitude = input.longitude;
59+
} else {
60+
return {
61+
error:
62+
"Please provide either a city name or both latitude and longitude coordinates.",
63+
};
64+
}
65+
66+
const response = await fetch(
67+
`https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current=temperature_2m&hourly=temperature_2m&daily=sunrise,sunset&timezone=auto`
68+
);
69+
70+
const weatherData = await response.json();
71+
72+
if ("city" in input) {
73+
weatherData.cityName = input.city;
74+
}
75+
76+
return weatherData;
77+
},
78+
});

lib/types.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import type { UIMessage } from "ai";
1+
import type { InferUITool, UIMessage } from "ai";
22
import { z } from "zod";
33
import type { ArtifactKind } from "@/components/artifact";
4-
import type { WeatherAtLocation } from "@/components/weather";
4+
import type { getWeather } from "./ai/tools/get-weather";
55
import type { Suggestion } from "./db/types";
66
import type { AppUsage } from "./usage";
77

@@ -13,8 +13,10 @@ export const messageMetadataSchema = z.object({
1313

1414
export type MessageMetadata = z.infer<typeof messageMetadataSchema>;
1515

16-
// Tool type definitions for UI rendering
17-
// These are placeholders in Chapter 0 - tools not registered in the API
16+
// Tool types - inferred from actual tool definitions
17+
type weatherTool = InferUITool<typeof getWeather>;
18+
19+
// Placeholder types for tools not yet implemented
1820
// UITools expects { input, output } shape for each tool
1921
type DocumentResult = {
2022
id: string;
@@ -23,10 +25,7 @@ type DocumentResult = {
2325
};
2426

2527
export type ChatTools = {
26-
getWeather: {
27-
input: { latitude: number; longitude: number };
28-
output: WeatherAtLocation;
29-
};
28+
getWeather: weatherTool;
3029
createDocument: {
3130
input: { title: string; kind: ArtifactKind };
3231
output: DocumentResult | { error: string };

0 commit comments

Comments
 (0)