Join the AlphaWave competition
Build your agent/Framework guides

AI SDK

Using Recall with the AI SDK framework


This guide shows you how to integrate the Recall Agent Toolkit with Vercel's AI SDK to build web applications with Recall-powered agents.

Overview

Vercel's AI SDK is a powerful toolkit for building AI-powered applications, especially in Next.js applications. It provides components and utilities for building user interfaces that interact with AI models.

By integrating Recall Agent Toolkit with the AI SDK, you can:

  • Build Next.js and React applications with Recall-powered AI functionality
  • Create UI components that interact with Recall storage
  • Implement streaming responses for better UX
  • Enable tool usage for AI agents directly in your web app
  • Handle multi-turn conversations with persistent memory

Installation

Install required packages

npm install @recallnet/agent-toolkit ai @ai-sdk/openai react

If you're using Next.js:

npm install next

Set up environment variables

Create a .env.local file in your project root:

RECALL_PRIVATE_KEY=your_recall_private_key
OPENAI_API_KEY=your_openai_api_key

Configure Next.js environment variables

In next.config.js, add:

module.exports = {
  env: {
    RECALL_PRIVATE_KEY: process.env.RECALL_PRIVATE_KEY,
    OPENAI_API_KEY: process.env.OPENAI_API_KEY,
  },
};

Basic integration

Here's how to integrate Recall with the AI SDK in a Next.js application:

// app/api/chat/route.ts
import { OpenAI } from "@ai-sdk/openai";
import { RecallAgentToolkit } from "@recallnet/agent-toolkit/ai-sdk";
import { Message, StreamingTextResponse } from "ai";
 
// Create the OpenAI client
const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY!,
});
 
// Create the Recall toolkit
const toolkit = new RecallAgentToolkit({
  privateKey: process.env.RECALL_PRIVATE_KEY!,
  configuration: {
    actions: {
      bucket: { read: true, write: true },
    },
    context: {},
  },
});
 
// Get the tools
const tools = toolkit.getTools();
 
export async function POST(req: Request) {
  const { messages } = await req.json();
 
  // Add system message if not present
  if (!messages.find((m: Message) => m.role === "system")) {
    messages.unshift({
      role: "system",
      content:
        "You are a helpful assistant with access to Recall storage. Use this to remember important information for the user.",
    });
  }
 
  // Generate a response with the AI SDK
  const response = await openai.chat({
    model: "gpt-4o",
    messages,
    tools,
  });
 
  // Create a streaming response
  return new StreamingTextResponse(response.content);
}

And the client-side React component:

app/page.tsx
"use client";
 
import { useChat } from "ai/react";
 
export default function ChatPage() {
  const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat();
 
  return (
    <div className="stretch mx-auto flex w-full max-w-md flex-col py-24">
      <h1 className="mb-4 text-2xl font-bold">Recall-Powered Chat</h1>
 
      {messages.length > 0 ? (
        messages.map((m) => (
          <div
            key={m.id}
            className={`mb-4 whitespace-pre-wrap ${
              m.role === "user" ? "rounded bg-blue-100 p-2" : "rounded bg-gray-100 p-2"
            }`}
          >
            <b>{m.role === "user" ? "You: " : "AI: "}</b>
            {m.content}
          </div>
        ))
      ) : (
        <div className="text-gray-500">Send a message to get started</div>
      )}
 
      <form onSubmit={handleSubmit} className="mt-4 flex gap-2">
        <input
          className="flex-1 rounded border border-gray-300 p-2"
          value={input}
          placeholder="Say something..."
          onChange={handleInputChange}
        />
        <button type="submit" className="rounded bg-blue-500 p-2 text-white" disabled={isLoading}>
          Send
        </button>
      </form>
    </div>
  );
}

This basic example sets up a chat interface that can use Recall for storage via tools.

Function calling

For more advanced implementations, you'll need to handle function calls properly. Here's how to do that:

app/api/chat/route.ts
import { OpenAI } from "@ai-sdk/openai";
import { RecallAgentToolkit } from "@recallnet/agent-toolkit/ai-sdk";
import { Message, StreamingTextResponse } from "ai";
 
// Create the OpenAI client
const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY!,
});
 
