在 Next.js 15 和 Vercel 上使用 Google AI 配置 Langchain 和 Langraph

langchainlangraphnextjsvercelgoogle-aigeminitypescriptai-agents
By sko X opus 4.19/20/202514 min read

本指南展示如何在 Next.js 15 项目中配置 Langchain 和 Langraph,使用 Google 的 Gemini 模型构建 AI 智能体,并部署到 Vercel,支持 TypeScript。

心智模型:构建块架构

将此配置想象成组装现代 Web API 的专用中间件。Next.js 15 提供服务器框架,Langchain 充当您的 AI 编排层(类似于 LLM 的 Express 中间件),Langraph 添加有状态工作流功能(类似于 AI 工作流的 Redux Saga)。Google 的 Gemini 模型作为智能层,而 Vercel 平台处理无服务器部署,自动扩展您的 AI 端点,类似于 Google Cloud Functions 但具有更好的开发体验。

基础设置:项目初始化

1. 创建 Next.js 15 项目

npx create-next-app@latest my-ai-agent --typescript --tailwind --app --no-src-dir
cd my-ai-agent

创建一个新的 Next.js 15 项目,支持 TypeScript、Tailwind CSS v4、App Router,文件位于根目录。

2. 安装核心依赖

npm install langchain @langchain/core @langchain/langgraph @langchain/google-genai
npm install @langchain/community ai @ai-sdk/google
npm install daisyui@latest

安装 Langchain 核心库、用于工作流的 Langraph、Google Gemini 集成、社区工具、Vercel AI SDK 和用于 UI 组件的 DaisyUI v5。

3. TypeScript 配置

使用 --typescript 标志创建项目时,Next.js 15 会自动配置 TypeScript。默认的 tsconfig.json 已经针对 Langchain 使用进行了优化,无需更改。

4. 环境变量

# .env.local
GOOGLE_API_KEY=your-google-ai-api-key
LANGCHAIN_TRACING_V2=true  # 可选:启用 LangSmith 跟踪
LANGCHAIN_API_KEY=your-langsmith-key  # 可选:用于 LangSmith 调试
LANGCHAIN_CALLBACKS_BACKGROUND=false  # 必需:用于 Vercel 无服务器

重要:LANGCHAIN_CALLBACKS_BACKGROUND 说明

当设置为 true(默认值)时,Langchain 在发送响应后在后台异步运行回调。这在传统服务器上工作正常,但在 Vercel 等无服务器环境中会导致问题,因为:

  1. 无服务器函数会立即终止 返回响应后
  2. 后台回调被终止 在完成之前,导致请求挂起或超时
  3. Vercel 可能报告错误 如"函数执行时间过长",即使是简单请求

将其设置为 false 确保所有回调在发送响应之前同步完成,防止无服务器函数终止问题。这对于 Vercel、AWS Lambda 和类似平台是必需的。

5. 更新 globals.css 用于 Tailwind v4 和 DaisyUI v5

/* app/globals.css */
@import "tailwindcss";
@plugin "daisyui";

导入 Tailwind CSS v4 并使用新的基于 CSS 的配置将 DaisyUI v5 作为插件加载。

6. 使用 Google Gemini 的基本 Langchain API 路由

// app/api/chat/route.ts
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { HumanMessage, AIMessage } from '@langchain/core/messages';
import { NextResponse } from 'next/server';

export const runtime = 'nodejs';
export const maxDuration = 300; // Pro 计划的 5 分钟

export async function POST(req: Request) {
  try {
    const { message } = await req.json();

    const model = new ChatGoogleGenerativeAI({
      modelName: 'gemini-2.5-pro',
      temperature: 0.7,
      streaming: false,
      maxOutputTokens: 2048,
    });

    const response = await model.invoke([
      new HumanMessage(message)
    ]);

    return NextResponse.json({
      content: response.content
    });
  } catch (error) {
    console.error('Chat API error:', error);
    return NextResponse.json(
      { error: 'Failed to process chat' },
      { status: 500 }
    );
  }
}

创建一个 POST 端点,接受消息,通过 Gemini Pro 处理,并返回 AI 响应,使用 Node.js 运行时以获得完整的 Langchain 支持。

高级示例:带流式的 Langraph 智能体

1. 安装额外依赖

npm install @langchain/google-genai @langchain/langgraph zod
npm install @vercel/kv uuid @google/generative-ai
npm install @tanstack/react-query

添加 Google AI 嵌入、Langraph 智能体、使用 Zod 的模式验证、用于缓存的 Vercel KV 和用于数据获取的 TanStack Query 的依赖项。

