LLMs can do more than generate text — with Tool Calling they execute actions, and with Structured Output they deliver type-safe, machine-readable data. Both together turn a chatbot into a full-fledged application.
The LLM decides based on the user request which tool (function) should be called and generates the matching parameters. The actual execution happens in your code.
import { tool } from 'ai'
import { z } from 'zod'
const weatherTool = tool({
description: 'Fetches the current weather for a city',
parameters: z.object({
city: z.string().describe('The name of the city'),
unit: z.enum(['celsius', 'fahrenheit']).default('celsius'),
}),
execute: async ({ city, unit }) => {
const data = await fetchWeatherAPI(city, unit)
return { temperature: data.temp, condition: data.condition }
},
})
import { streamText } from 'ai'
import { openai } from '@ai-sdk/openai'
export async function POST(req: Request) {
const { messages } = await req.json()
const result = streamText({
model: openai('gpt-4.1'),
messages,
tools: {
getWeather: weatherTool,
searchProducts: productSearchTool,
createOrder: orderTool,
},
})
return result.toDataStreamResponse()
}
The LLM decides itself which tool is relevant for the request:
getWeathersearchProductscreateOrderFor complex tasks, the LLM calls multiple tools sequentially:
const result = streamText({
model: openai('gpt-4.1'),
messages,
tools: { getWeather, searchHotels, bookHotel },
maxSteps: 5, // Allow up to 5 tool calls
onStepFinish: ({ stepType, toolResults }) => {
console.log(`Step: ${stepType}`, toolResults)
},
})
Example flow:
getWeather({ city: 'Munich' }) → "15°C, sunny"searchHotels({ city: 'Munich', dates: '...' }) → 5 hotelsbookHotel({ hotelId: '...', dates: '...' }) → Booking confirmationInstead of generating unstructured text, generateObject delivers Zod-validated objects:
import { generateObject } from 'ai'
import { z } from 'zod'
const { object } = await generateObject({
model: openai('gpt-4.1'),
schema: z.object({
title: z.string(),
summary: z.string().max(200),
tags: z.array(z.string()).max(5),
sentiment: z.enum(['positive', 'neutral', 'negative']),
confidence: z.number().min(0).max(1),
}),
prompt: 'Analyze this customer comment: "The product is fantastic!"',
})
// object is type-safe: { title: string, summary: string, ... }
For large objects or progressive UIs:
import { streamObject } from 'ai'
const result = streamObject({
model: openai('gpt-4.1'),
schema: productAnalysisSchema,
prompt: 'Analyze the top 5 products...',
})
for await (const partialObject of result.partialObjectStream) {
// Progressively update UI
updateUI(partialObject)
}
The AI SDK + Zod enables complete type safety from schema definition to UI:
typeof schema._type → TypeScript type| Mode | Description | Ideal For |
|---|---|---|
| json | Complete JSON object | Complex analyses, data extraction |
| tool | As tool call parameters | Integration into tool workflows |
Best practice: Use
generateObjectinstead ofgenerateText+ manual JSON parsing. The AI SDK guarantees schema-conformant outputs and throws type-safe errors on validation failures.