---
title: "Tools and Agents"
description: "Create tools that extend AI capabilities and build multi-step agents that can chain operations together."
canonical_url: "https://vercel.com/academy/svelte-on-vercel/tools-and-agents"
md_url: "https://vercel.com/academy/svelte-on-vercel/tools-and-agents.md"
docset_id: "vercel-academy"
doc_version: "1.0"
last_updated: "2026-04-11T10:33:33.102Z"
content_type: "lesson"
course: "svelte-on-vercel"
course_title: "Svelte on Vercel"
prerequisites:  []
---

<agent-instructions>
Vercel Academy — structured learning, not reference docs.
Lessons are sequenced.
Adapt commands to the human's actual environment (OS, package manager, shell, editor) — detect from project context or ask, don't assume.
The lesson shows one path; if the human's project diverges, adapt concepts to their setup.
Preserve the learning goal over literal steps.
Quizzes are pedagogical — engage, don't spoil.
Quiz answers are included for your reference.
</agent-instructions>

# Tools and Agents

# Tools and Multi-Step Agents

Streaming text is useful, but it's not enough. When a user says "alert me when Mammoth gets fresh powder," you need the AI to do something: parse that into a structured alert and save it. AI SDK tools give the model the ability to call functions with validated parameters.

## Outcome

Add a `create_alert` tool to the chat endpoint so the AI can create structured ski alerts from natural language.

## Fast Track

1. Import `CreateAlertToolInputSchema` and wrap it with `valibotSchema()` for the tool's `inputSchema`
2. Register the tool with `tool()` from the AI SDK
3. Handle `tool-result` events in the SSE stream to notify the frontend

## How Tools Work

```
User: "Alert me when Grand Targhee gets more than 6 inches of snow"
  │
  ▼
Claude sees the create_alert tool is available
  │
  ▼
Claude calls create_alert({ resortId: "grand-targhee", condition: { type: "snowfall", operator: "gt", value: 6, unit: "inches" }})
  │
  ▼
Tool execute() runs → returns result
  │
  ▼
Claude gets the tool result → writes a confirmation message
  │
  ▼
User sees: "I've created an alert for Grand Targhee. You'll be notified when snowfall exceeds 6 inches."
```

The `stopWhen: stepCountIs(3)` parameter controls how many tool-call/result rounds the model can do before finishing. With 3 steps, the model can call a tool, get the result, and then respond, or chain multiple tool calls.

## Hands-on exercise 2.2

Let's extend the streaming chat endpoint from lesson 2.1 with tool support:

**Requirements:**

1. Import `CreateAlertToolInputSchema` from `$lib/schemas/alert` and wrap it with `valibotSchema()` from `@ai-sdk/valibot`
2. Register it as a `create_alert` tool using the AI SDK's `tool()` function with `inputSchema`
3. Set `stopWhen: stepCountIs(3)` to allow the model to call the tool and respond
4. Handle `tool-result` events in the stream and emit an `alert_created` SSE event when a tool succeeds
5. Update the system prompt to instruct the model on how to parse natural language into alerts

**Implementation hints:**

- The Valibot schema `CreateAlertToolInputSchema` already defines the three alert types (snowfall, temperature, conditions). Wrap it with `valibotSchema()` for the AI SDK
- The tool's `execute` function should validate the resort exists and return structured data
- The `Chat.svelte` component already handles `alert_created` events. It calls `createAlert()` to save to localStorage
- The system prompt should explain how to map phrases like "fresh powder" to condition types

## Try It

1. **Test natural language alert creation:**

   ```
   Alert me when Mammoth gets fresh powder
   ```

   The AI should:

   - Call the `create_alert` tool with `{ resortId: "mammoth", condition: { type: "conditions", match: "powder" } }`
   - Return a confirmation message explaining the alert

2. **Test a numeric condition:**

   ```
   Notify me when Grand Targhee gets more than 6 inches of snow
   ```

   Expected tool call: `{ resortId: "grand-targhee", condition: { type: "snowfall", operator: "gt", value: 6, unit: "inches" } }`

3. **Test a temperature condition:**

   ```
   Let me know when Steamboat drops below 10°F
   ```

   Expected tool call: `{ resortId: "steamboat", condition: { type: "temperature", operator: "lt", value: 10, unit: "fahrenheit" } }`

4. **Check the Alerts page:**
   Navigate to `/alerts`. Your created alerts should appear there, saved to localStorage.

## Commit

```bash
git add -A
git commit -m "feat(chat): add create_alert tool with multi-step agent"
git push
```

## Done-When

- [ ] AI can create alerts from natural language requests
- [ ] Three condition types work: snowfall, temperature, conditions
- [ ] Created alerts appear on the `/alerts` page
- [ ] The AI responds with a confirmation after creating an alert
- [ ] Invalid resort names are handled gracefully

## Solution