2. 创建带工具的智能体

// lib/agent.ts
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { Calculator } from '@langchain/community/tools/calculator';
import { WebBrowser } from '@langchain/community/tools/webbrowser';
import { createReactAgent } from '@langchain/langgraph/prebuilt';
import { HumanMessage } from '@langchain/core/messages';
import { GoogleGenerativeAIEmbeddings } from '@langchain/google-genai';

export function createAgent() {
  const model = new ChatGoogleGenerativeAI({
    modelName: 'gemini-2.5-flash',
    temperature: 0,
    streaming: true,
    maxOutputTokens: 8192,
  });

  const embeddings = new GoogleGenerativeAIEmbeddings({
    modelName: "embedding-001",
  });

  const tools = [
    new Calculator(),
    new WebBrowser({ model, embeddings }),
  ];

  return createReactAgent({
    llm: model,
    tools,
  });
}

使用 Gemini Flash 设置 ReAct 智能体以获得快速响应,配备计算器和网络浏览器工具以增强功能。

3. 使用 Langraph 的流式 API 路由

// app/api/agent/route.ts
import { createAgent } from '@/lib/agent';
import { HumanMessage } from '@langchain/core/messages';

export const runtime = 'nodejs';
export const maxDuration = 300;

export async function POST(req: Request) {
  const { message } = await req.json();

  const encoder = new TextEncoder();
  const stream = new TransformStream();
  const writer = stream.writable.getWriter();

  const agent = createAgent();

  // 在后台开始流式传输
  (async () => {
    try {
      const eventStream = await agent.stream({
        messages: [new HumanMessage(message)],
      });

      for await (const event of eventStream) {
        if (event.agent?.messages?.[0]?.content) {
          await writer.write(
            encoder.encode(`data: ${JSON.stringify({
              content: event.agent.messages[0].content
            })}\n\n`)
          );
        }
      }
    } catch (error) {
      console.error('Streaming error:', error);
    } finally {
      await writer.close();
    }
  })();

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

实现服务器发送事件(SSE)流式传输,实时发送智能体响应,提高感知性能。

4. 带流式的客户端组件

// app/providers.tsx
'use client';

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useState } from 'react';

export default function Providers({ children }: { children: React.ReactNode }) {
  const [queryClient] = useState(() => new QueryClient({
    defaultOptions: {
      queries: {
        staleTime: 60 * 1000, // 1 分钟
        retry: 1,
      },
    },
  }));

  return (
    <QueryClientProvider client={queryClient}>
      {children}
    </QueryClientProvider>
  );
}

使用针对 AI 应用程序优化的默认设置设置 TanStack Query 提供者。

// app/layout.tsx
import './globals.css';
import Providers from './providers';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}

使用 TanStack Query 提供者包装应用程序以进行全局访问。

// components/ChatInterface.tsx
'use client';

import { useState } from 'react';
import { useMutation } from '@tanstack/react-query';

export default function ChatInterface() {
  const [message, setMessage] = useState('');
  const [response, setResponse] = useState('');

  const streamChat = useMutation({
    mutationFn: async (userMessage: string) => {
      setResponse('');

      const res = await fetch('/api/agent', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ message: userMessage }),
      });

      if (!res.ok) throw new Error('Failed to send message');

      const reader = res.body?.getReader();
      const decoder = new TextDecoder();

      while (reader) {
        const { done, value } = await reader.read();
        if (done) break;

        const chunk = decoder.decode(value);
        const lines = chunk.split('\n');

        for (const line of lines) {
          if (line.startsWith('data: ')) {
            try {
              const data = JSON.parse(line.slice(6));
              setResponse(prev => prev + data.content);
            } catch {}
          }
        }
      }

      return response;
    },
    onError: (error) => {
      console.error('Chat error:', error);
    },
  });

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (message.trim()) {
      streamChat.mutate(message);
    }
  };

  return (
    <div className="card w-full bg-base-100 shadow-xl">
      <div className="card-body">
        <h2 className="card-title">Google Gemini AI 智能体</h2>

        <form onSubmit={handleSubmit}>
          <div className="form-control">
            <textarea
              className="textarea textarea-bordered"
              placeholder="问您的问题..."
              value={message}
              onChange={(e) => setMessage(e.target.value)}
              rows={3}
              disabled={streamChat.isPending}
            />
          </div>

          <div className="card-actions justify-end mt-4">
            <button
              type="submit"
              className="btn btn-primary"
              disabled={streamChat.isPending || !message.trim()}
            >
              {streamChat.isPending ? (
                <>
                  <span className="loading loading-spinner"></span>
                  思考中...
                </>
              ) : '发送'}
            </button>
          </div>
        </form>

        {streamChat.isError && (
          <div className="alert alert-error mt-4">
            <span>发送消息失败。请重试。</span>
          </div>
        )}

        {response && (
          <div className="alert mt-4">
            <span>{response}</span>
          </div>
        )}
      </div>
    </div>
  );
}

