Recommended resources
The freely available resources this lesson will direct you to.
Welcome back. In our last two lessons, we designed and implemented the backend APIs for our Prompt Store, establishing a versioned, contract-based system for managing prompts. With the APIs for both the Tool Store and Prompt Store now in place, we have the foundational services ready.
This lesson marks a pivotal shift from building the infrastructure to consuming it. We will focus on bringing our agent to life by enabling it to dynamically load its capabilities from the Tool Store you built. Instead of hardcoding which tools an agent can use, we will implement a system where tools are fetched from our API at agent initialization. This makes the agent's skillset configurable, scalable, and manageable without redeploying the agent's code.
1. The Challenge: Bridging Metadata and Execution
Let's start by revisiting the structure of a tool within the Vercel AI SDK. As you'll recall from our earlier work and the resources below, a tool is more than just a description; it's an executable unit.
A Complete Guide To Vercel’s AI SDK // The ESSENTIAL Tool For Shipping AI Apps
To refresh your memory on how tools are defined, watch this segment from the "A Complete Guide To Vercel’s AI SDK" video by Matt Pocock.
Focus on the part explaining tool definition from tool creation. Notice the key components: a description for the LLM, a Zod schema for the parameters, and an execute function that contains the actual implementation logic.
This presents a fundamental challenge. Our Tool Store API serves JSON data, which can easily represent a tool's name, description, and even its parameter schema. However, it cannot serialize and serve the execute function, which is JavaScript/TypeScript code.
Our task, therefore, is to create a mechanism that combines the dynamic metadata from our API with the static, compiled execute functions residing within our agent's application code.
2. The Registry and Hydration Pattern
The standard solution to this problem is a pattern I call "Registry and Hydration." It's a robust and common approach in building modular systems, and your experience with backend development will make this pattern feel intuitive.
It works in three steps:
- Local Registry: Create a registry in your agent's application code that maps tool names to their actual implementations (the
executefunction and Zod schema). - Fetch Metadata: At agent initialization, call your Tool Store API to get the list of tools that should be available for a given context (e.g., for a specific tenant or workspace).
- Hydration: Combine the fetched metadata with the corresponding implementations from the local registry to construct the final, fully-formed
Toolobjects that the Vercel AI SDK can use.
Step 1: The Local Tool Registry
First, let's define the implementations of our tools in the agent's codebase. This registry will act as the single source of truth for the logic of every possible tool your system can offer.
Let's imagine our agent application has a file, say src/tools/index.ts, where we define the implementations.
// src/tools/index.ts
import { z } from 'zod';
import { tool } from 'ai';
// Define the implementation for a weather tool
const weatherToolImplementation = {
parameters: z.object({
city: z.string().describe('The city to get the weather for'),
}),
execute: async ({ city }: { city: string }) => {
// In a real app, you'd call a weather API
console.log(`Fetching weather for ${city}...`);
return { temperature: 25, condition: 'Sunny' };
},
};
// Define the implementation for a calculator tool
const calculatorToolImplementation = {
parameters: z.object({
operation: z.enum(['add', 'subtract']),
a: z.number(),
b: z.number(),
}),
execute: async ({ operation, a, b }: { operation: 'add' | 'subtract', a: number, b: number }) => {
if (operation === 'add') {
return { result: a + b };
}
return { result: a - b };
},
};
// The registry maps tool names to their implementation
export const toolRegistry = new Map<string, any>([
['getWeather', weatherToolImplementation],
['calculate', calculatorToolImplementation],
]);
This registry is static. It contains all possible tools the application knows how to execute.
Step 2: Fetching Tool Metadata
Next, at agent initialization, we'll call the Tool Store API endpoint you created in the previous module (GET /tools). This endpoint returns an array of tool metadata objects.
Let's assume the API returns the following JSON for a particular tenant:
[
{
"name": "getWeather",
"description": "Provides the current weather conditions for a specified city.",
"schema": { /* a JSON schema representation of the parameters */ },
"is_active": true
// ... other metadata
}
// Note: The 'calculate' tool is not in this list, so this agent won't have it.
]
Step 3: Hydration
Now for the crucial step. We'll write a function that takes the fetched metadata, looks up the corresponding implementation in our toolRegistry, and "hydrates" it into a complete Tool object.
// src/agent/tool-loader.ts
import { tool } from 'ai';
import { toolRegistry } from '../tools'; // Our local registry
interface ToolMetadata {
name: string;
description: string;
// ... any other fields from your API
}
export async function loadToolsForAgent(): Promise<Record<string, any>> {
// 1. Fetch metadata from your API
const response = await fetch('http://localhost:3001/tools'); // Your Tool Store API
const toolMetadataList: ToolMetadata[] = await response.json();
const loadedTools: Record<string, any> = {};
// 2. Hydrate each tool
for (const metadata of toolMetadataList) {
const implementation = toolRegistry.get(metadata.name);
if (implementation) {
// 3. Combine metadata and implementation using the 'tool' helper
loadedTools[metadata.name] = tool({
description: metadata.description,
parameters: implementation.parameters,
execute: implementation.execute,
});
} else {
console.warn(`Tool implementation for "${metadata.name}" not found in registry.`);
}
}
return loadedTools;
}
Finally, you would use this loadToolsForAgent function when setting up your agent.
// src/agent/main.ts
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { loadToolsForAgent } from './tool-loader';
async function runAgent() {
// Dynamically load the tools at initialization
const dynamicTools = await loadToolsForAgent();
const result = await generateText({
model: openai('gpt-4o'),
prompt: "What's the weather in San Francisco?",
tools: dynamicTools, // Pass the dynamically loaded tools
});
// ... process results
}
runAgent();
When the agent runs this prompt, it will correctly identify that it has a getWeather tool, extract "San Francisco" as the argument, and call its execute function, just as if the tool were statically defined.

