feat(sdk-js): add docs, how-to guide

This commit is contained in:
Tat Dat Duong
2025-02-13 07:44:01 -08:00
parent 49c74dd569
commit 208d9d165d
5 changed files with 135 additions and 122 deletions
@@ -1,15 +1,19 @@
# How to stream runs into an React app
# How to Stream LangGraph Runs in Your React App
!!! info "Prerequisites" - [LangGraph Platform](../concepts/langgraph_platform.md) - [LangGraph Server](../concepts/langgraph_server.md)
!!! info "Prerequisites"
- [LangGraph Platform](../concepts/langgraph_platform.md)
- [LangGraph Server](../concepts/langgraph_server.md)
The `useStream()` hook allows you to easily stream values from a LangGraph run. It enables the following features:
The `useStream()` React hook provides a seamless way to integrate LangGraph runs into your React applications. It handles all the complexities of streaming, state management, and branching logic, letting you focus on building great chat experiences.
- Streaming messages: Streams messages from the run as they are generated.
- State management: Thread state is managed for you, including messages, loading and error states.
- Branching support: We handle checkpoint branching for you, so you can focus on building your chat interface.
- Headless: Bring your own chat UI and implement streaming into any design or layout.
Key features:
This guide will show you how you can use `useStream()` to stream values within your React application.
- Messages streaming: Handle a stream of message chunks to form a complete message
- Automatic state management for messages, loading states, and errors
- Conversation branching: Create alternate conversation paths from any point in the chat history
- UI-agnostic design - bring your own components and styling
Let's explore how to use `useStream()` in your React application.
## Example
@@ -62,24 +66,24 @@ export default function App() {
}
```
## Customise UI
## Customizing Your UI
The `useStream()` hook provides built-in state management capabilities to simplify your application development. It handles:
The `useStream()` hook takes care of all the complex state management behind the scenes, providing you with simple interfaces to build your UI. Here's what you get out of the box:
- Thread state management
- Loading states during stream operations
- Error handling and error states
- Message management
- Loading and error states
- Message handling and updates
- Branching support
This allows you to focus on building your UI while the `useStream()` hook takes care of the underlying state complexity.
Here are some examples on how to use these features effectively:
### Loading state
### Loading States
The `isLoading` property is set to `true` whenever the stream is running. This is useful for:
The `isLoading` property tells you when a stream is active, enabling you to:
1. Showing a loading spinner to indicate that the stream is running.
2. Disabling the input box to prevent multiple submissions.
3. Showing a cancellation button to cancel a run.
- Show a loading indicator
- Disable input fields during processing
- Display a cancel button
```tsx
export default function App() {
@@ -101,9 +105,9 @@ export default function App() {
}
```
### Thread management
### Thread Management
The `useStream()` hook manages a thread for you. You can use the `threadId` property to get the thread ID. Pass in the `onThreadId` callback to get notified when the new thread is created.
Keep track of conversations with built-in thread management. You can access the current thread ID and get notified when new threads are created:
```tsx
const [threadId, setThreadId] = useState<string | null>(null);
@@ -117,11 +121,13 @@ const thread = useStream<{ messages: Message[] }>({
});
```
We recommend setting the `threadId` as a query parameter in the URL, so that you can resume the conversation from the same thread even when the page is refreshed.
We recommend storing the `threadId` in your URL's query parameters to let users resume conversations after page refreshes.
### Messages handling
### Messages Handling
To enable messages handling, you need to pass the `messagesKey` option to the `useStream()` hook. When enabled, the `useStream()` hook will keep track of the message chunks received from the server and concatenate them together to form a complete message. The completed message chunks can be retrieved via the `messages` property.
To enable messages handling, you need to pass the `messagesKey` option to the `useStream()` hook.
When enabled, the `useStream()` hook will keep track of the message chunks received from the server and concatenate them together to form a complete message. The completed message chunks can be retrieved via the `messages` property.
```tsx
import type { Message } from "@langchain/langgraph-sdk";
@@ -144,7 +150,7 @@ export default function HomePage() {
}
```
### Branching
### Branching Support
To enable branching, you need to enable messages handling. Pass the `messagesKey` option to the `useStream()` hook. For each message, you can use `getMessagesMetadata()` to get the first checkpoint from which the message has been first seen. You can then create a new run from the checkpoint preceding the first seen checkpoint to create a new branch in a thread.
@@ -274,7 +280,7 @@ export default function App() {
onEdit={(message) =>
thread.submit(
{ messages: [message] },
{ checkpoint: parentCheckpoint },
{ checkpoint: parentCheckpoint }
)
}
/>
@@ -329,37 +335,42 @@ export default function App() {
}
```
### TypeScript and Type safety
### TypeScript
The `useStream()` hook accepts generic parameters that can be used to specify the thread state and update type as well as the custom event type, avoiding the need to manually type-cast.
The `useStream()` hook is fully typed to help catch errors early and provide better IDE support. You can specify types for:
- State shape
- Update format
- Custom events
```tsx
// Type definition of the state
type StateType = { messages: Message[] };
// Define your types
type State = {
messages: Message[];
context?: Record<string, unknown>;
};
// Type definition of the update
type UpdateType = { messages: Message[] | Message };
type Update = {
messages: Message[] | Message;
context?: Record<string, unknown>;
};
// Type definition of the custom event
type CustomEventType = { counter: number };
type CustomEvent = {
type: "progress" | "debug";
payload: unknown;
};
const thread = useStream<StateType, UpdateType, CustomEventType>({
// Use them with the hook
const thread = useStream<State, Update, CustomEvent>({
apiUrl: "http://localhost:2024",
assistantId: "agent",
messagesKey: "messages",
});
```
If you use `LangGraph.js`, you can re-use the same `Annotation` as the one used within `StateGraph`.
!!! warning "Importing from @langchain/langgraph/web"
Make sure to import from `@langchain/langgraph/web` and not from `@langchain/langgraph`, as the default entrypoint will attempt to initialize `AsyncLocalStorage`, which is not available in the browser.
If you're using LangGraph.js, you can reuse your graph's annotation types:
```tsx
"use client";
import { useStream } from "@langchain/langgraph-sdk/react";
import {
Annotation,
MessagesAnnotation,
@@ -369,57 +380,22 @@ import {
const AgentState = Annotation.Root({
...MessagesAnnotation.spec,
context: Annotation.Optional(Annotation.Any()),
});
export default function HomePage() {
const thread = useStream<
StateType<typeof AgentState.spec>,
UpdateType<typeof AgentState.spec>
>({
apiUrl: "http://localhost:2024",
assistantId: "agent",
messagesKey: "messages",
});
return (
<div>
<div>
{thread.messages.map((message) => (
<div key={message.id}>{message.content as string}</div>
))}
</div>
<form
onSubmit={(e) => {
e.preventDefault();
const form = e.target as HTMLFormElement;
const message = new FormData(form).get("message") as string;
form.reset();
thread.submit({ messages: [message] });
}}
>
<input type="text" name="message" />
{thread.isLoading ? (
<button key="stop" type="button" onClick={() => thread.stop()}>
Stop
</button>
) : (
<button key="submit" type="submit">
Send
</button>
)}
</form>
</div>
);
}
const thread = useStream<
StateType<typeof AgentState.spec>,
UpdateType<typeof AgentState.spec>
>({
apiUrl: "http://localhost:2024",
assistantId: "agent",
messagesKey: "messages",
});
```
## Event callbacks
## Event Handling
The `useStream()` hook provides few event callbacks that you can use to react to specific events.
The `useStream()` hook provides several callback options to help you respond to different events:
- `onError`: Called when an error occurs.
- `onFinish`: Called when the stream is finished.
@@ -427,6 +403,6 @@ The `useStream()` hook provides few event callbacks that you can use to react to
- `onCustomEvent`: Called when a custom event is received. See [Custom events](../concepts/custom-events.md) to learn how to stream custom events.
- `onMetadataEvent`: Called when a metadata event is received.
## Learn more
## Learn More
TODO: add a link to the `useStream()` hook documentation.
- [JS/TS SDK Reference](../reference/sdk/js_ts_sdk_ref.md)
+1
View File
@@ -13,3 +13,4 @@ react.d.cts
node_modules
dist
.yarn
docs
+5 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@langchain/langgraph-sdk",
"version": "0.0.40",
"version": "0.0.41",
"description": "Client library for interacting with the LangGraph API",
"type": "module",
"packageManager": "yarn@1.22.19",
@@ -10,7 +10,8 @@
"prepublish": "yarn run build",
"format": "prettier --write src",
"lint": "prettier --check src && tsc --noEmit",
"test": "NODE_OPTIONS=--experimental-vm-modules jest --testPathIgnorePatterns=\\.int\\.test.ts"
"test": "NODE_OPTIONS=--experimental-vm-modules jest --testPathIgnorePatterns=\\.int\\.test.ts",
"typedoc": "typedoc && typedoc src/react/index.ts --out docs/react --options typedoc.react.json"
},
"main": "index.js",
"license": "MIT",
@@ -33,8 +34,8 @@
"jest": "^29.7.0",
"prettier": "^3.2.5",
"ts-jest": "^29.1.2",
"typedoc": "^0.26.1",
"typedoc-plugin-markdown": "^4.1.0",
"typedoc": "^0.27.7",
"typedoc-plugin-markdown": "^4.4.2",
"typescript": "^5.4.5",
"react": "^18.3.1"
},
+5
View File
@@ -0,0 +1,5 @@
{
"pageTitleTemplates": {
"index": "{projectName}/react"
}
}
+57 -27
View File
@@ -301,6 +301,15 @@
resolved "https://registry.yarnpkg.com/@cfworker/json-schema/-/json-schema-4.1.1.tgz#4a2a3947ee9fa7b7c24be981422831b8674c3be6"
integrity sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==
"@gerrit0/mini-shiki@^1.24.0":
version "1.27.2"
resolved "https://registry.yarnpkg.com/@gerrit0/mini-shiki/-/mini-shiki-1.27.2.tgz#cf2a9fcb08a6581c78fc94821f0c854ec4b9f899"
integrity sha512-GeWyHz8ao2gBiUW4OJnQDxXQnFgZQwwQk05t/CVVgNBN7/rK8XZ7xY6YhLVv9tH3VppWWmr9DCl3MwemB/i+Og==
dependencies:
"@shikijs/engine-oniguruma" "^1.27.2"
"@shikijs/types" "^1.27.2"
"@shikijs/vscode-textmate" "^10.0.1"
"@isaacs/cliui@^8.0.2":
version "8.0.2"
resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550"
@@ -806,10 +815,26 @@
optionalDependencies:
fsevents "~2.3.2"
"@shikijs/core@1.9.0":
version "1.9.0"
resolved "https://registry.yarnpkg.com/@shikijs/core/-/core-1.9.0.tgz#ff717fef5e0e9882f0848272699fd8f04d6f9a07"
integrity sha512-cbSoY8P/jgGByG8UOl3jnP/CWg/Qk+1q+eAKWtcrU3pNoILF8wTsLB0jT44qUBV8Ce1SvA9uqcM9Xf+u3fJFBw==
"@shikijs/engine-oniguruma@^1.27.2":
version "1.29.2"
resolved "https://registry.yarnpkg.com/@shikijs/engine-oniguruma/-/engine-oniguruma-1.29.2.tgz#d879717ced61d44e78feab16f701f6edd75434f1"
integrity sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA==
dependencies:
"@shikijs/types" "1.29.2"
"@shikijs/vscode-textmate" "^10.0.1"
"@shikijs/types@1.29.2", "@shikijs/types@^1.27.2":
version "1.29.2"
resolved "https://registry.yarnpkg.com/@shikijs/types/-/types-1.29.2.tgz#a93fdb410d1af8360c67bf5fc1d1a68d58e21c4f"
integrity sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw==
dependencies:
"@shikijs/vscode-textmate" "^10.0.1"
"@types/hast" "^3.0.4"
"@shikijs/vscode-textmate@^10.0.1":
version "10.0.1"
resolved "https://registry.yarnpkg.com/@shikijs/vscode-textmate/-/vscode-textmate-10.0.1.tgz#d06d45b67ac5e9b0088e3f67ebd3f25c6c3d711a"
integrity sha512-fTIQwLF+Qhuws31iw7Ncl1R3HUDtGwIipiJ9iU+UsDUwMhegFcQKQHd51nZjb7CArq0MvON8rbgCGQYWHUKAdg==
"@sinclair/typebox@^0.27.8":
version "0.27.8"
@@ -910,6 +935,13 @@
dependencies:
"@types/node" "*"
"@types/hast@^3.0.4":
version "3.0.4"
resolved "https://registry.yarnpkg.com/@types/hast/-/hast-3.0.4.tgz#1d6b39993b82cea6ad783945b0508c25903e15aa"
integrity sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==
dependencies:
"@types/unist" "*"
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
version "2.0.6"
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz#7739c232a1fee9b4d3ce8985f314c0c6d33549d7"
@@ -996,6 +1028,11 @@
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8"
integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==
"@types/unist@*":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.3.tgz#acaab0f919ce69cce629c2d4ed2eb4adc1b6c20c"
integrity sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==
"@types/unist@^2", "@types/unist@^2.0.0", "@types/unist@^2.0.2":
version "2.0.10"
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.10.tgz#04ffa7f406ab628f7f7e97ca23e290cd8ab15efc"
@@ -3271,7 +3308,7 @@ minimatch@^5.0.1:
dependencies:
brace-expansion "^2.0.1"
minimatch@^9.0.3:
minimatch@^9.0.3, minimatch@^9.0.5:
version "9.0.5"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5"
integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==
@@ -3857,13 +3894,6 @@ shebang-regex@^3.0.0:
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
shiki@^1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/shiki/-/shiki-1.9.0.tgz#e4d3a044d9c746aefbea47615e83323fdc3dc361"
integrity sha512-i6//Lqgn7+7nZA0qVjoYH0085YdNk4MC+tJV4bo+HgjgRMJ0JmkLZzFAuvVioJqLkcGDK5GAMpghZEZkCnwxpQ==
dependencies:
"@shikijs/core" "1.9.0"
side-channel@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2"
@@ -4231,21 +4261,21 @@ typedarray.prototype.slice@^1.0.3:
typed-array-buffer "^1.0.2"
typed-array-byte-offset "^1.0.2"
typedoc-plugin-markdown@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.1.0.tgz#0969e82d9821c956145a4b8a9a70f4e00bde27e8"
integrity sha512-sUiEJVaa6+MOFShRy14j1OP/VXC5OLyHNecJ2nKeGuBy2M3YiMatSLoIiddFAqVptSuILJTZiJzCBIY6yzAVyg==
typedoc-plugin-markdown@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.4.2.tgz#fc31779595aa9bf00e66709f3894e048345bf7ed"
integrity sha512-kJVkU2Wd+AXQpyL6DlYXXRrfNrHrEIUgiABWH8Z+2Lz5Sq6an4dQ/hfvP75bbokjNDUskOdFlEEm/0fSVyC7eg==
typedoc@^0.26.1:
version "0.26.1"
resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.26.1.tgz#fc43108abdea64929a2e636877e250d5dea50957"
integrity sha512-APsVXqh93jTlpkLuw6+/IORx7n5LN8hzJV8nvMIrYYaIva0VCq0CoDN7Z3hsRThEYVExI/qoFHnAAxrhG+Wd7Q==
typedoc@^0.27.7:
version "0.27.7"
resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.27.7.tgz#09047ffb5c845f45765de26c68b77260867fe967"
integrity sha512-K/JaUPX18+61W3VXek1cWC5gwmuLvYTOXJzBvD9W7jFvbPnefRnCHQCEPw7MSNrP/Hj7JJrhZtDDLKdcYm6ucg==
dependencies:
"@gerrit0/mini-shiki" "^1.24.0"
lunr "^2.3.9"
markdown-it "^14.1.0"
minimatch "^9.0.4"
shiki "^1.9.0"
yaml "^2.4.5"
minimatch "^9.0.5"
yaml "^2.6.1"
typescript@^5.4.5:
version "5.4.5"
@@ -4468,10 +4498,10 @@ yallist@^4.0.0:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yaml@^2.4.5:
version "2.4.5"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.4.5.tgz#60630b206dd6d84df97003d33fc1ddf6296cca5e"
integrity sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==
yaml@^2.6.1:
version "2.7.0"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.7.0.tgz#aef9bb617a64c937a9a748803786ad8d3ffe1e98"
integrity sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==
yargs-parser@^20.2.3:
version "20.2.9"