使用 TanStack Query 的 useMutation 钩子管理流式 API 调用,内置加载和错误状态的 React 组件。

5. 使用 Gemini 的提示链实现

// lib/chains/prompt-chain.ts
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { PromptTemplate } from '@langchain/core/prompts';
import { RunnableSequence } from '@langchain/core/runnables';
import { StringOutputParser } from '@langchain/core/output_parsers';
import { NextResponse } from 'next/server';

export function createPromptChain() {
  const model = new ChatGoogleGenerativeAI({
    modelName: 'gemini-2.5-flash',
    temperature: 0
  });

  // 步骤 1:提取规格
  const extractPrompt = PromptTemplate.fromTemplate(
    "从以下内容提取技术规格:{input}"
  );

  // 步骤 2:转换为 JSON
  const transformPrompt = PromptTemplate.fromTemplate(
    "转换为具有 'cpu'、'memory'、'storage' 键的 JSON:{specs}"
  );

  // 创建链
  const chain = RunnableSequence.from([
    extractPrompt,
    model,
    new StringOutputParser(),
    { specs: (prev: string) => prev },
    transformPrompt,
    model,
    new StringOutputParser(),
  ]);

  return chain;
}

// 在 API 路由中使用
export async function POST(req: Request) {
  const { text } = await req.json();
  const chain = createPromptChain();

  const result = await chain.invoke({ input: text });
  return NextResponse.json({ result });
}

通过将复杂任务分解为顺序步骤来实现提示链模式 - 首先提取数据,然后将其转换为 JSON 格式。

6. 使用 Google Gemini 的上下文工程

// lib/context-engineer.ts
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { SystemMessage, HumanMessage } from '@langchain/core/messages';
import { Document } from '@langchain/core/documents';

interface ContextConfig {
  systemPrompt: string;
  retrievedDocs?: Document[];
  userHistory?: string[];
  environmentContext?: Record<string, any>;
}

export class ContextEngineer {
  private model: ChatGoogleGenerativeAI;

  constructor() {
    this.model = new ChatGoogleGenerativeAI({
      modelName: 'gemini-2.5-pro',
      temperature: 0.3,
      maxOutputTokens: 8192,
    });
  }

  async processWithContext(
    userQuery: string,
    config: ContextConfig
  ) {
    const messages = [];

    // 添加系统上下文
    messages.push(new SystemMessage(config.systemPrompt));

    // 添加检索到的文档作为上下文
    if (config.retrievedDocs?.length) {
      const docContext = config.retrievedDocs
        .map(doc => doc.pageContent)
        .join('\n\n');
      messages.push(new SystemMessage(
        `相关上下文:\n${docContext}`
      ));
    }

    // 添加用户历史
    if (config.userHistory?.length) {
      messages.push(new SystemMessage(
        `之前的对话:\n${config.userHistory.join('\n')}`
      ));
    }

    // 添加环境上下文
    if (config.environmentContext) {
      messages.push(new SystemMessage(
        `环境:${JSON.stringify(config.environmentContext)}`
      ));
    }

    // 添加实际的用户查询
    messages.push(new HumanMessage(userQuery));

    return await this.model.invoke(messages);
  }
}

通过在处理查询之前结合系统提示、检索到的文档、对话历史和环境数据来构建综合上下文层。

7. Vercel 部署配置

// vercel.json
{
  "functions": {
    "app/api/agent/route.ts": {
      "maxDuration": 300
    },
    "app/api/chat/route.ts": {
      "maxDuration": 60
    }
  }
}

配置 Vercel 以允许 AI 操作的更长执行时间。环境变量由 Next.js 和 Vercel 自动处理。

8. 主页集成

// app/page.tsx
import ChatInterface from '@/components/ChatInterface';

export default function Home() {
  return (
    <main className="min-h-screen bg-base-200">
      <div className="container mx-auto p-4">
        <div className="text-center mb-8">
          <h1 className="text-5xl font-bold">AI 智能体平台</h1>
          <p className="py-6">由 Google Gemini 和 Langraph 提供支持</p>
        </div>

        <div className="flex justify-center">
          <div className="w-full max-w-2xl">
            <ChatInterface />
          </div>
        </div>
      </div>
    </main>
  );
}

