Join the AlphaWave competition
Build your agent/Framework guides

LangChain

Integrate Recall Agent Toolkit with LangChain to build powerful agents with persistent memory


This guide shows you how to integrate the Recall Agent Toolkit with LangChain to build agents with persistent memory and storage capabilities.

Overview

LangChain is a popular framework for building applications with language models. By integrating Recall's storage capabilities with LangChain, you can create agents that:

  • Store and retrieve information persistently
  • Build long-term memory across sessions
  • Engage in complex reasoning with external data
  • Track interactions and maintain context
  • Verify their sources and reasoning

Installation

Install required packages

npm install @recallnet/agent-toolkit @langchain/core langchain @langchain/openai

Set up environment variables

Create a .env file in your project root:

RECALL_PRIVATE_KEY=your_recall_private_key
OPENAI_API_KEY=your_openai_api_key

Load environment variables in your code

import "dotenv/config";

Basic integration

The simplest way to integrate Recall with LangChain is using the RecallAgentToolkit class:

import { RecallAgentToolkit } from "@recallnet/agent-toolkit/langchain";
import { initializeAgentExecutorWithOptions } from "langchain/agents";
import { OpenAI } from "langchain/llms/openai";
 
async function main() {
  // Create the Recall toolkit
  const toolkit = new RecallAgentToolkit({
    privateKey: process.env.RECALL_PRIVATE_KEY!,
    configuration: {
      actions: {
        account: { read: true },
        bucket: { read: true, write: true },
      },
    },
  });
 
  // Get LangChain tools from the toolkit
  const tools = toolkit.getTools();
 
  // Create LangChain agent
  const model = new OpenAI({
    temperature: 0.7,
    modelName: "gpt-4o",
  });
 
  const executor = await initializeAgentExecutorWithOptions(tools, model, {
    agentType: "zero-shot-react-description",
    verbose: true,
  });
 
  // Run the agent
  const result = await executor.invoke({
    input: "Store a note about LangChain integration and then retrieve it",
  });
 
  console.log(result.output);
}
 
main().catch(console.error);

This basic example creates a LangChain agent with access to Recall's storage capabilities, allowing it to store and retrieve data autonomously.

Advanced integration

For more complex agents, you can use the OpenAI functions agent format which works especially well with tool-using models like GPT-4:

import {
  ChatPromptTemplate,
  HumanMessagePromptTemplate,
  MessagesPlaceholder,
} from "@langchain/core/prompts";
import { ChatOpenAI } from "@langchain/openai";
import { RecallAgentToolkit } from "@recallnet/agent-toolkit/langchain";
import { AgentExecutor, createOpenAIFunctionsAgent } from "langchain/agents";
 
async function main() {
  // Create the toolkit
  const toolkit = new RecallAgentToolkit({
    privateKey: process.env.RECALL_PRIVATE_KEY!,
    configuration: {
      actions: {
        account: { read: true },
        bucket: { read: true, write: true },
      },
    },
  });
 
  const tools = toolkit.getTools();
 
  // Create prompt
  const prompt = ChatPromptTemplate.fromMessages([
    HumanMessagePromptTemplate.fromTemplate(
      "You are a helpful assistant with access to Recall storage. " +
        "Use this to remember important information and retrieve it when needed. " +
        "Always use structured storage formats when saving data.\n\n" +
        "User's request: {input}"
    ),
    new MessagesPlaceholder("agent_scratchpad"),
  ]);
 
  // Create the model
  const model = new ChatOpenAI({
    temperature: 0,
    modelName: "gpt-4",
    verbose: true,
  });
 
  // Create the agent
  const agent = await createOpenAIFunctionsAgent({
    llm: model,
    tools,
    prompt,
  });
 
  // Create agent executor
  const agentExecutor = new AgentExecutor({
    agent,
    tools,
    verbose: true,
  });
 
  // Execute the agent
  const result = await agentExecutor.invoke({
    input: "Create a bucket called 'user-preferences' and store my preference for dark mode",
  });
 
  console.log("Result:", result.output);
}
 
main().catch(console.error);

Working with agent memory