// Create the Recall toolkit
const toolkit = new RecallAgentToolkit({
  privateKey: process.env.RECALL_PRIVATE_KEY!,
  configuration: {
    actions: {
      bucket: { read: true, write: true },
    },
  },
});
 
// Get the tools
const tools = toolkit.getTools();
 
// Keep track of conversation buckets
const conversationBuckets = new Map<string, string>();
 
export async function POST(req: Request) {
  const { messages, id: conversationId } = await req.json();
 
  // Create a bucket for this conversation if it doesn't exist
  if (!conversationBuckets.has(conversationId)) {
    try {
      const result = await toolkit.run("get_or_create_bucket", {
        bucketAlias: `conversation-${conversationId}`,
      });
      conversationBuckets.set(conversationId, result.bucket);
    } catch (error) {
      console.error("Failed to create bucket:", error);
    }
  }
 
  // Add system message if not present
  if (!messages.find((m: Message) => m.role === "system")) {
    messages.unshift({
      role: "system",
      content: `You are a helpful assistant with access to Recall storage.
      Use storage to remember important information between conversations.
      The user has an assigned conversation bucket that you can use for storage.
      Always consider the contents of that bucket as the user's memory.`,
    });
  }
 
  // Generate a response with OpenAI
  const response = await openai.chat({
    model: "gpt-4o",
    messages,
    tools,
    tool_choice: "auto",
  });
 
  // Check if the response includes tool calls
  const toolCalls = response.tool_calls;
 
  if (toolCalls && toolCalls.length > 0) {
    // Process tool calls one by one
    const results = await Promise.all(
      toolCalls.map(async (toolCall) => {
        try {
          const toolName = toolCall.name;
          const toolArgs = toolCall.args;
 
          // Execute the tool using the toolkit
          const result = await toolkit.run(toolName, toolArgs);
 
          return {
            tool_call_id: toolCall.id,
            name: toolName,
            result,
          };
        } catch (error) {
          console.error(`Error executing tool ${toolCall.name}:`, error);
          return {
            tool_call_id: toolCall.id,
            name: toolCall.name,
            error: error.message,
          };
        }
      })
    );
 
    // Create an updated messages array with the tool results
    const updatedMessages = [
      ...messages,
      {
        role: "assistant",
        content: null,
        tool_calls: toolCalls.map((tc) => ({
          id: tc.id,
          type: "function",
          function: {
            name: tc.name,
            arguments: JSON.stringify(tc.args),
          },
        })),
      },
      ...results.map((result) => ({
        role: "tool",
        tool_call_id: result.tool_call_id,
        content: result.error ? `Error: ${result.error}` : JSON.stringify(result.result),
      })),
    ];
 
    // Get final assistant response
    const finalResponse = await openai.chat({
      model: "gpt-4o",
      messages: updatedMessages,
    });
 
    // Return the final response
    return new StreamingTextResponse(finalResponse.content);
  }
 
  // If no tool calls, just return the original response
  return new StreamingTextResponse(response.content);
}

Memory management

For maintaining conversation context across sessions:

// app/api/chat/route.ts
import { OpenAI } from "@ai-sdk/openai";
import { RecallAgentToolkit } from "@recallnet/agent-toolkit/ai-sdk";
import { Message, StreamingTextResponse } from "ai";
 
// Create the client and toolkit
const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY!,
});
 
const toolkit = new RecallAgentToolkit({
  privateKey: process.env.RECALL_PRIVATE_KEY!,
  configuration: {
    actions: {
      bucket: { read: true, write: true },
    },
  },
});
 
const tools = toolkit.getTools();
 
// Handle conversation memory
async function loadConversationMemory(conversationId: string, recentMessages: Message[]) {
  try {
    // Create or get bucket for this conversation
    const bucketResult = await toolkit.run("get_or_create_bucket", {
      bucketAlias: `memory-${conversationId}`,
    });
 
    // Try to load past memory
    try {
      const memoryResult = await toolkit.run("get_object", {
        bucket: bucketResult.bucket,
        key: "conversation-summary",
      });
 
      const memoryData = JSON.parse(memoryResult.value);
 
      // Return both the memory and the recent messages
      return {
        memory: memoryData.summary,
        bucketId: bucketResult.bucket,
      };
    } catch (error) {
      // No memory exists yet, which is fine
      return {
        memory: "No previous conversation found.",
        bucketId: bucketResult.bucket,
      };
    }
  } catch (error) {
    console.error("Error loading memory:", error);
    return {
      memory: "Failed to load previous conversation.",
      bucketId: null,
    };
  }
}
 
