diff --git a/.github/workflows/deploy_docs.yml b/.github/workflows/deploy_docs.yml index bd8719ee2..e7ea2cc1e 100644 --- a/.github/workflows/deploy_docs.yml +++ b/.github/workflows/deploy_docs.yml @@ -127,6 +127,7 @@ jobs: --check-links-ignore "https://openai\.com/.*" \ --check-links-ignore "https://www\.uber\.com/.*" \ --check-links-ignore "https://pepy\.tech/.*" \ + --check-links-ignore "docs/docs/static/wordmark_*" \ --check-links $(find site -name "index.html" | grep -v 'storm/index.html') else @@ -147,6 +148,7 @@ jobs: --check-links-ignore "https://twitter.com/.*" \ --check-links-ignore "https://github\.com/.*" \ --check-links-ignore "/.*\.(ipynb|html)$" \ + --check-links-ignore "docs/docs/static/wordmark_*" \ --check-links ${CHANGED_FILES} \ || ([ $? = 5 ] && exit 0 || exit $?) else diff --git a/README.md b/README.md index f656b962d..5f5cd6acf 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,12 @@ -# 🦜🕸️LangGraph + + + + LangGraph Logo + + +
+
+
[![Version](https://img.shields.io/pypi/v/langgraph.svg)](https://pypi.org/project/langgraph/) [![Downloads](https://static.pepy.tech/badge/langgraph/month)](https://pepy.tech/project/langgraph) @@ -17,7 +25,7 @@ pip install -U langgraph To learn more about how to use LangGraph, check out [the docs](https://langchain-ai.github.io/langgraph/). We show a simple example below of how to create a ReAct agent. ```python -from langchain_anthropic import ChatAnthropic +# This code depends on pip install langchain[anthropic] from langgraph.prebuilt import create_react_agent def search(query: str): @@ -26,9 +34,7 @@ def search(query: str): return "It's 60 degrees and foggy." return "It's 90 degrees and sunny." -model = ChatAnthropic(model=“claude-3-7-sonnet-latest”) -tools = [search] -agent = create_react_agent(model, tools) +agent = create_react_agent("anthropic:claude-3-7-sonnet-latest", tools=[search]) agent.invoke( {"messages": [{"role": "user", "content": "what is the weather in sf"}]} ) diff --git a/docs/docs/cloud/how-tos/generative_ui_react.md b/docs/docs/cloud/how-tos/generative_ui_react.md new file mode 100644 index 000000000..14b0dd097 --- /dev/null +++ b/docs/docs/cloud/how-tos/generative_ui_react.md @@ -0,0 +1,311 @@ +# How to implement Generative User Interfaces with LangGraph + +!!! info "Prerequisites" + + - [LangGraph Platform](../../concepts/langgraph_platform.md) + - [LangGraph Server](../../concepts/langgraph_server.md) + - [`useStream()` React Hook](./use_stream_react.md) + +Generative user interfaces (Generative UI) allows agents to go beyond text and generate rich user interfaces. This enables creating more interactive and context-aware applications where the UI adapts based on the conversation flow and AI responses. + +![Generative UI Sample](./img/generative_ui_sample.jpg) + +LangGraph Platform supports colocating your React components with your graph code. This allows you to focus on building specific UI components for your graph while easily plugging into existing chat interfaces such as [Agent Chat](https://agentchat.vercel.app) and loading the code only when actually needed. + +!!! warning "LangGraph.js only" + + Currently only LangGraph.js supports Generative UI. Support for Python is coming soon. + +## Tutorial + +### 1. Define and configure UI components + +First, create your first UI component. For each component you need to provide an unique identifier that will be used to reference the component in your graph code. + +```tsx title="src/agent/ui.tsx" +const WeatherComponent = (props: { city: string }) => { + return
Weather for {props.city}
; +}; + +export default { + weather: WeatherComponent, +}; +``` + +Next, define your UI components in your `langgraph.json` configuration: + +```json +{ + "node_version": "20", + "graphs": { + "agent": "./src/agent/index.ts:graph" + }, + "ui": { + "agent": "./src/agent/ui.tsx" + } +} +``` + +The `ui` section points to the UI components that will be used by graphs. By default, we recommend using the same key as the graph name, but you can split out the components however you like, see [Customise the namespace of UI components](#customise-the-namespace-of-ui-components) for more details. + +LangGraph Platform will automatically bundle your UI components code and styles and serve them as external assets that can be loaded by the `LoadExternalComponent` component. Some dependencies such as `react` and `react-dom` will be automatically excluded from the bundle. + +CSS and Tailwind 4.x is also supported out of the box, so you can freely use Tailwind classes as well as `shadcn/ui` in your UI components. + +=== "`src/agent/ui.tsx`" + + ```tsx + import "./styles.css"; + + const WeatherComponent = (props: { city: string }) => { + return
Weather for {props.city}
; + }; + + export default { + weather: WeatherComponent, + }; + ``` + +=== "`src/agent/styles.css`" + + ```css + @import "tailwindcss"; + ``` + +### 2. Send the UI components in your graph + +Use the `typedUi` utility to emit UI elements from your agent nodes: + +```typescript title="src/agent/index.ts" +import { + typedUi, + uiMessageReducer, +} from "@langchain/langgraph-sdk/react-ui/server"; + +import { ChatOpenAI } from "@langchain/openai"; +import { v4 as uuidv4 } from "uuid"; +import { z } from "zod"; + +import type ComponentMap from "./ui.js"; + +import { + Annotation, + MessagesAnnotation, + StateGraph, + type LangGraphRunnableConfig, +} from "@langchain/langgraph"; + +const AgentState = Annotation.Root({ + ...MessagesAnnotation.spec, + ui: Annotation({ reducer: uiMessageReducer, default: () => [] }), +}); + +export const graph = new StateGraph(AgentState) + .addNode("weather", async (state, config) => { + // Provide the type of the component map to ensure + // type safety of `ui.push()` calls. + const ui = typedUi(config); + + const weather = await new ChatOpenAI({ model: "gpt-4o-mini" }) + .withStructuredOutput(z.object({ city: z.string() })) + .withConfig({ tags: ["langsmith:nostream"] }) + .invoke(state.messages); + + const response = { + id: uuidv4(), + type: "ai", + content: `Here's the weather for ${weather.city}`, + }; + + // Emit UI elements with associated AI message + ui.push({ name: "weather", props: weather }, { message: response }); + + return { messages: [response], ui: ui.items }; + }) + .addEdge("__start__", "weather") + .compile(); +``` + +### 3. Handle UI elements in your React application + +On the client side, you can use `useStream()` and `LoadExternalComponent` to display the UI elements. + +```tsx title="src/app/page.tsx" +"use client"; + +import { useStream } from "@langchain/langgraph-sdk/react"; +import { LoadExternalComponent } from "@langchain/langgraph-sdk/react-ui"; + +export default function Page() { + const { thread, values } = useStream({ + apiUrl: "http://localhost:2024", + assistantId: "agent", + }); + + return ( +
+ {thread.messages.map((message) => ( +
+ {message.content} + {values.ui + ?.filter((ui) => ui.metadata?.message_id === message.id) + .map((ui) => ( + + ))} +
+ ))} +
+ ); +} +``` + +Behind the scenes, `LoadExternalComponent` will fetch the JS and CSS for the UI components from LangGraph Platform and render them in a shadow DOM, thus ensuring style isolation from the rest of your application. + +## 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 +Loading...} +/> +``` + +### 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. + +```tsx +const clientComponents = { + weather: WeatherComponent, +}; + +; +``` + +### 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. + +=== "`src/app/page.tsx`" + + ```tsx + + ``` + +=== "`langgraph.json`" + + ```json + { + "ui": { + "custom-namespace": "./src/agent/ui.tsx" + } + } + ``` + +### Access and interact with the thread state from the UI component + +You can access the thread state inside the UI component by using the `useStreamContext` hook. + +```tsx +import { useStreamContext } from "@langchain/langgraph-sdk/react-ui"; + +const WeatherComponent = (props: { city: string }) => { + const { thread, submit } = useStreamContext(); + return ( + <> +
Weather for {props.city}
+ + + + ); +}; +``` + +### Pass additional context to the client components + +You can pass additional context to the client components by providing a `meta` prop to the `LoadExternalComponent` component. + +```tsx + +``` + +Then, you can access the `meta` prop in the UI component by using the `useStreamContext` hook. + +```tsx +import { useStreamContext } from "@langchain/langgraph-sdk/react-ui"; + +const WeatherComponent = (props: { city: string }) => { + const { meta } = useStreamContext< + { city: string }, + { MetaType: { userId?: string } } + >(); + + return ( +
+ Weather for {props.city} (user: {meta?.userId}) +
+ ); +}; +``` + +### Streaming UI updates before the node execution is finished + +You can stream UI updates before the node execution is finished by using the `onCustomEvent` callback of the `useStream()` hook. + +```tsx +import { uiMessageReducer } from "@langchain/langgraph-sdk/react-ui"; + +const { thread, submit } = useStream({ + apiUrl: "http://localhost:2024", + assistantId: "agent", + onCustomEvent: (event, options) => { + options.mutate((prev) => { + const ui = uiMessageReducer(prev.ui ?? [], event); + return { ...prev, ui }; + }); + }, +}); +``` + +### 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 `ui.delete` with the ID of the UI message. + +```tsx +// pushed message +const message = ui.push({ name: "weather", props: { city: "London" } }); + +// remove said message +ui.delete(message.id); + +// return new state to persist changes +return { ui: ui.items }; +``` + +## Learn more + +- [JS/TS SDK Reference](../reference/sdk/js_ts_sdk_ref.md) diff --git a/docs/docs/cloud/how-tos/img/generative_ui_sample.jpg b/docs/docs/cloud/how-tos/img/generative_ui_sample.jpg new file mode 100644 index 000000000..4d32a5e91 Binary files /dev/null and b/docs/docs/cloud/how-tos/img/generative_ui_sample.jpg differ diff --git a/docs/docs/how-tos/index.md b/docs/docs/how-tos/index.md index 11acef33c..978642c81 100644 --- a/docs/docs/how-tos/index.md +++ b/docs/docs/how-tos/index.md @@ -198,7 +198,6 @@ Learn how to set up your app for deployment to LangGraph Platform: - [How to test locally](../cloud/deployment/test_locally.md) - [How to rebuild graph at runtime](../cloud/deployment/graph_rebuild.md) - [How to use LangGraph Platform to deploy CrewAI, AutoGen, and other frameworks](autogen-langgraph-platform.ipynb) -- [How to integrate LangGraph into your React application](../cloud/how-tos/use_stream_react.md) ### Deployment @@ -257,6 +256,13 @@ Streaming the results of your LLM application is vital for ensuring a good user - [How to stream in debug mode](../cloud/how-tos/stream_debug.md) - [How to stream multiple modes](../cloud/how-tos/stream_multiple.md) +### Frontend and Generative UI + +With LangGraph Platform you can integrate LangGraph agents into your React applications and colocate UI components with your agent code. + +- [How to integrate LangGraph into your React application](../cloud/how-tos/use_stream_react.md) +- [How to implement Generative User Interfaces with LangGraph](../cloud/how-tos/generative_ui_react.md) + ### Human-in-the-loop When designing complex graphs, relying entirely on the LLM for decision-making can be risky, particularly when it involves tools that interact with files, APIs, or databases. These interactions may lead to unintended data access or modifications, depending on the use case. To mitigate these risks, LangGraph allows you to integrate human-in-the-loop behavior, ensuring your LLM applications operate as intended without undesirable outcomes. diff --git a/docs/docs/index.md b/docs/docs/index.md index 5bfc6c095..066f0775c 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -3,4 +3,26 @@ hide_comments: true title: Home --- + + +