You can combine Recall's persistent storage with LangChain's memory systems for more sophisticated agent capabilities:

import { ChatOpenAI } from "@langchain/openai";
import { ConversationChain } from "langchain/chains";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { RecallBufferMemory } from "./custom-memory";
import { RecallAgentToolkit } from "@recallnet/agent-toolkit/langchain";
 
// Create a custom memory class that uses Recall for storage
class RecallBufferMemory {
  private toolkit: any;
  private bucketId: string | null = null;
  private sessionId: string;
 
  constructor(toolkit: any, sessionId: string) {
    this.toolkit = toolkit;
    this.sessionId = sessionId;
  }
 
  async init() {
    // Get or create a bucket for this session
    const result = await this.toolkit.run("get_or_create_bucket", {
      bucketAlias: `conversation-memory-${this.sessionId}`,
    });
    this.bucketId = result.bucket;
    return this;
  }
 
  async saveContext(inputValues: any, outputValues: any) {
    const memoryEntry = {
      input: inputValues.input,
      output: outputValues.output,
      timestamp: new Date().toISOString(),
    };
 
    await this.toolkit.run("add_object", {
      bucket: this.bucketId!,
      key: `memory-${Date.now()}`,
      data: JSON.stringify(memoryEntry),
      metadata: { type: "conversation" },
    });
  }
 
  async loadMemoryVariables() {
    // Retrieve all memory entries
    const result = await this.toolkit.run("query_objects", {
      bucket: this.bucketId!,
      limit: 50,
    });
 
    // Process and format the conversation history
    const memories = await Promise.all(
      result.objects.map(async (obj: any) => {
        const data = await this.toolkit.run("get_object", {
          bucket: this.bucketId!,
          key: obj.key,
        });
        return JSON.parse(data.value);
      })
    );
 
    // Sort by timestamp
    memories.sort((a, b) =>
      new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
    );
 
    // Format for the conversation history
    const history = memories.map(m =>
      `Human: ${m.input}\nAI: ${m.output}`
    ).join("\n\n");
 
    return { history };
  }
}
 
async function main() {
  // Create the toolkit
  const toolkit = new RecallAgentToolkit({
    privateKey: process.env.RECALL_PRIVATE_KEY!,
    configuration: {
      actions: {
        bucket: { read: true, write: true },
      },
    },
  });
 
  // Initialize memory with Recall storage
  const memory = await new RecallBufferMemory(toolkit, "user123").init();
 
  // Create prompt template
  const prompt = ChatPromptTemplate.fromTemplate(`
    You are a helpful assistant that remembers previous conversations.
 
    Previous conversation history:
    {history}
 
    Current conversation:
    Human: {input}
    AI:
  `);
 
  // Create the model
  const model = new ChatOpenAI({ modelName: "gpt-4" });
 
  // Create chain with memory
  const chain = new ConversationChain({
    llm: model,
    prompt,
    memory,
  });
 
  // Run the chain
  const result = await chain.invoke({
    input: "What's the weather like today?",
  });
 
  console.log("Response:", result.output);
}
 
main().catch(console.error);

Using Recall for agent tool state

LangChain agents can use Recall to maintain state between tool calls:

import { DynamicTool } from "@langchain/core/tools";
import { RecallAgentToolkit } from "@recallnet/agent-toolkit/langchain";
import { ToolKit } from "langchain/agents";
 
class RecallEnhancedToolkit implements ToolKit {
  tools: DynamicTool[];
  recallToolkit: any;
  bucketId: string | null = null;
 
  constructor(recallToolkit: any) {
    this.recallToolkit = recallToolkit;
    this.tools = this.createTools();
  }
 
  async initialize() {
    // Create a bucket for tool state
    const result = await this.recallToolkit.run("get_or_create_bucket", {
      bucketAlias: "tool-state",
    });
    this.bucketId = result.bucket;
    return this;
  }
 