```typescript title="src/routes/api/chat/+server.ts"
import { createGateway, streamText, tool, stepCountIs } from 'ai';
import { valibotSchema } from '@ai-sdk/valibot';
import { resorts } from '$lib/data/resorts';
import { CreateAlertToolInputSchema } from '$lib/schemas/alert';
import { AI_GATEWAY_API_KEY } from '$env/static/private';
import type { RequestHandler } from './$types';

const gateway = createGateway({
  apiKey: AI_GATEWAY_API_KEY
});

export const POST: RequestHandler = async ({ request }) => {
  const { message } = await request.json();

  const resortList = resorts
    .map((r) => `- ${r.name} (id: ${r.id})`)
    .join('\n');

  const result = streamText({
    model: gateway('anthropic/claude-sonnet-4'),
    system: `You are a helpful ski conditions assistant. Users want to create alerts for ski resort conditions.

Available resorts:
${resortList}

When a user asks you to create an alert, use the create_alert tool with the appropriate parameters.
Parse natural language like "notify me when Mammoth gets fresh powder" into structured alert conditions.

For "fresh powder" or "new snow", use the conditions type with match: "powder".
For specific snow amounts like "more than 6 inches", use snowfall type with the appropriate operator.
For temperature conditions, use the temperature type.

Always confirm the alert was created and explain what conditions will trigger it.`,
    messages: [{ role: 'user', content: message }],
    tools: {
      create_alert: tool({
        description: 'Create a new alert for ski resort conditions',
        inputSchema: valibotSchema(CreateAlertToolInputSchema),
        execute: async ({ resortId, condition }) => {
          const resort = resorts.find((r) => r.id === resortId);
          if (!resort) {
            return {
              success: false,
              error: `Resort "${resortId}" not found`
            };
          }

          return {
            success: true,
            alert: {
              resortId,
              resortName: resort.name,
              condition,
              message: `Alert created for ${resort.name}`
            }
          };
        }
      })
    },
    stopWhen: stepCountIs(3)
  });

  const encoder = new TextEncoder();
  const stream = new ReadableStream({
    async start(controller) {
      for await (const part of result.fullStream) {
        if (part.type === 'text-delta') {
          controller.enqueue(
            encoder.encode(
              `data: ${JSON.stringify({ type: 'text', content: part.text })}\n\n`
            )
          );
        } else if (part.type === 'tool-result') {
          const toolResult = part.output as {
            success: boolean;
            alert?: unknown;
          };
          if (toolResult.success && toolResult.alert) {
            controller.enqueue(
              encoder.encode(
                `data: ${JSON.stringify({ type: 'alert_created', alert: toolResult.alert })}\n\n`
              )
            );
          }
        }
      }
      controller.enqueue(encoder.encode('data: [DONE]\n\n'));
      controller.close();
    }
  });

  return new Response(stream, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      Connection: 'keep-alive'
    }
  });
};
```

What changed from lesson 2.1:

1. **Added imports:** `tool`, `stepCountIs` alongside `createGateway` and `streamText` from `ai`, `valibotSchema` from `@ai-sdk/valibot`, `CreateAlertToolInputSchema` from the shared schemas
2. **Used `valibotSchema()`** to wrap the existing Valibot schema for the AI SDK tool's `inputSchema`
3. **Registered the tool** in `streamText()` with `tools: { create_alert: tool({ ... }) }`
4. **Added `stopWhen: stepCountIs(3)`** so the model can call the tool, get the result, and write a response
5. **Handle `tool-result`** events in the stream to emit `alert_created` SSE events
6. **Updated system prompt** with instructions for parsing natural language into alert conditions

## Troubleshooting

\*\*Warning: AI responds with text instead of calling the tool\*\*

Check your system prompt. The model needs explicit instructions about when to use `create_alert` vs. when to just answer a question. If the prompt doesn't mention the tool or describe when to use it, the model will default to text responses.

\*\*Warning: Valibot validation error in server logs\*\*

The model generated a condition that doesn't match the schema. The `stopWhen: stepCountIs(3)` gives it multiple rounds to self-correct, but if it keeps failing, make the tool description more specific about the expected condition shapes.

## Advanced: Multiple Tools

You can register multiple tools. For example, adding a `check_conditions` tool that fetches live weather:

```typescript {3-14}
tools: {
  create_alert: tool({ /* ... */ }),
  check_conditions: tool({
    description: 'Check current conditions at a ski resort',
    inputSchema: valibotSchema(v.object({
      resortId: v.string()
    })),
    execute: async ({ resortId }) => {
      const resort = resorts.find((r) => r.id === resortId);
      if (!resort) return { error: 'Resort not found' };
      const weather = await fetchWeather(resort);
      return { resort: resort.name, ...weather };
    }
  })
}
```

With `stopWhen: stepCountIs(3)`, the model could check conditions first, then create an alert based on what it finds.


---

[Full course index](/academy/llms.txt) · [Sitemap](/academy/sitemap.md)