async function saveConversationMemory(bucketId: string, messages: Message[]) {
  if (!bucketId) return;
 
  try {
    // Create a summary of the conversation
    const summaryResponse = await openai.chat({
      model: "gpt-4o",
      messages: [
        {
          role: "system",
          content:
            "Summarize the following conversation in a concise paragraph that captures the key points, important information, and any decisions or actions taken.",
        },
        ...messages,
      ],
    });
 
    // Save the summary and the last few messages
    await toolkit.run("add_object", {
      bucket: bucketId,
      key: "conversation-summary",
      data: JSON.stringify({
        summary: summaryResponse.content,
        lastUpdated: new Date().toISOString(),
        recentMessages: messages.slice(-5),
      }),
      overwrite: true,
    });
  } catch (error) {
    console.error("Error saving memory:", error);
  }
}
 
export async function POST(req: Request) {
  const { messages, id: conversationId = "default" } = await req.json();
 
  // Load conversation memory
  const { memory, bucketId } = await loadConversationMemory(conversationId, messages);
 
  // Construct messages with memory context
  const messagesWithMemory = [
    {
      role: "system",
      content: `You are a helpful assistant with access to Recall storage and memory of past conversations.
 
Memory from previous conversations:
${memory}
 
Use this context to provide more consistent and personalized responses. If the user refers to something from a past conversation, use this memory to understand the reference.
 
You can also use Recall storage tools to save and retrieve information as needed.`,
    },
    ...messages,
  ];
 
  // Generate response
  const response = await openai.chat({
    model: "gpt-4o",
    messages: messagesWithMemory,
    tools,
  });
 
  // Save the conversation after getting a response
  if (bucketId) {
    // We save it in the background to not block the response
    saveConversationMemory(bucketId, [
      ...messages,
      { role: "assistant", content: response.content },
    ]).catch(console.error);
  }
 
  return new StreamingTextResponse(response.content);
}

Next.js App Router integration

For a more complete Next.js application with the App Router:

my-recall-app/
├── app/
│   ├── api/
│   │   └── chat/
│   │       └── route.ts   # API route handler
│   ├── chat/
│   │   └── page.tsx       # Chat page component
│   ├── layout.tsx         # App layout
│   └── page.tsx           # Home page
├── components/
│   ├── chat-interface.tsx # Reusable chat component
│   └── recall-provider.tsx # Recall context provider
├── lib/
│   ├── recall.ts          # Recall toolkit setup
│   └── utils.ts           # Utility functions
├── .env.local             # Environment variables
├── next.config.js         # Next.js configuration
├── package.json           # Dependencies
└── tsconfig.json          # TypeScript configuration

Recall Agent Toolkit setup

A common pattern is to centralize your Recall toolkit setup:

lib/recall.ts
import { RecallAgentToolkit } from "@recallnet/agent-toolkit/ai-sdk";
 
let toolkit: RecallAgentToolkit | null = null;
 
export function getRecallToolkit() {
  if (!toolkit) {
    toolkit = new RecallAgentToolkit({
      privateKey: process.env.RECALL_PRIVATE_KEY!,
      configuration: {
        actions: {
          account: { read: true },
          bucket: { read: true, write: true },
        },
      },
      middleware: [
        {
          beforeToolCall: (tool, input) => {
            console.log(`Calling ${tool.name} with:`, input);
            return input;
          },
          afterToolCall: (tool, input, output) => {
            console.log(`${tool.name} result:`, output);
            return output;
          },
          onToolError: (tool, input, error) => {
            console.error(`${tool.name} error:`, error);
            throw error;
          },
        },
      ],
    });
  }
 
  return toolkit;
}

Best practices

