LangChain Next.js Template

repository·main·Indexed 25 days ago

https://github.com/langchain-ai/langchain-nextjs-template

A starter template for building AI-powered applications using LangChain.js and Next.js. It demonstrates patterns for simple chat, structured output using Zod, agentic workflows with LangGraph, and Retrieval Augmented Generation (RAG) with Supabase vector stores. The template includes specific implementations for streaming agent data and structured tool outputs from React Server Components to client components using the Vercel AI SDK.

Tokens
3.5K
Snippets
7
Records
13
Agent score
82%

What's inside langchain-nextjs-template

  1. Implement agent streaming with React Server Components

    main

    To stream agent data from a Server Action to a Client Component, follow these steps:

    1. Create a Server Action (action.ts):

      • Use createStreamableValue from ai/rsc to initialize a stream.
      • Initialize your LLM (e.g., ChatOpenAI) and tools (e.g., TavilySearchResults).
      • Use createToolCallingAgent and AgentExecutor to set up the agent.
      • Call agentExecutor.streamEvents to get the event stream.
      • Iterate through the events and update the stream using stream.update().
      • Note: To avoid a known RSC streaming bug, stringify and parse the event data: stream.update(JSON.parse(JSON.stringify(item, null, 2))).
      • Return the stream value: return { streamData: stream.value };.
    2. Consume the stream in a Client Component (page.tsx):

      • Import readStreamableValue from ai/rsc and your server action.
      • Call the server action to get the streamData.
      • Use a for await...of loop with readStreamableValue(streamData) to process incoming chunks and update your local state.
    // Server Action (action.ts)
    "use server";
    
    import { ChatOpenAI } from "@langchain/openai";
    import { ChatPromptTemplate } from "@langchain/core/prompts";
    import { TavilySearchResults } from "@langchain/community/tools/tavily_search";
    import { AgentExecutor, createToolCallingAgent } from "langchain/agents";
    import { pull } from "langchain/hub";
    import { createStreamableValue } from "ai/rsc";
    
    export async function runAgent(input: string) {
      const llm = new ChatOpenAI({
        model: "gpt-4o-mini",
        temperature: 0,
      });
    
      const stream = createStreamableValue();
    
      (async () => {
        const tools = [new TavilySearchResults({ maxResults: 1 })];
        const prompt = await pull<ChatPromptTemplate>("hwchase17/openai-tools-agent");
    
        const agent = createToolCallingAgent({
          llm,
          tools,
          prompt,
        });
    
        const agentExecutor = new AgentExecutor({
          agent,
          tools,
        });
    
        const streamingEvents = agentExecutor.streamEvents(
          { input },
          { version: "v1" },
        );
    
        for await (const item of streamingEvents) {
          // Stringify/Parse workaround for RSC streaming bug
          stream.update(JSON.parse(JSON.stringify(item, null, 2)));
        }
    
        stream.done();
      })();
    
      return { streamData: stream.value };
    }
    
    // Client Component (page.tsx)
    "use client";
    
    import { useState } from "react";
    import { readStreamableValue } from "ai/rsc";
    import { runAgent } from "./action";
    
    export default function Page() {
      const [input, setInput] = useState("");
      const [data, setData] = useState<any[]>([]); // StreamEvent type used in actual code
    
      async function handleSubmit(e: React.FormEvent) {
        e.preventDefault();
    
        const { streamData } = await runAgent(input);
        for await (const item of readStreamableValue(streamData)) {
          setData((prev) => [...prev, item]);
        }
      }
    
      return (
        // ... JSX implementation
      );
    }
  2. Get started with the LangChain + Next.js template

    main

    To set up the development environment, follow these steps:

    1. Clone the repository and navigate to the directory.
    2. Configure environment variables: Copy .env.example to .env.local.
      • For basic chat examples, add your OPENAI_API_KEY.
      • If using LangSmith tracing in serverless Edge functions, set LANGCHAIN_CALLBACKS_BACKGROUND=false to ensure tracing completes.
    3. Install dependencies: Use your preferred package manager (e.g., yarn).
    4. Run the development server: Execute yarn dev and open http://localhost:3000.

    To modify the UI, edit app/page.tsx. To modify the backend logic (prompts, models, or modules), edit app/api/chat/route.ts.

    yarn dev
  3. Set up Retrieval Augmented Generation (RAG) with Supabase

    main

    The template provides two RAG implementations: a standard chain and an agentic RAG using LangGraph. Both default to using Supabase as a vector store.

    Configuration

    1. Set up a Supabase database following the Supabase integration guide.
    2. Add your Supabase database URL and private key to .env.local.

    Usage

    • Ingestion: Use the ingestion route to split, embed, and upload text. Note that pressing Upload multiple times for the same text will create duplicates. To clear the store, run DELETE FROM documents; in your Supabase console.
    • Retrieval Chain: Located at app/api/chat/retrieval/route.ts. It uses LangChain Expression Language (LCEL) and returns cited sources in the response header.
    • Retrieval Agent: Located at app/api/chat/retrieval_agents/route.ts. It uses LangGraph for agentic RAG workflows.

    You can swap Supabase for any other supported vector store by modifying the routes in app/api/chat/retrieval/route.ts, app/api/chat/retrieval_agents/route.ts, and app/api/retrieval/ingest/route.ts.

  4. Stream structured tool output to the client

    main

    This guide demonstrates how to use React Server Components and the AI SDK to stream structured data (like tool calls) from a server action to a client.

    Workflow

    1. Define a Schema: Use zod to define the structure of the tool's input.
    2. Create a Server Action: Use the "use server" directive and createStreamableValue from ai/rsc to manage the stream.
    3. Bind Tools to LLM: Use llm.bind to attach tool definitions (converted to JSON schema via zodToJsonSchema) to the model.
    4. Construct an LCEL Chain: Pipe the prompt, the model with tools, and a JsonOutputKeyToolsParser together.
    5. Stream Results: Iterate over the chain's .stream() output and update the streamable value using stream.update().
    "use server";
    
    import { ChatOpenAI } from "@langchain/openai";
    import { ChatPromptTemplate } from "@langchain/core/prompts";
    import { createStreamableValue } from "ai/rsc";
    import { z } from "zod";
    import { zodToJsonSchema } from "zod-to-json-schema";
    import { JsonOutputKeyToolsParser } from "@langchain/core/output_parsers/openai_tools";
    
    const Weather = z
      .object({
        city: z.string().describe("City to search for weather"),
        state: z.string().describe("State abbreviation to search for weather"),
      })
      .describe("Weather search parameters");
    
    export async function executeTool(
      input: string,
    ) {
      "use server";
    
      const stream = createStreamableValue();
    
      (async () => {
        const prompt = ChatPromptTemplate.fromMessages([
          [
            "system",
            `You are a helpful assistant. Use the tools provided to best assist the user.`,
          ],
          ["human", "{input}"],
        ]);
    
        const llm = new ChatOpenAI({
          model: "gpt-4o-mini",
          temperature: 0,
        });
    
        const modelWithTools = llm.bind({
          tools: [
            {
              type: "function" as const,
              function: {
                name: "get_weather",
                description: Weather.description,
                parameters: zodToJsonSchema(Weather),
              },
            },
          ],
        });
    
        const chain = prompt.pipe(modelWithTools).pipe(
          new JsonOutputKeyToolsParser<z.infer<typeof Weather>>({
            keyName: "get_weather",
            zodSchema: Weather,
          }),
        );
    
        const streamResult = await chain.stream({
          input,
        });
    
        for await (const item of streamResult) {
          stream.update(JSON.parse(JSON.stringify(item, null, 2)));
        }
    
        stream.done();
      })();
    
      return { streamData: stream.value };
    }
  5. Implement structured output using Zod and OpenAI Functions

    main

    The template demonstrates how to force an LLM to return output matching a specific schema. It uses the Zod library to define the schema, which is then formatted for OpenAI. The implementation passes the schema as a function and uses the function_call parameter to ensure the model returns arguments in the specified format.

    Refer to app/api/chat/structured_output/route.ts for the implementation details.