mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-09-07 18:27:52 +02:00
Add docs
This commit is contained in:
@@ -207,18 +207,6 @@ Behind the scenes, `LoadExternalComponent` will fetch the JS and CSS for the UI
|
||||
|
||||
## How-to guides
|
||||
|
||||
### Show loading UI when components are loading
|
||||
|
||||
You can provide a fallback UI to be rendered when the components are loading.
|
||||
|
||||
```tsx
|
||||
<LoadExternalComponent
|
||||
stream={thread}
|
||||
message={ui}
|
||||
fallback={<div>Loading...</div>}
|
||||
/>
|
||||
```
|
||||
|
||||
### Provide custom components on the client side
|
||||
|
||||
If you already have the components loaded in your client application, you can provide a map of such components to be rendered directly without fetching the UI code from LangGraph Platform.
|
||||
@@ -235,6 +223,18 @@ const clientComponents = {
|
||||
/>;
|
||||
```
|
||||
|
||||
### Show loading UI when components are loading
|
||||
|
||||
You can provide a fallback UI to be rendered when the components are loading.
|
||||
|
||||
```tsx
|
||||
<LoadExternalComponent
|
||||
stream={thread}
|
||||
message={ui}
|
||||
fallback={<div>Loading...</div>}
|
||||
/>
|
||||
```
|
||||
|
||||
### Customise the namespace of UI components.
|
||||
|
||||
By default `LoadExternalComponent` will use the `assistantId` from `useStream()` hook to fetch the code for UI components. You can customise this by providing a `namespace` prop to the `LoadExternalComponent` component.
|
||||
@@ -316,9 +316,9 @@ const WeatherComponent = (props: { city: string }) => {
|
||||
};
|
||||
```
|
||||
|
||||
### Streaming UI updates before the node execution is finished
|
||||
### Streaming UI messages from the server
|
||||
|
||||
You can stream UI updates before the node execution is finished by using the `onCustomEvent` callback of the `useStream()` hook.
|
||||
You can stream UI messages before the node execution is finished by using the `onCustomEvent` callback of the `useStream()` hook. This is especially useful when updating the UI component as the LLM is generating the response.
|
||||
|
||||
```tsx
|
||||
import { uiMessageReducer } from "@langchain/langgraph-sdk/react-ui";
|
||||
@@ -335,6 +335,162 @@ const { thread, submit } = useStream({
|
||||
});
|
||||
```
|
||||
|
||||
Then you can pushing updates to the UI component by calling `ui.push()` / `push_ui_message()` with the same ID as the UI message you wish to update.
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
from typing import Annotated, Sequence, TypedDict
|
||||
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
from langchain_core.messages import AIMessage, AIMessageChunk, BaseMessage
|
||||
from langgraph.graph import StateGraph
|
||||
from langgraph.graph.message import add_messages
|
||||
from langgraph.graph.ui import AnyUIMessage, push_ui_message, ui_message_reducer
|
||||
|
||||
|
||||
class AgentState(TypedDict): # noqa: D101
|
||||
messages: Annotated[Sequence[BaseMessage], add_messages]
|
||||
ui: Annotated[Sequence[AnyUIMessage], ui_message_reducer]
|
||||
|
||||
|
||||
class CreateTextDocument(TypedDict):
|
||||
"""Prepare a document heading for the user."""
|
||||
|
||||
title: str
|
||||
|
||||
|
||||
async def writer_node(state: AgentState):
|
||||
model = ChatAnthropic(model="claude-3-5-sonnet-latest")
|
||||
message: AIMessage = await model.bind_tools(
|
||||
tools=[CreateTextDocument],
|
||||
tool_choice={"type": "tool", "name": "CreateTextDocument"},
|
||||
).ainvoke(state["messages"])
|
||||
|
||||
tool_call = next(
|
||||
(x["args"] for x in message.tool_calls if x["name"] == "CreateTextDocument"),
|
||||
None,
|
||||
)
|
||||
|
||||
if tool_call:
|
||||
ui_message = push_ui_message("writer", tool_call, message=message)
|
||||
ui_message_id = ui_message["id"]
|
||||
|
||||
# We're already streaming the LLM response to the client through UI messages
|
||||
# so we don't need to stream it again to the `messages` stream mode.
|
||||
content_stream = model.with_config({"tags": ["nostream"]}).astream(
|
||||
f"Create a document with the title: {tool_call['title']}"
|
||||
)
|
||||
|
||||
content: AIMessageChunk | None = None
|
||||
async for chunk in content_stream:
|
||||
content = content + chunk if content else chunk
|
||||
|
||||
push_ui_message(
|
||||
"writer",
|
||||
{"content": content.text()},
|
||||
id=ui_message_id,
|
||||
message=message,
|
||||
# Use `merge=rue` to merge props with the existing UI message
|
||||
merge=True,
|
||||
)
|
||||
|
||||
return {"messages": [message]}
|
||||
```
|
||||
|
||||
=== "JS"
|
||||
|
||||
```tsx
|
||||
import {
|
||||
Annotation,
|
||||
MessagesAnnotation,
|
||||
type LangGraphRunnableConfig,
|
||||
} from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
import {
|
||||
typedUi,
|
||||
uiMessageReducer,
|
||||
} from "@langchain/langgraph-sdk/react-ui/server";
|
||||
import type { AIMessageChunk } from "@langchain/core/messages";
|
||||
|
||||
import type ComponentMap from "./ui";
|
||||
|
||||
const AgentState = Annotation.Root({
|
||||
...MessagesAnnotation.spec,
|
||||
ui: Annotation({ reducer: uiMessageReducer, default: () => [] }),
|
||||
});
|
||||
|
||||
async function writerNode(
|
||||
state: typeof AgentState.State,
|
||||
config: LangGraphRunnableConfig
|
||||
): Promise<typeof AgentState.Update> {
|
||||
const ui = typedUi<typeof ComponentMap>(config);
|
||||
|
||||
const model = new ChatAnthropic({ model: "claude-3-5-sonnet-latest" });
|
||||
const message = await model
|
||||
.bindTools(
|
||||
[
|
||||
{
|
||||
name: "create_text_document",
|
||||
description: "Prepare a document heading for the user.",
|
||||
schema: z.object({ title: z.string() }),
|
||||
},
|
||||
],
|
||||
{ tool_choice: { type: "tool", name: "create_text_document" } }
|
||||
)
|
||||
.invoke(state.messages);
|
||||
|
||||
type ToolCall = { name: "create_text_document"; args: { title: string } };
|
||||
const toolCall = message.tool_calls?.find(
|
||||
(tool): tool is ToolCall => tool.name === "create_text_document"
|
||||
);
|
||||
|
||||
if (toolCall) {
|
||||
const { id, name } = ui.push(
|
||||
{ name: "writer", props: { title: toolCall.args.title } },
|
||||
{ message }
|
||||
);
|
||||
|
||||
const contentStream = await model
|
||||
// We're already streaming the LLM response to the client through UI messages
|
||||
// so we don't need to stream it again to the `messages` stream mode.
|
||||
.withConfig({ tags: ["nostream"] })
|
||||
.stream(`Create a short poem with the topic: ${message.text}`);
|
||||
|
||||
let content: AIMessageChunk | undefined;
|
||||
for await (const chunk of contentStream) {
|
||||
content = content?.concat(chunk) ?? chunk;
|
||||
|
||||
ui.push(
|
||||
{ id, name, props: { content: content?.text } },
|
||||
// Use `merge: true` to merge props with the existing UI message
|
||||
{ message, merge: true }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return { messages: [message] };
|
||||
}
|
||||
```
|
||||
|
||||
=== "`ui.tsx`"
|
||||
|
||||
```tsx
|
||||
function WriterComponent(props: { title: string; content?: string }) {
|
||||
return (
|
||||
<article>
|
||||
<h2>{props.title}</h2>
|
||||
<p style={{ whiteSpace: "pre-wrap" }}>{props.content}</p>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
export default {
|
||||
weather: WriterComponent,
|
||||
};
|
||||
```
|
||||
|
||||
### Remove UI messages from state
|
||||
|
||||
Similar to how messages can be removed from the state by appending a RemoveMessage you can remove an UI message from the state by calling `remove_ui_message` / `ui.delete` with the ID of the UI message.
|
||||
|
||||
@@ -46,7 +46,7 @@ export const typedUi = <Decl extends Record<string, ElementType>>(
|
||||
metadata?: Record<string, unknown>;
|
||||
},
|
||||
options?: { message?: MessageLike; merge?: boolean },
|
||||
): UIMessage;
|
||||
): UIMessage<K, PropMap[K]>;
|
||||
|
||||
function handlePush<K extends keyof PropMap & string>(
|
||||
message: {
|
||||
@@ -56,7 +56,7 @@ export const typedUi = <Decl extends Record<string, ElementType>>(
|
||||
metadata?: Record<string, unknown>;
|
||||
},
|
||||
options: { message?: MessageLike; merge: true },
|
||||
): UIMessage;
|
||||
): UIMessage<K, Partial<PropMap[K]>>;
|
||||
|
||||
function handlePush<K extends keyof PropMap & string>(
|
||||
message: {
|
||||
@@ -66,8 +66,8 @@ export const typedUi = <Decl extends Record<string, ElementType>>(
|
||||
metadata?: Record<string, unknown>;
|
||||
},
|
||||
options?: { message?: MessageLike; merge?: boolean },
|
||||
): UIMessage {
|
||||
const evt: UIMessage = {
|
||||
): UIMessage<K, PropMap[K] | Partial<PropMap[K]>> {
|
||||
const evt: UIMessage<K, PropMap[K] | Partial<PropMap[K]>> = {
|
||||
type: "ui" as const,
|
||||
id: message?.id ?? uuidv4(),
|
||||
name: message?.name,
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
export interface UIMessage {
|
||||
export interface UIMessage<
|
||||
TName extends string = string,
|
||||
TProps extends Record<string, unknown> = Record<string, unknown>,
|
||||
> {
|
||||
type: "ui";
|
||||
|
||||
id: string;
|
||||
name: string;
|
||||
props: Record<string, unknown>;
|
||||
name: TName;
|
||||
props: TProps;
|
||||
metadata: {
|
||||
merge?: boolean;
|
||||
run_id?: string;
|
||||
|
||||
Reference in New Issue
Block a user