3. Advanced Patterns for Dynamic Loading
The Registry/Hydration pattern is a solid foundation. For an experienced developer like you, it's worth exploring more advanced, modular approaches that can scale elegantly.
Pattern 1: File-based Skills (RAG for Tools)
This pattern, popularized by Anthropic's "Claude Skills," treats tool definitions or related knowledge as modular files. Instead of a central registry, you can have a directory of files, and the agent can even be given tools to discover and load other tools or knowledge on the fly.
This article provides an excellent conceptual overview and implementation guide.
Empowering your AI app with Claude Skills using the Vercel AI SDK
This article explains how to implement a dynamic knowledge retrieval system for an agent, which is conceptually identical to dynamic tool loading.
First, read the mental model to understand the retrieval-based approach. Then, study the implementation sections for structuring skills on disk and creating a list_skills tool. Finally, see how to create the load_skill tool and integrate it. Notice the meta-pattern: you're creating tools (list_skills, load_skill) that allow the agent to manage its own knowledge base at runtime.
This approach is incredibly powerful for building agents that can adapt to new information without being restarted.
Pattern 2: Class-based Generation with typia
For systems with a large number of tools, maintaining Zod schemas and registry maps can become cumbersome. The typia library offers a compelling alternative: it can generate Vercel AI SDK-compatible tools directly from TypeScript classes at compile time.
The typia documentation demonstrates this clearly.
Guide Documents > Utilization Cases > Vercel AI SDK - Typia
This guide shows how to automatically generate tool definitions from strongly-typed TypeScript classes, significantly reducing boilerplate.
Focus on the section Vercel AI Tools to see how a Calculator class is transformed into a set of tools using toVercelTools. Also, review The Function Calling Harness and Error Handling sections to appreciate the built-in validation and error handling that typia provides, which is crucial for production-grade systems.
Using typia, you could dynamically import class files from a directory and generate tools from them, combining file-based modularity with strong type safety.
Conclusion
In this lesson, you have implemented a crucial piece of our agentic infrastructure: the ability to dynamically load tools from the Tool Store. This decouples the agent's capabilities from its core logic, allowing you to manage its toolset through an API.
Key Takeaways:
- The Registry/Hydration Pattern: This is the core solution for bridging the gap between serializable tool metadata (from an API) and non-serializable execution logic (in code).
- Decoupling Capabilities from Code: By loading tools dynamically, you can add, remove, or update an agent's available tools by simply changing records in your database, without needing to redeploy the agent application.
- Scalable Modularity: Advanced patterns like file-based skills or class-based generation with
typiaprovide elegant ways to manage a large and growing collection of tools while maintaining code organization and type safety.
We now have an agent that can dynamically equip itself with the right tools for the job. In our next lesson, we will apply the very same principles to our Prompt Store, implementing a system to dynamically resolve and load versioned prompt templates at runtime.