Skip to content

Commit 37c9d4c

Browse files
authoredMay 10, 2024··
feat (ai/streams): add LangChainAdapter.toAIStream() (#1549)
1 parent ceb44bc commit 37c9d4c

File tree

34 files changed

+770
-557
lines changed

34 files changed

+770
-557
lines changed
 

‎.changeset/lovely-stingrays-fail.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'ai': patch
3+
---
4+
5+
feat (ai/streams): add LangChainAdapter.toAIStream()
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
title: Adapters
3+
description: Learn how to use AI SDK Adapters.
4+
---
5+
6+
# Adapters
7+
8+
Adapters are lightweight integrations that enable you to use
9+
the Vercel AI SDK UI functions (`useChat` and `useCompletion`)
10+
with 3rd party libraries.
11+
12+
The following adapters are currently available:
13+
14+
- [LangChain](./langchain)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
---
2+
title: LangChain
3+
description: Learn how to use LangChain with the Vercel AI SDK.
4+
---
5+
6+
# LangChain
7+
8+
[LangChain](https://js.langchain.com/docs/) is a framework for developing applications powered by language models.
9+
It provides tools and abstractions for working with AI models, agents, vector stores, and other data sources for retrieval augmented generation (RAG).
10+
However, LangChain does not provide a way to easily build UIs or a standard way to stream data to the client.
11+
12+
## Example: Completion
13+
14+
Here is a basic example that uses both Vercel AI SDK and LangChain together with the [Next.js](https://nextjs.org/docs) App Router.
15+
16+
The AI SDK `LangChainAdapter` uses the result from [LangChain ExpressionLanguage streaming](https://js.langchain.com/docs/expression_language/streaming) to pipe text to the client.
17+
`LangChainAdapter.toAIStream()` is compatible with the LangChain Expression Language `.stream()` function response.
18+
19+
```tsx filename="app/api/completion/route.ts" highlight={"17"}
20+
import { ChatOpenAI } from '@langchain/openai';
21+
import { LangChainAdapter, StreamingTextResponse } from 'ai';
22+
23+
export const dynamic = 'force-dynamic';
24+
export const maxDuration = 60;
25+
26+
export async function POST(req: Request) {
27+
const { prompt } = await req.json();
28+
29+
const model = new ChatOpenAI({
30+
model: 'gpt-3.5-turbo-0125',
31+
temperature: 0,
32+
});
33+
34+
const stream = await model.stream(prompt);
35+
36+
const aiStream = LangChainAdapter.toAIStream(stream);
37+
38+
return new StreamingTextResponse(aiStream);
39+
}
40+
```
41+
42+
Then, we use the Vercel AI SDK's [`useCompletion`](/docs/ai-sdk-ui/completion) method in the page component to handle the completion:
43+
44+
```tsx filename="app/page.tsx"
45+
'use client';
46+
47+
import { useCompletion } from 'ai/react';
48+
49+
export default function Chat() {
50+
const { completion, input, handleInputChange, handleSubmit } =
51+
useCompletion();
52+
53+
return (
54+
<div>
55+
{completion}
56+
<form onSubmit={handleSubmit}>
57+
<input value={input} onChange={handleInputChange} />
58+
</form>
59+
</div>
60+
);
61+
}
62+
```
63+
64+
## More Examples
65+
66+
You can find additional examples in the Vercel AI SDK [examples/next-langchain](https://github.com/vercel/ai/tree/main/examples/next-langchain) folder.

‎content/providers/04-legacy-providers/langchain.mdx

-134
This file was deleted.
+19-19
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,29 @@
1-
import { StreamingTextResponse, LangChainStream, Message } from 'ai';
2-
import { ChatOpenAI } from 'langchain/chat_models/openai';
1+
import { ChatOpenAI } from '@langchain/openai';
2+
import { LangChainAdapter, Message, StreamingTextResponse } from 'ai';
33
import { AIMessage, HumanMessage } from 'langchain/schema';
44

55
export const dynamic = 'force-dynamic';
6+
export const maxDuration = 60;
67

78
export async function POST(req: Request) {
8-
const { messages } = await req.json();
9+
const {
10+
messages,
11+
}: {
12+
messages: Message[];
13+
} = await req.json();
914

10-
const { stream, handlers } = LangChainStream();
11-
12-
const llm = new ChatOpenAI({
13-
streaming: true,
15+
const model = new ChatOpenAI({
16+
model: 'gpt-3.5-turbo-0125',
17+
temperature: 0,
1418
});
1519

16-
llm
17-
.call(
18-
(messages as Message[]).map(m =>
19-
m.role == 'user'
20-
? new HumanMessage(m.content)
21-
: new AIMessage(m.content),
22-
),
23-
{},
24-
[handlers],
25-
)
26-
.catch(console.error);
20+
const stream = await model.stream(
21+
messages.map(message =>
22+
message.role == 'user'
23+
? new HumanMessage(message.content)
24+
: new AIMessage(message.content),
25+
),
26+
);
2727

28-
return new StreamingTextResponse(stream);
28+
return new StreamingTextResponse(LangChainAdapter.toAIStream(stream));
2929
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { ChatOpenAI } from '@langchain/openai';
2+
import { LangChainAdapter, StreamData, StreamingTextResponse } from 'ai';
3+
4+
export const dynamic = 'force-dynamic';
5+
export const maxDuration = 60;
6+
7+
export async function POST(req: Request) {
8+
const { prompt } = await req.json();
9+
10+
const model = new ChatOpenAI({
11+
model: 'gpt-3.5-turbo-0125',
12+
temperature: 0,
13+
});
14+
15+
const stream = await model.stream(prompt);
16+
17+
const data = new StreamData();
18+
19+
data.append({ test: 'value' });
20+
21+
const aiStream = LangChainAdapter.toAIStream(stream, {
22+
onFinal() {
23+
data.close();
24+
},
25+
});
26+
27+
return new StreamingTextResponse(aiStream, {}, data);
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { ChatOpenAI } from '@langchain/openai';
2+
import { LangChainAdapter, StreamingTextResponse } from 'ai';
3+
4+
export const dynamic = 'force-dynamic';
5+
export const maxDuration = 60;
6+
7+
export async function POST(req: Request) {
8+
const { prompt } = await req.json();
9+
10+
const model = new ChatOpenAI({
11+
model: 'gpt-3.5-turbo-0125',
12+
temperature: 0,
13+
});
14+
15+
const stream = await model.stream(prompt);
16+
17+
return new StreamingTextResponse(LangChainAdapter.toAIStream(stream));
18+
}

‎examples/next-langchain/app/api/docs/route.ts

-67
This file was deleted.

‎examples/next-langchain/app/api/stream-data-basic/route.ts

-42
This file was deleted.

‎examples/next-langchain/app/api/stream-data-chain/route.ts

-30
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use client';
2+
3+
import { useCompletion } from 'ai/react';
4+
5+
export default function Completion() {
6+
const { completion, input, handleInputChange, handleSubmit, error, data } =
7+
useCompletion({ api: '/api/completion-stream-data' });
8+
9+
return (
10+
<div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
11+
<h4 className="pb-4 text-xl font-bold text-gray-900 md:text-xl">
12+
useCompletion Example
13+
</h4>
14+
{data && (
15+
<pre className="p-4 text-sm bg-gray-100">
16+
{JSON.stringify(data, null, 2)}
17+
</pre>
18+
)}
19+
{error && (
20+
<div className="fixed top-0 left-0 w-full p-4 text-center text-white bg-red-500">
21+
{error.message}
22+
</div>
23+
)}
24+
{completion}
25+
<form onSubmit={handleSubmit}>
26+
<input
27+
className="fixed bottom-0 w-full max-w-md p-2 mb-8 border border-gray-300 rounded shadow-xl"
28+
value={input}
29+
placeholder="Say something..."
30+
onChange={handleInputChange}
31+
/>
32+
</form>
33+
</div>
34+
);
35+
}

‎examples/next-langchain/app/stream-data-chain/page.tsx renamed to ‎examples/next-langchain/app/completion/page.tsx

+5-5
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ import { useCompletion } from 'ai/react';
44

55
export default function Chat() {
66
const { completion, input, handleInputChange, handleSubmit, error } =
7-
useCompletion({ api: '/api/stream-data-chain' });
7+
useCompletion();
88

99
return (
1010
<div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
11-
<h4 className="text-xl font-bold text-gray-900 md:text-xl pb-4">
12-
useCompletion / stream-data-chain Example
11+
<h4 className="pb-4 text-xl font-bold text-gray-900 md:text-xl">
12+
useCompletion Example
1313
</h4>
1414
{error && (
15-
<div className="fixed top-0 left-0 w-full p-4 text-center bg-red-500 text-white">
15+
<div className="fixed top-0 left-0 w-full p-4 text-center text-white bg-red-500">
1616
{error.message}
1717
</div>
1818
)}
@@ -21,7 +21,7 @@ export default function Chat() {
2121
<input
2222
className="fixed bottom-0 w-full max-w-md p-2 mb-8 border border-gray-300 rounded shadow-xl"
2323
value={input}
24-
placeholder="Enter a product..."
24+
placeholder="Say something..."
2525
onChange={handleInputChange}
2626
/>
2727
</form>

‎examples/next-langchain/app/docs/page.tsx

-32
This file was deleted.

‎examples/next-langchain/app/page.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export default function Chat() {
66
const { messages, input, handleInputChange, handleSubmit } = useChat();
77

88
return (
9-
<div className="mx-auto w-full max-w-md py-24 flex flex-col stretch">
9+
<div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
1010
{messages.length > 0
1111
? messages.map(m => (
1212
<div key={m.id} className="whitespace-pre-wrap">
@@ -18,7 +18,7 @@ export default function Chat() {
1818

1919
<form onSubmit={handleSubmit}>
2020
<input
21-
className="fixed w-full max-w-md bottom-0 border border-gray-300 rounded mb-8 shadow-xl p-2"
21+
className="fixed bottom-0 w-full max-w-md p-2 mb-8 border border-gray-300 rounded shadow-xl"
2222
value={input}
2323
placeholder="Say something..."
2424
onChange={handleInputChange}

‎examples/next-langchain/app/stream-data-basic/page.tsx

-31
This file was deleted.

‎examples/next-langchain/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
"lint": "next lint"
1010
},
1111
"dependencies": {
12+
"@langchain/openai": "0.0.28",
1213
"ai": "latest",
13-
"langchain": "^0.0.196",
14+
"langchain": "0.1.36",
1415
"next": "latest",
1516
"react": "18.2.0",
1617
"react-dom": "^18.2.0"

‎packages/core/streams/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export * from './cohere-stream';
99
export * from './google-generative-ai-stream';
1010
export * from './huggingface-stream';
1111
export * from './inkeep-stream';
12+
export * as LangChainAdapter from './langchain-adapter';
1213
export * from './langchain-stream';
1314
export * from './mistral-stream';
1415
export * from './openai-stream';
+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import {
2+
AIStreamCallbacksAndOptions,
3+
createCallbacksTransformer,
4+
} from './ai-stream';
5+
import { createStreamDataTransformer } from './stream-data';
6+
7+
type LangChainImageDetail = 'auto' | 'low' | 'high';
8+
9+
type LangChainMessageContentText = {
10+
type: 'text';
11+
text: string;
12+
};
13+
14+
type LangChainMessageContentImageUrl = {
15+
type: 'image_url';
16+
image_url:
17+
| string
18+
| {
19+
url: string;
20+
detail?: LangChainImageDetail;
21+
};
22+
};
23+
24+
type LangChainMessageContentComplex =
25+
| LangChainMessageContentText
26+
| LangChainMessageContentImageUrl
27+
| (Record<string, any> & {
28+
type?: 'text' | 'image_url' | string;
29+
})
30+
| (Record<string, any> & {
31+
type?: never;
32+
});
33+
34+
type LangChainMessageContent = string | LangChainMessageContentComplex[];
35+
36+
type LangChainAIMessageChunk = {
37+
content: LangChainMessageContent;
38+
};
39+
40+
/**
41+
Converts the result of a LangChain Expression Language stream invocation to an AIStream.
42+
*/
43+
export function toAIStream(
44+
stream: ReadableStream<LangChainAIMessageChunk>,
45+
callbacks?: AIStreamCallbacksAndOptions,
46+
) {
47+
return stream
48+
.pipeThrough(
49+
new TransformStream({
50+
transform: async (chunk, controller) => {
51+
if (typeof chunk.content === 'string') {
52+
controller.enqueue(chunk.content);
53+
} else {
54+
const content: LangChainMessageContentComplex[] = chunk.content;
55+
for (const item of content) {
56+
if (item.type === 'text') {
57+
controller.enqueue(item.text);
58+
}
59+
}
60+
}
61+
},
62+
}),
63+
)
64+
.pipeThrough(createCallbacksTransformer(callbacks))
65+
.pipeThrough(createStreamDataTransformer());
66+
}

‎packages/core/streams/langchain-stream.ts

+3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import {
44
} from './ai-stream';
55
import { createStreamDataTransformer } from './stream-data';
66

7+
/**
8+
@deprecated Use LangChainAdapter.toAIStream() instead.
9+
*/
710
export function LangChainStream(callbacks?: AIStreamCallbacksAndOptions) {
811
const stream = new TransformStream();
912
const writer = stream.writable.getWriter();

‎pnpm-lock.yaml

+506-194
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
Please sign in to comment.