Next.js 15에서 Langchain과 Langraph를 Google AI와 함께 Vercel에 설정하기

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

이 가이드는 TypeScript로 AI 에이전트를 구축하기 위해 Google Gemini 모델을 사용하여 Next.js 15 프로젝트에서 Langchain과 Langraph를 Vercel 배포용으로 구성하는 방법을 보여줍니다.

멘탈 모델: 빌딩 블록 아키텍처

이 설정을 전문화된 미들웨어가 있는 현대적인 웹 API를 조립하는 것으로 생각하세요. Next.js 15는 서버 프레임워크를 제공하고, Langchain은 AI 오케스트레이션 레이어(LLM용 Express 미들웨어와 같은) 역할을 하며, Langraph는 상태 저장 워크플로 기능(AI 워크플로용 Redux Saga와 유사)을 추가합니다. Google의 Gemini 모델은 인텔리전스 레이어 역할을 하고, Vercel의 플랫폼은 서버리스 배포를 처리하여 Google Cloud Functions와 같지만 더 나은 DX로 AI 엔드포인트를 자동으로 확장합니다.

기본 설정: 프로젝트 초기화

1. Next.js 15 프로젝트 생성

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

TypeScript, Tailwind CSS v4, App Router, 그리고 루트 디렉토리의 파일들로 새로운 Next.js 15 프로젝트를 생성합니다.

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 구성

Next.js 15는 --typescript 플래그로 프로젝트를 생성할 때 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에서 오류 보고 가능 - 간단한 요청에 대해서도 "Function execution took too long"과 같은 오류

false로 설정하면 모든 콜백이 응답을 보내기 전에 동기적으로 완료되어 서버리스 함수 종료 문제를 방지합니다. 이는 Vercel, AWS Lambda 및 유사한 플랫폼에 필수입니다.

5. Tailwind v4와 DaisyUI v5를 위한 globals.css 업데이트

/* 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 }
    );
  }
}

메시지를 받아 Gemini Pro를 통해 처리하고 전체 Langchain 지원을 위한 Node.js 런타임으로 AI 응답을 반환하는 POST 엔드포인트를 생성합니다.

고급 예제: 스트리밍을 사용한 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 에이전트를 설정하고, 향상된 기능을 위해 Calculator와 WebBrowser 도구를 장착합니다.

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>
  );
}

내장된 로딩 및 오류 상태로 스트리밍 API 호출을 관리하기 위해 TanStack Query의 useMutation 훅을 사용하는 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
    }
  }
}

AI 작업을 위해 더 긴 실행 시간을 허용하도록 Vercel을 구성합니다. 환경 변수는 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 에이전트 작업을 모니터링하기 위한 깔끔한 레이아웃을 제공하는 워크플로 인터페이스 전용 페이지입니다.

결론

이 설정은 Google의 Gemini 모델을 사용하여 Next.js 15와 Vercel에서 Langchain과 Langraph로 AI 에이전트를 구축하기 위한 프로덕션 준비 기반을 제공합니다. 이 구성은 Gemini의 뛰어난 성능과 비용 효율성을 활용하면서 완전한 스트리밍 지원과 고급 에이전트 기능을 유지합니다. 주요 측면으로는 적절한 Google AI 통합, DaisyUI v5와 함께 Tailwind v4의 간소화된 구성, 그리고 에이전틱 디자인 패턴 문서의 패턴을 따르는 포괄적인 프롬프트 체이닝 구현이 포함됩니다.