Skip to content

Commit 177ae36

Browse files
authored
feat(sdk): useStream with custom transport (#1662)
1 parent aa2a782 commit 177ae36

File tree

19 files changed

+1318
-637
lines changed

19 files changed

+1318
-637
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
node_modules
11+
dist
12+
dist-ssr
13+
*.local
14+
15+
# Editor directories and files
16+
.vscode/*
17+
!.vscode/extensions.json
18+
.idea
19+
.DS_Store
20+
*.suo
21+
*.ntvs*
22+
*.njsproj
23+
*.sln
24+
*.sw?
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
8+
<title>Vite + React + TS</title>
9+
</head>
10+
<body>
11+
<div id="root"></div>
12+
<script type="module" src="/src/client.tsx"></script>
13+
</body>
14+
</html>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"name": "stream-transport-vite",
3+
"private": true,
4+
"version": "0.0.0",
5+
"type": "module",
6+
"scripts": {
7+
"dev:client": "vite",
8+
"dev:server": "tsx watch --env-file=.env --clear-screen=false src/server.mts",
9+
"build:internal": "tsc -b && vite build",
10+
"format": "prettier --write src",
11+
"lint": "prettier --check src",
12+
"preview": "vite preview"
13+
},
14+
"dependencies": {
15+
"@hono/node-server": "^1.12.0",
16+
"@langchain/core": "^1.0.0-alpha",
17+
"@langchain/langgraph": "workspace:*",
18+
"@langchain/langgraph-sdk": "workspace:*",
19+
"@langchain/openai": "^1.0.0-alpha",
20+
"hono": "^4.8.2",
21+
"react": "^19.0.0",
22+
"react-dom": "^19.0.0",
23+
"zod": "^3.23.8"
24+
},
25+
"devDependencies": {
26+
"@types/react": "^19.0.8",
27+
"@types/react-dom": "^19.0.3",
28+
"@vitejs/plugin-react": "^4.4.1",
29+
"prettier": "^2.8.3",
30+
"tsx": "^4.19.3",
31+
"typescript": "~5.8.3",
32+
"vite": "^6.0.0"
33+
}
34+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@import "tailwindcss";
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import "./client.css";
2+
import { StrictMode } from "react";
3+
import { createRoot } from "react-dom/client";
4+
5+
import {
6+
useStream,
7+
FetchStreamTransport,
8+
} from "@langchain/langgraph-sdk/react";
9+
10+
export function App() {
11+
const stream = useStream({
12+
transport: new FetchStreamTransport({
13+
apiUrl: "/api/stream",
14+
}),
15+
});
16+
17+
return (
18+
<div className="max-w-xl mx-auto">
19+
<div className="flex flex-col gap-2">
20+
{stream.messages.map((message) => (
21+
<div key={message.id} className="whitespace-pre-wrap">
22+
{message.content as string}
23+
</div>
24+
))}
25+
</div>
26+
<form
27+
className="grid grid-cols-[1fr_auto] gap-2"
28+
onSubmit={(e) => {
29+
e.preventDefault();
30+
31+
const form = e.target as HTMLFormElement;
32+
const formData = new FormData(form);
33+
const content = formData.get("content") as string;
34+
35+
form.reset();
36+
stream.submit({ messages: [{ content, type: "human" }] });
37+
}}
38+
>
39+
<textarea
40+
name="content"
41+
className="field-sizing-content"
42+
onKeyDown={(e) => {
43+
const target = e.target as HTMLTextAreaElement;
44+
45+
if (e.key === "Enter" && !e.shiftKey) {
46+
e.preventDefault();
47+
target.form?.requestSubmit();
48+
}
49+
}}
50+
/>
51+
<button type="submit">Submit</button>
52+
</form>
53+
</div>
54+
);
55+
}
56+
57+
createRoot(document.getElementById("root")!).render(
58+
<StrictMode>
59+
<App />
60+
</StrictMode>
61+
);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import type { BaseMessage } from "@langchain/core/messages";
2+
import { StateGraph, MessagesZodMeta, START } from "@langchain/langgraph";
3+
import { toLangGraphEventStreamResponse } from "@langchain/langgraph/ui";
4+
import { registry } from "@langchain/langgraph/zod";
5+
import { ChatOpenAI } from "@langchain/openai";
6+
import { z } from "zod/v4";
7+
8+
import { serve } from "@hono/node-server";
9+
import { Hono } from "hono";
10+
11+
const llm = new ChatOpenAI({ model: "gpt-4o-mini" });
12+
13+
const graph = new StateGraph(
14+
z.object({
15+
messages: z.custom<BaseMessage[]>().register(registry, MessagesZodMeta),
16+
})
17+
)
18+
.addNode("agent", async ({ messages }) => ({
19+
messages: await llm.invoke(messages),
20+
}))
21+
.addEdge(START, "agent")
22+
.compile();
23+
24+
export type GraphType = typeof graph;
25+
26+
const app = new Hono();
27+
28+
app.post("/api/stream", async (c) => {
29+
type InputType = GraphType["~InputType"];
30+
const { input } = await c.req.json<{ input: InputType }>();
31+
32+
return toLangGraphEventStreamResponse({
33+
stream: graph.streamEvents(input, {
34+
version: "v2",
35+
streamMode: ["values", "messages"],
36+
}),
37+
});
38+
});
39+
40+
serve({ fetch: app.fetch, port: 9123 }, (c) => {
41+
console.log(`Server running at ${c.address}:${c.port}`);
42+
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/// <reference types="vite/client" />
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"compilerOptions": {
3+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4+
"target": "ES2022",
5+
"useDefineForClassFields": true,
6+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
7+
"module": "ESNext",
8+
"skipLibCheck": true,
9+
10+
/* Bundler mode */
11+
"moduleResolution": "bundler",
12+
"allowImportingTsExtensions": true,
13+
"verbatimModuleSyntax": true,
14+
"moduleDetection": "force",
15+
"noEmit": true,
16+
"jsx": "react-jsx",
17+
18+
/* Linting */
19+
"strict": true,
20+
"noUnusedLocals": true,
21+
"noUnusedParameters": true,
22+
"erasableSyntaxOnly": true,
23+
"noFallthroughCasesInSwitch": true,
24+
"noUncheckedSideEffectImports": true
25+
},
26+
"include": ["src"]
27+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"files": [],
3+
"references": [
4+
{ "path": "./tsconfig.app.json" },
5+
{ "path": "./tsconfig.node.json" }
6+
]
7+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"compilerOptions": {
3+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4+
"target": "ES2023",
5+
"lib": ["ES2023"],
6+
"module": "ESNext",
7+
"skipLibCheck": true,
8+
9+
/* Bundler mode */
10+
"moduleResolution": "bundler",
11+
"allowImportingTsExtensions": true,
12+
"verbatimModuleSyntax": true,
13+
"moduleDetection": "force",
14+
"noEmit": true,
15+
16+
/* Linting */
17+
"strict": true,
18+
"noUnusedLocals": true,
19+
"noUnusedParameters": true,
20+
"erasableSyntaxOnly": true,
21+
"noFallthroughCasesInSwitch": true,
22+
"noUncheckedSideEffectImports": true
23+
},
24+
"include": ["vite.config.ts"]
25+
}

0 commit comments

Comments
 (0)