主登录页面,使用响应式布局和 DaisyUI 样式将聊天界面组件居中。

9. 高级 Langraph 工作流

// lib/workflows/stateful-workflow.ts
import { StateGraph, END } from '@langchain/langgraph';
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
import { BaseMessage } from '@langchain/core/messages';

interface WorkflowState {
  messages: BaseMessage[];
  currentStep: string;
  metadata: Record<string, any>;
}

export function createStatefulWorkflow() {
  const model = new ChatGoogleGenerativeAI({
    modelName: 'gemini-2.5-flash',
  });

  const workflow = new StateGraph<WorkflowState>({
    channels: {
      messages: {
        value: (x: BaseMessage[], y: BaseMessage[]) => [...x, ...y],
        default: () => [],
      },
      currentStep: {
        value: (x: string, y: string) => y || x,
        default: () => 'start',
      },
      metadata: {
        value: (x: Record<string, any>, y: Record<string, any>) => ({...x, ...y}),
        default: () => ({}),
      },
    },
  });

  // 定义节点
  workflow.addNode('analyze', async (state) => {
    const response = await model.invoke(state.messages);
    return {
      messages: [response],
      currentStep: 'process',
    };
  });

  workflow.addNode('process', async (state) => {
    // 处理分析
    return {
      currentStep: 'complete',
      metadata: { processed: true },
    };
  });

  // 定义边缘
  workflow.addEdge('analyze', 'process');
  workflow.addEdge('process', END);
  workflow.setEntryPoint('analyze');

  return workflow.compile();
}

使用 Langraph 的 StateGraph 创建有状态工作流,管理消息历史、跟踪执行步骤,并通过定义的节点和边缘处理数据。

10. 将有状态工作流与前端集成

// app/api/workflow/route.ts
import { createStatefulWorkflow } from '@/lib/workflows/stateful-workflow';
import { HumanMessage } from '@langchain/core/messages';

export const runtime = 'nodejs';
export const maxDuration = 300;

export async function POST(req: Request) {
  const { message, sessionId } = await req.json();

  const encoder = new TextEncoder();
  const stream = new TransformStream();
  const writer = stream.writable.getWriter();

  const workflow = createStatefulWorkflow();

  (async () => {
    try {
      // 流式工作流事件
      const events = await workflow.stream({
        messages: [new HumanMessage(message)],
        currentStep: 'start',
        metadata: { sessionId },
      });

      for await (const event of events) {
        // 向前端发送步骤更新
        await writer.write(
          encoder.encode(`data: ${JSON.stringify({
            type: 'step',
            step: event.currentStep || 'processing',
            content: event.messages?.[event.messages.length - 1]?.content,
            metadata: event.metadata
          })}\n\n`)
        );
      }

      await writer.write(
        encoder.encode(`data: ${JSON.stringify({ type: 'complete' })}\n\n`)
      );
    } catch (error) {
      await writer.write(
        encoder.encode(`data: ${JSON.stringify({ type: 'error', error: String(error) })}\n\n`)
      );
    } finally {
      await writer.close();
    }
  })();

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

流式工作流执行事件的 API 路由,包括步骤转换和向前端的中间结果。

// hooks/useWorkflowStream.ts
import { useState } from 'react';
import { useMutation } from '@tanstack/react-query';

interface WorkflowStep {
  name: string;
  status: 'pending' | 'active' | 'complete';
  output?: string;
}

interface WorkflowData {
  message: string;
  sessionId: string;
}

export function useWorkflowStream() {
  const [steps, setSteps] = useState<WorkflowStep[]>([
    { name: 'analyze', status: 'pending' },
    { name: 'process', status: 'pending' },
    { name: 'complete', status: 'pending' }
  ]);

  const mutation = useMutation({
    mutationFn: async (data: WorkflowData) => {
      // 重置步骤
      setSteps(steps.map(s => ({ ...s, status: 'pending', output: undefined })));

      const res = await fetch('/api/workflow', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data),
      });

      if (!res.ok) throw new Error('Workflow failed');

      const reader = res.body?.getReader();
      const decoder = new TextDecoder();

      while (reader) {
        const { done, value } = await reader.read();
        if (done) break;

        const chunk = decoder.decode(value);
        const lines = chunk.split('\n');

        for (const line of lines) {
          if (line.startsWith('data: ')) {
            try {
              const data = JSON.parse(line.slice(6));

              if (data.type === 'step') {
                setSteps(prev => prev.map(step => {
                  if (step.name === data.step) {
                    return { ...step, status: 'active', output: data.content };
                  } else if (prev.findIndex(s => s.name === data.step) >
                            prev.findIndex(s => s.name === step.name)) {
                    return { ...step, status: 'complete' };
                  }
                  return step;
                }));
              } else if (data.type === 'complete') {
                setSteps(prev => prev.map(s => ({ ...s, status: 'complete' })));
              } else if (data.type === 'error') {
                throw new Error(data.error);
              }
            } catch (error) {
              if (error instanceof SyntaxError) continue;
              throw error;
            }
          }
        }
      }

      return { success: true };
    },
    onError: (error) => {
      console.error('Workflow error:', error);
      setSteps(prev => prev.map(s => ({ ...s, status: 'pending' })));
    },
  });

  return {
    steps,
    runWorkflow: mutation.mutate,
    isLoading: mutation.isPending,
    isError: mutation.isError,
    error: mutation.error,
    reset: mutation.reset,
  };
}

