LangChain 어댑터
LangChain 어댑터
LangChain과 LangGraph를 AI SDK UI 컴포넌트와 함께 사용할 수 있게 해주는 어댑터예요. LangChain 모델과 LangGraph 에이전트를 AI SDK UI 함수(useChat, useCompletion)와 함께 쓸 수 있어요.
출처: 문서
본문
LangChain은 대규모 언어 모델로 구동되는 애플리케이션을 구축하기 위한 프레임워크예요. AI 모델, 프롬프트, 체인, 벡터 스토어, 그리고 검색 증강 생성(RAG)을 위한 기타 데이터 소스 작업에 도구와 추상화를 제공해요.
LangGraph은 LangChain 위에 구축된 라이브러리로, 상태를 가진(stateful)·다중 에이전트(multi-actor) 애플리케이션을 만들기 위한 것이에요. 복잡한 에이전트 워크플로를 그래프로 정의할 수 있으며, 사이클, 영속성, 인간-개입(human-in-the-loop) 패턴을 지원해요.
@ai-sdk/langchain 어댑터는 LangChain, LangGraph, AI SDK 사이의 원활한 통합을 제공하며, AI SDK UI 컴포넌트와 함께 LangChain 모델과 LangGraph 에이전트를 사용할 수 있게 해줘요.
설치 (Installation)
@langchain/core는 필수 peer dependency예요.
기능 (Features)
toBaseMessages를 사용해 AI SDKUIMessage를 LangChainBaseMessage형식으로 변환toUIMessageStream으로 LangChain/LangGraph 스트림을 AI SDKUIMessageStream으로 변환- 세분화된 이벤트 스트리밍과 관찰 가능성을 위한
streamEvents()출력 지원 - 배포된 LangGraph 그래프에 직접 연결하기 위한
LangSmithDeploymentTransport - 텍스트, 툴 호출, 툴 결과, 멀티모달 콘텐츠의 완전한 지원
- 타입이 있는 이벤트(
data-{type})를 사용한 커스텀 데이터 스트리밍
예시: 기본 채팅 (Basic Chat)
AI SDK와 LangChain을 Next.js App Router와 함께 사용하는 기본 예시예요.
import { toBaseMessages, toUIMessageStream } from '@ai-sdk/langchain';
import { ChatOpenAI } from '@langchain/openai';
import { createUIMessageStreamResponse, UIMessage } from 'ai';
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages }: { messages: UIMessage[] } = await req.json();
const model = new ChatOpenAI({
model: 'gpt-4.1-mini',
temperature: 0,
});
// Convert AI SDK UIMessages to LangChain messages
const langchainMessages = await toBaseMessages(messages);
// Stream the response from the model
const stream = await model.stream(langchainMessages);
// Convert the LangChain stream to UI message stream
return createUIMessageStreamResponse({
stream: toUIMessageStream(stream),
});
}
그런 다음 페이지 컴포넌트에서 AI SDK의 useChat 훅을 사용하세요:
'use client';
import { useChat } from '@ai-sdk/react';
export default function Chat() {
const { messages, sendMessage, status } = useChat();
return (
<div>
{messages.map(m => (
<div key={m.id}>
{m.parts.map((part, i) =>
part.type === 'text' ? <span key={i}>{part.text}</span> : null,
)}
</div>
))}
<form
onSubmit={e => {
e.preventDefault();
const input = e.currentTarget.elements.namedItem(
'message',
) as HTMLInputElement;
sendMessage({ text: input.value });
input.value = '';
}}
>
<input name="message" placeholder="Say something..." />
<button type="submit" disabled={status === 'streaming'}>
Send
</button>
</form>
</div>
);
}
예시: 툴을 가진 LangChain 에이전트 (LangChain Agent with Tools)
LangChain의 createAgent로 툴이 있는 에이전트를 만드세요:
import { createUIMessageStreamResponse, UIMessage } from 'ai';
import { createAgent } from 'langchain';
import { ChatOpenAI, tools } from '@langchain/openai';
import { toBaseMessages, toUIMessageStream } from '@ai-sdk/langchain';
export const maxDuration = 60;
const model = new ChatOpenAI({
model: 'gpt-4.1',
temperature: 0.7,
});
// Image generation tool configuration
const imageGenerationTool = tools.imageGeneration({
size: '1024x1024',
quality: 'high',
outputFormat: 'png',
});
// Create a LangChain agent with tools
const agent = createAgent({
model,
tools: [imageGenerationTool],
systemPrompt: 'You are a creative AI artist assistant.',
});
export async function POST(req: Request) {
const { messages }: { messages: UIMessage[] } = await req.json();
const langchainMessages = await toBaseMessages(messages);
const stream = await agent.stream(
{ messages: langchainMessages },
{ streamMode: ['values', 'messages', 'tools'] },
);
return createUIMessageStreamResponse({
stream: toUIMessageStream(stream),
});
}
tools 스트림 모드를 사용해 LangGraph 툴 진행 상황을 스트리밍하세요. 어댑터는 on_tool_event 이벤트를 예비 툴 출력(preliminary: true)으로, 최종 on_tool_end 이벤트를 최종 툴 출력으로 변환해요.
예시: LangGraph
어댑터를 LangGraph와 함께 사용해 에이전트 워크플로를 구축하세요:
import { toBaseMessages, toUIMessageStream } from '@ai-sdk/langchain';
import { ChatOpenAI } from '@langchain/openai';
import { createUIMessageStreamResponse, UIMessage } from 'ai';
import { StateGraph, MessagesAnnotation } from '@langchain/langgraph';
export const maxDuration = 30;
const model = new ChatOpenAI({
model: 'gpt-4.1-mini',
temperature: 0,
});
async function callModel(state: typeof MessagesAnnotation.State) {
const response = await model.invoke(state.messages);
return { messages: [response] };
}
export async function POST(req: Request) {
const { messages }: { messages: UIMessage[] } = await req.json();
// Create the LangGraph agent
const graph = new StateGraph(MessagesAnnotation)
.addNode('agent', callModel)
.addEdge('__start__', 'agent')
.addEdge('agent', '__end__')
.compile();
// Convert AI SDK UIMessages to LangChain messages
const langchainMessages = await toBaseMessages(messages);
// Stream from the graph using LangGraph's streaming format
const stream = await graph.stream(
{ messages: langchainMessages },
{ streamMode: ['values', 'messages'] },
);
// Convert the LangGraph stream to UI message stream
return createUIMessageStreamResponse({
stream: toUIMessageStream(stream),
});
}
예시: streamEvents로 스트리밍
LangChain의 streamEvents() 메서드는 메타데이터와 함께 세분화되고 의미 있는 이벤트를 제공해요. 디버깅, 관찰 가능성, 기존 LCEL 애플리케이션 마이그레이션에 유용해요:
import { toBaseMessages, toUIMessageStream } from '@ai-sdk/langchain';
import { ChatOpenAI } from '@langchain/openai';
import { createUIMessageStreamResponse, UIMessage } from 'ai';
export const maxDuration = 30;
const model = new ChatOpenAI({
model: 'gpt-4.1-mini',
temperature: 0,
});
export async function POST(req: Request) {
const { messages }: { messages: UIMessage[] } = await req.json();
const langchainMessages = await toBaseMessages(messages);
// Use streamEvents() for granular event streaming
// Produces events like on_chat_model_stream, on_tool_start, on_tool_end
const streamEvents = model.streamEvents(langchainMessages, {
version: 'v2',
});
// The adapter automatically detects and handles streamEvents format
return createUIMessageStreamResponse({
stream: toUIMessageStream(streamEvents),
});
}
예시: 커스텀 데이터 스트리밍
LangChain 툴은 config.writer()를 사용해 커스텀 데이터 이벤트를 방출할 수 있어요. 어댑터는 이를 UI에서 렌더링하거나 onData 콜백으로 처리할 수 있는 타입이 있는 data-{type} 파트로 변환해요:
import { createUIMessageStreamResponse, UIMessage } from 'ai';
import { createAgent, tool, type ToolRuntime } from 'langchain';
import { ChatOpenAI } from '@langchain/openai';
import { toBaseMessages, toUIMessageStream } from '@ai-sdk/langchain';
import { z } from 'zod';
export const maxDuration = 60;
const model = new ChatOpenAI({ model: 'gpt-6-luna' });
// Tool that emits progress updates during execution
const analyzeDataTool = tool(
async ({ dataSource, analysisType }, config: ToolRuntime) => {
const steps = ['connecting', 'fetching', 'processing', 'generating'];
for (let i = 0; i < steps.length; i++) {
// Emit progress event - becomes 'data-progress' in the UI
// Include 'id' to persist in message.parts for rendering
config.writer?.({
type: 'progress',
id: `analysis-${Date.now()}`,
step: steps[i],
message: `${steps[i]}...`,
progress: Math.round(((i + 1) / steps.length) * 100),
});
await new Promise(resolve => setTimeout(resolve, 500));
}
// Emit completion status
config.writer?.({
type: 'status',
id: `status-${Date.now()}`,
status: 'complete',
message: 'Analysis finished',
});
return JSON.stringify({ result: 'Analysis complete', confidence: 0.94 });
},
{
name: 'analyze_data',
description: 'Analyze data with progress updates',
schema: z.object({
dataSource: z.enum(['sales', 'inventory', 'customers']),
analysisType: z.enum(['trends', 'anomalies', 'summary']),
}),
},
);
const agent = createAgent({
model,
tools: [analyzeDataTool],
});
export async function POST(req: Request) {
const { messages }: { messages: UIMessage[] } = await req.json();
const langchainMessages = await toBaseMessages(messages);
// Enable 'custom' stream mode to receive custom data events
const stream = await agent.stream(
{ messages: langchainMessages },
{ streamMode: ['values', 'messages', 'custom'] },
);
return createUIMessageStreamResponse({
stream: toUIMessageStream(stream),
});
}
클라이언트에서 onData 콜백으로 커스텀 데이터를 처리하거나 영구 데이터 파트를 렌더링하세요:
'use client';
import { useChat } from '@ai-sdk/react';
export default function Chat() {
const { messages, sendMessage } = useChat({
onData: dataPart => {
// Handle transient data events (without 'id')
console.log('Received:', dataPart.type, dataPart.data);
},
});
return (
<div>
{messages.map(m => (
<div key={m.id}>
{m.parts.map((part, i) => {
if (part.type === 'text') {
return <span key={i}>{part.text}</span>;
}
// Render persistent custom data parts (with 'id')
if (part.type === 'data-progress') {
return (
<div key={i}>
Progress: {part.data.progress}% - {part.data.message}
</div>
);
}
if (part.type === 'data-status') {
return <div key={i}>Status: {part.data.message}</div>;
}
return null;
})}
</div>
))}
</div>
);
}
예시: LangSmith 배포 전송 (LangSmith Deployment Transport)
LangSmithDeploymentTransport를 사용해 브라우저에서 LangGraph 배포에 직접 연결하여 백엔드 API 라우트의 필요성을 우회할 수 있어요:
'use client';
import { useChat } from '@ai-sdk/react';
import { LangSmithDeploymentTransport } from '@ai-sdk/langchain';
import { useMemo } from 'react';
export default function LangSmithChat() {
const transport = useMemo(
() =>
new LangSmithDeploymentTransport({
// Local development server
url: 'http://localhost:2024',
// Or for LangSmith deployment:
// url: 'https://your-deployment.us.langgraph.app',
// apiKey: proces...KEY,
}),
[],
);
const { messages, sendMessage, status } = useChat({
transport,
});
return (
<div>
{messages.map(m => (
<div key={m.id}>
{m.parts.map((part, i) =>
part.type === 'text' ? <span key={i}>{part.text}</span> : null,
)}
</div>
))}
<form
onSubmit={e => {
e.preventDefault();
const input = e.currentTarget.elements.namedItem(
'message',
) as HTMLInputElement;
sendMessage({ text: input.value });
input.value = '';
}}
>
<input name="message" placeholder="Send a message..." />
<button type="submit">Send</button>
</form>
</div>
);
}
LangSmithDeploymentTransport 생성자는 다음 옵션을 받아요:
url: LangSmith 배포 URL 또는 로컬 서버 URL (필수)apiKey: 인증용 API 키 (로컬 개발에서는 선택)graphId: 연결할 그래프의 ID (기본값:'agent')
API 참조 (API Reference)
toBaseMessages(messages)
AI SDK UIMessage 객체를 LangChain BaseMessage 객체로 변환해요.
import { toBaseMessages } from '@ai-sdk/langchain';
const langchainMessages = await toBaseMessages(uiMessages);
파라미터:
messages:UIMessage[]- AI SDK UI 메시지 배열
반환: Promise<BaseMessage[]>
convertModelMessages(modelMessages)
AI SDK ModelMessage 객체를 LangChain BaseMessage 객체로 변환해요. convertToModelMessages에서 이미 모델 메시지가 있을 때 유용해요.
import { convertModelMessages } from '@ai-sdk/langchain';
const langchainMessages = convertModelMessages(modelMessages);
파라미터:
modelMessages:ModelMessage[]- 모델 메시지 배열
반환: BaseMessage[]
toUIMessageStream(stream, options?)
LangChain/LangGraph 스트림을 AI SDK UIMessageStream으로 변환해요. 스트림 유형을 자동으로 감지하고 직접 모델 스트림, LangGraph 스트림, streamEvents() 출력을 처리해요.
import { toUIMessageStream } from '@ai-sdk/langchain';
import { createUIMessageStreamResponse } from 'ai';
// Works with direct model streams
const modelStream = await model.stream(messages);
return createUIMessageStreamResponse({
stream: toUIMessageStream(modelStream),
});
// Works with LangGraph streams
const graphStream = await graph.stream(
{ messages },
{ streamMode: ['values', 'messages', 'tools'] },
);
return createUIMessageStreamResponse({
stream: toUIMessageStream(graphStream),
});
// Works with streamEvents() output
const streamEvents = model.streamEvents(messages, { version: 'v2' });
return createUIMessageStreamResponse({
stream: toUIMessageStream(streamEvents),
});
파라미터:
stream:AsyncIterable<AIMessageChunk> | ReadableStream- LangChain 모델 스트림, LangGraph 스트림 또는streamEvents()출력options:ToUIMessageStreamOptions<TState>(선택)sendStart: 바깥start청크를 방출할지 여부. 기본값은true.sendFinish: 바깥finish청크를 방출할지 여부. 기본값은true.onStart,onToken,onText,onFinal,onFinish,onError,onAbort: 선택적 스트림 라이프사이클 콜백.
반환: ReadableStream<UIMessageChunk>
호출자가 소유한 스트림으로 구성하기
기본적으로 toUIMessageStream은 바깥 start와 finish 청크를 방출해요. 호출자가 메시지 라이프사이클을 소유할 때는 중복 경계를 피하기 위해 해당 청크를 억제하세요. 변환된 텍스트, 추론, 툴, 데이터, 스텝 청크는 모두 변경되지 않아요.
import { toUIMessageStream } from '@ai-sdk/langchain';
import { createUIMessageStream } from 'ai';
const stream = createUIMessageStream({
async execute({ writer }) {
writer.write({ type: 'start' });
const reader = toUIMessageStream(langchainStream, {
sendStart: false,
sendFinish: false,
}).getReader();
while (true) {
const { done, value: chunk } = await reader.read();
if (done) break;
writer.write(chunk);
}
writer.write({ type: 'finish' });
},
});
호출자가 하나의 라이프사이클 경계만 소유할 때는 각 옵션을 독립적으로 설정하세요. 경계가 억제되면 최종 스트림 프로토콜이 요구할 때 호출자가 이를 제공할 책임이 있어요.
LangSmithDeploymentTransport
LangSmith/LangGraph 배포를 위한 ChatTransport 구현이에요. useChat 훅의 transport 옵션과 함께 사용하세요.
import { LangSmithDeploymentTransport } from '@ai-sdk/langchain';
import { useChat } from '@ai-sdk/react';
import { useMemo } from 'react';
const transport = useMemo(
() =>
new LangSmithDeploymentTransport({
url: 'https://your-deployment.us.langgraph.app',
apiKey: ***
}),
[],
);
const { messages, sendMessage } = useChat({
transport,
});
생성자 파라미터:
options:LangSmithDeploymentTransportOptionsurl:string- LangSmith 배포 URL 또는 로컬 서버 URL (필수)apiKey?:string- 인증용 API 키 (선택)graphId?:string- 연결할 그래프의 ID (기본값:'agent')
구현: ChatTransport
더 많은 예시 (More Examples)
추가 예시는 AI SDK examples/next-langchain 폴더에서 찾을 수 있어요.