When integrating Recall with the AI SDK, follow these best practices:

  1. Server-side execution: Always run Recall tools on the server side to protect your private key

  2. Error handling: Implement robust error handling for tool calls and API requests

  3. Loading states: Show appropriate loading states during tool execution for better UX

  4. Streaming responses: Use streaming for better user experience

  5. Environment variables: Keep sensitive keys in environment variables

  6. Middleware for debugging: Use middleware to log tool calls during development:

    middleware: [
      {
        beforeToolCall: (tool, input) => {
          console.log(`Calling ${tool.name} with:`, input);
          return input;
        },
        afterToolCall: (tool, input, output) => {
          console.log(`${tool.name} result:`, output);
          return output;
        },
        onToolError: (tool, input, error) => {
          console.error(`${tool.name} error:`, error);
          throw error;
        },
      },
    ];
  7. Route protection: Add authentication to protect your API routes if needed

Troubleshooting

IssuePossible causeSolution
"Private key not found"Missing environment variableEnsure RECALL_PRIVATE_KEY is set in .env.local
"Cannot read properties of undefined"Incorrect tool usageVerify tool parameters match expected schema
"Error: ECONNREFUSED"Network issue or API service downCheck internet connection and API status
Slow responsesComplex tool operationsImplement proper loading states in UI
"Function not found"Incorrect tool nameEnsure tool names match exactly

AI SDK helper components

The AI SDK provides several useful React components and hooks:

  • useChat: Main hook for chat functionality
  • useCompletion: Hook for single-turn completions
  • Message: Type for chat messages
  • StreamingTextResponse: Class for streaming responses

Here's how to use them effectively:

"use client";
 
import { useChat, useCompletion } from "ai/react";
 
// For chat interfaces
export function ChatComponent() {
  const { messages, input, handleInputChange, handleSubmit } = useChat({
    api: "/api/chat",
    initialMessages: [
      { id: "1", role: "system", content: "You are a helpful assistant with Recall storage." },
    ],
  });
 
  // Your chat UI
}
 
// For single-turn completions
export function CompletionComponent() {
  const { completion, input, handleInputChange, handleSubmit } = useCompletion({
    api: "/api/completion",
  });
 
  // Your completion UI
}

Advanced Vercel AI SDK features

Vercel's AI SDK offers advanced features for production applications:

  1. Rate limiting: Add rate limiting to prevent abuse

    import { Ratelimit } from "@upstash/ratelimit";
    import { Redis } from "@upstash/redis";
     
    const ratelimit = new Ratelimit({
      redis: Redis.fromEnv(),
      limiter: Ratelimit.slidingWindow(5, "60 s"),
    });
     
    export async function POST(req: Request) {
      const ip = req.headers.get("x-forwarded-for") || "anonymous";
      const { success, limit, reset } = await ratelimit.limit(ip);
     
      if (!success) {
        return new Response("Too many requests", {
          status: 429,
          headers: {
            "X-RateLimit-Limit": limit.toString(),
            "X-RateLimit-Remaining": "0",
            "X-RateLimit-Reset": reset.toString(),
          },
        });
      }
     
      // Continue with normal request processing
    }
  2. Caching: Cache responses for similar queries

    export async function POST(req: Request) {
      const { messages } = await req.json();
      const cacheKey = JSON.stringify(messages);
     
      // Check cache
      const cache = await caches.open("ai-responses");
      const cachedResponse = await cache.match(cacheKey);
     
      if (cachedResponse) {
        return cachedResponse;
      }
     
      // Generate response
      const response = await openai.chat({
        model: "gpt-4o",
        messages,
        tools,
      });
     
      // Cache response
      const streamingResponse = new StreamingTextResponse(response.content);
      const clonedResponse = streamingResponse.clone();
      cache.put(cacheKey, clonedResponse);
     
      return streamingResponse;
    }
  3. Feedback collection: Add feedback mechanisms to improve your model

    function FeedbackButtons({ messageId }: { messageId: string }) {
      const submitFeedback = async (type: "positive" | "negative") => {
        await fetch("/api/feedback", {
          method: "POST",
          body: JSON.stringify({ messageId, type }),
        });
      };
     
      return (
        <div className="mt-1 flex space-x-2">
          <button
            onClick={() => submitFeedback("positive")}
            className="text-sm text-gray-500 hover:text-green-600"
          >
            👍 Helpful
          </button>
          <button
            onClick={() => submitFeedback("negative")}
            className="text-sm text-gray-500 hover:text-red-600"
          >
            👎 Not helpful
          </button>
        </div>
      );
    }

Next steps

The Vercel AI SDK is regularly updated with new features. Check the official documentation for the latest updates.