From 208d9d165d64162ab4e9af9d7bc600462ebbdd76 Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Thu, 13 Feb 2025 07:44:01 -0800 Subject: [PATCH] feat(sdk-js): add docs, how-to guide --- .../how-tos/use_stream_react.md} | 158 ++++++++---------- libs/sdk-js/.gitignore | 1 + libs/sdk-js/package.json | 9 +- libs/sdk-js/typedoc.react.json | 5 + libs/sdk-js/yarn.lock | 84 +++++++--- 5 files changed, 135 insertions(+), 122 deletions(-) rename docs/docs/{how-tos/use-stream-react.md => cloud/how-tos/use_stream_react.md} (63%) create mode 100644 libs/sdk-js/typedoc.react.json diff --git a/docs/docs/how-tos/use-stream-react.md b/docs/docs/cloud/how-tos/use_stream_react.md similarity index 63% rename from docs/docs/how-tos/use-stream-react.md rename to docs/docs/cloud/how-tos/use_stream_react.md index 45cf5450d..66a7fb9ed 100644 --- a/docs/docs/how-tos/use-stream-react.md +++ b/docs/docs/cloud/how-tos/use_stream_react.md @@ -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(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; +}; -// Type definition of the update -type UpdateType = { messages: Message[] | Message }; +type Update = { + messages: Message[] | Message; + context?: Record; +}; -// Type definition of the custom event -type CustomEventType = { counter: number }; +type CustomEvent = { + type: "progress" | "debug"; + payload: unknown; +}; -const thread = useStream({ +// Use them with the hook +const thread = useStream({ 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, - UpdateType - >({ - apiUrl: "http://localhost:2024", - assistantId: "agent", - messagesKey: "messages", - }); - - return ( -
-
- {thread.messages.map((message) => ( -
{message.content as string}
- ))} -
- -
{ - e.preventDefault(); - - const form = e.target as HTMLFormElement; - const message = new FormData(form).get("message") as string; - - form.reset(); - thread.submit({ messages: [message] }); - }} - > - - - {thread.isLoading ? ( - - ) : ( - - )} -
-
- ); -} +const thread = useStream< + StateType, + UpdateType +>({ + 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) diff --git a/libs/sdk-js/.gitignore b/libs/sdk-js/.gitignore index 4d7124e5f..8eaba233a 100644 --- a/libs/sdk-js/.gitignore +++ b/libs/sdk-js/.gitignore @@ -13,3 +13,4 @@ react.d.cts node_modules dist .yarn +docs \ No newline at end of file diff --git a/libs/sdk-js/package.json b/libs/sdk-js/package.json index 759708e4d..cb34bb373 100644 --- a/libs/sdk-js/package.json +++ b/libs/sdk-js/package.json @@ -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" }, diff --git a/libs/sdk-js/typedoc.react.json b/libs/sdk-js/typedoc.react.json new file mode 100644 index 000000000..6359dac1b --- /dev/null +++ b/libs/sdk-js/typedoc.react.json @@ -0,0 +1,5 @@ +{ + "pageTitleTemplates": { + "index": "{projectName}/react" + } +} diff --git a/libs/sdk-js/yarn.lock b/libs/sdk-js/yarn.lock index 4d4e4ccf8..635f0c5f3 100644 --- a/libs/sdk-js/yarn.lock +++ b/libs/sdk-js/yarn.lock @@ -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"