+ LangGraph Logo + LangGraph Logo +

+ + + {!../README.md!} diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 6672888e1..2189d5dc8 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -85,7 +85,7 @@ plugins: nav: - Home: - - Introduction: index.md + - index.md - Get started: - Learn the basics: tutorials/introduction.ipynb - Deployment: @@ -231,6 +231,7 @@ nav: - cloud/how-tos/stream_debug.md - cloud/how-tos/stream_multiple.md - cloud/how-tos/use_stream_react.md + - cloud/how-tos/generative_ui_react.md - Human-in-the-loop: - Human-in-the-loop: how-tos#human-in-the-loop_1 - cloud/how-tos/human_in_the_loop_breakpoint.md diff --git a/libs/langgraph/README.md b/libs/langgraph/README.md index f656b962d..5f5cd6acf 100644 --- a/libs/langgraph/README.md +++ b/libs/langgraph/README.md @@ -1,4 +1,12 @@ -# 🦜🕸️LangGraph + + + + LangGraph Logo + + +
+
+
[![Version](https://img.shields.io/pypi/v/langgraph.svg)](https://pypi.org/project/langgraph/) [![Downloads](https://static.pepy.tech/badge/langgraph/month)](https://pepy.tech/project/langgraph) @@ -17,7 +25,7 @@ pip install -U langgraph To learn more about how to use LangGraph, check out [the docs](https://langchain-ai.github.io/langgraph/). We show a simple example below of how to create a ReAct agent. ```python -from langchain_anthropic import ChatAnthropic +# This code depends on pip install langchain[anthropic] from langgraph.prebuilt import create_react_agent def search(query: str): @@ -26,9 +34,7 @@ def search(query: str): return "It's 60 degrees and foggy." return "It's 90 degrees and sunny." -model = ChatAnthropic(model=“claude-3-7-sonnet-latest”) -tools = [search] -agent = create_react_agent(model, tools) +agent = create_react_agent("anthropic:claude-3-7-sonnet-latest", tools=[search]) agent.invoke( {"messages": [{"role": "user", "content": "what is the weather in sf"}]} ) diff --git a/libs/prebuilt/langgraph/prebuilt/chat_agent_executor.py b/libs/prebuilt/langgraph/prebuilt/chat_agent_executor.py index 0fead9c1e..405a18d2f 100644 --- a/libs/prebuilt/langgraph/prebuilt/chat_agent_executor.py +++ b/libs/prebuilt/langgraph/prebuilt/chat_agent_executor.py @@ -257,7 +257,7 @@ def _validate_chat_history( @_convert_modifier_to_prompt def create_react_agent( model: Union[str, LanguageModelLike], - tools: Union[Sequence[BaseTool], ToolNode], + tools: Union[Sequence[Union[BaseTool, Callable]], ToolNode], *, prompt: Optional[Prompt] = None, response_format: Optional[ diff --git a/libs/prebuilt/pyproject.toml b/libs/prebuilt/pyproject.toml index 3246572e3..dbdf36bd6 100644 --- a/libs/prebuilt/pyproject.toml +++ b/libs/prebuilt/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langgraph-prebuilt" -version = "0.1.2" +version = "0.1.3" description = "Library with high-level APIs for creating and executing LangGraph agents and tools." authors = [] license = "MIT" diff --git a/libs/sdk-js/src/react-ui/client.tsx b/libs/sdk-js/src/react-ui/client.tsx index ada4b8c11..6c8804d72 100644 --- a/libs/sdk-js/src/react-ui/client.tsx +++ b/libs/sdk-js/src/react-ui/client.tsx @@ -115,6 +115,9 @@ interface LoadExternalComponentProps /** Stream of the assistant */ stream: ReturnType; + /** Namespace of UI components. Defaults to assistant ID. */ + namespace?: string; + /** UI message to be rendered */ message: UIMessage; @@ -133,6 +136,7 @@ interface LoadExternalComponentProps export function LoadExternalComponent({ stream, + namespace, message, meta, fallback, @@ -152,10 +156,11 @@ export function LoadExternalComponent({ const clientComponent = components?.[message.name]; const hasClientComponent = clientComponent != null; + const uiNamespace = namespace ?? stream.assistantId; const uiClient = stream.client["~ui"]; React.useEffect(() => { if (hasClientComponent) return; - uiClient.getComponent(stream.assistantId, message.name).then((html) => { + uiClient.getComponent(uiNamespace, message.name).then((html) => { const dom = ref.current; if (!dom) return; const root = dom.shadowRoot ?? dom.attachShadow({ mode: "open" }); @@ -166,13 +171,7 @@ export function LoadExternalComponent({ ); root.appendChild(fragment); }); - }, [ - uiClient, - stream.assistantId, - message.name, - shadowRootId, - hasClientComponent, - ]); + }, [uiClient, uiNamespace, message.name, shadowRootId, hasClientComponent]); if (hasClientComponent) { return React.createElement(clientComponent, message.props);