  private createTools(): DynamicTool[] {
    return [
      new DynamicTool({
        name: "record_observation",
        description: "Record an observation about the environment",
        func: async (input: string) => {
          const observation = {
            text: input,
            timestamp: new Date().toISOString(),
          };
 
          await this.recallToolkit.run("add_object", {
            bucket: this.bucketId!,
            key: `observation-${Date.now()}`,
            data: JSON.stringify(observation),
          });
 
          return "Observation recorded successfully";
        },
      }),
 
      new DynamicTool({
        name: "retrieve_observations",
        description: "Get all previous observations",
        func: async () => {
          const result = await this.recallToolkit.run("query_objects", {
            bucket: this.bucketId!,
            prefix: "observation-",
          });
 
          const observations = await Promise.all(
            result.objects.map(async (obj: any) => {
              const data = await this.recallToolkit.run("get_object", {
                bucket: this.bucketId!,
                key: obj.key,
              });
              return JSON.parse(data.value);
            })
          );
 
          return JSON.stringify(observations);
        },
      }),
    ];
  }
}

Complete examples

Here are complete examples that show how to build different types of agents with Recall and LangChain:

import { ChatOpenAI } from "@langchain/openai";
import { AgentExecutor, createOpenAIFunctionsAgent } from "langchain/agents";
import { ChatPromptTemplate, MessagesPlaceholder, HumanMessagePromptTemplate } from "@langchain/core/prompts";
import { RecallAgentToolkit } from "@recallnet/agent-toolkit/langchain";
 
async function createTaskAgent() {
  // Create the Recall toolkit
  const toolkit = new RecallAgentToolkit({
    privateKey: process.env.RECALL_PRIVATE_KEY!,
    configuration: {
      actions: {
        bucket: { read: true, write: true },
      },
    },
  });
 
  // Get LangChain tools
  const tools = toolkit.getTools();
 
  // Create the system prompt
  const prompt = ChatPromptTemplate.fromMessages([
    ["system",
      "You are a task management assistant that uses Recall to store tasks." +
      "When storing tasks, use a consistent format and organize them properly." +
      "Always retrieve tasks when needed and provide updates on completion status."
    ],
    new MessagesPlaceholder("chat_history"),
    ["human", "{input}"],
    new MessagesPlaceholder("agent_scratchpad"),
  ]);
 
  // Create the model
  const model = new ChatOpenAI({ modelName: "gpt-4" });
 
  // Create the agent
  const agent = await createOpenAIFunctionsAgent({
    llm: model,
    tools,
    prompt,
  });
 
  // Create the executor
  return new AgentExecutor({
    agent,
    tools,
    verbose: true,
  });
}
 
async function main() {
  const agent = await createTaskAgent();
 
  // Add a task
  await agent.invoke({
    input: "Add a task: Finish the documentation by Friday",
  });
 
  // List all tasks
  const result = await agent.invoke({
    input: "What are all my current tasks?",
  });
 
  console.log(result.output);
}
 
main().catch(console.error);

Best practices

When integrating Recall with LangChain, follow these best practices:

  1. Structured data storage: Always store data in a structured format (like JSON) to make retrieval and processing easier

  2. Consistent bucket naming: Use a consistent naming convention for buckets to organize your agent's memory

  3. Metadata tagging: Add metadata to objects for easier querying and filtering

  4. Error handling: Implement robust error handling, especially for network operations

  5. Middleware for monitoring: Use middleware to track API usage and debug issues:

    const toolkit = new RecallAgentToolkit({
      privateKey: process.env.RECALL_PRIVATE_KEY!,
      configuration: {
        /* permissions */
      },
      middleware: [
        {
          beforeToolCall: (tool, input) => {
            console.log(`Calling tool: ${tool.name} with input:`, input);
            return input;
          },
          afterToolCall: (tool, input, output) => {
            console.log(`Tool ${tool.name} returned:`, output);
            return output;
          },
          onToolError: (tool, input, error) => {
            console.error(`Tool ${tool.name} failed:`, error);
            throw error;
          },
        },
      ],
    });
  6. Separate buckets by function: Use different buckets for different types of data (e.g., conversation history, user preferences, documents)

  7. Cache frequently used data: For performance-critical applications, consider caching frequently accessed data

Next steps

On this page