使用 TanStack Query 管理工作流流式传输的自定义钩子,具有自动状态更新和错误处理。

// components/WorkflowInterface.tsx
'use client';

import { useState } from 'react';
import { useWorkflowStream } from '@/hooks/useWorkflowStream';

export default function WorkflowInterface() {
  const [message, setMessage] = useState('');
  const { steps, runWorkflow, isLoading, isError, error, reset } = useWorkflowStream();

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (message.trim()) {
      runWorkflow({
        message,
        sessionId: crypto.randomUUID()
      });
    }
  };

  return (
    <div className="card w-full bg-base-100 shadow-xl">
      <div className="card-body">
        <h2 className="card-title">工作流执行</h2>

        <form onSubmit={handleSubmit}>
          <div className="form-control">
            <input
              type="text"
              className="input input-bordered"
              placeholder="输入您的请求..."
              value={message}
              onChange={(e) => setMessage(e.target.value)}
              disabled={isLoading}
            />
          </div>

          <div className="flex gap-2 mt-4">
            <button
              type="submit"
              className="btn btn-primary"
              disabled={isLoading || !message.trim()}
            >
              {isLoading ? (
                <>
                  <span className="loading loading-spinner"></span>
                  运行工作流...
                </>
              ) : '执行工作流'}
            </button>

            {isError && (
              <button type="button" className="btn btn-outline" onClick={() => reset()}>
                重置
              </button>
            )}
          </div>
        </form>

        {isError && (
          <div className="alert alert-error mt-4">
            <span>错误:{error?.message || '工作流失败'}</span>
          </div>
        )}

        {/* 工作流步骤可视化 */}
        <div className="mt-6">
          <ul className="steps steps-vertical">
            {steps.map((step, idx) => (
              <li
                key={idx}
                className={`step ${
                  step.status === 'complete' ? 'step-success' :
                  step.status === 'active' ? 'step-primary' : ''
                }`}
              >
                <div className="text-left ml-4">
                  <div className="font-semibold capitalize">{step.name}</div>
                  {step.output && (
                    <div className="text-sm opacity-70 mt-1">
                      {step.output.substring(0, 100)}
                      {step.output.length > 100 && '...'}
                    </div>
                  )}
                </div>
              </li>
            ))}
          </ul>
        </div>
      </div>
    </div>
  );
}

使用自定义工作流钩子和 TanStack Query 进行清洁状态管理和错误处理的前端组件。

// app/workflow/page.tsx
import WorkflowInterface from '@/components/WorkflowInterface';

export default function WorkflowPage() {
  return (
    <main className="min-h-screen bg-base-200">
      <div className="container mx-auto p-4">
        <div className="text-center mb-8">
          <h1 className="text-4xl font-bold">AI 工作流执行器</h1>
          <p className="py-4">观看您的 AI 智能体逐步完成复杂任务</p>
        </div>

        <div className="flex justify-center">
          <div className="w-full max-w-3xl">
            <WorkflowInterface />
          </div>
        </div>
      </div>
    </main>
  );
}

工作流界面的专用页面,为监控多步骤 AI 智能体操作提供清洁的布局。

结论

此配置为在 Next.js 15 和 Vercel 上使用 Google 的 Gemini 模型构建 AI 智能体提供了生产就绪的基础,支持 Langchain 和 Langraph。该配置利用 Gemini 的卓越性能和成本效益,同时保持完整的流式支持和高级智能体功能。关键方面包括适当的 Google AI 集成、Tailwind v4 与 DaisyUI v5 的简化配置,以及遵循智能体设计模式文档中模式的全面提示链实现。