mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-09-27 20:15:00 +02:00
feat: add docs translations (#5552)
Co-authored-by: Eugene Yurtsev <eyurtsev@gmail.com> Co-authored-by: Tat Dat Duong <david@duong.cz>
This commit is contained in:
co-authored by
Eugene Yurtsev
Tat Dat Duong
parent
72e418e4d0
commit
d59091672f
@@ -22,8 +22,9 @@ To deploy using the LangGraph Platform, the following information should be prov
|
||||
|
||||
## File Structure
|
||||
|
||||
Below are examples of directory structures for Python and JavaScript applications:
|
||||
Below are examples of directory structures for applications:
|
||||
|
||||
:::python
|
||||
=== "Python (requirements.txt)"
|
||||
|
||||
```plaintext
|
||||
@@ -40,6 +41,7 @@ Below are examples of directory structures for Python and JavaScript application
|
||||
├── requirements.txt # package dependencies
|
||||
└── langgraph.json # configuration file for LangGraph
|
||||
```
|
||||
|
||||
=== "Python (pyproject.toml)"
|
||||
|
||||
```plaintext
|
||||
@@ -57,20 +59,24 @@ Below are examples of directory structures for Python and JavaScript application
|
||||
└── pyproject.toml # dependencies for your project
|
||||
```
|
||||
|
||||
=== "JS (package.json)"
|
||||
:::
|
||||
|
||||
```plaintext
|
||||
my-app/
|
||||
├── src # all project code lies within here
|
||||
│ ├── utils # optional utilities for your graph
|
||||
│ │ ├── tools.ts # tools for your graph
|
||||
│ │ ├── nodes.ts # node functions for your graph
|
||||
│ │ └── state.ts # state definition of your graph
|
||||
│ └── agent.ts # code for constructing your graph
|
||||
├── package.json # package dependencies
|
||||
├── .env # environment variables
|
||||
└── langgraph.json # configuration file for LangGraph
|
||||
```
|
||||
:::js
|
||||
|
||||
```plaintext
|
||||
my-app/
|
||||
├── src # all project code lies within here
|
||||
│ ├── utils # optional utilities for your graph
|
||||
│ │ ├── tools.ts # tools for your graph
|
||||
│ │ ├── nodes.ts # node functions for your graph
|
||||
│ │ └── state.ts # state definition of your graph
|
||||
│ └── agent.ts # code for constructing your graph
|
||||
├── package.json # package dependencies
|
||||
├── .env # environment variables
|
||||
└── langgraph.json # configuration file for LangGraph
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
!!! note
|
||||
|
||||
@@ -88,52 +94,66 @@ See the [LangGraph configuration file reference](../cloud/reference/cli.md#confi
|
||||
|
||||
### Examples
|
||||
|
||||
=== "Python"
|
||||
:::python
|
||||
|
||||
* The dependencies involve a custom local package and the `langchain_openai` package.
|
||||
* A single graph will be loaded from the file `./your_package/your_file.py` with the variable `variable`.
|
||||
* The environment variables are loaded from the `.env` file.
|
||||
- The dependencies involve a custom local package and the `langchain_openai` package.
|
||||
- A single graph will be loaded from the file `./your_package/your_file.py` with the variable `variable`.
|
||||
- The environment variables are loaded from the `.env` file.
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": [
|
||||
"langchain_openai",
|
||||
"./your_package"
|
||||
],
|
||||
"graphs": {
|
||||
"my_agent": "./your_package/your_file.py:agent"
|
||||
},
|
||||
"env": "./.env"
|
||||
}
|
||||
```
|
||||
```json
|
||||
{
|
||||
"dependencies": ["langchain_openai", "./your_package"],
|
||||
"graphs": {
|
||||
"my_agent": "./your_package/your_file.py:agent"
|
||||
},
|
||||
"env": "./.env"
|
||||
}
|
||||
```
|
||||
|
||||
=== "JavaScript"
|
||||
:::
|
||||
|
||||
* The dependencies will be loaded from a dependency file in the local directory (e.g., `package.json`).
|
||||
* A single graph will be loaded from the file `./your_package/your_file.js` with the function `agent`.
|
||||
* The environment variable `OPENAI_API_KEY` is set inline.
|
||||
:::js
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": [
|
||||
"."
|
||||
],
|
||||
"graphs": {
|
||||
"my_agent": "./your_package/your_file.js:agent"
|
||||
},
|
||||
"env": {
|
||||
"OPENAI_API_KEY": "secret-key"
|
||||
}
|
||||
}
|
||||
```
|
||||
- The dependencies will be loaded from a dependency file in the local directory (e.g., `package.json`).
|
||||
- A single graph will be loaded from the file `./your_package/your_file.js` with the function `agent`.
|
||||
- The environment variable `OPENAI_API_KEY` is set inline.
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": ["."],
|
||||
"graphs": {
|
||||
"my_agent": "./your_package/your_file.js:agent"
|
||||
},
|
||||
"env": {
|
||||
"OPENAI_API_KEY": "secret-key"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Dependencies
|
||||
|
||||
A LangGraph application may depend on other Python packages or JavaScript libraries (depending on the programming language in which the application is written).
|
||||
:::python
|
||||
A LangGraph application may depend on other Python packages.
|
||||
:::
|
||||
|
||||
:::js
|
||||
A LangGraph application may depend on other TypeScript/JavaScript libraries.
|
||||
:::
|
||||
|
||||
You will generally need to specify the following information for dependencies to be set up correctly:
|
||||
|
||||
:::python
|
||||
|
||||
1. A file in the directory that specifies the dependencies (e.g. `requirements.txt`, `pyproject.toml`, or `package.json`).
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
1. A file in the directory that specifies the dependencies (e.g. `package.json`).
|
||||
:::
|
||||
|
||||
2. A `dependencies` key in the [LangGraph configuration file](#configuration-file-concepts) that specifies the dependencies required to run the LangGraph application.
|
||||
3. Any additional binaries or system libraries can be specified using `dockerfile_lines` key in the [LangGraph configuration file](#configuration-file-concepts).
|
||||
|
||||
|
||||
+355
-27
@@ -16,7 +16,13 @@ While often used interchangeably, these terms represent distinct security concep
|
||||
- [**Authentication**](#authentication) ("AuthN") verifies _who_ you are. This runs as middleware for every request.
|
||||
- [**Authorization**](#authorization) ("AuthZ") determines _what you can do_. This validates the user's privileges and roles on a per-resource basis.
|
||||
|
||||
:::python
|
||||
In LangGraph Platform, authentication is handled by your [`@auth.authenticate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.authenticate) handler, and authorization is handled by your [`@auth.on`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.on) handlers.
|
||||
:::
|
||||
|
||||
:::js
|
||||
In LangGraph Platform, authentication is handled by your [`@auth.authenticate`](../cloud/reference/sdk/typescript_sdk_ref.md#auth.authenticate) handler, and authorization is handled by your [`@auth.on`](../cloud/reference/sdk/typescript_sdk_ref.md#auth.on) handlers.
|
||||
:::
|
||||
|
||||
## Default Security Models
|
||||
|
||||
@@ -29,7 +35,7 @@ LangGraph Platform provides different security defaults:
|
||||
- Can be customized with your auth handler
|
||||
|
||||
!!! note "Custom auth"
|
||||
Custom auth **is supported** for all plans in LangGraph Platform.
|
||||
Custom auth **is supported** for all plans in LangGraph Platform.
|
||||
|
||||
### Self-Hosted
|
||||
|
||||
@@ -38,6 +44,7 @@ LangGraph Platform provides different security defaults:
|
||||
- You control all aspects of authentication and authorization
|
||||
|
||||
!!! note "Custom auth"
|
||||
|
||||
Custom auth is supported for **Enterprise** self-hosted deployments.
|
||||
Standalone Container (Lite) deployments do not support custom auth natively.
|
||||
|
||||
@@ -47,24 +54,24 @@ A typical authentication setup involves three main components:
|
||||
|
||||
1. **Authentication Provider** (Identity Provider/IdP)
|
||||
|
||||
* A dedicated service that manages user identities and credentials
|
||||
* Handles user registration, login, password resets, etc.
|
||||
* Issues tokens (JWT, session tokens, etc.) after successful authentication
|
||||
* Examples: Auth0, Supabase Auth, Okta, or your own auth server
|
||||
- A dedicated service that manages user identities and credentials
|
||||
- Handles user registration, login, password resets, etc.
|
||||
- Issues tokens (JWT, session tokens, etc.) after successful authentication
|
||||
- Examples: Auth0, Supabase Auth, Okta, or your own auth server
|
||||
|
||||
2. **LangGraph Backend** (Resource Server)
|
||||
|
||||
* Your LangGraph application that contains business logic and protected resources
|
||||
* Validates tokens with the auth provider
|
||||
* Enforces access control based on user identity and permissions
|
||||
* Doesn't store user credentials directly
|
||||
- Your LangGraph application that contains business logic and protected resources
|
||||
- Validates tokens with the auth provider
|
||||
- Enforces access control based on user identity and permissions
|
||||
- Doesn't store user credentials directly
|
||||
|
||||
3. **Client Application** (Frontend)
|
||||
|
||||
* Web app, mobile app, or API client
|
||||
* Collects time-sensitive user credentials and sends to auth provider
|
||||
* Receives tokens from auth provider
|
||||
* Includes these tokens in requests to LangGraph backend
|
||||
- Web app, mobile app, or API client
|
||||
- Collects time-sensitive user credentials and sends to auth provider
|
||||
- Receives tokens from auth provider
|
||||
- Includes these tokens in requests to LangGraph backend
|
||||
|
||||
Here's how these components typically interact:
|
||||
|
||||
@@ -84,15 +91,22 @@ sequenceDiagram
|
||||
LG-->>Client: 8. Return resources
|
||||
```
|
||||
|
||||
:::python
|
||||
Your [`@auth.authenticate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.authenticate) handler in LangGraph handles steps 4-6, while your [`@auth.on`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.on) handlers implement step 7.
|
||||
:::
|
||||
|
||||
:::js
|
||||
Your [`auth.authenticate`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#authenticate) handler in LangGraph handles steps 4-6, while your [`auth.on`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#on>) handlers implement step 7.
|
||||
:::
|
||||
|
||||
## Authentication
|
||||
|
||||
:::python
|
||||
Authentication in LangGraph runs as middleware on every request. Your [`@auth.authenticate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.authenticate) handler receives request information and should:
|
||||
|
||||
1. Validate the credentials
|
||||
2. Return [user info](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.MinimalUserDict) containing the user's identity and user information if valid
|
||||
3. Raise an [HTTP exception](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.exceptions.HTTPException) or AssertionError if invalid
|
||||
3. Raise an [HTTPException](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.exceptions.HTTPException) or AssertionError if invalid
|
||||
|
||||
```python
|
||||
from langgraph_sdk import Auth
|
||||
@@ -126,9 +140,49 @@ The returned user information is available:
|
||||
|
||||
- To your authorization handlers via [`ctx.user`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.AuthContext)
|
||||
- In your application via `config["configuration"]["langgraph_auth_user"]`
|
||||
:::
|
||||
|
||||
:::js
|
||||
Authentication in LangGraph runs as middleware on every request. Your [`authenticate`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#authenticate>) handler receives request information and should:
|
||||
|
||||
1. Validate the credentials
|
||||
2. Return user information containing the user's identity and user information if valid
|
||||
3. Raise an [HTTPException](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#class-httpexception>) if invalid
|
||||
|
||||
```typescript
|
||||
import { Auth, HTTPException } from "@langchain/langgraph-sdk";
|
||||
|
||||
export const auth = new Auth();
|
||||
|
||||
auth.authenticate(async (request) => {
|
||||
// Validate credentials (e.g., API key, JWT token)
|
||||
const apiKey = request.headers.get("x-api-key");
|
||||
if (!apiKey || !isValidKey(apiKey)) {
|
||||
throw new HTTPException(401, "Invalid API key");
|
||||
}
|
||||
|
||||
// Return user info - only identity and isAuthenticated are required
|
||||
// Add any additional fields you need for authorization
|
||||
return {
|
||||
identity: "user-123", // Required: unique user identifier
|
||||
isAuthenticated: true, // Optional: assumed true by default
|
||||
permissions: ["read", "write"], // Optional: for permission-based auth
|
||||
// You can add more custom fields if you want to implement other auth patterns
|
||||
role: "admin",
|
||||
orgId: "org-456",
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
The returned user information is available:
|
||||
|
||||
- To your authorization handlers via the `user` property in a [callback handler](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#on)
|
||||
- In your application via `config.configurable.langgraph_auth_user`
|
||||
:::
|
||||
|
||||
??? tip "Supported Parameters"
|
||||
|
||||
:::python
|
||||
The [`@auth.authenticate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.authenticate) handler can accept any of the following parameters by name:
|
||||
|
||||
* request (Request): The raw ASGI request object
|
||||
@@ -139,13 +193,27 @@ The returned user information is available:
|
||||
* query_params (dict[str, str]): URL query parameters, e.g., {"stream": "true"}
|
||||
* headers (dict[bytes, bytes]): Request headers
|
||||
* authorization (str | None): The Authorization header value (e.g., "Bearer <token>")
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
The [`authenticate`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#authenticate) handler can accept any of the following parameters:
|
||||
|
||||
* request (Request): The raw request object
|
||||
* body (object): The parsed request body
|
||||
* path (string): The request path, e.g., "/threads/abcd-1234-abcd-1234/runs/abcd-1234-abcd-1234/stream"
|
||||
* method (string): The HTTP method, e.g., "GET"
|
||||
* pathParams (Record<string, string>): URL path parameters, e.g., {"threadId": "abcd-1234-abcd-1234", "runId": "abcd-1234-abcd-1234"}
|
||||
* queryParams (Record<string, string>): URL query parameters, e.g., {"stream": "true"}
|
||||
* headers (Record<string, string>): Request headers
|
||||
* authorization (string | null): The Authorization header value (e.g., "Bearer <token>")
|
||||
:::
|
||||
|
||||
In many of our tutorials, we will just show the "authorization" parameter to be concise, but you can opt to accept more information as needed
|
||||
to implement your custom authentication scheme.
|
||||
|
||||
### Agent authentication
|
||||
|
||||
Custom authentication permits delegated access. The values you return in `@auth.authenticate` are added to the run context, giving agents user-scoped credentials lets them access resources on the user’s behalf.
|
||||
Custom authentication permits delegated access. The values you return in `@auth.authenticate` are added to the run context, giving agents user-scoped credentials lets them access resources on the user’s behalf.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
@@ -177,7 +245,7 @@ sequenceDiagram
|
||||
ExternalService -->> LangGraph: 10. Service response
|
||||
|
||||
%% Return to caller
|
||||
LangGraph -->> ClientApp: 11. Return resources
|
||||
LangGraph -->> ClientApp: 11. Return resources
|
||||
```
|
||||
|
||||
After authentication, the platform creates a special configuration object that is passed to your graph and all nodes via the configurable context.
|
||||
@@ -193,13 +261,16 @@ For information on how to authenticate an agent to an MCP server, see the [MCP c
|
||||
|
||||
## Authorization
|
||||
|
||||
After authentication, LangGraph calls your [`@auth.on`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.on) handlers to control access to specific resources (e.g., threads, assistants, crons). These handlers can:
|
||||
After authentication, LangGraph calls your authorization handlers to control access to specific resources (e.g., threads, assistants, crons). These handlers can:
|
||||
|
||||
1. Add metadata to be saved during resource creation by mutating the `value["metadata"]` dictionary directly. See the [supported actions table](#supported-actions) for the list of types the value can take for each action.
|
||||
2. Filter resources by metadata during search/list or read operations by returning a [filter dictionary](#filter-operations).
|
||||
1. Add metadata to be saved during resource creation by mutating the metadata. See the [supported actions table](#supported-actions) for the list of types the value can take for each action.
|
||||
2. Filter resources by metadata during search/list or read operations by returning a [filter](#filter-operations).
|
||||
3. Raise an HTTP exception if access is denied.
|
||||
|
||||
If you want to just implement simple user-scoped access control, you can use a single [`@auth.on`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.on) handler for all resources and actions. If you want to have different control depending on the resource and action, you can use [resource-specific handlers](#resource-specific-handlers). See the [Supported Resources](#supported-resources) section for a full list of the resources that support access control.
|
||||
If you want to just implement simple user-scoped access control, you can use a single authorization handler for all resources and actions. If you want to have different control depending on the resource and action, you can use [resource-specific handlers](#resource-specific-handlers). See the [Supported Resources](#supported-resources) section for a full list of the resources that support access control.
|
||||
|
||||
:::python
|
||||
Your [`@auth.on`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.on) handlers control access by mutating the `value["metadata"]` dictionary directly and returning a [filter dictionary](#filter-operations).
|
||||
|
||||
```python
|
||||
@auth.on
|
||||
@@ -241,9 +312,42 @@ async def add_owner(
|
||||
return filters
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
You can granularly control access by mutating the `value.metadata` object directly and returning a [filter object](#filter-operations) when registering an [`on()`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#on) handler.
|
||||
|
||||
```typescript
|
||||
import { Auth, HTTPException } from "@langchain/langgraph-sdk/auth";
|
||||
|
||||
export const auth = new Auth()
|
||||
.authenticate(async (request: Request) => ({
|
||||
identity: "user-123",
|
||||
permissions: [],
|
||||
}))
|
||||
.on("*", ({ value, user }) => {
|
||||
// Create filter to restrict access to just this user's resources
|
||||
const filters = { owner: user.identity };
|
||||
|
||||
// If the operation supports metadata, add the user identity
|
||||
// as metadata to the resource.
|
||||
if ("metadata" in value) {
|
||||
value.metadata ??= {};
|
||||
value.metadata.owner = user.identity;
|
||||
}
|
||||
|
||||
// Return filters to restrict access
|
||||
// These filters are applied to ALL operations (create, read, update, search, etc.)
|
||||
// to ensure users can only access their own resources
|
||||
return filters;
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Resource-Specific Handlers {#resource-specific-handlers}
|
||||
|
||||
You can register handlers for specific resources and actions by chaining the resource and action names together with the [`@auth.on`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.on) decorator.
|
||||
You can register handlers for specific resources and actions by chaining the resource and action names together with the authorization decorator.
|
||||
When a request is made, the most specific handler that matches that resource and action is called. Below is an example of how to register handlers for specific resources and actions. For the following setup:
|
||||
|
||||
1. Authenticated users are able to create threads, read threads, and create runs on threads
|
||||
@@ -254,6 +358,8 @@ When a request is made, the most specific handler that matches that resource and
|
||||
|
||||
For a full list of supported resources and actions, see the [Supported Resources](#supported-resources) section below.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
# Generic / global handler catches calls that aren't handled by more specific handlers
|
||||
@auth.on
|
||||
@@ -338,11 +444,104 @@ async def on_assistant_create(
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { Auth, HTTPException } from "@langchain/langgraph-sdk/auth";
|
||||
|
||||
export const auth = new Auth()
|
||||
.authenticate(async (request: Request) => ({
|
||||
identity: "user-123",
|
||||
permissions: ["threads:write", "threads:read"],
|
||||
}))
|
||||
.on("*", ({ event, user }) => {
|
||||
console.log(`Request for ${event} by ${user.identity}`);
|
||||
throw new HTTPException(403, { message: "Forbidden" });
|
||||
})
|
||||
|
||||
// Matches the "threads" resource and all actions - create, read, update, delete, search
|
||||
// Since this is **more specific** than the generic `on("*")` handler, it will take precedence over the generic handler for all actions on the "threads" resource
|
||||
.on("threads", ({ permissions, value, user }) => {
|
||||
if (!permissions.includes("write")) {
|
||||
throw new HTTPException(403, {
|
||||
message: "User lacks the required permissions.",
|
||||
});
|
||||
}
|
||||
|
||||
// Not all events do include `metadata` property in `value`.
|
||||
// So we need to add this type guard.
|
||||
if ("metadata" in value) {
|
||||
value.metadata ??= {};
|
||||
value.metadata.owner = user.identity;
|
||||
}
|
||||
|
||||
return { owner: user.identity };
|
||||
})
|
||||
|
||||
// Thread creation. This will match only on thread create actions.
|
||||
// Since this is **more specific** than both the generic `on("*")` handler and the `on("threads")` handler, it will take precedence for any "create" actions on the "threads" resources
|
||||
.on("threads:create", ({ value, user, permissions }) => {
|
||||
if (!permissions.includes("write")) {
|
||||
throw new HTTPException(403, {
|
||||
message: "User lacks the required permissions.",
|
||||
});
|
||||
}
|
||||
|
||||
// Setting metadata on the thread being created will ensure that the resource contains an "owner" field
|
||||
// Then any time a user tries to access this thread or runs within the thread,
|
||||
// we can filter by owner
|
||||
value.metadata ??= {};
|
||||
value.metadata.owner = user.identity;
|
||||
|
||||
return { owner: user.identity };
|
||||
})
|
||||
|
||||
// Reading a thread. Since this is also more specific than the generic `on("*")` handler, and the `on("threads")` handler,
|
||||
.on("threads:read", ({ user }) => {
|
||||
// Since we are reading (and not creating) a thread,
|
||||
// we don't need to set metadata. We just need to
|
||||
// return a filter to ensure users can only see their own threads.
|
||||
return { owner: user.identity };
|
||||
})
|
||||
|
||||
// Run creation, streaming, updates, etc.
|
||||
// This takes precedence over the generic `on("*")` handler and the `on("threads")` handler
|
||||
.on("threads:create_run", ({ value, user }) => {
|
||||
value.metadata ??= {};
|
||||
value.metadata.owner = user.identity;
|
||||
|
||||
return { owner: user.identity };
|
||||
})
|
||||
|
||||
// Assistant creation. This will match only on assistant create actions.
|
||||
// Since this is **more specific** than both the generic `on("*")` handler and the `on("assistants")` handler, it will take precedence for any "create" actions on the "assistants" resources
|
||||
.on("assistants:create", ({ value, user, permissions }) => {
|
||||
if (!permissions.includes("assistants:create")) {
|
||||
throw new HTTPException(403, {
|
||||
message: "User lacks the required permissions.",
|
||||
});
|
||||
}
|
||||
|
||||
// Setting metadata on the assistant being created will ensure that the resource contains an "owner" field.
|
||||
// Then any time a user tries to access this assistant, we can filter by owner
|
||||
value.metadata ??= {};
|
||||
value.metadata.owner = user.identity;
|
||||
|
||||
return { owner: user.identity };
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Notice that we are mixing global and resource-specific handlers in the above example. Since each request is handled by the most specific handler, a request to create a `thread` would match the `on_thread_create` handler but NOT the `reject_unhandled_requests` handler. A request to `update` a thread, however would be handled by the global handler, since we don't have a more specific handler for that resource and action.
|
||||
|
||||
### Filter Operations {#filter-operations}
|
||||
|
||||
Authorization handlers can return `None`, a boolean, or a filter dictionary.
|
||||
:::python
|
||||
Authorization handlers can return different types of values:
|
||||
|
||||
- `None` and `True` mean "authorize access to all underling resources"
|
||||
- `False` means "deny access to all underling resources (raises a 403 exception)"
|
||||
- A metadata filter dictionary will restrict access to resources
|
||||
@@ -355,6 +554,24 @@ A filter dictionary is a dictionary with keys that match the resource metadata.
|
||||
|
||||
A dictionary with multiple keys is treated using a logical `AND` filter. For example, `{"owner": org_id, "allowed_users": {"$contains": user_id}}` will only match resources with metadata whose "owner" is `org_id` and whose "allowed_users" list contains `user_id`.
|
||||
See the reference [here](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.FilterType) for more information.
|
||||
:::
|
||||
|
||||
:::js
|
||||
Authorization handlers can return different types of values:
|
||||
|
||||
- `null` and `true` mean "authorize access to all underling resources"
|
||||
- `false` means "deny access to all underling resources (raises a 403 exception)"
|
||||
- A metadata filter object will restrict access to resources
|
||||
|
||||
A filter object is an object with keys that match the resource metadata. It supports three operators:
|
||||
|
||||
- The default value is a shorthand for exact match, or "$eq", below. For example, `{ owner: userId}` will include only resources with metadata containing `{ owner: userId }`
|
||||
- `$eq`: Exact match (e.g., `{ owner: { $eq: userId } }`) - this is equivalent to the shorthand above, `{ owner: userId }`
|
||||
- `$contains`: List membership (e.g., `{ allowedUsers: { $contains: userId} }`) The value here must be an element of the list. The metadata in the stored resource must be a list/container type.
|
||||
|
||||
An object with multiple keys is treated using a logical `AND` filter. For example, `{ owner: orgId, allowedUsers: { $contains: userId} }` will only match resources with metadata whose "owner" is `orgId` and whose "allowedUsers" list contains `userId`.
|
||||
See the reference [here](../cloud/reference/sdk/typescript_sdk_ref.md#auth.types.FilterType) for more information.
|
||||
:::
|
||||
|
||||
## Common Access Patterns
|
||||
|
||||
@@ -364,6 +581,8 @@ Here are some typical authorization patterns:
|
||||
|
||||
This common pattern lets you scope all threads, assistants, crons, and runs to a single user. It's useful for common single-user use cases like regular chatbot-style apps.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
@auth.on
|
||||
async def owner_only(ctx: Auth.types.AuthContext, value: dict):
|
||||
@@ -372,10 +591,33 @@ async def owner_only(ctx: Auth.types.AuthContext, value: dict):
|
||||
return {"owner": ctx.user.identity}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
export const auth = new Auth()
|
||||
.authenticate(async (request: Request) => ({
|
||||
identity: "user-123",
|
||||
permissions: ["threads:write", "threads:read"],
|
||||
}))
|
||||
.on("*", ({ value, user }) => {
|
||||
if ("metadata" in value) {
|
||||
value.metadata ??= {};
|
||||
value.metadata.owner = user.identity;
|
||||
}
|
||||
return { owner: user.identity };
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Permission-based Access
|
||||
|
||||
This pattern lets you control access based on **permissions**. It's useful if you want certain roles to have broader or more restricted access to resources.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
# In your auth handler:
|
||||
@auth.authenticate
|
||||
@@ -412,19 +654,72 @@ async def rbac_create(ctx: Auth.types.AuthContext, value: dict):
|
||||
return _default(ctx, value)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { Auth, HTTPException } from "@langchain/langgraph-sdk/auth";
|
||||
|
||||
export const auth = new Auth()
|
||||
.authenticate(async (request: Request) => ({
|
||||
identity: "user-123",
|
||||
// Define permissions in auth
|
||||
permissions: ["threads:write", "threads:read"],
|
||||
}))
|
||||
.on("threads:create", ({ value, user, permissions }) => {
|
||||
if (!permissions.includes("threads:write")) {
|
||||
throw new HTTPException(403, { message: "Unauthorized" });
|
||||
}
|
||||
|
||||
if ("metadata" in value) {
|
||||
value.metadata ??= {};
|
||||
value.metadata.owner = user.identity;
|
||||
}
|
||||
return { owner: user.identity };
|
||||
})
|
||||
.on("threads:read", ({ user, permissions }) => {
|
||||
if (
|
||||
!permissions.includes("threads:read") &&
|
||||
!permissions.includes("threads:write")
|
||||
) {
|
||||
throw new HTTPException(403, { message: "Unauthorized" });
|
||||
}
|
||||
|
||||
return { owner: user.identity };
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Supported Resources
|
||||
|
||||
LangGraph provides three levels of authorization handlers, from most general to most specific:
|
||||
|
||||
:::python
|
||||
|
||||
1. **Global Handler** (`@auth.on`): Matches all resources and actions
|
||||
2. **Resource Handler** (e.g., `@auth.on.threads`, `@auth.on.assistants`, `@auth.on.crons`): Matches all actions for a specific resource
|
||||
3. **Action Handler** (e.g., `@auth.on.threads.create`, `@auth.on.threads.read`): Matches a specific action on a specific resource
|
||||
|
||||
The most specific matching handler will be used. For example, `@auth.on.threads.create` takes precedence over `@auth.on.threads` for thread creation.
|
||||
If a more specific handler is registered, the more general handler will not be called for that resource and action.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
1. **Global Handler** (`on("*")`): Matches all resources and actions
|
||||
2. **Resource Handler** (e.g., `on("threads")`, `on("assistants")`, `on("crons")`): Matches all actions for a specific resource
|
||||
3. **Action Handler** (e.g., `on("threads:create")`, `on("threads:read")`): Matches a specific action on a specific resource
|
||||
|
||||
The most specific matching handler will be used. For example, `on("threads:create")` takes precedence over `on("threads")` for thread creation.
|
||||
If a more specific handler is registered, the more general handler will not be called for that resource and action.
|
||||
:::
|
||||
|
||||
:::python
|
||||
???+ tip "Type Safety"
|
||||
Each handler has type hints available for its `value` parameter at `Auth.types.on.<resource>.<action>.value`. For example:
|
||||
Each handler has type hints available for its `value` parameter. For example:
|
||||
|
||||
```python
|
||||
@auth.on.threads.create
|
||||
async def on_thread_create(
|
||||
@@ -432,14 +727,14 @@ If a more specific handler is registered, the more general handler will not be c
|
||||
value: Auth.types.on.threads.create.value # Specific type for thread creation
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
@auth.on.threads
|
||||
async def on_threads(
|
||||
ctx: Auth.types.AuthContext,
|
||||
value: Auth.types.on.threads.value # Union type of all thread actions
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
@auth.on
|
||||
async def on_all(
|
||||
ctx: Auth.types.AuthContext,
|
||||
@@ -447,11 +742,16 @@ If a more specific handler is registered, the more general handler will not be c
|
||||
):
|
||||
...
|
||||
```
|
||||
|
||||
More specific handlers provide better type hints since they handle fewer action types.
|
||||
|
||||
:::
|
||||
|
||||
#### Supported actions and types {#supported-actions}
|
||||
|
||||
Here are all the supported action handlers:
|
||||
|
||||
:::python
|
||||
| Resource | Handler | Description | Value Type |
|
||||
|----------|---------|-------------|------------|
|
||||
| **Threads** | `@auth.on.threads.create` | Thread creation | [`ThreadsCreate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.ThreadsCreate) |
|
||||
@@ -470,12 +770,40 @@ Here are all the supported action handlers:
|
||||
| | `@auth.on.crons.update` | Cron job updates | [`CronsUpdate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.CronsUpdate) |
|
||||
| | `@auth.on.crons.delete` | Cron job deletion | [`CronsDelete`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.CronsDelete) |
|
||||
| | `@auth.on.crons.search` | Listing cron jobs | [`CronsSearch`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.CronsSearch) |
|
||||
:::
|
||||
|
||||
:::js
|
||||
| Resource | Event | Description | Value Type |
|
||||
| -------------- | -------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| **Threads** | `threads:create` | Thread creation | [`ThreadsCreate`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#threadscreate) |
|
||||
| | `threads:read` | Thread retrieval | [`ThreadsRead`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#threadsread) |
|
||||
| | `threads:update` | Thread updates | [`ThreadsUpdate`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#threadsupdate) |
|
||||
| | `threads:delete` | Thread deletion | [`ThreadsDelete`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#threadsdelete) |
|
||||
| | `threads:search` | Listing threads | [`ThreadsSearch`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#threadssearch) |
|
||||
| | `threads:create_run` | Creating or updating a run | [`RunsCreate`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#threadscreate_run) |
|
||||
| **Assistants** | `assistants:create` | Assistant creation | [`AssistantsCreate`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#assistantscreate) |
|
||||
| | `assistants:read` | Assistant retrieval | [`AssistantsRead`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#assistantsread) |
|
||||
| | `assistants:update` | Assistant updates | [`AssistantsUpdate`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#assistantsupdate) |
|
||||
| | `assistants:delete` | Assistant deletion | [`AssistantsDelete`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#assistantsdelete) |
|
||||
| | `assistants:search` | Listing assistants | [`AssistantsSearch`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#assistantssearch) |
|
||||
| **Crons** | `crons:create` | Cron job creation | [`CronsCreate`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#cronscreate) |
|
||||
| | `crons:read` | Cron job retrieval | [`CronsRead`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#cronsread) |
|
||||
| | `crons:update` | Cron job updates | [`CronsUpdate`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#cronsupdate) |
|
||||
| | `crons:delete` | Cron job deletion | [`CronsDelete`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#cronsdelete) |
|
||||
| | `crons:search` | Listing cron jobs | [`CronsSearch`](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#cronssearch) |
|
||||
:::
|
||||
|
||||
???+ note "About Runs"
|
||||
|
||||
Runs are scoped to their parent thread for access control. This means permissions are typically inherited from the thread, reflecting the conversational nature of the data model. All run operations (reading, listing) except creation are controlled by the thread's handlers.
|
||||
There is a specific `create_run` handler for creating new runs because it had more arguments that you can view in the handler.
|
||||
|
||||
:::python
|
||||
There is a specific `create_run` handler for creating new runs because it had more arguments that you can view in the handler.
|
||||
:::
|
||||
|
||||
:::js
|
||||
There is a specific `threads:create_run` handler for creating new runs because it had more arguments that you can view in the handler.
|
||||
:::
|
||||
|
||||
## Next Steps
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ search:
|
||||
|
||||
# Durable Execution
|
||||
|
||||
**Durable execution** is a technique in which a process or workflow saves its progress at key points, allowing it to pause and later resume exactly where it left off. This is particularly useful in scenarios that require [human-in-the-loop](./human_in_the_loop.md), where users can inspect, validate, or modify the process before continuing, and in long-running tasks that might encounter interruptions or errors (e.g., calls to an LLM timing out). By preserving completed work, durable execution enables a process to resume without reprocessing previous steps -- even after a significant delay (e.g., a week later).
|
||||
**Durable execution** is a technique in which a process or workflow saves its progress at key points, allowing it to pause and later resume exactly where it left off. This is particularly useful in scenarios that require [human-in-the-loop](./human_in_the_loop.md), where users can inspect, validate, or modify the process before continuing, and in long-running tasks that might encounter interruptions or errors (e.g., calls to an LLM timing out). By preserving completed work, durable execution enables a process to resume without reprocessing previous steps -- even after a significant delay (e.g., a week later).
|
||||
|
||||
LangGraph's built-in [persistence](./persistence.md) layer provides durable execution for workflows, ensuring that the state of each execution step is saved to a durable store. This capability guarantees that if a workflow is interrupted -- whether by a system failure or for [human-in-the-loop](./human_in_the_loop.md) interactions -- it can be resumed from its last recorded state.
|
||||
|
||||
@@ -20,7 +20,14 @@ To leverage durable execution in LangGraph, you need to:
|
||||
|
||||
1. Enable [persistence](./persistence.md) in your workflow by specifying a [checkpointer](./persistence.md#checkpointer-libraries) that will save workflow progress.
|
||||
2. Specify a [thread identifier](./persistence.md#threads) when executing a workflow. This will track the execution history for a particular instance of the workflow.
|
||||
3. Wrap any non-deterministic operations (e.g., random number generation) or operations with side effects (e.g., file writes, API calls) inside [tasks][langgraph.func.task] to ensure that when a workflow is resumed, these operations are not repeated for the particular run, and instead their results are retrieved from the persistence layer. For more information, see [Determinism and Consistent Replay](#determinism-and-consistent-replay).
|
||||
|
||||
:::python
|
||||
3. Wrap any non-deterministic operations (e.g., random number generation) or operations with side effects (e.g., file writes, API calls) inside @[tasks][task] to ensure that when a workflow is resumed, these operations are not repeated for the particular run, and instead their results are retrieved from the persistence layer. For more information, see [Determinism and Consistent Replay](#determinism-and-consistent-replay).
|
||||
:::
|
||||
|
||||
:::js
|
||||
3. Wrap any non-deterministic operations (e.g., random number generation) or operations with side effects (e.g., file writes, API calls) inside @[tasks][task] to ensure that when a workflow is resumed, these operations are not repeated for the particular run, and instead their results are retrieved from the persistence layer. For more information, see [Determinism and Consistent Replay](#determinism-and-consistent-replay).
|
||||
:::
|
||||
|
||||
## Determinism and Consistent Replay
|
||||
|
||||
@@ -30,17 +37,25 @@ As a result, when you are writing a workflow for durable execution, you must wra
|
||||
|
||||
To ensure that your workflow is deterministic and can be consistently replayed, follow these guidelines:
|
||||
|
||||
- **Avoid Repeating Work**: If a [node](./low_level.md#nodes) contains multiple operations with side effects (e.g., logging, file writes, or network calls), wrap each operation in a separate **task**. This ensures that when the workflow is resumed, the operations are not repeated, and their results are retrieved from the persistence layer.
|
||||
- **Encapsulate Non-Deterministic Operations:** Wrap any code that might yield non-deterministic results (e.g., random number generation) inside **tasks** or **nodes**. This ensures that, upon resumption, the workflow follows the exact recorded sequence of steps with the same outcomes.
|
||||
- **Avoid Repeating Work**: If a [node](./low_level.md#nodes) contains multiple operations with side effects (e.g., logging, file writes, or network calls), wrap each operation in a separate **task**. This ensures that when the workflow is resumed, the operations are not repeated, and their results are retrieved from the persistence layer.
|
||||
- **Encapsulate Non-Deterministic Operations:** Wrap any code that might yield non-deterministic results (e.g., random number generation) inside **tasks** or **nodes**. This ensures that, upon resumption, the workflow follows the exact recorded sequence of steps with the same outcomes.
|
||||
- **Use Idempotent Operations**: When possible ensure that side effects (e.g., API calls, file writes) are idempotent. This means that if an operation is retried after a failure in the workflow, it will have the same effect as the first time it was executed. This is particularly important for operations that result in data writes. In the event that a **task** starts but fails to complete successfully, the workflow's resumption will re-run the **task**, relying on recorded outcomes to maintain consistency. Use idempotency keys or verify existing results to avoid unintended duplication, ensuring a smooth and predictable workflow execution.
|
||||
|
||||
:::python
|
||||
For some examples of pitfalls to avoid, see the [Common Pitfalls](./functional_api.md#common-pitfalls) section in the functional API, which shows
|
||||
how to structure your code using **tasks** to avoid these issues. The same principles apply to the [StateGraph (Graph API)][langgraph.graph.state.StateGraph].
|
||||
how to structure your code using **tasks** to avoid these issues. The same principles apply to the @[StateGraph (Graph API)][StateGraph].
|
||||
:::
|
||||
|
||||
:::js
|
||||
For some examples of pitfalls to avoid, see the [Common Pitfalls](./functional_api.md#common-pitfalls) section in the functional API, which shows
|
||||
how to structure your code using **tasks** to avoid these issues. The same principles apply to the @[StateGraph (Graph API)][StateGraph].
|
||||
:::
|
||||
|
||||
## Using tasks in nodes
|
||||
|
||||
If a [node](./low_level.md#nodes) contains multiple operations, you may find it easier to convert each operation into a **task** rather than refactor the operations into individual nodes.
|
||||
|
||||
:::python
|
||||
=== "Original"
|
||||
|
||||
```python
|
||||
@@ -142,16 +157,136 @@ If a [node](./low_level.md#nodes) contains multiple operations, you may find it
|
||||
graph.invoke({"urls": ["https://www.example.com"]}, config)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
=== "Original"
|
||||
|
||||
```typescript
|
||||
import { StateGraph, START, END } from "@langchain/langgraph";
|
||||
import { MemorySaver } from "@langchain/langgraph";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { z } from "zod";
|
||||
|
||||
// Define a Zod schema to represent the state
|
||||
const State = z.object({
|
||||
url: z.string(),
|
||||
result: z.string().optional(),
|
||||
});
|
||||
|
||||
const callApi = async (state: z.infer<typeof State>) => {
|
||||
// highlight-next-line
|
||||
const response = await fetch(state.url);
|
||||
const text = await response.text();
|
||||
const result = text.slice(0, 100); // Side-effect
|
||||
return {
|
||||
result,
|
||||
};
|
||||
};
|
||||
|
||||
// Create a StateGraph builder and add a node for the callApi function
|
||||
const builder = new StateGraph(State)
|
||||
.addNode("callApi", callApi)
|
||||
.addEdge(START, "callApi")
|
||||
.addEdge("callApi", END);
|
||||
|
||||
// Specify a checkpointer
|
||||
const checkpointer = new MemorySaver();
|
||||
|
||||
// Compile the graph with the checkpointer
|
||||
const graph = builder.compile({ checkpointer });
|
||||
|
||||
// Define a config with a thread ID.
|
||||
const threadId = uuidv4();
|
||||
const config = { configurable: { thread_id: threadId } };
|
||||
|
||||
// Invoke the graph
|
||||
await graph.invoke({ url: "https://www.example.com" }, config);
|
||||
```
|
||||
|
||||
=== "With task"
|
||||
|
||||
```typescript
|
||||
import { StateGraph, START, END } from "@langchain/langgraph";
|
||||
import { MemorySaver } from "@langchain/langgraph";
|
||||
import { task } from "@langchain/langgraph";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { z } from "zod";
|
||||
|
||||
// Define a Zod schema to represent the state
|
||||
const State = z.object({
|
||||
urls: z.array(z.string()),
|
||||
results: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
const makeRequest = task("makeRequest", async (url: string) => {
|
||||
// highlight-next-line
|
||||
const response = await fetch(url);
|
||||
const text = await response.text();
|
||||
return text.slice(0, 100);
|
||||
});
|
||||
|
||||
const callApi = async (state: z.infer<typeof State>) => {
|
||||
// highlight-next-line
|
||||
const requests = state.urls.map((url) => makeRequest(url));
|
||||
const results = await Promise.all(requests);
|
||||
return {
|
||||
results,
|
||||
};
|
||||
};
|
||||
|
||||
// Create a StateGraph builder and add a node for the callApi function
|
||||
const builder = new StateGraph(State)
|
||||
.addNode("callApi", callApi)
|
||||
.addEdge(START, "callApi")
|
||||
.addEdge("callApi", END);
|
||||
|
||||
// Specify a checkpointer
|
||||
const checkpointer = new MemorySaver();
|
||||
|
||||
// Compile the graph with the checkpointer
|
||||
const graph = builder.compile({ checkpointer });
|
||||
|
||||
// Define a config with a thread ID.
|
||||
const threadId = uuidv4();
|
||||
const config = { configurable: { thread_id: threadId } };
|
||||
|
||||
// Invoke the graph
|
||||
await graph.invoke({ urls: ["https://www.example.com"] }, config);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Resuming Workflows
|
||||
|
||||
Once you have enabled durable execution in your workflow, you can resume execution for the following scenarios:
|
||||
|
||||
- **Pausing and Resuming Workflows:** Use the [interrupt][langgraph.types.interrupt] function to pause a workflow at specific points and the [Command][langgraph.types.Command] primitive to resume it with updated state. See [**Human-in-the-Loop**](./human_in_the_loop.md) for more details.
|
||||
:::python
|
||||
|
||||
- **Pausing and Resuming Workflows:** Use the @[interrupt][interrupt] function to pause a workflow at specific points and the @[Command] primitive to resume it with updated state. See [**Human-in-the-Loop**](./human_in_the_loop.md) for more details.
|
||||
- **Recovering from Failures:** Automatically resume workflows from the last successful checkpoint after an exception (e.g., LLM provider outage). This involves executing the workflow with the same thread identifier by providing it with a `None` as the input value (see this [example](../how-tos/use-functional-api.md#resuming-after-an-error) with the functional API).
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
- **Pausing and Resuming Workflows:** Use the @[interrupt][interrupt] function to pause a workflow at specific points and the @[Command] primitive to resume it with updated state. See [**Human-in-the-Loop**](./human_in_the_loop.md) for more details.
|
||||
- **Recovering from Failures:** Automatically resume workflows from the last successful checkpoint after an exception (e.g., LLM provider outage). This involves executing the workflow with the same thread identifier by providing it with a `null` as the input value (see this [example](../how-tos/use-functional-api.md#resuming-after-an-error) with the functional API).
|
||||
:::
|
||||
|
||||
## Starting Points for Resuming Workflows
|
||||
|
||||
* If you're using a [StateGraph (Graph API)][langgraph.graph.state.StateGraph], the starting point is the beginning of the [**node**](./low_level.md#nodes) where execution stopped.
|
||||
* If you're making a subgraph call inside a node, the starting point will be the **parent** node that called the subgraph that was halted.
|
||||
Inside the subgraph, the starting point will be the specific [**node**](./low_level.md#nodes) where execution stopped.
|
||||
* If you're using the Functional API, the starting point is the beginning of the [**entrypoint**](./functional_api.md#entrypoint) where execution stopped.
|
||||
:::python
|
||||
|
||||
- If you're using a @[StateGraph (Graph API)][StateGraph], the starting point is the beginning of the [**node**](./low_level.md#nodes) where execution stopped.
|
||||
- If you're making a subgraph call inside a node, the starting point will be the **parent** node that called the subgraph that was halted.
|
||||
Inside the subgraph, the starting point will be the specific [**node**](./low_level.md#nodes) where execution stopped.
|
||||
- If you're using the Functional API, the starting point is the beginning of the [**entrypoint**](./functional_api.md#entrypoint) where execution stopped.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
- If you're using a [StateGraph (Graph API)](./low_level.md), the starting point is the beginning of the [**node**](./low_level.md#nodes) where execution stopped.
|
||||
- If you're making a subgraph call inside a node, the starting point will be the **parent** node that called the subgraph that was halted.
|
||||
Inside the subgraph, the starting point will be the specific [**node**](./low_level.md#nodes) where execution stopped.
|
||||
- If you're using the Functional API, the starting point is the beginning of the [**entrypoint**](./functional_api.md#entrypoint) where execution stopped.
|
||||
:::
|
||||
|
||||
@@ -13,7 +13,7 @@ No. LangGraph is an orchestration framework for complex agentic systems and is m
|
||||
|
||||
## How is LangGraph different from other agent frameworks?
|
||||
|
||||
Other agentic frameworks can work for simple, generic tasks but fall short for complex tasks bespoke to a company’s needs. LangGraph provides a more expressive framework to handle companies’ unique tasks without restricting users to a single black-box cognitive architecture.
|
||||
Other agentic frameworks can work for simple, generic tasks but fall short for complex tasks. LangGraph provides a more expressive framework to handle your unique tasks without restricting you to a single black-box cognitive architecture.
|
||||
|
||||
## Does LangGraph impact the performance of my app?
|
||||
|
||||
@@ -28,14 +28,14 @@ Yes. LangGraph is an MIT-licensed open-source library and is free to use.
|
||||
LangGraph is a stateful, orchestration framework that brings added control to agent workflows. LangGraph Platform is a service for deploying and scaling LangGraph applications, with an opinionated API for building agent UXs, plus an integrated developer studio.
|
||||
|
||||
| Features | LangGraph (open source) | LangGraph Platform |
|
||||
|---------------------|-----------------------------------------------------------|--------------------------------------------------------------------------------------------------------|
|
||||
| ------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
|
||||
| Description | Stateful orchestration framework for agentic applications | Scalable infrastructure for deploying LangGraph applications |
|
||||
| SDKs | Python and JavaScript | Python and JavaScript |
|
||||
| HTTP APIs | None | Yes - useful for retrieving & updating state or long-term memory, or creating a configurable assistant |
|
||||
| Streaming | Basic | Dedicated mode for token-by-token messages |
|
||||
| Checkpointer | Community contributed | Supported out-of-the-box |
|
||||
| Persistence Layer | Self-managed | Managed Postgres with efficient storage |
|
||||
| Deployment | Self-managed | • Cloud SaaS <br> • Free self-hosted <br> • Enterprise (paid self-hosted) |
|
||||
| Deployment | Self-managed | • Cloud SaaS <br> • Free self-hosted <br> • Enterprise (paid self-hosted) |
|
||||
| Scalability | Self-managed | Auto-scaling of task queues and servers |
|
||||
| Fault-tolerance | Self-managed | Automated retries |
|
||||
| Concurrency Control | Simple threading | Supports double-texting |
|
||||
@@ -67,4 +67,4 @@ If you set an environment variable of `LANGSMITH_TRACING=false`, then no traces
|
||||
|
||||
## What does "nodes executed" mean for LangGraph Platform usage?
|
||||
|
||||
**Nodes Executed** is the aggregate number of nodes in a LangGraph application that are called and completed successfully during an invocation of the application. If a node in the graph is not called during execution or ends in an error state, these nodes will not be counted. If a node is called and completes successfully multiple times, each occurrence will be counted.
|
||||
**Nodes Executed** is the aggregate number of nodes in a LangGraph application that are called and completed successfully during an invocation of the application. If a node in the graph is not called during execution or ends in an error state, these nodes will not be counted. If a node is called and completes successfully multiple times, each occurrence will be counted.
|
||||
|
||||
@@ -9,12 +9,21 @@ search:
|
||||
|
||||
The **Functional API** allows you to add LangGraph's key features — [persistence](./persistence.md), [memory](../how-tos/memory/add-memory.md), [human-in-the-loop](./human_in_the_loop.md), and [streaming](./streaming.md) — to your applications with minimal changes to your existing code.
|
||||
|
||||
It is designed to integrate these features into existing code that may use standard language primitives for branching and control flow, such as `if` statements, `for` loops, and function calls. Unlike many data orchestration frameworks that require restructuring code into an explicit pipeline or DAG, the Functional API allows you to incorporate these capabilities without enforcing a rigid execution model.
|
||||
It is designed to integrate these features into existing code that may use standard language primitives for branching and control flow, such as `if` statements, `for` loops, and function calls. Unlike many data orchestration frameworks that require restructuring code into an explicit pipeline or DAG, the Functional API allows you to incorporate these capabilities without enforcing a rigid execution model.
|
||||
|
||||
The Functional API uses two key building blocks:
|
||||
The Functional API uses two key building blocks:
|
||||
|
||||
- **`@entrypoint`** – Marks a function as the starting point of a workflow, encapsulating logic and managing execution flow, including handling long-running tasks and interrupts.
|
||||
:::python
|
||||
|
||||
- **`@entrypoint`** – Marks a function as the starting point of a workflow, encapsulating logic and managing execution flow, including handling long-running tasks and interrupts.
|
||||
- **`@task`** – Represents a discrete unit of work, such as an API call or data processing step, that can be executed asynchronously within an entrypoint. Tasks return a future-like object that can be awaited or resolved synchronously.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
- **`entrypoint`** – An entrypoint encapsulates workflow logic and manages execution flow, including handling long-running tasks and interrupts.
|
||||
- **`task`** – Represents a discrete unit of work, such as an API call or data processing step, that can be executed asynchronously within an entrypoint. Tasks return a future-like object that can be awaited or resolved synchronously.
|
||||
:::
|
||||
|
||||
This provides a minimal abstraction for building workflows with state management and streaming.
|
||||
|
||||
@@ -33,17 +42,17 @@ Here are some key differences:
|
||||
- **Checkpointing**: Both APIs generate and use checkpoints. In the **Graph API** a new checkpoint is generated after every [superstep](./low_level.md). In the **Functional API**, when tasks are executed, their results are saved to an existing checkpoint associated with the given entrypoint instead of creating a new checkpoint.
|
||||
- **Visualization**: The Graph API makes it easy to visualize the workflow as a graph which can be useful for debugging, understanding the workflow, and sharing with others. The Functional API does not support visualization as the graph is dynamically generated during runtime.
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
Below we demonstrate a simple application that writes an essay and [interrupts](human_in_the_loop.md) to request human review.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.func import entrypoint, task
|
||||
from langgraph.types import interrupt
|
||||
|
||||
|
||||
@task
|
||||
def write_essay(topic: str) -> str:
|
||||
"""Write an essay about the given topic."""
|
||||
@@ -70,12 +79,50 @@ def workflow(topic: str) -> dict:
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { MemorySaver, entrypoint, task, interrupt } from "@langchain/langgraph";
|
||||
|
||||
const writeEssay = task("writeEssay", async (topic: string) => {
|
||||
// A placeholder for a long-running task.
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
return `An essay about topic: ${topic}`;
|
||||
});
|
||||
|
||||
const workflow = entrypoint(
|
||||
{ checkpointer: new MemorySaver(), name: "workflow" },
|
||||
async (topic: string) => {
|
||||
const essay = await writeEssay(topic);
|
||||
const isApproved = interrupt({
|
||||
// Any json-serializable payload provided to interrupt as argument.
|
||||
// It will be surfaced on the client side as an Interrupt when streaming data
|
||||
// from the workflow.
|
||||
essay, // The essay we want reviewed.
|
||||
// We can add any additional information that we need.
|
||||
// For example, introduce a key called "action" with some instructions.
|
||||
action: "Please approve/reject the essay",
|
||||
});
|
||||
|
||||
return {
|
||||
essay, // The essay that was generated
|
||||
isApproved, // Response from HIL
|
||||
};
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
??? example "Detailed Explanation"
|
||||
|
||||
This workflow will write an essay about the topic "cat" and then pause to get a review from a human. The workflow can be interrupted for an indefinite amount of time until a review is provided.
|
||||
|
||||
When the workflow is resumed, it executes from the very start, but because the result of the `write_essay` task was already saved, the task result will be loaded from the checkpoint instead of being recomputed.
|
||||
When the workflow is resumed, it executes from the very start, but because the result of the `writeEssay` task was already saved, the task result will be loaded from the checkpoint instead of being recomputed.
|
||||
|
||||
:::python
|
||||
```python
|
||||
import time
|
||||
import uuid
|
||||
@@ -147,18 +194,104 @@ def workflow(topic: str) -> dict:
|
||||
```
|
||||
|
||||
The workflow has been completed and the review has been added to the essay.
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { MemorySaver, entrypoint, task, interrupt } from "@langchain/langgraph";
|
||||
|
||||
const writeEssay = task("writeEssay", async (topic: string) => {
|
||||
// This is a placeholder for a long-running task.
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
return `An essay about topic: ${topic}`;
|
||||
});
|
||||
|
||||
const workflow = entrypoint(
|
||||
{ checkpointer: new MemorySaver(), name: "workflow" },
|
||||
async (topic: string) => {
|
||||
const essay = await writeEssay(topic);
|
||||
const isApproved = interrupt({
|
||||
// Any json-serializable payload provided to interrupt as argument.
|
||||
// It will be surfaced on the client side as an Interrupt when streaming data
|
||||
// from the workflow.
|
||||
essay, // The essay we want reviewed.
|
||||
// We can add any additional information that we need.
|
||||
// For example, introduce a key called "action" with some instructions.
|
||||
action: "Please approve/reject the essay",
|
||||
});
|
||||
|
||||
return {
|
||||
essay, // The essay that was generated
|
||||
isApproved, // Response from HIL
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const threadId = uuidv4();
|
||||
|
||||
const config = {
|
||||
configurable: {
|
||||
thread_id: threadId
|
||||
}
|
||||
};
|
||||
|
||||
for await (const item of workflow.stream("cat", config)) {
|
||||
console.log(item);
|
||||
}
|
||||
```
|
||||
|
||||
```console
|
||||
{ writeEssay: 'An essay about topic: cat' }
|
||||
{
|
||||
__interrupt__: [{
|
||||
value: { essay: 'An essay about topic: cat', action: 'Please approve/reject the essay' },
|
||||
resumable: true,
|
||||
ns: ['workflow:f7b8508b-21c0-8b4c-5958-4e8de74d2684'],
|
||||
when: 'during'
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
An essay has been written and is ready for review. Once the review is provided, we can resume the workflow:
|
||||
|
||||
```typescript
|
||||
import { Command } from "@langchain/langgraph";
|
||||
|
||||
// Get review from a user (e.g., via a UI)
|
||||
// In this case, we're using a bool, but this can be any json-serializable value.
|
||||
const humanReview = true;
|
||||
|
||||
for await (const item of workflow.stream(new Command({ resume: humanReview }), config)) {
|
||||
console.log(item);
|
||||
}
|
||||
```
|
||||
|
||||
```console
|
||||
{ workflow: { essay: 'An essay about topic: cat', isApproved: true } }
|
||||
```
|
||||
|
||||
The workflow has been completed and the review has been added to the essay.
|
||||
:::
|
||||
|
||||
## Entrypoint
|
||||
|
||||
The [`@entrypoint`][langgraph.func.entrypoint] decorator can be used to create a workflow from a function. It encapsulates workflow logic and manages execution flow, including handling *long-running tasks* and [interrupts](./human_in_the_loop.md).
|
||||
:::python
|
||||
The @[`@entrypoint`][entrypoint] decorator can be used to create a workflow from a function. It encapsulates workflow logic and manages execution flow, including handling _long-running tasks_ and [interrupts](./human_in_the_loop.md).
|
||||
:::
|
||||
|
||||
:::js
|
||||
The @[`entrypoint`][entrypoint] function can be used to create a workflow from a function. It encapsulates workflow logic and manages execution flow, including handling _long-running tasks_ and [interrupts](./human_in_the_loop.md).
|
||||
:::
|
||||
|
||||
### Definition
|
||||
|
||||
An **entrypoint** is defined by decorating a function with the `@entrypoint` decorator.
|
||||
:::python
|
||||
An **entrypoint** is defined by decorating a function with the `@entrypoint` decorator.
|
||||
|
||||
The function **must accept a single positional argument**, which serves as the workflow input. If you need to pass multiple pieces of data, use a dictionary as the input type for the first argument.
|
||||
|
||||
Decorating a function with an `entrypoint` produces a [`Pregel`][langgraph.pregel.Pregel.stream] instance which helps to manage the execution of the workflow (e.g., handles streaming, resumption, and checkpointing).
|
||||
Decorating a function with an `entrypoint` produces a @[`Pregel`][Pregel.stream] instance which helps to manage the execution of the workflow (e.g., handles streaming, resumption, and checkpointing).
|
||||
|
||||
You will usually want to pass a **checkpointer** to the `@entrypoint` decorator to enable persistence and use features like **human-in-the-loop**.
|
||||
|
||||
@@ -185,22 +318,48 @@ You will usually want to pass a **checkpointer** to the `@entrypoint` decorator
|
||||
# some logic that may involve long-running tasks like API calls,
|
||||
# and may be interrupted for human-in-the-loop
|
||||
...
|
||||
return result
|
||||
return result
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
An **entrypoint** is defined by calling the `entrypoint` function with configuration and a function.
|
||||
|
||||
The function **must accept a single positional argument**, which serves as the workflow input. If you need to pass multiple pieces of data, use an object as the input type for the first argument.
|
||||
|
||||
Creating an entrypoint with a function produces a workflow instance which helps to manage the execution of the workflow (e.g., handles streaming, resumption, and checkpointing).
|
||||
|
||||
You will often want to pass a **checkpointer** to the `entrypoint` function to enable persistence and use features like **human-in-the-loop**.
|
||||
|
||||
```typescript
|
||||
import { entrypoint } from "@langchain/langgraph";
|
||||
|
||||
const myWorkflow = entrypoint(
|
||||
{ checkpointer, name: "workflow" },
|
||||
async (someInput: Record<string, any>): Promise<number> => {
|
||||
// some logic that may involve long-running tasks like API calls,
|
||||
// and may be interrupted for human-in-the-loop
|
||||
return result;
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
!!! important "Serialization"
|
||||
|
||||
The **inputs** and **outputs** of entrypoints must be JSON-serializable to support checkpointing. Please see the [serialization](#serialization) section for more details.
|
||||
|
||||
:::python
|
||||
|
||||
### Injectable parameters
|
||||
|
||||
When declaring an `entrypoint`, you can request access to additional parameters that will be injected automatically at run time. These parameters include:
|
||||
|
||||
|
||||
| Parameter | Description |
|
||||
|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **previous** | Access the state associated with the previous `checkpoint` for the given thread. See [short-term-memory](#short-term-memory). |
|
||||
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **previous** | Access the state associated with the previous `checkpoint` for the given thread. See [short-term-memory](#short-term-memory). |
|
||||
| **store** | An instance of [BaseStore][langgraph.store.base.BaseStore]. Useful for [long-term memory](../how-tos/use-functional-api.md#long-term-memory). |
|
||||
| **writer** | Use to access the StreamWriter when working with Async Python < 3.11. See [streaming with functional API for details](../how-tos/use-functional-api.md#streaming). |
|
||||
| **config** | For accessing run time configuration. See [RunnableConfig](https://python.langchain.com/docs/concepts/runnables/#runnableconfig) for information. |
|
||||
@@ -222,7 +381,7 @@ When declaring an `entrypoint`, you can request access to additional parameters
|
||||
@entrypoint(
|
||||
checkpointer=checkpointer, # Specify the checkpointer
|
||||
store=in_memory_store # Specify the store
|
||||
)
|
||||
)
|
||||
def my_workflow(
|
||||
some_input: dict, # The input (e.g., passed via `invoke`)
|
||||
*,
|
||||
@@ -233,9 +392,12 @@ When declaring an `entrypoint`, you can request access to additional parameters
|
||||
) -> ...:
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Executing
|
||||
|
||||
Using the [`@entrypoint`](#entrypoint) yields a [`Pregel`][langgraph.pregel.Pregel.stream] object that can be executed using the `invoke`, `ainvoke`, `stream`, and `astream` methods.
|
||||
:::python
|
||||
Using the [`@entrypoint`](#entrypoint) yields a @[`Pregel`][Pregel.stream] object that can be executed using the `invoke`, `ainvoke`, `stream`, and `astream` methods.
|
||||
|
||||
=== "Invoke"
|
||||
|
||||
@@ -260,7 +422,7 @@ Using the [`@entrypoint`](#entrypoint) yields a [`Pregel`][langgraph.pregel.Preg
|
||||
```
|
||||
|
||||
=== "Stream"
|
||||
|
||||
|
||||
```python
|
||||
config = {
|
||||
"configurable": {
|
||||
@@ -285,9 +447,42 @@ Using the [`@entrypoint`](#entrypoint) yields a [`Pregel`][langgraph.pregel.Preg
|
||||
print(chunk)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
Using the [`entrypoint`](#entrypoint) function will return an object that can be executed using the `invoke` and `stream` methods.
|
||||
|
||||
=== "Invoke"
|
||||
|
||||
```typescript
|
||||
const config = {
|
||||
configurable: {
|
||||
thread_id: "some_thread_id"
|
||||
}
|
||||
};
|
||||
await myWorkflow.invoke(someInput, config); // Wait for the result
|
||||
```
|
||||
|
||||
=== "Stream"
|
||||
|
||||
```typescript
|
||||
const config = {
|
||||
configurable: {
|
||||
thread_id: "some_thread_id"
|
||||
}
|
||||
};
|
||||
|
||||
for await (const chunk of myWorkflow.stream(someInput, config)) {
|
||||
console.log(chunk);
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Resuming
|
||||
|
||||
Resuming an execution after an [interrupt][langgraph.types.interrupt] can be done by passing a **resume** value to the [Command][langgraph.types.Command] primitive.
|
||||
:::python
|
||||
Resuming an execution after an @[interrupt][interrupt] can be done by passing a **resume** value to the @[Command] primitive.
|
||||
|
||||
=== "Invoke"
|
||||
|
||||
@@ -299,7 +494,7 @@ Resuming an execution after an [interrupt][langgraph.types.interrupt] can be don
|
||||
"thread_id": "some_thread_id"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
my_workflow.invoke(Command(resume=some_resume_value), config)
|
||||
```
|
||||
|
||||
@@ -313,7 +508,7 @@ Resuming an execution after an [interrupt][langgraph.types.interrupt] can be don
|
||||
"thread_id": "some_thread_id"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
await my_workflow.ainvoke(Command(resume=some_resume_value), config)
|
||||
```
|
||||
|
||||
@@ -327,7 +522,7 @@ Resuming an execution after an [interrupt][langgraph.types.interrupt] can be don
|
||||
"thread_id": "some_thread_id"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for chunk in my_workflow.stream(Command(resume=some_resume_value), config):
|
||||
print(chunk)
|
||||
```
|
||||
@@ -347,8 +542,51 @@ Resuming an execution after an [interrupt][langgraph.types.interrupt] can be don
|
||||
print(chunk)
|
||||
```
|
||||
|
||||
**Resuming after an error**
|
||||
:::
|
||||
|
||||
:::js
|
||||
Resuming an execution after an @[interrupt][interrupt] can be done by passing a **resume** value to the @[`Command`][Command] primitive.
|
||||
|
||||
=== "Invoke"
|
||||
|
||||
```typescript
|
||||
import { Command } from "@langchain/langgraph";
|
||||
|
||||
const config = {
|
||||
configurable: {
|
||||
thread_id: "some_thread_id"
|
||||
}
|
||||
};
|
||||
|
||||
await myWorkflow.invoke(new Command({ resume: someResumeValue }), config);
|
||||
```
|
||||
|
||||
=== "Stream"
|
||||
|
||||
```typescript
|
||||
import { Command } from "@langchain/langgraph";
|
||||
|
||||
const config = {
|
||||
configurable: {
|
||||
thread_id: "some_thread_id"
|
||||
}
|
||||
};
|
||||
|
||||
const stream = await myWorkflow.stream(
|
||||
new Command({ resume: someResumableValue }),
|
||||
config,
|
||||
)
|
||||
|
||||
for await (const chunk of stream) {
|
||||
console.log(chunk);
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::python
|
||||
|
||||
**Resuming after an error**
|
||||
|
||||
To resume after an error, run the `entrypoint` with a `None` and the same **thread id** (config).
|
||||
|
||||
@@ -363,7 +601,7 @@ This assumes that the underlying **error** has been resolved and execution can p
|
||||
"thread_id": "some_thread_id"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
my_workflow.invoke(None, config)
|
||||
```
|
||||
|
||||
@@ -376,7 +614,7 @@ This assumes that the underlying **error** has been resolved and execution can p
|
||||
"thread_id": "some_thread_id"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
await my_workflow.ainvoke(None, config)
|
||||
```
|
||||
|
||||
@@ -389,7 +627,7 @@ This assumes that the underlying **error** has been resolved and execution can p
|
||||
"thread_id": "some_thread_id"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for chunk in my_workflow.stream(None, config):
|
||||
print(chunk)
|
||||
```
|
||||
@@ -408,10 +646,49 @@ This assumes that the underlying **error** has been resolved and execution can p
|
||||
print(chunk)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
**Resuming after an error**
|
||||
|
||||
To resume after an error, run the `entrypoint` with `null` and the same **thread id** (config).
|
||||
|
||||
This assumes that the underlying **error** has been resolved and execution can proceed successfully.
|
||||
|
||||
=== "Invoke"
|
||||
|
||||
```typescript
|
||||
const config = {
|
||||
configurable: {
|
||||
thread_id: "some_thread_id"
|
||||
}
|
||||
};
|
||||
|
||||
await myWorkflow.invoke(null, config);
|
||||
```
|
||||
|
||||
=== "Stream"
|
||||
|
||||
```typescript
|
||||
const config = {
|
||||
configurable: {
|
||||
thread_id: "some_thread_id"
|
||||
}
|
||||
};
|
||||
|
||||
for await (const chunk of myWorkflow.stream(null, config)) {
|
||||
console.log(chunk);
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Short-term memory
|
||||
|
||||
When an `entrypoint` is defined with a `checkpointer`, it stores information between successive invocations on the same **thread id** in [checkpoints](persistence.md#checkpoints).
|
||||
When an `entrypoint` is defined with a `checkpointer`, it stores information between successive invocations on the same **thread id** in [checkpoints](persistence.md#checkpoints).
|
||||
|
||||
:::python
|
||||
This allows accessing the state from the previous invocation using the `previous` parameter.
|
||||
|
||||
By default, the `previous` parameter is the return value of the previous invocation.
|
||||
@@ -432,9 +709,40 @@ my_workflow.invoke(1, config) # 1 (previous was None)
|
||||
my_workflow.invoke(2, config) # 3 (previous was 1 from the previous invocation)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
This allows accessing the state from the previous invocation using the `getPreviousState` function.
|
||||
|
||||
By default, the `getPreviousState` function returns the return value of the previous invocation.
|
||||
|
||||
```typescript
|
||||
import { entrypoint, getPreviousState } from "@langchain/langgraph";
|
||||
|
||||
const myWorkflow = entrypoint(
|
||||
{ checkpointer, name: "workflow" },
|
||||
async (number: number) => {
|
||||
const previous = getPreviousState<number>() ?? 0;
|
||||
return number + previous;
|
||||
}
|
||||
);
|
||||
|
||||
const config = {
|
||||
configurable: {
|
||||
thread_id: "some_thread_id",
|
||||
},
|
||||
};
|
||||
|
||||
await myWorkflow.invoke(1, config); // 1 (previous was undefined)
|
||||
await myWorkflow.invoke(2, config); // 3 (previous was 1 from the previous invocation)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
#### `entrypoint.final`
|
||||
|
||||
[entrypoint.final][langgraph.func.entrypoint.final] is a special primitive that can be returned from an entrypoint and allows **decoupling** the value that is **saved in the checkpoint** from the **return value of the entrypoint**.
|
||||
:::python
|
||||
@[`entrypoint.final`][entrypoint.final] is a special primitive that can be returned from an entrypoint and allows **decoupling** the value that is **saved in the checkpoint** from the **return value of the entrypoint**.
|
||||
|
||||
The first value is the return value of the entrypoint, and the second value is the value that will be saved in the checkpoint. The type annotation is `entrypoint.final[return_type, save_type]`.
|
||||
|
||||
@@ -443,7 +751,7 @@ The first value is the return value of the entrypoint, and the second value is t
|
||||
def my_workflow(number: int, *, previous: Any = None) -> entrypoint.final[int, int]:
|
||||
previous = previous or 0
|
||||
# This will return the previous value to the caller, saving
|
||||
# 2 * number to the checkpoint, which will be used in the next invocation
|
||||
# 2 * number to the checkpoint, which will be used in the next invocation
|
||||
# for the `previous` parameter.
|
||||
return entrypoint.final(value=previous, save=2 * number)
|
||||
|
||||
@@ -457,15 +765,52 @@ my_workflow.invoke(3, config) # 0 (previous was None)
|
||||
my_workflow.invoke(1, config) # 6 (previous was 3 * 2 from the previous invocation)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
@[`entrypoint.final`][entrypoint.final] is a special primitive that can be returned from an entrypoint and allows **decoupling** the value that is **saved in the checkpoint** from the **return value of the entrypoint**.
|
||||
|
||||
The first value is the return value of the entrypoint, and the second value is the value that will be saved in the checkpoint.
|
||||
|
||||
```typescript
|
||||
import { entrypoint, getPreviousState } from "@langchain/langgraph";
|
||||
|
||||
const myWorkflow = entrypoint(
|
||||
{ checkpointer, name: "workflow" },
|
||||
async (number: number) => {
|
||||
const previous = getPreviousState<number>() ?? 0;
|
||||
// This will return the previous value to the caller, saving
|
||||
// 2 * number to the checkpoint, which will be used in the next invocation
|
||||
// for the `previous` parameter.
|
||||
return entrypoint.final({
|
||||
value: previous,
|
||||
save: 2 * number,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
const config = {
|
||||
configurable: {
|
||||
thread_id: "1",
|
||||
},
|
||||
};
|
||||
|
||||
await myWorkflow.invoke(3, config); // 0 (previous was undefined)
|
||||
await myWorkflow.invoke(1, config); // 6 (previous was 3 * 2 from the previous invocation)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Task
|
||||
|
||||
A **task** represents a discrete unit of work, such as an API call or data processing step. It has two key characteristics:
|
||||
|
||||
* **Asynchronous Execution**: Tasks are designed to be executed asynchronously, allowing multiple operations to run concurrently without blocking.
|
||||
* **Checkpointing**: Task results are saved to a checkpoint, enabling resumption of the workflow from the last saved state. (See [persistence](persistence.md) for more details).
|
||||
- **Asynchronous Execution**: Tasks are designed to be executed asynchronously, allowing multiple operations to run concurrently without blocking.
|
||||
- **Checkpointing**: Task results are saved to a checkpoint, enabling resumption of the workflow from the last saved state. (See [persistence](persistence.md) for more details).
|
||||
|
||||
### Definition
|
||||
|
||||
:::python
|
||||
Tasks are defined using the `@task` decorator, which wraps a regular Python function.
|
||||
|
||||
```python
|
||||
@@ -478,21 +823,37 @@ def slow_computation(input_value):
|
||||
return result
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
Tasks are defined using the `task` function, which wraps a regular function.
|
||||
|
||||
```typescript
|
||||
import { task } from "@langchain/langgraph";
|
||||
|
||||
const slowComputation = task("slowComputation", async (inputValue: any) => {
|
||||
// Simulate a long-running operation
|
||||
return result;
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
!!! important "Serialization"
|
||||
|
||||
The **outputs** of tasks must be JSON-serializable to support checkpointing.
|
||||
|
||||
### Execution
|
||||
|
||||
**Tasks** can only be called from within an **entrypoint**, another **task**, or a [state graph node](./low_level.md#nodes).
|
||||
**Tasks** can only be called from within an **entrypoint**, another **task**, or a [state graph node](./low_level.md#nodes).
|
||||
|
||||
Tasks *cannot* be called directly from the main application code.
|
||||
Tasks _cannot_ be called directly from the main application code.
|
||||
|
||||
When you call a **task**, it returns *immediately* with a future object. A future is a placeholder for a result that will be available later.
|
||||
:::python
|
||||
When you call a **task**, it returns _immediately_ with a future object. A future is a placeholder for a result that will be available later.
|
||||
|
||||
To obtain the result of a **task**, you can either wait for it synchronously (using `result()`) or await it asynchronously (using `await`).
|
||||
|
||||
|
||||
=== "Synchronous Invocation"
|
||||
|
||||
```python
|
||||
@@ -510,6 +871,22 @@ To obtain the result of a **task**, you can either wait for it synchronously (us
|
||||
return await slow_computation(some_input) # Await result asynchronously
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
When you call a **task**, it returns a Promise that can be awaited.
|
||||
|
||||
```typescript
|
||||
const myWorkflow = entrypoint(
|
||||
{ checkpointer, name: "workflow" },
|
||||
async (someInput: number): Promise<number> => {
|
||||
return await slowComputation(someInput);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## When to use a task
|
||||
|
||||
**Tasks** are useful in the following scenarios:
|
||||
@@ -519,16 +896,21 @@ To obtain the result of a **task**, you can either wait for it synchronously (us
|
||||
- **Parallel Execution**: For I/O-bound tasks, **tasks** enable parallel execution, allowing multiple operations to run concurrently without blocking (e.g., calling multiple APIs).
|
||||
- **Observability**: Wrapping operations in **tasks** provides a way to track the progress of the workflow and monitor the execution of individual operations using [LangSmith](https://docs.smith.langchain.com/).
|
||||
- **Retryable Work**: When work needs to be retried to handle failures or inconsistencies, **tasks** provide a way to encapsulate and manage the retry logic.
|
||||
|
||||
|
||||
## Serialization
|
||||
|
||||
There are two key aspects to serialization in LangGraph:
|
||||
|
||||
1. `@entrypoint` inputs and outputs must be JSON-serializable.
|
||||
2. `@task` outputs must be JSON-serializable.
|
||||
1. `entrypoint` inputs and outputs must be JSON-serializable.
|
||||
2. `task` outputs must be JSON-serializable.
|
||||
|
||||
These requirements are necessary for enabling checkpointing and workflow resumption. Use python primitives
|
||||
like dictionaries, lists, strings, numbers, and booleans to ensure that your inputs and outputs are serializable.
|
||||
:::python
|
||||
These requirements are necessary for enabling checkpointing and workflow resumption. Use python primitives like dictionaries, lists, strings, numbers, and booleans to ensure that your inputs and outputs are serializable.
|
||||
:::
|
||||
|
||||
:::js
|
||||
These requirements are necessary for enabling checkpointing and workflow resumption. Use primitives like objects, arrays, strings, numbers, and booleans to ensure that your inputs and outputs are serializable.
|
||||
:::
|
||||
|
||||
Serialization ensures that workflow state, such as task results and intermediate values, can be reliably saved and restored. This is critical for enabling human-in-the-loop interactions, fault tolerance, and parallel execution.
|
||||
|
||||
@@ -536,9 +918,9 @@ Providing non-serializable inputs or outputs will result in a runtime error when
|
||||
|
||||
## Determinism
|
||||
|
||||
To utilize features like **human-in-the-loop**, any randomness should be encapsulated inside of **tasks**. This guarantees that when execution is halted (e.g., for human in the loop) and then resumed, it will follow the same *sequence of steps*, even if **task** results are non-deterministic.
|
||||
To utilize features like **human-in-the-loop**, any randomness should be encapsulated inside of **tasks**. This guarantees that when execution is halted (e.g., for human in the loop) and then resumed, it will follow the same _sequence of steps_, even if **task** results are non-deterministic.
|
||||
|
||||
LangGraph achieves this behavior by persisting **task** and [**subgraph**](./subgraphs.md) results as they execute. A well-designed workflow ensures that resuming execution follows the *same sequence of steps*, allowing previously computed results to be retrieved correctly without having to re-execute them. This is particularly useful for long-running **tasks** or **tasks** with non-deterministic results, as it avoids repeating previously done work and allows resuming from essentially the same.
|
||||
LangGraph achieves this behavior by persisting **task** and [**subgraph**](./subgraphs.md) results as they execute. A well-designed workflow ensures that resuming execution follows the _same sequence of steps_, allowing previously computed results to be retrieved correctly without having to re-execute them. This is particularly useful for long-running **tasks** or **tasks** with non-deterministic results, as it avoids repeating previously done work and allows resuming from essentially the same.
|
||||
|
||||
While different runs of a workflow can produce different results, resuming a **specific** run should always follow the same sequence of recorded steps. This allows LangGraph to efficiently look up **task** and **subgraph** results that were executed prior to the graph being interrupted and avoid recomputing them.
|
||||
|
||||
@@ -556,6 +938,7 @@ Encapsulate side effects (e.g., writing to a file, sending an email) in tasks to
|
||||
|
||||
In this example, a side effect (writing to a file) is directly included in the workflow, so it will be executed a second time when resuming the workflow.
|
||||
|
||||
:::python
|
||||
```python
|
||||
@entrypoint(checkpointer=checkpointer)
|
||||
def my_workflow(inputs: dict) -> int:
|
||||
@@ -568,11 +951,31 @@ Encapsulate side effects (e.g., writing to a file, sending an email) in tasks to
|
||||
value = interrupt("question")
|
||||
return value
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { entrypoint, interrupt } from "@langchain/langgraph";
|
||||
import fs from "fs";
|
||||
|
||||
const myWorkflow = entrypoint(
|
||||
{ checkpointer, name: "workflow },
|
||||
async (inputs: Record<string, any>) => {
|
||||
// This code will be executed a second time when resuming the workflow.
|
||||
// Which is likely not what you want.
|
||||
fs.writeFileSync("output.txt", "Side effect executed");
|
||||
const value = interrupt("question");
|
||||
return value;
|
||||
}
|
||||
);
|
||||
```
|
||||
:::
|
||||
|
||||
=== "Correct"
|
||||
|
||||
In this example, the side effect is encapsulated in a task, ensuring consistent execution upon resumption.
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langgraph.func import task
|
||||
|
||||
@@ -590,17 +993,43 @@ Encapsulate side effects (e.g., writing to a file, sending an email) in tasks to
|
||||
value = interrupt("question")
|
||||
return value
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { entrypoint, task, interrupt } from "@langchain/langgraph";
|
||||
import * as fs from "fs";
|
||||
|
||||
const writeToFile = task("writeToFile", async () => {
|
||||
fs.writeFileSync("output.txt", "Side effect executed");
|
||||
});
|
||||
|
||||
const myWorkflow = entrypoint(
|
||||
{ checkpointer, name: "workflow" },
|
||||
async (inputs: Record<string, any>) => {
|
||||
// The side effect is now encapsulated in a task.
|
||||
await writeToFile();
|
||||
const value = interrupt("question");
|
||||
return value;
|
||||
}
|
||||
);
|
||||
```
|
||||
:::
|
||||
|
||||
### Non-deterministic control flow
|
||||
|
||||
Operations that might give different results each time (like getting current time or random numbers) should be encapsulated in tasks to ensure that on resume, the same result is returned.
|
||||
|
||||
* In a task: Get random number (5) → interrupt → resume → (returns 5 again) → ...
|
||||
* Not in a task: Get random number (5) → interrupt → resume → get new random number (7) → ...
|
||||
- In a task: Get random number (5) → interrupt → resume → (returns 5 again) → ...
|
||||
- Not in a task: Get random number (5) → interrupt → resume → get new random number (7) → ...
|
||||
|
||||
This is especially important when using **human-in-the-loop** workflows with multiple interrupts calls. LangGraph keeps a list
|
||||
of resume values for each task/entrypoint. When an interrupt is encountered, it's matched with the corresponding resume value.
|
||||
This matching is strictly **index-based**, so the order of the resume values should match the order of the interrupts.
|
||||
:::python
|
||||
This is especially important when using **human-in-the-loop** workflows with multiple interrupts calls. LangGraph keeps a list of resume values for each task/entrypoint. When an interrupt is encountered, it's matched with the corresponding resume value. This matching is strictly **index-based**, so the order of the resume values should match the order of the interrupts.
|
||||
:::
|
||||
|
||||
:::js
|
||||
This is especially important when using **human-in-the-loop** workflows with multiple interrupt calls. LangGraph keeps a list of resume values for each task/entrypoint. When an interrupt is encountered, it's matched with the corresponding resume value. This matching is strictly **index-based**, so the order of the resume values should match the order of the interrupts.
|
||||
:::
|
||||
|
||||
If order of execution is not maintained when resuming, one `interrupt` call may be matched with the wrong `resume` value, leading to incorrect results.
|
||||
|
||||
@@ -610,6 +1039,7 @@ Please read the section on [determinism](#determinism) for more details.
|
||||
|
||||
In this example, the workflow uses the current time to determine which task to execute. This is non-deterministic because the result of the workflow depends on the time at which it is executed.
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langgraph.func import entrypoint
|
||||
|
||||
@@ -618,24 +1048,51 @@ Please read the section on [determinism](#determinism) for more details.
|
||||
t0 = inputs["t0"]
|
||||
# highlight-next-line
|
||||
t1 = time.time()
|
||||
|
||||
|
||||
delta_t = t1 - t0
|
||||
|
||||
|
||||
if delta_t > 1:
|
||||
result = slow_task(1).result()
|
||||
value = interrupt("question")
|
||||
else:
|
||||
result = slow_task(2).result()
|
||||
value = interrupt("question")
|
||||
|
||||
|
||||
return {
|
||||
"result": result,
|
||||
"value": value
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { entrypoint, interrupt } from "@langchain/langgraph";
|
||||
|
||||
const myWorkflow = entrypoint(
|
||||
{ checkpointer, name: "workflow" },
|
||||
async (inputs: { t0: number }) => {
|
||||
const t1 = Date.now();
|
||||
|
||||
const deltaT = t1 - inputs.t0;
|
||||
|
||||
if (deltaT > 1000) {
|
||||
const result = await slowTask(1);
|
||||
const value = interrupt("question");
|
||||
return { result, value };
|
||||
} else {
|
||||
const result = await slowTask(2);
|
||||
const value = interrupt("question");
|
||||
return { result, value };
|
||||
}
|
||||
}
|
||||
);
|
||||
```
|
||||
:::
|
||||
|
||||
=== "Correct"
|
||||
|
||||
:::python
|
||||
In this example, the workflow uses the input `t0` to determine which task to execute. This is deterministic because the result of the workflow depends only on the input.
|
||||
|
||||
```python
|
||||
@@ -654,19 +1111,48 @@ Please read the section on [determinism](#determinism) for more details.
|
||||
t0 = inputs["t0"]
|
||||
# highlight-next-line
|
||||
t1 = get_time().result()
|
||||
|
||||
|
||||
delta_t = t1 - t0
|
||||
|
||||
|
||||
if delta_t > 1:
|
||||
result = slow_task(1).result()
|
||||
value = interrupt("question")
|
||||
else:
|
||||
result = slow_task(2).result()
|
||||
value = interrupt("question")
|
||||
|
||||
|
||||
return {
|
||||
"result": result,
|
||||
"value": value
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
In this example, the workflow uses the input `t0` to determine which task to execute. This is deterministic because the result of the workflow depends only on the input.
|
||||
|
||||
```typescript
|
||||
import { entrypoint, task, interrupt } from "@langchain/langgraph";
|
||||
|
||||
const getTime = task("getTime", () => Date.now());
|
||||
|
||||
const myWorkflow = entrypoint(
|
||||
{ checkpointer, name: "workflow" },
|
||||
async (inputs: { t0: number }): Promise<any> => {
|
||||
const t1 = await getTime();
|
||||
|
||||
const deltaT = t1 - inputs.t0;
|
||||
|
||||
if (deltaT > 1000) {
|
||||
const result = await slowTask(1);
|
||||
const value = interrupt("question");
|
||||
return { result, value };
|
||||
} else {
|
||||
const result = await slowTask(2);
|
||||
const value = interrupt("question");
|
||||
return { result, value };
|
||||
}
|
||||
}
|
||||
);
|
||||
```
|
||||
:::
|
||||
|
||||
@@ -7,29 +7,66 @@ search:
|
||||
|
||||
**LangGraph CLI** is a multi-platform command-line tool for building and running the [LangGraph API server](./langgraph_server.md) locally. The resulting server includes all API endpoints for your graph's runs, threads, assistants, etc. as well as the other services required to run your agent, including a managed database for checkpointing and storage.
|
||||
|
||||
:::python
|
||||
|
||||
## Installation
|
||||
|
||||
The LangGraph CLI can be installed via pip or [Homebrew](https://brew.sh/):
|
||||
|
||||
=== "pip"
|
||||
=== "pip"
|
||||
|
||||
```bash
|
||||
pip install langgraph-cli
|
||||
```
|
||||
|
||||
=== "Homebrew"
|
||||
|
||||
```bash
|
||||
brew install langgraph-cli
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
## Installation
|
||||
|
||||
The LangGraph.js CLI can be installed from the NPM registry:
|
||||
|
||||
=== "npx"
|
||||
```bash
|
||||
npx @langchain/langgraph-cli
|
||||
```
|
||||
|
||||
=== "npm"
|
||||
```bash
|
||||
npm install @langchain/langgraph-cli
|
||||
```
|
||||
|
||||
=== "yarn"
|
||||
```bash
|
||||
yarn add @langchain/langgraph-cli
|
||||
```
|
||||
|
||||
=== "pnpm"
|
||||
```bash
|
||||
pnpm add @langchain/langgraph-cli
|
||||
```
|
||||
|
||||
=== "bun"
|
||||
```bash
|
||||
bun add @langchain/langgraph-cli
|
||||
```
|
||||
:::
|
||||
|
||||
## Commands
|
||||
|
||||
LangGraph CLI provides the following core functionality:
|
||||
|
||||
| Command | Description |
|
||||
| -------- | -------|
|
||||
| [`langgraph build`](../cloud/reference/cli.md#build) | Builds a Docker image for the [LangGraph API server](./langgraph_server.md) that can be directly deployed. |
|
||||
| [`langgraph dev`](../cloud/reference/cli.md#dev) | Starts a lightweight development server that requires no Docker installation. This server is ideal for rapid development and testing. This is available in version 0.1.55 and up.
|
||||
| Command | Description |
|
||||
| -------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [`langgraph build`](../cloud/reference/cli.md#build) | Builds a Docker image for the [LangGraph API server](./langgraph_server.md) that can be directly deployed. |
|
||||
| [`langgraph dev`](../cloud/reference/cli.md#dev) | Starts a lightweight development server that requires no Docker installation. This server is ideal for rapid development and testing. |
|
||||
| [`langgraph dockerfile`](../cloud/reference/cli.md#dockerfile) | Generates a [Dockerfile](https://docs.docker.com/reference/dockerfile/) that can be used to build images for and deploy instances of the [LangGraph API server](./langgraph_server.md). This is useful if you want to further customize the dockerfile or deploy in a more custom way. |
|
||||
| [`langgraph up`](../cloud/reference/cli.md#up) | Starts an instance of the [LangGraph API server](./langgraph_server.md) locally in a docker container. This requires the docker server to be running locally. It also requires a LangSmith API key for local development or a license key for production use. |
|
||||
| [`langgraph up`](../cloud/reference/cli.md#up) | Starts an instance of the [LangGraph API server](./langgraph_server.md) locally in a docker container. This requires the docker server to be running locally. It also requires a LangSmith API key for local development or a license key for production use. |
|
||||
|
||||
For more information, see the [LangGraph CLI Reference](../cloud/reference/cli.md).
|
||||
|
||||
@@ -11,11 +11,11 @@ To deploy a [LangGraph Server](../concepts/langgraph_server.md), follow the how-
|
||||
|
||||
The Cloud SaaS deployment option is a fully managed model for deployment where we manage the [control plane](./langgraph_control_plane.md) and [data plane](./langgraph_data_plane.md) in our cloud.
|
||||
|
||||
| | [Control plane](../concepts/langgraph_control_plane.md) | [Data plane](../concepts/langgraph_data_plane.md) |
|
||||
|-------------------|-------------------|------------|
|
||||
| **What is it?** | <ul><li>Control plane UI for creating deployments and revisions</li><li>Control plane APIs for creating deployments and revisions</li></ul> | <ul><li>Data plane "listener" for reconciling deployments with control plane state</li><li>LangGraph Servers</li><li>Postgres, Redis, etc</li></ul> |
|
||||
| **Where is it hosted?** | LangChain's cloud | LangChain's cloud |
|
||||
| **Who provisions and manages it?** | LangChain | LangChain |
|
||||
| | [Control plane](../concepts/langgraph_control_plane.md) | [Data plane](../concepts/langgraph_data_plane.md) |
|
||||
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **What is it?** | <ul><li>Control plane UI for creating deployments and revisions</li><li>Control plane APIs for creating deployments and revisions</li></ul> | <ul><li>Data plane "listener" for reconciling deployments with control plane state</li><li>LangGraph Servers</li><li>Postgres, Redis, etc</li></ul> |
|
||||
| **Where is it hosted?** | LangChain's cloud | LangChain's cloud |
|
||||
| **Who provisions and manages it?** | LangChain | LangChain |
|
||||
|
||||
## Architecture
|
||||
|
||||
|
||||
@@ -10,4 +10,4 @@ The LangGraph Platform consists of components that work together to support the
|
||||
- [LangGraph control plane](./langgraph_control_plane.md): The LangGraph Control Plane refers to the Control Plane UI where users create and update LangGraph Servers and the Control Plane APIs that support the UI experience.
|
||||
- [LangGraph data plane](./langgraph_data_plane.md): The LangGraph Data Plane refers to LangGraph Servers, the corresponding infrastructure for each server, and the "listener" application that continuously polls for updates from the LangGraph Control Plane.
|
||||
|
||||

|
||||

|
||||
|
||||
@@ -44,8 +44,8 @@ This section describes various features of the control plane.
|
||||
|
||||
For simplicity, the control plane offers two deployment types with different resource allocations: `Development` and `Production`.
|
||||
|
||||
| **Deployment Type** | **CPU/Memory** | **Scaling** | **Database** |
|
||||
|---------------------|-----------------|---------------------|----------------------------------------------------------------------------------|
|
||||
| **Deployment Type** | **CPU/Memory** | **Scaling** | **Database** |
|
||||
| ------------------- | --------------- | ----------------- | -------------------------------------------------------------------------------- |
|
||||
| Development | 1 CPU, 1 GB RAM | Up to 1 replica | 10 GB disk, no backups |
|
||||
| Production | 2 CPU, 2 GB RAM | Up to 10 replicas | Autoscaling disk, automatic backups, highly available (multi-zone configuration) |
|
||||
|
||||
@@ -56,7 +56,7 @@ CPU and memory resources are per replica.
|
||||
Once a deployment is created, the deployment type cannot be changed.
|
||||
|
||||
!!! info "Self-Hosted Deployment"
|
||||
Resources for [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md) deployments can be fully customized. Deployment types are only applicable for [Cloud SaaS](../concepts/langgraph_cloud.md) deployments.
|
||||
Resources for [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md) deployments can be fully customized. Deployment types are only applicable for [Cloud SaaS](../concepts/langgraph_cloud.md) deployments.
|
||||
|
||||
#### Production
|
||||
|
||||
@@ -69,12 +69,12 @@ Resources for `Production` type deployments can be manually increased on a case-
|
||||
`Development` type deployments are suitable development and testing. For example, select `Development` for internal testing environments. `Development` type deployments are not suitable for "production" workloads.
|
||||
|
||||
!!! danger "Preemptible Compute Infrastructure"
|
||||
`Development` type deployments (API server, queue server, and database) are provisioned on preemptible compute infrastructure. This means the compute infrastructure **may be terminated at any time without notice**. This may result in intermittent...
|
||||
`Development` type deployments (API server, queue server, and database) are provisioned on preemptible compute infrastructure. This means the compute infrastructure **may be terminated at any time without notice**. This may result in intermittent...
|
||||
|
||||
- Redis connection timeouts/errors
|
||||
- Postgres connection timeouts/errors
|
||||
- Failed or retrying background runs
|
||||
|
||||
|
||||
This behavior is expected. Preemptible compute infrastructure **significantly reduces the cost to provision a `Development` type deployment**. By design, LangGraph Server is fault-tolerant. The implementation will automatically attempt to recover from Redis/Postgres connection errors and retry failed background runs.
|
||||
|
||||
`Production` type deployments are provisioned on durable compute infrastructure, not preemptible compute infrastructure.
|
||||
@@ -92,7 +92,7 @@ There is no direct access to the database. All access to the database occurs thr
|
||||
The database is never deleted until the deployment itself is deleted.
|
||||
|
||||
!!! info
|
||||
A custom Postgres instance can be configured for [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md) deployments.
|
||||
A custom Postgres instance can be configured for [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md) deployments.
|
||||
|
||||
### Asynchronous Deployment
|
||||
|
||||
|
||||
@@ -78,25 +78,25 @@ Scale down actions are delayed for 30 minutes before any action is taken. In oth
|
||||
### Static IP Addresses
|
||||
|
||||
!!! info "Only for Cloud SaaS"
|
||||
Static IP addresses are only available for [Cloud SaaS](../concepts/langgraph_cloud.md) deployments.
|
||||
Static IP addresses are only available for [Cloud SaaS](../concepts/langgraph_cloud.md) deployments.
|
||||
|
||||
All traffic from deployments created after January 6th 2025 will come through a NAT gateway. This NAT gateway will have several static IP addresses depending on the data region. Refer to the table below for the list of static IP addresses:
|
||||
|
||||
| US | EU |
|
||||
|----------------|----------------|
|
||||
| -------------- | -------------- |
|
||||
| 35.197.29.146 | 34.13.192.67 |
|
||||
| 34.145.102.123 | 34.147.105.64 |
|
||||
| 34.169.45.153 | 34.90.22.166 |
|
||||
| 34.82.222.17 | 34.147.36.213 |
|
||||
| 35.227.171.135 | 34.32.137.113 |
|
||||
| 35.227.171.135 | 34.32.137.113 |
|
||||
| 34.169.88.30 | 34.91.238.184 |
|
||||
| 34.19.93.202 | 35.204.101.241 |
|
||||
| 34.19.34.50 | 35.204.48.32 |
|
||||
|
||||
### Custom Postgres
|
||||
|
||||
!!! info
|
||||
Custom Postgres instances are only available for [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md) deployments.
|
||||
!!! info
|
||||
Custom Postgres instances are only available for [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md) deployments.
|
||||
|
||||
A custom Postgres instance can be used instead of the [one automatically created by the control plane](./langgraph_control_plane.md#database-provisioning). Specify the [`POSTGRES_URI_CUSTOM`](../cloud/reference/env_var.md#postgres_uri_custom) environment variable to use a custom Postgres instance.
|
||||
|
||||
@@ -105,33 +105,32 @@ Multiple deployments can share the same Postgres instance. For example, for `Dep
|
||||
### Custom Redis
|
||||
|
||||
!!! info
|
||||
Custom Redis instances are only available for [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_control_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md) deployments.
|
||||
Custom Redis instances are only available for [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_control_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md) deployments.
|
||||
|
||||
A custom Redis instance can be used instead of the one automatically created by the control plane. Specify the [REDIS_URI_CUSTOM](../cloud/reference/env_var.md#redis_uri_custom) environment variable to use a custom Redis instance.
|
||||
|
||||
|
||||
Multiple deployments can share the same Redis instance. For example, for `Deployment A`, `REDIS_URI_CUSTOM` can be set to `redis://<hostname_1>:<port>/1` and for `Deployment B`, `REDIS_URI_CUSTOM` can be set to `redis://<hostname_1>:<port>/2`. `1` and `2` are different database numbers within the same instance, but `<hostname_1>` is shared. **The same database number cannot be used for separate deployments**.
|
||||
|
||||
### LangSmith Tracing
|
||||
|
||||
LangGraph Server is automatically configured to send traces to LangSmith. See the table below for details with respect to each deployment option.
|
||||
|
||||
| Cloud SaaS | Self-Hosted Data Plane | Self-Hosted Control Plane | Standalone Container |
|
||||
|------------|------------------------|---------------------------|----------------------|
|
||||
| Cloud SaaS | Self-Hosted Data Plane | Self-Hosted Control Plane | Standalone Container |
|
||||
| ---------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- |
|
||||
| Required<br><br>Trace to LangSmith SaaS. | Optional<br><br>Disable tracing or trace to LangSmith SaaS. | Optional<br><br>Disable tracing or trace to Self-Hosted LangSmith. | Optional<br><br>Disable tracing, trace to LangSmith SaaS, or trace to Self-Hosted LangSmith. |
|
||||
|
||||
### Telemetry
|
||||
|
||||
LangGraph Server is automatically configured to report telemetry metadata for billing purposes. See the table below for details with respect to each deployment option.
|
||||
|
||||
| Cloud SaaS | Self-Hosted Data Plane | Self-Hosted Control Plane | Standalone Container |
|
||||
|------------|------------------------|---------------------------|----------------------|
|
||||
| Cloud SaaS | Self-Hosted Data Plane | Self-Hosted Control Plane | Standalone Container |
|
||||
| --------------------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Telemetry sent to LangSmith SaaS. | Telemetry sent to LangSmith SaaS. | Self-reported usage (audit) for air-gapped license key.<br><br>Telemetry sent to LangSmith SaaS for LangGraph Platform License Key. | Self-reported usage (audit) for air-gapped license key.<br><br>Telemetry sent to LangSmith SaaS for LangGraph Platform License Key. |
|
||||
|
||||
### Licensing
|
||||
|
||||
LangGraph Server is automatically configured to perform license key validation. See the table below for details with respect to each deployment option.
|
||||
|
||||
| Cloud SaaS | Self-Hosted Data Plane | Self-Hosted Control Plane | Standalone Container |
|
||||
|------------|------------------------|---------------------------|----------------------|
|
||||
| Cloud SaaS | Self-Hosted Data Plane | Self-Hosted Control Plane | Standalone Container |
|
||||
| --------------------------------------------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ |
|
||||
| LangSmith API Key validated against LangSmith SaaS. | LangSmith API Key validated against LangSmith SaaS. | Air-gapped license key or LangGraph Platform License Key validated against LangSmith SaaS. | Air-gapped license key or LangGraph Platform License Key validated against LangSmith SaaS. |
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
There are two versions of the self-hosted deployment: [Self-Hosted Data Plane](./deployment_options.md#self-hosted-data-plane) and [Self-Hosted Control Plane](./deployment_options.md#self-hosted-control-plane).
|
||||
|
||||
!!! info "Important"
|
||||
|
||||
The Self-Hosted Control Plane deployment option requires an [Enterprise](plans.md) plan.
|
||||
|
||||
## Requirements
|
||||
|
||||
- You use `langgraph-cli` and/or [LangGraph Studio](./langgraph_studio.md) app to test graph locally.
|
||||
- You use the [LangGraph CLI](./langgraph_cli.md) and/or [LangGraph Studio](./langgraph_studio.md) app to test graph locally.
|
||||
- You use `langgraph build` command to build image.
|
||||
- You have a Self-Hosted LangSmith instance deployed.
|
||||
- You are using Ingress for your LangSmith instance. All agents will be deployed as Kubernetes services behind this ingress.
|
||||
@@ -16,11 +17,11 @@ There are two versions of the self-hosted deployment: [Self-Hosted Data Plane](.
|
||||
|
||||
The [Self-Hosted Control Plane](./langgraph_self_hosted_control_plane.md) deployment option is a fully self-hosted model for deployment where you manage the [control plane](./langgraph_control_plane.md) and [data plane](./langgraph_data_plane.md) in your cloud. This option gives you full control and responsibility of the control plane and data plane infrastructure.
|
||||
|
||||
| | [Control plane](../concepts/langgraph_control_plane.md) | [Data plane](../concepts/langgraph_data_plane.md) |
|
||||
|-------------------|-------------------|------------|
|
||||
| **What is it?** | <ul><li>Control plane UI for creating deployments and revisions</li><li>Control plane APIs for creating deployments and revisions</li></ul> | <ul><li>Data plane "listener" for reconciling deployments with control plane state</li><li>LangGraph Servers</li><li>Postgres, Redis, etc</li></ul> |
|
||||
| **Where is it hosted?** | Your cloud | Your cloud |
|
||||
| **Who provisions and manages it?** | You | You |
|
||||
| | [Control plane](../concepts/langgraph_control_plane.md) | [Data plane](../concepts/langgraph_data_plane.md) |
|
||||
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **What is it?** | <ul><li>Control plane UI for creating deployments and revisions</li><li>Control plane APIs for creating deployments and revisions</li></ul> | <ul><li>Data plane "listener" for reconciling deployments with control plane state</li><li>LangGraph Servers</li><li>Postgres, Redis, etc</li></ul> |
|
||||
| **Where is it hosted?** | Your cloud | Your cloud |
|
||||
| **Who provisions and manages it?** | You | You |
|
||||
|
||||
### Architecture
|
||||
|
||||
@@ -28,7 +29,7 @@ The [Self-Hosted Control Plane](./langgraph_self_hosted_control_plane.md) deploy
|
||||
|
||||
### Compute Platforms
|
||||
|
||||
- **Kubernetes**: The Self-Hosted Control Plane deployment option supports deploying control plane and data plane infrastructure to any Kubernetes cluster.
|
||||
- **Kubernetes**: The Self-Hosted Control Plane deployment option supports deploying control plane and data plane infrastructure to any Kubernetes cluster.
|
||||
|
||||
!!! tip
|
||||
If you would like to enable this on your LangSmith instance, please follow the [Self-Hosted Control Plane deployment guide](../cloud/deployment/self_hosted_control_plane.md).
|
||||
If you would like to enable this on your LangSmith instance, please follow the [Self-Hosted Control Plane deployment guide](../cloud/deployment/self_hosted_control_plane.md).
|
||||
|
||||
@@ -8,6 +8,7 @@ search:
|
||||
There are two versions of the self-hosted deployment: [Self-Hosted Data Plane](./deployment_options.md#self-hosted-data-plane) and [Self-Hosted Control Plane](./deployment_options.md#self-hosted-control-plane).
|
||||
|
||||
!!! info "Important"
|
||||
|
||||
The Self-Hosted Data Plane deployment option requires an [Enterprise](plans.md) plan.
|
||||
|
||||
## Requirements
|
||||
@@ -19,11 +20,11 @@ There are two versions of the self-hosted deployment: [Self-Hosted Data Plane](.
|
||||
|
||||
The [Self-Hosted Data Plane](../cloud/deployment/self_hosted_data_plane.md) deployment option is a "hybrid" model for deployment where we manage the [control plane](./langgraph_control_plane.md) in our cloud and you manage the [data plane](./langgraph_data_plane.md) in your cloud. This option provides a way to securely manage your data plane infrastructure, while offloading control plane management to us. When using the Self-Hosted Data Plane version, you authenticate with a [LangSmith](https://smith.langchain.com/) API key.
|
||||
|
||||
| | [Control plane](../concepts/langgraph_control_plane.md) | [Data plane](../concepts/langgraph_data_plane.md) |
|
||||
|-------------------|-------------------|------------|
|
||||
| **What is it?** | <ul><li>Control plane UI for creating deployments and revisions</li><li>Control plane APIs for creating deployments and revisions</li></ul> | <ul><li>Data plane "listener" for reconciling deployments with control plane state</li><li>LangGraph Servers</li><li>Postgres, Redis, etc</li></ul> |
|
||||
| **Where is it hosted?** | LangChain's cloud | Your cloud |
|
||||
| **Who provisions and manages it?** | LangChain | You |
|
||||
| | [Control plane](../concepts/langgraph_control_plane.md) | [Data plane](../concepts/langgraph_data_plane.md) |
|
||||
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **What is it?** | <ul><li>Control plane UI for creating deployments and revisions</li><li>Control plane APIs for creating deployments and revisions</li></ul> | <ul><li>Data plane "listener" for reconciling deployments with control plane state</li><li>LangGraph Servers</li><li>Postgres, Redis, etc</li></ul> |
|
||||
| **Where is it hosted?** | LangChain's cloud | Your cloud |
|
||||
| **Who provisions and manages it?** | LangChain | You |
|
||||
|
||||
For information on how to deploy a [LangGraph Server](../concepts/langgraph_server.md) to Self-Hosted Data Plane, see [Deploy to Self-Hosted Data Plane](../cloud/deployment/self_hosted_data_plane.md)
|
||||
|
||||
@@ -37,4 +38,4 @@ For information on how to deploy a [LangGraph Server](../concepts/langgraph_serv
|
||||
- **Amazon ECS**: Coming soon!
|
||||
|
||||
!!! tip
|
||||
If you would like to deploy to Kubernetes, you can follow the [Self-Hosted Data Plane deployment guide](../cloud/deployment/self_hosted_data_plane.md).
|
||||
If you would like to deploy to Kubernetes, you can follow the [Self-Hosted Data Plane deployment guide](../cloud/deployment/self_hosted_data_plane.md).
|
||||
|
||||
+595
-26
@@ -9,13 +9,13 @@ search:
|
||||
|
||||
At its core, LangGraph models agent workflows as graphs. You define the behavior of your agents using three key components:
|
||||
|
||||
1. [`State`](#state): A shared data structure that represents the current snapshot of your application. It can be any Python type, but is typically a `TypedDict` or Pydantic `BaseModel`.
|
||||
1. [`State`](#state): A shared data structure that represents the current snapshot of your application. It can be any data type, but is typically defined using a shared state schema.
|
||||
|
||||
2. [`Nodes`](#nodes): Python functions that encode the logic of your agents. They receive the current `State` as input, perform some computation or side-effect, and return an updated `State`.
|
||||
2. [`Nodes`](#nodes): Functions that encode the logic of your agents. They receive the current state as input, perform some computation or side-effect, and return an updated state.
|
||||
|
||||
3. [`Edges`](#edges): Python functions that determine which `Node` to execute next based on the current `State`. They can be conditional branches or fixed transitions.
|
||||
3. [`Edges`](#edges): Functions that determine which `Node` to execute next based on the current state. They can be conditional branches or fixed transitions.
|
||||
|
||||
By composing `Nodes` and `Edges`, you can create complex, looping workflows that evolve the `State` over time. The real power, though, comes from how LangGraph manages that `State`. To emphasize: `Nodes` and `Edges` are nothing more than Python functions - they can contain an LLM or just good ol' Python code.
|
||||
By composing `Nodes` and `Edges`, you can create complex, looping workflows that evolve the state over time. The real power, though, comes from how LangGraph manages that state. To emphasize: `Nodes` and `Edges` are nothing more than functions - they can contain an LLM or just good ol' code.
|
||||
|
||||
In short: _nodes do the work, edges tell what to do next_.
|
||||
|
||||
@@ -33,21 +33,51 @@ To build your graph, you first define the [state](#state), you then add [nodes](
|
||||
|
||||
Compiling is a pretty simple step. It provides a few basic checks on the structure of your graph (no orphaned nodes, etc). It is also where you can specify runtime args like [checkpointers](./persistence.md) and breakpoints. You compile your graph by just calling the `.compile` method:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
graph = graph_builder.compile(...)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
const graph = new StateGraph(StateAnnotation)
|
||||
.addNode("nodeA", nodeA)
|
||||
.addEdge(START, "nodeA")
|
||||
.addEdge("nodeA", END)
|
||||
.compile();
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
You **MUST** compile your graph before you can use it.
|
||||
|
||||
## State
|
||||
|
||||
:::python
|
||||
The first thing you do when you define a graph is define the `State` of the graph. The `State` consists of the [schema of the graph](#schema) as well as [`reducer` functions](#reducers) which specify how to apply updates to the state. The schema of the `State` will be the input schema to all `Nodes` and `Edges` in the graph, and can be either a `TypedDict` or a `Pydantic` model. All `Nodes` will emit updates to the `State` which are then applied using the specified `reducer` function.
|
||||
:::
|
||||
|
||||
:::js
|
||||
The first thing you do when you define a graph is define the `State` of the graph. The `State` consists of the [schema of the graph](#schema) as well as [`reducer` functions](#reducers) which specify how to apply updates to the state. The schema of the `State` will be the input schema to all `Nodes` and `Edges` in the graph, and can be either a Zod schema or a schema built using `Annotation.Root`. All `Nodes` will emit updates to the `State` which are then applied using the specified `reducer` function.
|
||||
:::
|
||||
|
||||
### Schema
|
||||
|
||||
:::python
|
||||
The main documented way to specify the schema of a graph is by using a [`TypedDict`](https://docs.python.org/3/library/typing.html#typing.TypedDict). If you want to provide default values in your state, use a [`dataclass`](https://docs.python.org/3/library/dataclasses.html). We also support using a Pydantic [BaseModel](../how-tos/graph-api.md#use-pydantic-models-for-graph-state) as your graph state if you want recursive data validation (though note that pydantic is less performant than a `TypedDict` or `dataclass`).
|
||||
|
||||
By default, the graph will have the same input and output schemas. If you want to change this, you can also specify explicit input and output schemas directly. This is useful when you have a lot of keys, and some are explicitly for input and others for output. See the [guide here](../how-tos/graph-api.md#define-input-and-output-schemas) for how to use.
|
||||
:::
|
||||
|
||||
:::js
|
||||
The main documented way to specify the schema of a graph is by using Zod schemas. However, we also support using the `Annotation` API to define the schema of the graph.
|
||||
|
||||
By default, the graph will have the same input and output schemas. If you want to change this, you can also specify explicit input and output schemas directly. This is useful when you have a lot of keys, and some are explicitly for input and others for output.
|
||||
:::
|
||||
|
||||
#### Multiple schemas
|
||||
|
||||
@@ -56,12 +86,16 @@ Typically, all graph nodes communicate with a single schema. This means that the
|
||||
- Internal nodes can pass information that is not required in the graph's input / output.
|
||||
- We may also want to use different input / output schemas for the graph. The output might, for example, only contain a single relevant output key.
|
||||
|
||||
It is possible to have nodes write to private state channels inside the graph for internal node communication. We can simply define a private schema, `PrivateState`. See [this guide](../how-tos/graph-api.md#pass-private-state-between-nodes) for more detail.
|
||||
It is possible to have nodes write to private state channels inside the graph for internal node communication. We can simply define a private schema, `PrivateState`.
|
||||
|
||||
See [this guide](../how-tos/graph-api.ipynb#pass-private-state-between-nodes) for more detail.
|
||||
|
||||
It is also possible to define explicit input and output schemas for a graph. In these cases, we define an "internal" schema that contains _all_ keys relevant to graph operations. But, we also define `input` and `output` schemas that are sub-sets of the "internal" schema to constrain the input and output of the graph. See [this guide](../how-tos/graph-api.md#define-input-and-output-schemas) for more detail.
|
||||
|
||||
Let's look at an example:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
class InputState(TypedDict):
|
||||
user_input: str
|
||||
@@ -100,14 +134,80 @@ builder.add_edge("node_3", END)
|
||||
|
||||
graph = builder.compile()
|
||||
graph.invoke({"user_input":"My"})
|
||||
{'graph_output': 'My name is Lance'}
|
||||
# {'graph_output': 'My name is Lance'}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
const InputState = z.object({
|
||||
userInput: z.string(),
|
||||
});
|
||||
|
||||
const OutputState = z.object({
|
||||
graphOutput: z.string(),
|
||||
});
|
||||
|
||||
const OverallState = z.object({
|
||||
foo: z.string(),
|
||||
userInput: z.string(),
|
||||
graphOutput: z.string(),
|
||||
});
|
||||
|
||||
const PrivateState = z.object({
|
||||
bar: z.string(),
|
||||
});
|
||||
|
||||
const graph = new StateGraph({
|
||||
state: OverallState,
|
||||
input: InputState,
|
||||
output: OutputState,
|
||||
})
|
||||
.addNode("node1", (state) => {
|
||||
// Write to OverallState
|
||||
return { foo: state.userInput + " name" };
|
||||
})
|
||||
.addNode("node2", (state) => {
|
||||
// Read from OverallState, write to PrivateState
|
||||
return { bar: state.foo + " is" };
|
||||
})
|
||||
.addNode(
|
||||
"node3",
|
||||
(state) => {
|
||||
// Read from PrivateState, write to OutputState
|
||||
return { graphOutput: state.bar + " Lance" };
|
||||
},
|
||||
{ input: PrivateState }
|
||||
)
|
||||
.addEdge(START, "node1")
|
||||
.addEdge("node1", "node2")
|
||||
.addEdge("node2", "node3")
|
||||
.addEdge("node3", END)
|
||||
.compile();
|
||||
|
||||
await graph.invoke({ userInput: "My" });
|
||||
// { graphOutput: 'My name is Lance' }
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
There are two subtle and important points to note here:
|
||||
|
||||
:::python
|
||||
|
||||
1. We pass `state: InputState` as the input schema to `node_1`. But, we write out to `foo`, a channel in `OverallState`. How can we write out to a state channel that is not included in the input schema? This is because a node _can write to any state channel in the graph state._ The graph state is the union of the state channels defined at initialization, which includes `OverallState` and the filters `InputState` and `OutputState`.
|
||||
|
||||
2. We initialize the graph with `StateGraph(OverallState,input_schema=InputState,output_schema=OutputState)`. So, how can we write to `PrivateState` in `node_2`? How does the graph gain access to this schema if it was not passed in the `StateGraph` initialization? We can do this because _nodes can also declare additional state channels_ as long as the state schema definition exists. In this case, the `PrivateState` schema is defined, so we can add `bar` as a new state channel in the graph and write to it.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
1. We pass `state` as the input schema to `node1`. But, we write out to `foo`, a channel in `OverallState`. How can we write out to a state channel that is not included in the input schema? This is because a node _can write to any state channel in the graph state._ The graph state is the union of the state channels defined at initialization, which includes `OverallState` and the filters `InputState` and `OutputState`.
|
||||
|
||||
2. We initialize the graph with `StateGraph({ state: OverallState, input: InputState, output: OutputState })`. So, how can we write to `PrivateState` in `node2`? How does the graph gain access to this schema if it was not passed in the `StateGraph` initialization? We can do this because _nodes can also declare additional state channels_ as long as the state schema definition exists. In this case, the `PrivateState` schema is defined, so we can add `bar` as a new state channel in the graph and write to it.
|
||||
:::
|
||||
|
||||
### Reducers
|
||||
|
||||
@@ -119,6 +219,8 @@ These two examples show how to use the default reducer:
|
||||
|
||||
**Example A:**
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
@@ -127,10 +229,33 @@ class State(TypedDict):
|
||||
bar: list[str]
|
||||
```
|
||||
|
||||
In this example, no reducer functions are specified for any key. Let's assume the input to the graph is `{"foo": 1, "bar": ["hi"]}`. Let's then assume the first `Node` returns `{"foo": 2}`. This is treated as an update to the state. Notice that the `Node` does not need to return the whole `State` schema - just an update. After applying this update, the `State` would then be `{"foo": 2, "bar": ["hi"]}`. If the second node returns `{"bar": ["bye"]}` then the `State` would then be `{"foo": 2, "bar": ["bye"]}`
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
const State = z.object({
|
||||
foo: z.number(),
|
||||
bar: z.array(z.string()),
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
In this example, no reducer functions are specified for any key. Let's assume the input to the graph is:
|
||||
|
||||
:::python
|
||||
`{"foo": 1, "bar": ["hi"]}`. Let's then assume the first `Node` returns `{"foo": 2}`. This is treated as an update to the state. Notice that the `Node` does not need to return the whole `State` schema - just an update. After applying this update, the `State` would then be `{"foo": 2, "bar": ["hi"]}`. If the second node returns `{"bar": ["bye"]}` then the `State` would then be `{"foo": 2, "bar": ["bye"]}`
|
||||
:::
|
||||
|
||||
:::js
|
||||
`{ foo: 1, bar: ["hi"] }`. Let's then assume the first `Node` returns `{ foo: 2 }`. This is treated as an update to the state. Notice that the `Node` does not need to return the whole `State` schema - just an update. After applying this update, the `State` would then be `{ foo: 2, bar: ["hi"] }`. If the second node returns `{ bar: ["bye"] }` then the `State` would then be `{ foo: 2, bar: ["bye"] }`
|
||||
:::
|
||||
|
||||
**Example B:**
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
from typing_extensions import TypedDict
|
||||
@@ -142,21 +267,56 @@ class State(TypedDict):
|
||||
```
|
||||
|
||||
In this example, we've used the `Annotated` type to specify a reducer function (`operator.add`) for the second key (`bar`). Note that the first key remains unchanged. Let's assume the input to the graph is `{"foo": 1, "bar": ["hi"]}`. Let's then assume the first `Node` returns `{"foo": 2}`. This is treated as an update to the state. Notice that the `Node` does not need to return the whole `State` schema - just an update. After applying this update, the `State` would then be `{"foo": 2, "bar": ["hi"]}`. If the second node returns `{"bar": ["bye"]}` then the `State` would then be `{"foo": 2, "bar": ["hi", "bye"]}`. Notice here that the `bar` key is updated by adding the two lists together.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { z } from "zod";
|
||||
import { withLangGraph } from "@langchain/langgraph/zod";
|
||||
|
||||
const State = z.object({
|
||||
foo: z.number(),
|
||||
bar: withLangGraph(z.array(z.string()), {
|
||||
reducer: {
|
||||
fn: (x, y) => x.concat(y),
|
||||
},
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
In this example, we've used the `withLangGraph` function to specify a reducer function for the second key (`bar`). Note that the first key remains unchanged. Let's assume the input to the graph is `{ foo: 1, bar: ["hi"] }`. Let's then assume the first `Node` returns `{ foo: 2 }`. This is treated as an update to the state. Notice that the `Node` does not need to return the whole `State` schema - just an update. After applying this update, the `State` would then be `{ foo: 2, bar: ["hi"] }`. If the second node returns `{ bar: ["bye"] }` then the `State` would then be `{ foo: 2, bar: ["hi", "bye"] }`. Notice here that the `bar` key is updated by adding the two arrays together.
|
||||
:::
|
||||
|
||||
### Working with Messages in Graph State
|
||||
|
||||
#### Why use messages?
|
||||
|
||||
:::python
|
||||
Most modern LLM providers have a chat model interface that accepts a list of messages as input. LangChain's [`ChatModel`](https://python.langchain.com/docs/concepts/#chat-models) in particular accepts a list of `Message` objects as inputs. These messages come in a variety of forms such as `HumanMessage` (user input) or `AIMessage` (LLM response). To read more about what message objects are, please refer to [this](https://python.langchain.com/docs/concepts/#messages) conceptual guide.
|
||||
:::
|
||||
|
||||
:::js
|
||||
Most modern LLM providers have a chat model interface that accepts a list of messages as input. LangChain's [`ChatModel`](https://js.langchain.com/docs/concepts/#chat-models) in particular accepts a list of `Message` objects as inputs. These messages come in a variety of forms such as `HumanMessage` (user input) or `AIMessage` (LLM response). To read more about what message objects are, please refer to [this](https://js.langchain.com/docs/concepts/#messages) conceptual guide.
|
||||
:::
|
||||
|
||||
#### Using Messages in your Graph
|
||||
|
||||
:::python
|
||||
In many cases, it is helpful to store prior conversation history as a list of messages in your graph state. To do so, we can add a key (channel) to the graph state that stores a list of `Message` objects and annotate it with a reducer function (see `messages` key in the example below). The reducer function is vital to telling the graph how to update the list of `Message` objects in the state with each state update (for example, when a node sends an update). If you don't specify a reducer, every state update will overwrite the list of messages with the most recently provided value. If you wanted to simply append messages to the existing list, you could use `operator.add` as a reducer.
|
||||
|
||||
However, you might also want to manually update messages in your graph state (e.g. human-in-the-loop). If you were to use `operator.add`, the manual state updates you send to the graph would be appended to the existing list of messages, instead of updating existing messages. To avoid that, you need a reducer that can keep track of message IDs and overwrite existing messages, if updated. To achieve this, you can use the prebuilt `add_messages` function. For brand new messages, it will simply append to existing list, but it will also handle the updates for existing messages correctly.
|
||||
:::
|
||||
|
||||
:::js
|
||||
In many cases, it is helpful to store prior conversation history as a list of messages in your graph state. To do so, we can add a key (channel) to the graph state that stores a list of `Message` objects and annotate it with a reducer function (see `messages` key in the example below). The reducer function is vital to telling the graph how to update the list of `Message` objects in the state with each state update (for example, when a node sends an update). If you don't specify a reducer, every state update will overwrite the list of messages with the most recently provided value. If you wanted to simply append messages to the existing list, you could use a function that concatenates arrays as a reducer.
|
||||
|
||||
However, you might also want to manually update messages in your graph state (e.g. human-in-the-loop). If you were to use a simple concatenation function, the manual state updates you send to the graph would be appended to the existing list of messages, instead of updating existing messages. To avoid that, you need a reducer that can keep track of message IDs and overwrite existing messages, if updated. To achieve this, you can use the prebuilt `MessagesZodState` schema. For brand new messages, it will simply append to existing list, but it will also handle the updates for existing messages correctly.
|
||||
:::
|
||||
|
||||
#### Serialization
|
||||
|
||||
:::python
|
||||
In addition to keeping track of message IDs, the `add_messages` function will also try to deserialize messages into LangChain `Message` objects whenever a state update is received on the `messages` channel. See more information on LangChain serialization/deserialization [here](https://python.langchain.com/docs/how_to/serialization/). This allows sending graph inputs / state updates in the following format:
|
||||
|
||||
```python
|
||||
@@ -179,6 +339,45 @@ class GraphState(TypedDict):
|
||||
messages: Annotated[list[AnyMessage], add_messages]
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
In addition to keeping track of message IDs, `MessagesZodState` will also try to deserialize messages into LangChain `Message` objects whenever a state update is received on the `messages` channel. This allows sending graph inputs / state updates in the following format:
|
||||
|
||||
```typescript
|
||||
// this is supported
|
||||
{
|
||||
messages: [new HumanMessage("message")];
|
||||
}
|
||||
|
||||
// and this is also supported
|
||||
{
|
||||
messages: [{ role: "human", content: "message" }];
|
||||
}
|
||||
```
|
||||
|
||||
Since the state updates are always deserialized into LangChain `Messages` when using `MessagesZodState`, you should use dot notation to access message attributes, like `state.messages[state.messages.length - 1].content`. Below is an example of a graph that uses `MessagesZodState`:
|
||||
|
||||
```typescript
|
||||
import { StateGraph, MessagesZodState } from "@langchain/langgraph";
|
||||
|
||||
const graph = new StateGraph(MessagesZodState)
|
||||
...
|
||||
```
|
||||
|
||||
`MessagesZodState` is defined with a single `messages` key which is a list of `BaseMessage` objects and uses the appropriate reducer. Typically, there is more state to track than just messages, so we see people extend this state and add more fields, like:
|
||||
|
||||
```typescript
|
||||
const State = z.object({
|
||||
messages: MessagesZodState.shape.messages,
|
||||
documents: z.array(z.string()),
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::python
|
||||
|
||||
#### MessagesState
|
||||
|
||||
Since having a list of messages in your state is so common, there exists a prebuilt state called `MessagesState` which makes it easy to use messages. `MessagesState` is defined with a single `messages` key which is a list of `AnyMessage` objects and uses the `add_messages` reducer. Typically, there is more state to track than just messages, so we see people subclass this state and add more fields, like:
|
||||
@@ -190,16 +389,19 @@ class State(MessagesState):
|
||||
documents: list[str]
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Nodes
|
||||
|
||||
:::python
|
||||
|
||||
In LangGraph, nodes are Python functions (either synchronous or asynchronous) that accept the following arguments:
|
||||
|
||||
1. `state`: The [state](#state) of the graph
|
||||
2. `config`: A `RunnableConfig` object that contains configuration information like `thread_id` and tracing information like `tags`
|
||||
3. `runtime`: A `Runtime` object that contains [runtime `context`](#runtime-context) and other information like `store` and `stream_writer`
|
||||
|
||||
|
||||
Similar to `NetworkX`, you add these nodes to a graph using the [add_node][langgraph.graph.StateGraph.add_node] method:
|
||||
Similar to `NetworkX`, you add these nodes to a graph using the @[add_node][add_node] method:
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass
|
||||
@@ -237,47 +439,123 @@ builder.add_node("node_with_config", node_with_config)
|
||||
...
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
In LangGraph, nodes are typically functions (sync or async) that accept the following arguments:
|
||||
|
||||
1. `state`: The [state](#state) of the graph
|
||||
2. `config`: A `RunnableConfig` object that contains configuration information like `thread_id` and tracing information like `tags`
|
||||
|
||||
You can add nodes to a graph using the `addNode` method.
|
||||
|
||||
```typescript
|
||||
import { StateGraph } from "@langchain/langgraph";
|
||||
import { RunnableConfig } from "@langchain/core/runnables";
|
||||
import { z } from "zod";
|
||||
|
||||
const State = z.object({
|
||||
input: z.string(),
|
||||
results: z.string(),
|
||||
});
|
||||
|
||||
const builder = new StateGraph(State);
|
||||
.addNode("myNode", (state, config) => {
|
||||
console.log("In node: ", config?.configurable?.user_id);
|
||||
return { results: `Hello, ${state.input}!` };
|
||||
})
|
||||
addNode("otherNode", (state) => {
|
||||
return state;
|
||||
})
|
||||
...
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Behind the scenes, functions are converted to [RunnableLambda](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.base.RunnableLambda.html#langchain_core.runnables.base.RunnableLambda)s, which add batch and async support to your function, along with native tracing and debugging.
|
||||
|
||||
If you add a node to a graph without specifying a name, it will be given a default name equivalent to the function name.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
builder.add_node(my_node)
|
||||
# You can then create edges to/from this node by referencing it as `"my_node"`
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
builder.addNode(myNode);
|
||||
// You can then create edges to/from this node by referencing it as `"myNode"`
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### `START` Node
|
||||
|
||||
The `START` Node is a special node that represents the node that sends user input to the graph. The main purpose for referencing this node is to determine which nodes should be called first.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from langgraph.graph import START
|
||||
|
||||
graph.add_edge(START, "node_a")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { START } from "@langchain/langgraph";
|
||||
|
||||
graph.addEdge(START, "nodeA");
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### `END` Node
|
||||
|
||||
The `END` Node is a special node that represents a terminal node. This node is referenced when you want to denote which edges have no actions after they are done.
|
||||
|
||||
```
|
||||
:::python
|
||||
|
||||
```python
|
||||
from langgraph.graph import END
|
||||
|
||||
graph.add_edge("node_a", END)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { END } from "@langchain/langgraph";
|
||||
|
||||
graph.addEdge("nodeA", END);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Node Caching
|
||||
|
||||
:::python
|
||||
LangGraph supports caching of tasks/nodes based on the input to the node. To use caching:
|
||||
|
||||
* Specify a cache when compiling a graph (or specifying an entrypoint)
|
||||
* Specify a cache policy for nodes. Each cache policy supports:
|
||||
* `key_func` used to generate a cache key based on the input to a node, which defaults to a `hash` of the input with pickle.
|
||||
* `ttl`, the time to live for the cache in seconds. If not specified, the cache will never expire.
|
||||
- Specify a cache when compiling a graph (or specifying an entrypoint)
|
||||
- Specify a cache policy for nodes. Each cache policy supports:
|
||||
- `key_func` used to generate a cache key based on the input to a node, which defaults to a `hash` of the input with pickle.
|
||||
- `ttl`, the time to live for the cache in seconds. If not specified, the cache will never expire.
|
||||
|
||||
For example:
|
||||
|
||||
```py
|
||||
```python
|
||||
import time
|
||||
from typing_extensions import TypedDict
|
||||
from langgraph.graph import StateGraph
|
||||
@@ -313,6 +591,40 @@ print(graph.invoke({"x": 5}, stream_mode='updates')) # (2)!
|
||||
|
||||
1. First run takes two seconds to run (due to mocked expensive computation).
|
||||
2. Second run utilizes cache and returns quickly.
|
||||
:::
|
||||
|
||||
:::js
|
||||
LangGraph supports caching of tasks/nodes based on the input to the node. To use caching:
|
||||
|
||||
- Specify a cache when compiling a graph (or specifying an entrypoint)
|
||||
- Specify a cache policy for nodes. Each cache policy supports:
|
||||
- `keyFunc`, which is used to generate a cache key based on the input to a node.
|
||||
- `ttl`, the time to live for the cache in seconds. If not specified, the cache will never expire.
|
||||
|
||||
```typescript
|
||||
import { StateGraph, MessagesZodState } from "@langchain/langgraph";
|
||||
import { InMemoryCache } from "@langchain/langgraph-checkpoint";
|
||||
|
||||
const graph = new StateGraph(MessagesZodState)
|
||||
.addNode(
|
||||
"expensive_node",
|
||||
async () => {
|
||||
// Simulate an expensive operation
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000));
|
||||
return { result: 10 };
|
||||
},
|
||||
{ cachePolicy: { ttl: 3 } }
|
||||
)
|
||||
.addEdge(START, "expensive_node")
|
||||
.compile({ cache: new InMemoryCache() });
|
||||
|
||||
await graph.invoke({ x: 5 }, { streamMode: "updates" }); // (1)!
|
||||
// [{"expensive_node": {"result": 10}}]
|
||||
await graph.invoke({ x: 5 }, { streamMode: "updates" }); // (2)!
|
||||
// [{"expensive_node": {"result": 10}, "__metadata__": {"cached": true}}]
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Edges
|
||||
|
||||
@@ -327,15 +639,28 @@ A node can have MULTIPLE outgoing edges. If a node has multiple out-going edges,
|
||||
|
||||
### Normal Edges
|
||||
|
||||
If you **always** want to go from node A to node B, you can use the [add_edge][langgraph.graph.StateGraph.add_edge] method directly.
|
||||
:::python
|
||||
If you **always** want to go from node A to node B, you can use the @[add_edge][add_edge] method directly.
|
||||
|
||||
```python
|
||||
graph.add_edge("node_a", "node_b")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
If you **always** want to go from node A to node B, you can use the @[`addEdge`][add_edge] method directly.
|
||||
|
||||
```typescript
|
||||
graph.addEdge("nodeA", "nodeB");
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Conditional Edges
|
||||
|
||||
If you want to **optionally** route to 1 or more edges (or optionally terminate), you can use the [add_conditional_edges][langgraph.graph.StateGraph.add_conditional_edges] method. This method accepts the name of a node and a "routing function" to call after that node is executed:
|
||||
:::python
|
||||
If you want to **optionally** route to 1 or more edges (or optionally terminate), you can use the @[add_conditional_edges][add_conditional_edges] method. This method accepts the name of a node and a "routing function" to call after that node is executed:
|
||||
|
||||
```python
|
||||
graph.add_conditional_edges("node_a", routing_function)
|
||||
@@ -351,12 +676,37 @@ You can optionally provide a dictionary that maps the `routing_function`'s outpu
|
||||
graph.add_conditional_edges("node_a", routing_function, {True: "node_b", False: "node_c"})
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
If you want to **optionally** route to 1 or more edges (or optionally terminate), you can use the @[`addConditionalEdges`][add_conditional_edges] method. This method accepts the name of a node and a "routing function" to call after that node is executed:
|
||||
|
||||
```typescript
|
||||
graph.addConditionalEdges("nodeA", routingFunction);
|
||||
```
|
||||
|
||||
Similar to nodes, the `routingFunction` accepts the current `state` of the graph and returns a value.
|
||||
|
||||
By default, the return value `routingFunction` is used as the name of the node (or list of nodes) to send the state to next. All those nodes will be run in parallel as a part of the next superstep.
|
||||
|
||||
You can optionally provide an object that maps the `routingFunction`'s output to the name of the next node.
|
||||
|
||||
```typescript
|
||||
graph.addConditionalEdges("nodeA", routingFunction, {
|
||||
true: "nodeB",
|
||||
false: "nodeC",
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
!!! tip
|
||||
Use [`Command`](#command) instead of conditional edges if you want to combine state updates and routing in a single function.
|
||||
Use [`Command`](#command) instead of conditional edges if you want to combine state updates and routing in a single function.
|
||||
|
||||
### Entry Point
|
||||
|
||||
The entry point is the first node(s) that are run when the graph starts. You can use the [`add_edge`][langgraph.graph.StateGraph.add_edge] method from the virtual [`START`][langgraph.constants.START] node to the first node to execute to specify where to enter the graph.
|
||||
:::python
|
||||
The entry point is the first node(s) that are run when the graph starts. You can use the @[`add_edge`][add_edge] method from the virtual @[`START`][START] node to the first node to execute to specify where to enter the graph.
|
||||
|
||||
```python
|
||||
from langgraph.graph import START
|
||||
@@ -364,9 +714,23 @@ from langgraph.graph import START
|
||||
graph.add_edge(START, "node_a")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
The entry point is the first node(s) that are run when the graph starts. You can use the @[`addEdge`][add_edge] method from the virtual @[`START`][START] node to the first node to execute to specify where to enter the graph.
|
||||
|
||||
```typescript
|
||||
import { START } from "@langchain/langgraph";
|
||||
|
||||
graph.addEdge(START, "nodeA");
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Conditional Entry Point
|
||||
|
||||
A conditional entry point lets you start at different nodes depending on custom logic. You can use [`add_conditional_edges`][langgraph.graph.StateGraph.add_conditional_edges] from the virtual [`START`][langgraph.constants.START] node to accomplish this.
|
||||
:::python
|
||||
A conditional entry point lets you start at different nodes depending on custom logic. You can use @[`add_conditional_edges`][add_conditional_edges] from the virtual @[`START`][START] node to accomplish this.
|
||||
|
||||
```python
|
||||
from langgraph.graph import START
|
||||
@@ -380,11 +744,34 @@ You can optionally provide a dictionary that maps the `routing_function`'s outpu
|
||||
graph.add_conditional_edges(START, routing_function, {True: "node_b", False: "node_c"})
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
A conditional entry point lets you start at different nodes depending on custom logic. You can use @[`addConditionalEdges`][add_conditional_edges] from the virtual @[`START`][START] node to accomplish this.
|
||||
|
||||
```typescript
|
||||
import { START } from "@langchain/langgraph";
|
||||
|
||||
graph.addConditionalEdges(START, routingFunction);
|
||||
```
|
||||
|
||||
You can optionally provide an object that maps the `routingFunction`'s output to the name of the next node.
|
||||
|
||||
```typescript
|
||||
graph.addConditionalEdges(START, routingFunction, {
|
||||
true: "nodeB",
|
||||
false: "nodeC",
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## `Send`
|
||||
|
||||
:::python
|
||||
By default, `Nodes` and `Edges` are defined ahead of time and operate on the same shared state. However, there can be cases where the exact edges are not known ahead of time and/or you may want different versions of `State` to exist at the same time. A common example of this is with [map-reduce](https://langchain-ai.github.io/langgraph/how-tos/map-reduce/) design patterns. In this design pattern, a first node may generate a list of objects, and you may want to apply some other node to all those objects. The number of objects may be unknown ahead of time (meaning the number of edges may not be known) and the input `State` to the downstream `Node` should be different (one for each generated object).
|
||||
|
||||
To support this design pattern, LangGraph supports returning [`Send`][langgraph.types.Send] objects from conditional edges. `Send` takes two arguments: first is the name of the node, and second is the state to pass to that node.
|
||||
To support this design pattern, LangGraph supports returning @[`Send`][Send] objects from conditional edges. `Send` takes two arguments: first is the name of the node, and second is the state to pass to that node.
|
||||
|
||||
```python
|
||||
def continue_to_jokes(state: OverallState):
|
||||
@@ -393,9 +780,27 @@ def continue_to_jokes(state: OverallState):
|
||||
graph.add_conditional_edges("node_a", continue_to_jokes)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
By default, `Nodes` and `Edges` are defined ahead of time and operate on the same shared state. However, there can be cases where the exact edges are not known ahead of time and/or you may want different versions of `State` to exist at the same time. A common example of this is with map-reduce design patterns. In this design pattern, a first node may generate a list of objects, and you may want to apply some other node to all those objects. The number of objects may be unknown ahead of time (meaning the number of edges may not be known) and the input `State` to the downstream `Node` should be different (one for each generated object).
|
||||
|
||||
To support this design pattern, LangGraph supports returning @[`Send`][Send] objects from conditional edges. `Send` takes two arguments: first is the name of the node, and second is the state to pass to that node.
|
||||
|
||||
```typescript
|
||||
import { Send } from "@langchain/langgraph";
|
||||
|
||||
graph.addConditionalEdges("nodeA", (state) => {
|
||||
return state.subjects.map((subject) => new Send("generateJoke", { subject }));
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## `Command`
|
||||
|
||||
It can be useful to combine control flow (edges) and state updates (nodes). For example, you might want to BOTH perform state updates AND decide which node to go to next in the SAME node. LangGraph provides a way to do so by returning a [`Command`][langgraph.types.Command] object from node functions:
|
||||
:::python
|
||||
It can be useful to combine control flow (edges) and state updates (nodes). For example, you might want to BOTH perform state updates AND decide which node to go to next in the SAME node. LangGraph provides a way to do so by returning a @[`Command`][Command] object from node functions:
|
||||
|
||||
```python
|
||||
def my_node(state: State) -> Command[Literal["my_other_node"]]:
|
||||
@@ -415,6 +820,49 @@ def my_node(state: State) -> Command[Literal["my_other_node"]]:
|
||||
return Command(update={"foo": "baz"}, goto="my_other_node")
|
||||
```
|
||||
|
||||
Check out this [how-to guide](../how-tos/graph-api.ipynb#combine-control-flow-and-state-updates-with-command) for an end-to-end example of how to use `Command`.
|
||||
:::
|
||||
|
||||
:::js
|
||||
It can be useful to combine control flow (edges) and state updates (nodes). For example, you might want to BOTH perform state updates AND decide which node to go to next in the SAME node. LangGraph provides a way to do so by returning a `Command` object from node functions:
|
||||
|
||||
```typescript
|
||||
import { Command } from "@langchain/langgraph";
|
||||
|
||||
graph.addNode("myNode", (state) => {
|
||||
return new Command({
|
||||
update: { foo: "bar" },
|
||||
goto: "myOtherNode",
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
With `Command` you can also achieve dynamic control flow behavior (identical to [conditional edges](#conditional-edges)):
|
||||
|
||||
```typescript
|
||||
import { Command } from "@langchain/langgraph";
|
||||
|
||||
graph.addNode("myNode", (state) => {
|
||||
if (state.foo === "bar") {
|
||||
return new Command({
|
||||
update: { foo: "baz" },
|
||||
goto: "myOtherNode",
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
When using `Command` in your node functions, you must add the `ends` parameter when adding the node to specify which nodes it can route to:
|
||||
|
||||
```typescript
|
||||
builder.addNode("myNode", myNode, {
|
||||
ends: ["myOtherNode", END],
|
||||
});
|
||||
```
|
||||
|
||||
Check out this [how-to guide](../how-tos/graph-api.ipynb#combine-control-flow-and-state-updates-with-command) for an end-to-end example of how to use `Command`.
|
||||
:::
|
||||
|
||||
!!! important
|
||||
|
||||
When returning `Command` in your node functions, you must add return type annotations with the list of node names the node is routing to, e.g. `Command[Literal["my_other_node"]]`. This is necessary for the graph rendering and tells LangGraph that `my_node` can navigate to `my_other_node`.
|
||||
@@ -423,12 +871,12 @@ Check out this [how-to guide](../how-tos/graph-api.md#combine-control-flow-and-s
|
||||
|
||||
### When should I use Command instead of conditional edges?
|
||||
|
||||
Use `Command` when you need to **both** update the graph state **and** route to a different node. For example, when implementing [multi-agent handoffs](./multi_agent.md#handoffs) where it's important to route to a different agent and pass some information to that agent.
|
||||
|
||||
Use [conditional edges](#conditional-edges) to route between nodes conditionally without updating the state.
|
||||
- Use `Command` when you need to **both** update the graph state **and** route to a different node. For example, when implementing [multi-agent handoffs](./multi_agent.md#handoffs) where it's important to route to a different agent and pass some information to that agent.
|
||||
- Use [conditional edges](#conditional-edges) to route between nodes conditionally without updating the state.
|
||||
|
||||
### Navigating to a node in a parent graph
|
||||
|
||||
:::python
|
||||
If you are using [subgraphs](./subgraphs.md), you might want to navigate from a node within a subgraph to a different subgraph (i.e. a different node in the parent graph). To do so, you can specify `graph=Command.PARENT` in `Command`:
|
||||
|
||||
```python
|
||||
@@ -448,6 +896,58 @@ def my_node(state: State) -> Command[Literal["other_subgraph"]]:
|
||||
|
||||
When you send updates from a subgraph node to a parent graph node for a key that's shared by both parent and subgraph [state schemas](#schema), you **must** define a [reducer](#reducers) for the key you're updating in the parent graph state. See this [example](../how-tos/graph-api.md#navigate-to-a-node-in-a-parent-graph).
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
If you are using [subgraphs](./subgraphs.md), you might want to navigate from a node within a subgraph to a different subgraph (i.e. a different node in the parent graph). To do so, you can specify `graph: Command.PARENT` in `Command`:
|
||||
|
||||
```typescript
|
||||
import { Command } from "@langchain/langgraph";
|
||||
|
||||
graph.addNode("myNode", (state) => {
|
||||
return new Command({
|
||||
update: { foo: "bar" },
|
||||
goto: "otherSubgraph", // where `otherSubgraph` is a node in the parent graph
|
||||
graph: Command.PARENT,
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
Setting `graph` to `Command.PARENT` will navigate to the closest parent graph.
|
||||
|
||||
!!! important "State updates with `Command.PARENT`"
|
||||
|
||||
When you send updates from a subgraph node to a parent graph node for a key that's shared by both parent and subgraph [state schemas](#schema), you **must** define a [reducer](#reducers) for the key you're updating in the parent graph state.
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
If you are using [subgraphs](./subgraphs.md), you might want to navigate from a node within a subgraph to a different subgraph (i.e. a different node in the parent graph). To do so, you can specify `graph: Command.PARENT` in `Command`:
|
||||
|
||||
```typescript
|
||||
import { Command } from "@langchain/langgraph";
|
||||
|
||||
graph.addNode("myNode", (state) => {
|
||||
return new Command({
|
||||
update: { foo: "bar" },
|
||||
goto: "otherSubgraph", // where `otherSubgraph` is a node in the parent graph
|
||||
graph: Command.PARENT,
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
Setting `graph` to `Command.PARENT` will navigate to the closest parent graph.
|
||||
|
||||
!!! important "State updates with `Command.PARENT`"
|
||||
|
||||
When you send updates from a subgraph node to a parent graph node for a key that's shared by both parent and subgraph [state schemas](#schema), you **must** define a [reducer](#reducers) for the key you're updating in the parent graph state.
|
||||
|
||||
:::
|
||||
|
||||
This is particularly useful when implementing [multi-agent handoffs](./multi_agent.md#handoffs).
|
||||
|
||||
Check out [this guide](../how-tos/graph-api.md#navigate-to-a-node-in-a-parent-graph) for detail.
|
||||
@@ -460,7 +960,13 @@ Refer to [this guide](../how-tos/graph-api.md#use-inside-tools) for detail.
|
||||
|
||||
### Human-in-the-loop
|
||||
|
||||
:::python
|
||||
`Command` is an important part of human-in-the-loop workflows: when using `interrupt()` to collect user input, `Command` is then used to supply the input and resume execution via `Command(resume="User input")`. Check out [this conceptual guide](./human_in_the_loop.md) for more information.
|
||||
:::
|
||||
|
||||
:::js
|
||||
`Command` is an important part of human-in-the-loop workflows: when using `interrupt()` to collect user input, `Command` is then used to supply the input and resume execution via `new Command({ resume: "User input" })`. Check out the [human-in-the-loop conceptual guide](./human_in_the_loop.md) for more information.
|
||||
:::
|
||||
|
||||
## Graph Migrations
|
||||
|
||||
@@ -472,6 +978,8 @@ LangGraph can easily handle migrations of graph definitions (nodes, edges, and s
|
||||
- State keys that are renamed lose their saved state in existing threads
|
||||
- State keys whose types change in incompatible ways could currently cause issues in threads with state from before the change -- if this is a blocker please reach out and we can prioritize a solution.
|
||||
|
||||
:::python
|
||||
|
||||
## Runtime Context
|
||||
|
||||
When creating a graph, you can specify a `context_schema` for runtime context passed to nodes. This is useful for passing
|
||||
@@ -485,12 +993,46 @@ class ContextSchema:
|
||||
graph = StateGraph(State, context_schema=ContextSchema)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
When creating a graph, you can also mark that certain parts of the graph are configurable. This is commonly done to enable easily switching between models or system prompts. This allows you to create a single "cognitive architecture" (the graph) but have multiple different instance of it.
|
||||
|
||||
You can optionally specify a config schema when creating a graph.
|
||||
|
||||
```typescript
|
||||
import { z } from "zod";
|
||||
|
||||
const ConfigSchema = z.object({
|
||||
llm: z.string(),
|
||||
});
|
||||
|
||||
const graph = new StateGraph(State, ConfigSchema);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::python
|
||||
You can then pass this context into the graph using the `context` parameter of the `invoke` method.
|
||||
|
||||
```python
|
||||
graph.invoke(inputs, context={"llm_provider": "anthropic"})
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
You can then pass this configuration into the graph using the `configurable` config field.
|
||||
|
||||
```typescript
|
||||
const config = { configurable: { llm: "anthropic" } };
|
||||
|
||||
await graph.invoke(inputs, config);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
You can then access and use this context inside a node or conditional edge:
|
||||
|
||||
```python
|
||||
@@ -501,10 +1043,24 @@ def node_a(state: State, runtime: Runtime[ContextSchema]):
|
||||
...
|
||||
```
|
||||
|
||||
See [this guide](../how-tos/graph-api.md#add-runtime-configuration) for a full breakdown on configuration.
|
||||
See [this guide](../how-tos/graph-api.ipynb#add-runtime-configuration) for a full breakdown on configuration.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
graph.addNode("myNode", (state, config) => {
|
||||
const llmType = config?.configurable?.llm || "openai";
|
||||
const llm = getLlm(llmType);
|
||||
return { results: `Hello, ${state.input}!` };
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Recursion Limit
|
||||
|
||||
:::python
|
||||
The recursion limit sets the maximum number of [super-steps](#graphs) the graph can execute during a single execution. Once the limit is reached, LangGraph will raise `GraphRecursionError`. By default this value is set to 25 steps. The recursion limit can be set on any graph at runtime, and is passed to `.invoke`/`.stream` via the config dictionary. Importantly, `recursion_limit` is a standalone `config` key and should not be passed inside the `configurable` key as all other user-defined configuration. See the example below:
|
||||
|
||||
```python
|
||||
@@ -512,6 +1068,19 @@ graph.invoke(inputs, config={"recursion_limit": 5}, context={"llm": "anthropic"}
|
||||
```
|
||||
|
||||
Read [this how-to](https://langchain-ai.github.io/langgraph/how-tos/recursion-limit/) to learn more about how the recursion limit works.
|
||||
:::
|
||||
|
||||
:::js
|
||||
The recursion limit sets the maximum number of [super-steps](#graphs) the graph can execute during a single execution. Once the limit is reached, LangGraph will raise `GraphRecursionError`. By default this value is set to 25 steps. The recursion limit can be set on any graph at runtime, and is passed to `.invoke`/`.stream` via the config object. Importantly, `recursionLimit` is a standalone `config` key and should not be passed inside the `configurable` key as all other user-defined configuration. See the example below:
|
||||
|
||||
```typescript
|
||||
await graph.invoke(inputs, {
|
||||
recursionLimit: 5,
|
||||
configurable: { llm: "anthropic" },
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Visualization
|
||||
|
||||
|
||||
@@ -12,9 +12,10 @@ pip install langchain-mcp-adapters
|
||||
|
||||
## Authenticate to an MCP server
|
||||
|
||||
You can set up [custom authentication middleware](../how-tos/auth/custom_auth.md) to authenticate a user with an MCP server to get access to user-scoped tools within your LangGraph Platform deployment.
|
||||
You can set up [custom authentication middleware](../how-tos/auth/custom_auth.md) to authenticate a user with an MCP server to get access to user-scoped tools within your LangGraph Platform deployment.
|
||||
|
||||
!!! note
|
||||
|
||||
Custom authentication is a LangGraph Platform feature.
|
||||
|
||||
An example architecture for this flow:
|
||||
@@ -53,5 +54,4 @@ sequenceDiagram
|
||||
LangGraph -->> ClientApp: 12. Return resources / tool output
|
||||
```
|
||||
|
||||
For more information, see [MCP endpoint in LangGraph Server](../concepts/server-mcp.md#use-user-scoped-mcp-tools-in-your-deployment).
|
||||
|
||||
For more information, see [MCP endpoint in LangGraph Server](../concepts/server-mcp.md).
|
||||
|
||||
@@ -87,11 +87,25 @@ Regardless of memory management approach, the central point is that the agent wi
|
||||
|
||||
[Episodic memory](https://en.wikipedia.org/wiki/Episodic_memory), in both humans and AI agents, involves recalling past events or actions. The [CoALA paper](https://arxiv.org/pdf/2309.02427) frames this well: facts can be written to semantic memory, whereas *experiences* can be written to episodic memory. For AI agents, episodic memory is often used to help an agent remember how to accomplish a task.
|
||||
|
||||
:::python
|
||||
In practice, episodic memories are often implemented through [few-shot example prompting](https://python.langchain.com/docs/concepts/few_shot_prompting/), where agents learn from past sequences to perform tasks correctly. Sometimes it's easier to "show" than "tell" and LLMs learn well from examples. Few-shot learning lets you ["program"](https://x.com/karpathy/status/1627366413840322562) your LLM by updating the prompt with input-output examples to illustrate the intended behavior. While various [best-practices](https://python.langchain.com/docs/concepts/#1-generating-examples) can be used to generate few-shot examples, often the challenge lies in selecting the most relevant examples based on user input.
|
||||
:::
|
||||
|
||||
:::js
|
||||
In practice, episodic memories are often implemented through few-shot example prompting, where agents learn from past sequences to perform tasks correctly. Sometimes it's easier to "show" than "tell" and LLMs learn well from examples. Few-shot learning lets you ["program"](https://x.com/karpathy/status/1627366413840322562) your LLM by updating the prompt with input-output examples to illustrate the intended behavior. While various best-practices can be used to generate few-shot examples, often the challenge lies in selecting the most relevant examples based on user input.
|
||||
:::
|
||||
|
||||
:::python
|
||||
Note that the memory [store](persistence.md#memory-store) is just one way to store data as few-shot examples. If you want to have more developer involvement, or tie few-shots more closely to your evaluation harness, you can also use a [LangSmith Dataset](https://docs.smith.langchain.com/evaluation/how_to_guides/datasets/index_datasets_for_dynamic_few_shot_example_selection) to store your data. Then dynamic few-shot example selectors can be used out-of-the box to achieve this same goal. LangSmith will index the dataset for you and enable retrieval of few shot examples that are most relevant to the user input based upon keyword similarity ([using a BM25-like algorithm](https://docs.smith.langchain.com/how_to_guides/datasets/index_datasets_for_dynamic_few_shot_example_selection) for keyword based similarity).
|
||||
|
||||
See this how-to [video](https://www.youtube.com/watch?v=37VaU7e7t5o) for example usage of dynamic few-shot example selection in LangSmith. Also, see this [blog post](https://blog.langchain.dev/few-shot-prompting-to-improve-tool-calling-performance/) showcasing few-shot prompting to improve tool calling performance and this [blog post](https://blog.langchain.dev/aligning-llm-as-a-judge-with-human-preferences/) using few-shot example to align an LLMs to human preferences.
|
||||
:::
|
||||
|
||||
:::js
|
||||
Note that the memory [store](persistence.md#memory-store) is just one way to store data as few-shot examples. If you want to have more developer involvement, or tie few-shots more closely to your evaluation harness, you can also use a LangSmith Dataset to store your data. Then dynamic few-shot example selectors can be used out-of-the box to achieve this same goal. LangSmith will index the dataset for you and enable retrieval of few shot examples that are most relevant to the user input based upon keyword similarity.
|
||||
|
||||
See this how-to [video](https://www.youtube.com/watch?v=37VaU7e7t5o) for example usage of dynamic few-shot example selection in LangSmith. Also, see this [blog post](https://blog.langchain.dev/few-shot-prompting-to-improve-tool-calling-performance/) showcasing few-shot prompting to improve tool calling performance and this [blog post](https://blog.langchain.dev/aligning-llm-as-a-judge-with-human-preferences/) using few-shot example to align an LLMs to human preferences.
|
||||
:::
|
||||
|
||||
#### Procedural memory
|
||||
|
||||
@@ -105,6 +119,7 @@ For example, we built a [Tweet generator](https://www.youtube.com/watch?v=Vn8A3B
|
||||
|
||||
The below pseudo-code shows how you might implement this with the LangGraph memory [store](persistence.md#memory-store), using the store to save a prompt, the `update_instructions` node to get the current prompt (as well as feedback from the conversation with the user captured in `state["messages"]`), update the prompt, and save the new prompt back to the store. Then, the `call_model` get the updated prompt from the store and uses it to generate a response.
|
||||
|
||||
:::python
|
||||
```python
|
||||
# Node that *uses* the instructions
|
||||
def call_model(state: State, store: BaseStore):
|
||||
@@ -125,6 +140,39 @@ def update_instructions(state: State, store: BaseStore):
|
||||
store.put(("agent_instructions",), "agent_a", {"instructions": new_instructions})
|
||||
...
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
// Node that *uses* the instructions
|
||||
const callModel = async (state: State, store: BaseStore) => {
|
||||
const namespace = ["agent_instructions"];
|
||||
const instructions = await store.get(namespace, "agent_a");
|
||||
// Application logic
|
||||
const prompt = promptTemplate.format({
|
||||
instructions: instructions[0].value.instructions
|
||||
});
|
||||
// ...
|
||||
};
|
||||
|
||||
// Node that updates instructions
|
||||
const updateInstructions = async (state: State, store: BaseStore) => {
|
||||
const namespace = ["instructions"];
|
||||
const currentInstructions = await store.search(namespace);
|
||||
// Memory logic
|
||||
const prompt = promptTemplate.format({
|
||||
instructions: currentInstructions[0].value.instructions,
|
||||
conversation: state.messages
|
||||
});
|
||||
const output = await llm.invoke(prompt);
|
||||
const newInstructions = output.new_instructions;
|
||||
await store.put(["agent_instructions"], "agent_a", {
|
||||
instructions: newInstructions
|
||||
});
|
||||
// ...
|
||||
};
|
||||
```
|
||||
:::
|
||||
|
||||

|
||||
|
||||
@@ -154,6 +202,7 @@ See our [memory-service](https://github.com/langchain-ai/memory-template) templa
|
||||
|
||||
LangGraph stores long-term memories as JSON documents in a [store](persistence.md#memory-store). Each memory is organized under a custom `namespace` (similar to a folder) and a distinct `key` (like a file name). Namespaces often include user or org IDs or other labels that makes it easier to organize information. This structure enables hierarchical organization of memories. Cross-namespace searching is then supported through content filters.
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langgraph.store.memory import InMemoryStore
|
||||
|
||||
@@ -186,5 +235,47 @@ items = store.search(
|
||||
namespace, filter={"my-key": "my-value"}, query="language preferences"
|
||||
)
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { InMemoryStore } from "@langchain/langgraph";
|
||||
|
||||
const embed = (texts: string[]): number[][] => {
|
||||
// Replace with an actual embedding function or LangChain embeddings object
|
||||
return texts.map(() => [1.0, 2.0]);
|
||||
};
|
||||
|
||||
// InMemoryStore saves data to an in-memory dictionary. Use a DB-backed store in production use.
|
||||
const store = new InMemoryStore({ index: { embed, dims: 2 } });
|
||||
const userId = "my-user";
|
||||
const applicationContext = "chitchat";
|
||||
const namespace = [userId, applicationContext];
|
||||
|
||||
await store.put(
|
||||
namespace,
|
||||
"a-memory",
|
||||
{
|
||||
rules: [
|
||||
"User likes short, direct language",
|
||||
"User only speaks English & TypeScript",
|
||||
],
|
||||
"my-key": "my-value",
|
||||
}
|
||||
);
|
||||
|
||||
// get the "memory" by ID
|
||||
const item = await store.get(namespace, "a-memory");
|
||||
|
||||
// search for "memories" within this namespace, filtering on content equivalence, sorted by vector similarity
|
||||
const items = await store.search(
|
||||
namespace,
|
||||
{
|
||||
filter: { "my-key": "my-value" },
|
||||
query: "language preferences"
|
||||
}
|
||||
);
|
||||
```
|
||||
:::
|
||||
|
||||
For more information about the memory store, see the [Persistence](persistence.md#memory-store) guide.
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
search:
|
||||
boost: 2
|
||||
---
|
||||
|
||||
# Multi-agent systems
|
||||
|
||||
An [agent](./agentic_concepts.md#agent-architectures) is _a system that uses an LLM to decide the control flow of an application_. As you develop these systems, they might grow more complex over time, making them harder to manage and scale. For example, you might run into the following problems:
|
||||
@@ -25,21 +20,23 @@ The primary benefits of using multi-agent systems are:
|
||||
|
||||
There are several ways to connect agents in a multi-agent system:
|
||||
|
||||
- **Network**: each agent can communicate with [every other agent](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/multi-agent-collaboration/). Any agent can decide which other agent to call next.
|
||||
- **Supervisor**: each agent communicates with a single [supervisor](../tutorials/multi_agent/agent_supervisor.md) agent. Supervisor agent makes decisions on which agent should be called next.
|
||||
- **Network**: each agent can communicate with [every other agent](../tutorials/multi_agent/multi-agent-collaboration.ipynb/). Any agent can decide which other agent to call next.
|
||||
- **Supervisor**: each agent communicates with a single [supervisor](../tutorials/multi_agent/agent_supervisor.md/) agent. Supervisor agent makes decisions on which agent should be called next.
|
||||
- **Supervisor (tool-calling)**: this is a special case of supervisor architecture. Individual agents can be represented as tools. In this case, a supervisor agent uses a tool-calling LLM to decide which of the agent tools to call, as well as the arguments to pass to those agents.
|
||||
- **Hierarchical**: you can define a multi-agent system with [a supervisor of supervisors](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/hierarchical_agent_teams/). This is a generalization of the supervisor architecture and allows for more complex control flows.
|
||||
- **Hierarchical**: you can define a multi-agent system with [a supervisor of supervisors](../tutorials/multi_agent/hierarchical_agent_teams.ipynb/). This is a generalization of the supervisor architecture and allows for more complex control flows.
|
||||
- **Custom multi-agent workflow**: each agent communicates with only a subset of agents. Parts of the flow are deterministic, and only some agents can decide which other agents to call next.
|
||||
|
||||
### Handoffs
|
||||
|
||||
In multi-agent architectures, agents can be represented as graph nodes. Each agent node executes its step(s) and decides whether to finish execution or route to another agent, including potentially routing to itself (e.g., running in a loop). A common pattern in multi-agent interactions is **handoffs**, where one agent *hands off* control to another. Handoffs allow you to specify:
|
||||
In multi-agent architectures, agents can be represented as graph nodes. Each agent node executes its step(s) and decides whether to finish execution or route to another agent, including potentially routing to itself (e.g., running in a loop). A common pattern in multi-agent interactions is **handoffs**, where one agent _hands off_ control to another. Handoffs allow you to specify:
|
||||
|
||||
- __destination__: target agent to navigate to (e.g., name of the node to go to)
|
||||
- __payload__: [information to pass to that agent](#communication-and-state-management) (e.g., state update)
|
||||
- **destination**: target agent to navigate to (e.g., name of the node to go to)
|
||||
- **payload**: [information to pass to that agent](#communication-and-state-management) (e.g., state update)
|
||||
|
||||
To implement handoffs in LangGraph, agent nodes can return [`Command`](./low_level.md#command) object that allows you to combine both control flow and state updates:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
def agent(state) -> Command[Literal["agent", "another_agent"]]:
|
||||
# the condition for routing/halting can be anything, e.g. LLM tool call / structured output, etc.
|
||||
@@ -52,6 +49,26 @@ def agent(state) -> Command[Literal["agent", "another_agent"]]:
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
graph.addNode((state) => {
|
||||
// the condition for routing/halting can be anything, e.g. LLM tool call / structured output, etc.
|
||||
const goto = getNextAgent(...); // 'agent' / 'another_agent'
|
||||
return new Command({
|
||||
// Specify which agent to call next
|
||||
goto,
|
||||
// Update the graph state
|
||||
update: { myStateKey: "myStateValue" }
|
||||
});
|
||||
})
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::python
|
||||
In a more complex scenario where each agent node is itself a graph (i.e., a [subgraph](./subgraphs.md)), a node in one of the agent subgraphs might want to navigate to a different agent. For example, if you have two agents, `alice` and `bob` (subgraph nodes in a parent graph), and `alice` needs to navigate to `bob`, you can set `graph=Command.PARENT` in the `Command` object:
|
||||
|
||||
```python
|
||||
@@ -64,8 +81,30 @@ def some_node_inside_alice(state):
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
In a more complex scenario where each agent node is itself a graph (i.e., a [subgraph](./subgraphs.md)), a node in one of the agent subgraphs might want to navigate to a different agent. For example, if you have two agents, `alice` and `bob` (subgraph nodes in a parent graph), and `alice` needs to navigate to `bob`, you can set `graph: Command.PARNT` in the `Command` object:
|
||||
|
||||
```typescript
|
||||
alice.addNode((state) => {
|
||||
return new Command({
|
||||
goto: "bob",
|
||||
update: { myStateKey: "myStateValue" },
|
||||
// specify which graph to navigate to (defaults to the current graph)
|
||||
graph: Command.PARENT,
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
!!! note
|
||||
If you need to support visualization for subgraphs communicating using `Command(graph=Command.PARENT)` you would need to wrap them in a node function with `Command` annotation, e.g. instead of this:
|
||||
|
||||
:::python
|
||||
|
||||
If you need to support visualization for subgraphs communicating using `Command(graph=Command.PARENT)` you would need to wrap them in a node function with `Command` annotation:
|
||||
Instead of this:
|
||||
|
||||
```python
|
||||
builder.add_node(alice)
|
||||
@@ -80,9 +119,30 @@ def some_node_inside_alice(state):
|
||||
builder.add_node("alice", call_alice)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
If you need to support visualization for subgraphs communicating using/ `Command({ graph: Command.PARENT })` you would need to wrap them in a node function with `Command` annotation:
|
||||
|
||||
Instead of this:
|
||||
|
||||
```typescript
|
||||
builder.addNode("alice", alice);
|
||||
```
|
||||
|
||||
you would need to do this:
|
||||
|
||||
```typescript
|
||||
builder.addNode("alice", (state) => alice.invoke(state), { ends: ["bob"] });
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
#### Handoffs as tools
|
||||
|
||||
One of the most common agent types is a [tool-calling agent](../agents/overview.md). For those types of agents, a common pattern is wrapping a handoff in a tool call, e.g.:
|
||||
One of the most common agent types is a [tool-calling agent](../agents/overview.md). For those types of agents, a common pattern is wrapping a handoff in a tool call:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from langchain_core.tools import tool
|
||||
@@ -101,18 +161,65 @@ def transfer_to_bob():
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { Command } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
const transferToBob = tool(
|
||||
async () => {
|
||||
return new Command({
|
||||
// name of the agent (node) to go to
|
||||
goto: "bob",
|
||||
// data to send to the agent
|
||||
update: { myStateKey: "myStateValue" },
|
||||
// indicate to LangGraph that we need to navigate to
|
||||
// agent node in a parent graph
|
||||
graph: Command.PARENT,
|
||||
});
|
||||
},
|
||||
{
|
||||
name: "transfer_to_bob",
|
||||
description: "Transfer to bob.",
|
||||
schema: z.object({}),
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
This is a special case of updating the graph state from tools where, in addition to the state update, the control flow is included as well.
|
||||
|
||||
!!! important
|
||||
|
||||
If you want to use tools that return `Command`, you can either use prebuilt [`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent] / [`ToolNode`][langgraph.prebuilt.tool_node.ToolNode] components, or implement your own tool-executing node that collects `Command` objects returned by the tools and returns a list of them, e.g.:
|
||||
|
||||
```python
|
||||
def call_tools(state):
|
||||
...
|
||||
commands = [tools_by_name[tool_call["name"]].invoke(tool_call) for tool_call in tool_calls]
|
||||
return commands
|
||||
```
|
||||
:::python
|
||||
If you want to use tools that return `Command`, you can use the prebuilt @[`create_react_agent`][create_react_agent] / @[`ToolNode`][ToolNode] components, or else implement your own logic:
|
||||
|
||||
```python
|
||||
def call_tools(state):
|
||||
...
|
||||
commands = [tools_by_name[tool_call["name"]].invoke(tool_call) for tool_call in tool_calls]
|
||||
return commands
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
If you want to use tools that return `Command`, you can use the prebuilt @[`createReactAgent`][create_react_agent] / @[ToolNode] components, or else implement your own logic:
|
||||
|
||||
```typescript
|
||||
graph.addNode("call_tools", async (state) => {
|
||||
// ... tool execution logic
|
||||
const commands = toolCalls.map((toolCall) =>
|
||||
toolsByName[toolCall.name].invoke(toolCall)
|
||||
);
|
||||
return commands;
|
||||
});
|
||||
```
|
||||
:::
|
||||
|
||||
Let's now take a closer look at the different multi-agent architectures.
|
||||
|
||||
@@ -120,6 +227,7 @@ Let's now take a closer look at the different multi-agent architectures.
|
||||
|
||||
In this architecture, agents are defined as graph nodes. Each agent can communicate with every other agent (many-to-many connections) and can decide which agent to call next. This architecture is good for problems that do not have a clear hierarchy of agents or a specific sequence in which agents should be called.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from typing import Literal
|
||||
@@ -164,10 +272,70 @@ builder.add_edge(START, "agent_1")
|
||||
network = builder.compile()
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { StateGraph, MessagesZodState, START, END } from "@langchain/langgraph";
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { Command } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
const model = new ChatOpenAI();
|
||||
|
||||
const agent1 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
// you can pass relevant parts of the state to the LLM (e.g., state.messages)
|
||||
// to determine which agent to call next. a common pattern is to call the model
|
||||
// with a structured output (e.g. force it to return an output with a "next_agent" field)
|
||||
const response = await model.invoke(...);
|
||||
// route to one of the agents or exit based on the LLM's decision
|
||||
// if the LLM returns "__end__", the graph will finish execution
|
||||
return new Command({
|
||||
goto: response.nextAgent,
|
||||
update: { messages: [response.content] },
|
||||
});
|
||||
};
|
||||
|
||||
const agent2 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
const response = await model.invoke(...);
|
||||
return new Command({
|
||||
goto: response.nextAgent,
|
||||
update: { messages: [response.content] },
|
||||
});
|
||||
};
|
||||
|
||||
const agent3 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
// ...
|
||||
return new Command({
|
||||
goto: response.nextAgent,
|
||||
update: { messages: [response.content] },
|
||||
});
|
||||
};
|
||||
|
||||
const builder = new StateGraph(MessagesZodState)
|
||||
.addNode("agent1", agent1, {
|
||||
ends: ["agent2", "agent3", END]
|
||||
})
|
||||
.addNode("agent2", agent2, {
|
||||
ends: ["agent1", "agent3", END]
|
||||
})
|
||||
.addNode("agent3", agent3, {
|
||||
ends: ["agent1", "agent2", END]
|
||||
})
|
||||
.addEdge(START, "agent1");
|
||||
|
||||
const network = builder.compile();
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Supervisor
|
||||
|
||||
In this architecture, we define agents as nodes and add a supervisor node (LLM) that decides which agent nodes should be called next. We use [`Command`](./low_level.md#command) to route execution to the appropriate agent node based on supervisor's decision. This architecture also lends itself well to running multiple agents in parallel or using [map-reduce](../how-tos/graph-api.md#map-reduce-and-the-send-api) pattern.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from typing import Literal
|
||||
from langchain_openai import ChatOpenAI
|
||||
@@ -211,12 +379,124 @@ builder.add_edge(START, "supervisor")
|
||||
supervisor = builder.compile()
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { StateGraph, MessagesZodState, Command, START, END } from "@langchain/langgraph";
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { z } from "zod";
|
||||
|
||||
const model = new ChatOpenAI();
|
||||
|
||||
const supervisor = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
// you can pass relevant parts of the state to the LLM (e.g., state.messages)
|
||||
// to determine which agent to call next. a common pattern is to call the model
|
||||
// with a structured output (e.g. force it to return an output with a "next_agent" field)
|
||||
const response = await model.invoke(...);
|
||||
// route to one of the agents or exit based on the supervisor's decision
|
||||
// if the supervisor returns "__end__", the graph will finish execution
|
||||
return new Command({ goto: response.nextAgent });
|
||||
};
|
||||
|
||||
const agent1 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
// you can pass relevant parts of the state to the LLM (e.g., state.messages)
|
||||
// and add any additional logic (different models, custom prompts, structured output, etc.)
|
||||
const response = await model.invoke(...);
|
||||
return new Command({
|
||||
goto: "supervisor",
|
||||
update: { messages: [response] },
|
||||
});
|
||||
};
|
||||
|
||||
const agent2 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
const response = await model.invoke(...);
|
||||
return new Command({
|
||||
goto: "supervisor",
|
||||
update: { messages: [response] },
|
||||
});
|
||||
};
|
||||
|
||||
const builder = new StateGraph(MessagesZodState)
|
||||
.addNode("supervisor", supervisor, {
|
||||
ends: ["agent1", "agent2", END]
|
||||
})
|
||||
.addNode("agent1", agent1, {
|
||||
ends: ["supervisor"]
|
||||
})
|
||||
.addNode("agent2", agent2, {
|
||||
ends: ["supervisor"]
|
||||
})
|
||||
.addEdge(START, "supervisor");
|
||||
|
||||
const supervisorGraph = builder.compile();
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { StateGraph, MessagesZodState, Command, START, END } from "@langchain/langgraph";
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { z } from "zod";
|
||||
|
||||
const model = new ChatOpenAI();
|
||||
|
||||
const supervisor = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
// you can pass relevant parts of the state to the LLM (e.g., state.messages)
|
||||
// to determine which agent to call next. a common pattern is to call the model
|
||||
// with a structured output (e.g. force it to return an output with a "next_agent" field)
|
||||
const response = await model.invoke(...);
|
||||
// route to one of the agents or exit based on the supervisor's decision
|
||||
// if the supervisor returns "__end__", the graph will finish execution
|
||||
return new Command({ goto: response.nextAgent });
|
||||
};
|
||||
|
||||
const agent1 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
// you can pass relevant parts of the state to the LLM (e.g., state.messages)
|
||||
// and add any additional logic (different models, custom prompts, structured output, etc.)
|
||||
const response = await model.invoke(...);
|
||||
return new Command({
|
||||
goto: "supervisor",
|
||||
update: { messages: [response] },
|
||||
});
|
||||
};
|
||||
|
||||
const agent2 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
const response = await model.invoke(...);
|
||||
return new Command({
|
||||
goto: "supervisor",
|
||||
update: { messages: [response] },
|
||||
});
|
||||
};
|
||||
|
||||
const builder = new StateGraph(MessagesZodState)
|
||||
.addNode("supervisor", supervisor, {
|
||||
ends: ["agent1", "agent2", END]
|
||||
})
|
||||
.addNode("agent1", agent1, {
|
||||
ends: ["supervisor"]
|
||||
})
|
||||
.addNode("agent2", agent2, {
|
||||
ends: ["supervisor"]
|
||||
})
|
||||
.addEdge(START, "supervisor");
|
||||
|
||||
const supervisorGraph = builder.compile();
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Check out this [tutorial](../tutorials/multi_agent/agent_supervisor.md) for an example of supervisor multi-agent architecture.
|
||||
|
||||
### Supervisor (tool-calling)
|
||||
|
||||
In this variant of the [supervisor](#supervisor) architecture, we define a supervisor [agent](./agentic_concepts.md#agent-architectures) which is responsible for calling sub-agents. The sub-agents are exposed to the supervisor as tools, and the supervisor agent decides which tool to call next. The supervisor agent follows a [standard implementation](./agentic_concepts.md#tool-calling-agent) as an LLM running in a while loop calling tools until it decides to stop.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
from langchain_openai import ChatOpenAI
|
||||
@@ -245,12 +525,67 @@ tools = [agent_1, agent_2]
|
||||
supervisor = create_react_agent(model, tools)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { z } from "zod";
|
||||
|
||||
const model = new ChatOpenAI();
|
||||
|
||||
// this is the agent function that will be called as tool
|
||||
// notice that you can pass the state to the tool via config parameter
|
||||
const agent1 = tool(
|
||||
async (_, config) => {
|
||||
const state = config.configurable?.state;
|
||||
// you can pass relevant parts of the state to the LLM (e.g., state.messages)
|
||||
// and add any additional logic (different models, custom prompts, structured output, etc.)
|
||||
const response = await model.invoke(...);
|
||||
// return the LLM response as a string (expected tool response format)
|
||||
// this will be automatically turned to ToolMessage
|
||||
// by the prebuilt createReactAgent (supervisor)
|
||||
return response.content;
|
||||
},
|
||||
{
|
||||
name: "agent1",
|
||||
description: "Agent 1 description",
|
||||
schema: z.object({}),
|
||||
}
|
||||
);
|
||||
|
||||
const agent2 = tool(
|
||||
async (_, config) => {
|
||||
const state = config.configurable?.state;
|
||||
const response = await model.invoke(...);
|
||||
return response.content;
|
||||
},
|
||||
{
|
||||
name: "agent2",
|
||||
description: "Agent 2 description",
|
||||
schema: z.object({}),
|
||||
}
|
||||
);
|
||||
|
||||
const tools = [agent1, agent2];
|
||||
// the simplest way to build a supervisor w/ tool-calling is to use prebuilt ReAct agent graph
|
||||
// that consists of a tool-calling LLM node (i.e. supervisor) and a tool-executing node
|
||||
const supervisor = createReactAgent({ llm: model, tools });
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Hierarchical
|
||||
|
||||
As you add more agents to your system, it might become too hard for the supervisor to manage all of them. The supervisor might start making poor decisions about which agent to call next, or the context might become too complex for a single supervisor to keep track of. In other words, you end up with the same problems that motivated the multi-agent architecture in the first place.
|
||||
|
||||
To address this, you can design your system _hierarchically_. For example, you can create separate, specialized teams of agents managed by individual supervisors, and a top-level supervisor to manage the teams.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from typing import Literal
|
||||
from langchain_openai import ChatOpenAI
|
||||
@@ -319,6 +654,97 @@ builder.add_edge("team_2_graph", "top_level_supervisor")
|
||||
graph = builder.compile()
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { StateGraph, MessagesZodState, Command, START, END } from "@langchain/langgraph";
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { z } from "zod";
|
||||
|
||||
const model = new ChatOpenAI();
|
||||
|
||||
// define team 1 (same as the single supervisor example above)
|
||||
|
||||
const team1Supervisor = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
const response = await model.invoke(...);
|
||||
return new Command({ goto: response.nextAgent });
|
||||
};
|
||||
|
||||
const team1Agent1 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
const response = await model.invoke(...);
|
||||
return new Command({
|
||||
goto: "team1Supervisor",
|
||||
update: { messages: [response] }
|
||||
});
|
||||
};
|
||||
|
||||
const team1Agent2 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
const response = await model.invoke(...);
|
||||
return new Command({
|
||||
goto: "team1Supervisor",
|
||||
update: { messages: [response] }
|
||||
});
|
||||
};
|
||||
|
||||
const team1Builder = new StateGraph(MessagesZodState)
|
||||
.addNode("team1Supervisor", team1Supervisor, {
|
||||
ends: ["team1Agent1", "team1Agent2", END]
|
||||
})
|
||||
.addNode("team1Agent1", team1Agent1, {
|
||||
ends: ["team1Supervisor"]
|
||||
})
|
||||
.addNode("team1Agent2", team1Agent2, {
|
||||
ends: ["team1Supervisor"]
|
||||
})
|
||||
.addEdge(START, "team1Supervisor");
|
||||
const team1Graph = team1Builder.compile();
|
||||
|
||||
// define team 2 (same as the single supervisor example above)
|
||||
const team2Supervisor = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
// ...
|
||||
};
|
||||
|
||||
const team2Agent1 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
// ...
|
||||
};
|
||||
|
||||
const team2Agent2 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
// ...
|
||||
};
|
||||
|
||||
const team2Builder = new StateGraph(MessagesZodState);
|
||||
// ... build team2Graph
|
||||
const team2Graph = team2Builder.compile();
|
||||
|
||||
// define top-level supervisor
|
||||
|
||||
const topLevelSupervisor = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
// you can pass relevant parts of the state to the LLM (e.g., state.messages)
|
||||
// to determine which team to call next. a common pattern is to call the model
|
||||
// with a structured output (e.g. force it to return an output with a "next_team" field)
|
||||
const response = await model.invoke(...);
|
||||
// route to one of the teams or exit based on the supervisor's decision
|
||||
// if the supervisor returns "__end__", the graph will finish execution
|
||||
return new Command({ goto: response.nextTeam });
|
||||
};
|
||||
|
||||
const builder = new StateGraph(MessagesZodState)
|
||||
.addNode("topLevelSupervisor", topLevelSupervisor, {
|
||||
ends: ["team1Graph", "team2Graph", END]
|
||||
})
|
||||
.addNode("team1Graph", team1Graph)
|
||||
.addNode("team2Graph", team2Graph)
|
||||
.addEdge(START, "topLevelSupervisor")
|
||||
.addEdge("team1Graph", "topLevelSupervisor")
|
||||
.addEdge("team2Graph", "topLevelSupervisor");
|
||||
|
||||
const graph = builder.compile();
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Custom multi-agent workflow
|
||||
|
||||
In this architecture we add individual agents as graph nodes and define the order in which agents are called ahead of time, in a custom workflow. In LangGraph the workflow can be defined in two ways:
|
||||
@@ -327,6 +753,8 @@ In this architecture we add individual agents as graph nodes and define the orde
|
||||
|
||||
- **Dynamic control flow (Command)**: in LangGraph you can allow LLMs to decide parts of your application control flow. This can be achieved by using [`Command`](./low_level.md#command). A special case of this is a [supervisor tool-calling](#supervisor-tool-calling) architecture. In that case, the tool-calling LLM powering the supervisor agent will make decisions about the order in which the tools (agents) are being called.
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langgraph.graph import StateGraph, MessagesState, START
|
||||
@@ -349,6 +777,37 @@ builder.add_edge(START, "agent_1")
|
||||
builder.add_edge("agent_1", "agent_2")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { StateGraph, MessagesZodState, START } from "@langchain/langgraph";
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
import { z } from "zod";
|
||||
|
||||
const model = new ChatOpenAI();
|
||||
|
||||
const agent1 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
const response = await model.invoke(...);
|
||||
return { messages: [response] };
|
||||
};
|
||||
|
||||
const agent2 = async (state: z.infer<typeof MessagesZodState>) => {
|
||||
const response = await model.invoke(...);
|
||||
return { messages: [response] };
|
||||
};
|
||||
|
||||
const builder = new StateGraph(MessagesZodState)
|
||||
.addNode("agent1", agent1)
|
||||
.addNode("agent2", agent2)
|
||||
// define the flow explicitly
|
||||
.addEdge(START, "agent1")
|
||||
.addEdge("agent1", "agent2");
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Communication and state management
|
||||
|
||||
The most important thing when building multi-agent systems is figuring out how the agents communicate.
|
||||
@@ -390,12 +849,27 @@ It can be helpful to indicate which agent a particular AI message is from, espec
|
||||
|
||||
### Representing handoffs in message history
|
||||
|
||||
:::python
|
||||
Handoffs are typically done via the LLM calling a dedicated [handoff tool](#handoffs-as-tools). This is represented as an [AI message](https://python.langchain.com/docs/concepts/messages/#aimessage) with tool calls that is passed to the next agent (LLM). Most LLM providers don't support receiving AI messages with tool calls **without** corresponding tool messages.
|
||||
:::
|
||||
|
||||
:::js
|
||||
Handoffs are typically done via the LLM calling a dedicated [handoff tool](#handoffs-as-tools). This is represented as an [AI message](https://js.langchain.com/docs/concepts/messages/#aimessage) with tool calls that is passed to the next agent (LLM). Most LLM providers don't support receiving AI messages with tool calls **without** corresponding tool messages.
|
||||
:::
|
||||
|
||||
You therefore have two options:
|
||||
|
||||
:::python
|
||||
|
||||
1. Add an extra [tool message](https://python.langchain.com/docs/concepts/messages/#toolmessage) to the message list, e.g., "Successfully transferred to agent X"
|
||||
2. Remove the AI message with the tool calls
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
1. Add an extra [tool message](https://js.langchain.com/docs/concepts/messages/#toolmessage) to the message list, e.g., "Successfully transferred to agent X"
|
||||
2. Remove the AI message with the tool calls
|
||||
:::
|
||||
|
||||
In practice, we see that most developers opt for option (1).
|
||||
|
||||
@@ -403,16 +877,25 @@ In practice, we see that most developers opt for option (1).
|
||||
|
||||
A common practice is to have multiple agents communicating on a shared message list, but only [adding their final messages to the list](#sharing-only-final-results). This means that any intermediate messages (e.g., tool calls) are not saved in this list.
|
||||
|
||||
What if you __do__ want to save these messages so that if this particular subagent is invoked in the future you can pass those back in?
|
||||
What if you **do** want to save these messages so that if this particular subagent is invoked in the future you can pass those back in?
|
||||
|
||||
There are two high-level approaches to achieve that:
|
||||
|
||||
:::python
|
||||
|
||||
1. Store these messages in the shared message list, but filter the list before passing it to the subagent LLM. For example, you can choose to filter out all tool calls from **other** agents.
|
||||
2. Store a separate message list for each agent (e.g., `alice_messages`) in the subagent's graph state. This would be their "view" of what the message history looks like.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
1. Store these messages in the shared message list, but filter the list before passing it to the subagent LLM. For example, you can choose to filter out all tool calls from **other** agents.
|
||||
2. Store a separate message list for each agent (e.g., `aliceMessages`) in the subagent's graph state. This would be their "view" of what the message history looks like.
|
||||
:::
|
||||
|
||||
### Using different state schemas
|
||||
|
||||
An agent might need to have a different state schema from the rest of the agents. For example, a search agent might only need to keep track of queries and retrieved documents. There are two ways to achieve this in LangGraph:
|
||||
|
||||
- Define [subgraph](./subgraphs.md) agents with a separate state schema. If there are no shared state keys (channels) between the subgraph and the parent graph, it’s important to [add input / output transformations](../how-tos/subgraph.md#different-state-schemas) so that the parent graph knows how to communicate with the subgraphs.
|
||||
- Define agent node functions with a [private input state schema](../how-tos/graph-api.md/#pass-private-state-between-nodes) that is distinct from the overall graph state schema. This allows passing information that is only needed for executing that particular agent.
|
||||
- Define [subgraph](./subgraphs.md) agents with a separate state schema. If there are no shared state keys (channels) between the subgraph and the parent graph, it's important to [add input / output transformations](../how-tos/subgraph.ipynb#different-state-schemas) so that the parent graph knows how to communicate with the subgraphs.
|
||||
- Define agent node functions with a [private input state schema](../how-tos/graph-api.ipynb#pass-private-state-between-nodes) that is distinct from the overall graph state schema. This allows passing information that is only needed for executing that particular agent.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+354
-13
@@ -5,13 +5,31 @@ search:
|
||||
|
||||
# LangGraph runtime
|
||||
|
||||
[Pregel][langgraph.pregel.Pregel] implements LangGraph's runtime, managing the execution of LangGraph applications.
|
||||
:::python
|
||||
@[Pregel] implements LangGraph's runtime, managing the execution of LangGraph applications.
|
||||
|
||||
Compiling a [StateGraph][langgraph.graph.StateGraph] or creating an [entrypoint][langgraph.func.entrypoint] produces a [Pregel][langgraph.pregel.Pregel] instance that can be invoked with input.
|
||||
Compiling a @[StateGraph][StateGraph] or creating an @[entrypoint][entrypoint] produces a @[Pregel] instance that can be invoked with input.
|
||||
:::
|
||||
|
||||
:::js
|
||||
@[Pregel] implements LangGraph's runtime, managing the execution of LangGraph applications.
|
||||
|
||||
Compiling a @[StateGraph][StateGraph] or creating an @[entrypoint][entrypoint] produces a @[Pregel] instance that can be invoked with input.
|
||||
:::
|
||||
|
||||
This guide explains the runtime at a high level and provides instructions for directly implementing applications with Pregel.
|
||||
|
||||
> **Note:** The [Pregel][langgraph.pregel.Pregel] runtime is named after [Google's Pregel algorithm](https://research.google/pubs/pub37252/), which describes an efficient method for large-scale parallel computation using graphs.
|
||||
:::python
|
||||
|
||||
> **Note:** The @[Pregel] runtime is named after [Google's Pregel algorithm](https://research.google/pubs/pub37252/), which describes an efficient method for large-scale parallel computation using graphs.
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
> **Note:** The @[Pregel] runtime is named after [Google's Pregel algorithm](https://research.google/pubs/pub37252/), which describes an efficient method for large-scale parallel computation using graphs.
|
||||
|
||||
:::
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -33,21 +51,36 @@ An **actor** is a `PregelNode`. It subscribes to channels, reads data from them,
|
||||
|
||||
Channels are used to communicate between actors (PregelNodes). Each channel has a value type, an update type, and an update function – which takes a sequence of updates and modifies the stored value. Channels can be used to send data from one chain to another, or to send data from a chain to itself in a future step. LangGraph provides a number of built-in channels:
|
||||
|
||||
- [LastValue][langgraph.channels.LastValue]: The default channel, stores the last value sent to the channel, useful for input and output values, or for sending data from one step to the next.
|
||||
- [Topic][langgraph.channels.Topic]: A configurable PubSub Topic, useful for sending multiple values between **actors**, or for accumulating output. Can be configured to deduplicate values or to accumulate values over the course of multiple steps.
|
||||
- [BinaryOperatorAggregate][langgraph.channels.BinaryOperatorAggregate]: stores a persistent value, updated by applying a binary operator to the current value and each update sent to the channel, useful for computing aggregates over multiple steps; e.g.,`total = BinaryOperatorAggregate(int, operator.add)`
|
||||
:::python
|
||||
|
||||
- @[LastValue][LastValue]: The default channel, stores the last value sent to the channel, useful for input and output values, or for sending data from one step to the next.
|
||||
- @[Topic][Topic]: A configurable PubSub Topic, useful for sending multiple values between **actors**, or for accumulating output. Can be configured to deduplicate values or to accumulate values over the course of multiple steps.
|
||||
- @[BinaryOperatorAggregate][BinaryOperatorAggregate]: stores a persistent value, updated by applying a binary operator to the current value and each update sent to the channel, useful for computing aggregates over multiple steps; e.g.,`total = BinaryOperatorAggregate(int, operator.add)`
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
- @[LastValue]: The default channel, stores the last value sent to the channel, useful for input and output values, or for sending data from one step to the next.
|
||||
- @[Topic]: A configurable PubSub Topic, useful for sending multiple values between **actors**, or for accumulating output. Can be configured to deduplicate values or to accumulate values over the course of multiple steps.
|
||||
- @[BinaryOperatorAggregate]: stores a persistent value, updated by applying a binary operator to the current value and each update sent to the channel, useful for computing aggregates over multiple steps; e.g.,`total = BinaryOperatorAggregate(int, operator.add)`
|
||||
:::
|
||||
|
||||
## Examples
|
||||
|
||||
While most users will interact with Pregel through the [StateGraph][langgraph.graph.StateGraph] API or
|
||||
the [entrypoint][langgraph.func.entrypoint] decorator, it is possible to interact with Pregel directly.
|
||||
:::python
|
||||
While most users will interact with Pregel through the @[StateGraph][StateGraph] API or the @[entrypoint][entrypoint] decorator, it is possible to interact with Pregel directly.
|
||||
:::
|
||||
|
||||
:::js
|
||||
While most users will interact with Pregel through the @[StateGraph] API or the @[entrypoint] decorator, it is possible to interact with Pregel directly.
|
||||
:::
|
||||
|
||||
Below are a few different examples to give you a sense of the Pregel API.
|
||||
|
||||
=== "Single node"
|
||||
|
||||
:::python
|
||||
```python
|
||||
|
||||
from langgraph.channels import EphemeralValue
|
||||
from langgraph.pregel import Pregel, NodeBuilder
|
||||
|
||||
@@ -73,9 +106,39 @@ Below are a few different examples to give you a sense of the Pregel API.
|
||||
```con
|
||||
{'b': 'foofoo'}
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { EphemeralValue } from "@langchain/langgraph/channels";
|
||||
import { Pregel, NodeBuilder } from "@langchain/langgraph/pregel";
|
||||
|
||||
const node1 = new NodeBuilder()
|
||||
.subscribeOnly("a")
|
||||
.do((x: string) => x + x)
|
||||
.writeTo("b");
|
||||
|
||||
const app = new Pregel({
|
||||
nodes: { node1 },
|
||||
channels: {
|
||||
a: new EphemeralValue<string>(),
|
||||
b: new EphemeralValue<string>(),
|
||||
},
|
||||
inputChannels: ["a"],
|
||||
outputChannels: ["b"],
|
||||
});
|
||||
|
||||
await app.invoke({ a: "foo" });
|
||||
```
|
||||
|
||||
```console
|
||||
{ b: 'foofoo' }
|
||||
```
|
||||
:::
|
||||
|
||||
=== "Multiple nodes"
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langgraph.channels import LastValue, EphemeralValue
|
||||
from langgraph.pregel import Pregel, NodeBuilder
|
||||
@@ -110,9 +173,45 @@ Below are a few different examples to give you a sense of the Pregel API.
|
||||
```con
|
||||
{'b': 'foofoo', 'c': 'foofoofoofoo'}
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { LastValue, EphemeralValue } from "@langchain/langgraph/channels";
|
||||
import { Pregel, NodeBuilder } from "@langchain/langgraph/pregel";
|
||||
|
||||
const node1 = new NodeBuilder()
|
||||
.subscribeOnly("a")
|
||||
.do((x: string) => x + x)
|
||||
.writeTo("b");
|
||||
|
||||
const node2 = new NodeBuilder()
|
||||
.subscribeOnly("b")
|
||||
.do((x: string) => x + x)
|
||||
.writeTo("c");
|
||||
|
||||
const app = new Pregel({
|
||||
nodes: { node1, node2 },
|
||||
channels: {
|
||||
a: new EphemeralValue<string>(),
|
||||
b: new LastValue<string>(),
|
||||
c: new EphemeralValue<string>(),
|
||||
},
|
||||
inputChannels: ["a"],
|
||||
outputChannels: ["b", "c"],
|
||||
});
|
||||
|
||||
await app.invoke({ a: "foo" });
|
||||
```
|
||||
|
||||
```console
|
||||
{ b: 'foofoo', c: 'foofoofoofoo' }
|
||||
```
|
||||
:::
|
||||
|
||||
=== "Topic"
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langgraph.channels import EphemeralValue, Topic
|
||||
from langgraph.pregel import Pregel, NodeBuilder
|
||||
@@ -146,11 +245,47 @@ Below are a few different examples to give you a sense of the Pregel API.
|
||||
```pycon
|
||||
{'c': ['foofoo', 'foofoofoofoo']}
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { EphemeralValue, Topic } from "@langchain/langgraph/channels";
|
||||
import { Pregel, NodeBuilder } from "@langchain/langgraph/pregel";
|
||||
|
||||
const node1 = new NodeBuilder()
|
||||
.subscribeOnly("a")
|
||||
.do((x: string) => x + x)
|
||||
.writeTo("b", "c");
|
||||
|
||||
const node2 = new NodeBuilder()
|
||||
.subscribeTo("b")
|
||||
.do((x: { b: string }) => x.b + x.b)
|
||||
.writeTo("c");
|
||||
|
||||
const app = new Pregel({
|
||||
nodes: { node1, node2 },
|
||||
channels: {
|
||||
a: new EphemeralValue<string>(),
|
||||
b: new EphemeralValue<string>(),
|
||||
c: new Topic<string>({ accumulate: true }),
|
||||
},
|
||||
inputChannels: ["a"],
|
||||
outputChannels: ["c"],
|
||||
});
|
||||
|
||||
await app.invoke({ a: "foo" });
|
||||
```
|
||||
|
||||
```console
|
||||
{ c: ['foofoo', 'foofoofoofoo'] }
|
||||
```
|
||||
:::
|
||||
|
||||
=== "BinaryOperatorAggregate"
|
||||
|
||||
This examples demonstrates how to use the BinaryOperatorAggregate channel to implement a reducer.
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langgraph.channels import EphemeralValue, BinaryOperatorAggregate
|
||||
from langgraph.pregel import Pregel, NodeBuilder
|
||||
@@ -187,12 +322,53 @@ Below are a few different examples to give you a sense of the Pregel API.
|
||||
|
||||
app.invoke({"a": "foo"})
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { EphemeralValue, BinaryOperatorAggregate } from "@langchain/langgraph/channels";
|
||||
import { Pregel, NodeBuilder } from "@langchain/langgraph/pregel";
|
||||
|
||||
const node1 = new NodeBuilder()
|
||||
.subscribeOnly("a")
|
||||
.do((x: string) => x + x)
|
||||
.writeTo("b", "c");
|
||||
|
||||
const node2 = new NodeBuilder()
|
||||
.subscribeOnly("b")
|
||||
.do((x: string) => x + x)
|
||||
.writeTo("c");
|
||||
|
||||
const reducer = (current: string, update: string) => {
|
||||
if (current) {
|
||||
return current + " | " + update;
|
||||
} else {
|
||||
return update;
|
||||
}
|
||||
};
|
||||
|
||||
const app = new Pregel({
|
||||
nodes: { node1, node2 },
|
||||
channels: {
|
||||
a: new EphemeralValue<string>(),
|
||||
b: new EphemeralValue<string>(),
|
||||
c: new BinaryOperatorAggregate<string>({ operator: reducer }),
|
||||
},
|
||||
inputChannels: ["a"],
|
||||
outputChannels: ["c"],
|
||||
});
|
||||
|
||||
await app.invoke({ a: "foo" });
|
||||
```
|
||||
:::
|
||||
|
||||
=== "Cycle"
|
||||
|
||||
:::python
|
||||
|
||||
This example demonstrates how to introduce a cycle in the graph, by having
|
||||
a chain write to a channel it subscribes to. Execution will continue
|
||||
until a None value is written to the channel.
|
||||
until a `None` value is written to the channel.
|
||||
|
||||
```python
|
||||
from langgraph.channels import EphemeralValue
|
||||
@@ -219,6 +395,39 @@ Below are a few different examples to give you a sense of the Pregel API.
|
||||
```pycon
|
||||
{'value': 'aaaaaaaaaaaaaaaa'}
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
This example demonstrates how to introduce a cycle in the graph, by having
|
||||
a chain write to a channel it subscribes to. Execution will continue
|
||||
until a `null` value is written to the channel.
|
||||
|
||||
```typescript
|
||||
import { EphemeralValue } from "@langchain/langgraph/channels";
|
||||
import { Pregel, NodeBuilder, ChannelWriteEntry } from "@langchain/langgraph/pregel";
|
||||
|
||||
const exampleNode = new NodeBuilder()
|
||||
.subscribeOnly("value")
|
||||
.do((x: string) => x.length < 10 ? x + x : null)
|
||||
.writeTo(new ChannelWriteEntry("value", { skipNone: true }));
|
||||
|
||||
const app = new Pregel({
|
||||
nodes: { exampleNode },
|
||||
channels: {
|
||||
value: new EphemeralValue<string>(),
|
||||
},
|
||||
inputChannels: ["value"],
|
||||
outputChannels: ["value"],
|
||||
});
|
||||
|
||||
await app.invoke({ value: "a" });
|
||||
```
|
||||
|
||||
```console
|
||||
{ value: 'aaaaaaaaaaaaaaaa' }
|
||||
```
|
||||
:::
|
||||
|
||||
## High-level API
|
||||
|
||||
@@ -226,7 +435,9 @@ LangGraph provides two high-level APIs for creating a Pregel application: the [S
|
||||
|
||||
=== "StateGraph (Graph API)"
|
||||
|
||||
The [StateGraph (Graph API)][langgraph.graph.StateGraph] is a higher-level abstraction that simplifies the creation of Pregel applications. It allows you to define a graph of nodes and edges. When you compile the graph, the StateGraph API automatically creates the Pregel application for you.
|
||||
:::python
|
||||
|
||||
The @[StateGraph (Graph API)][StateGraph] is a higher-level abstraction that simplifies the creation of Pregel applications. It allows you to define a graph of nodes and edges. When you compile the graph, the StateGraph API automatically creates the Pregel application for you.
|
||||
|
||||
```python
|
||||
from typing import TypedDict, Optional
|
||||
@@ -258,9 +469,53 @@ LangGraph provides two high-level APIs for creating a Pregel application: the [S
|
||||
# This will return a Pregel instance.
|
||||
graph = builder.compile()
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
The @[StateGraph (Graph API)][StateGraph] is a higher-level abstraction that simplifies the creation of Pregel applications. It allows you to define a graph of nodes and edges. When you compile the graph, the StateGraph API automatically creates the Pregel application for you.
|
||||
|
||||
```typescript
|
||||
import { START, StateGraph } from "@langchain/langgraph";
|
||||
|
||||
interface Essay {
|
||||
topic: string;
|
||||
content?: string;
|
||||
score?: number;
|
||||
}
|
||||
|
||||
const writeEssay = (essay: Essay) => {
|
||||
return {
|
||||
content: `Essay about ${essay.topic}`,
|
||||
};
|
||||
};
|
||||
|
||||
const scoreEssay = (essay: Essay) => {
|
||||
return {
|
||||
score: 10
|
||||
};
|
||||
};
|
||||
|
||||
const builder = new StateGraph<Essay>({
|
||||
channels: {
|
||||
topic: null,
|
||||
content: null,
|
||||
score: null,
|
||||
}
|
||||
})
|
||||
.addNode("writeEssay", writeEssay)
|
||||
.addNode("scoreEssay", scoreEssay)
|
||||
.addEdge(START, "writeEssay");
|
||||
|
||||
// Compile the graph.
|
||||
// This will return a Pregel instance.
|
||||
const graph = builder.compile();
|
||||
```
|
||||
:::
|
||||
|
||||
The compiled Pregel instance will be associated with a list of nodes and channels. You can inspect the nodes and channels by printing them.
|
||||
|
||||
:::python
|
||||
```python
|
||||
print(graph.nodes)
|
||||
```
|
||||
@@ -294,11 +549,53 @@ LangGraph provides two high-level APIs for creating a Pregel application: the [S
|
||||
'branch:score_essay:__self__:score_essay': <langgraph.channels.ephemeral_value.EphemeralValue at 0x7d05e2d8b400>,
|
||||
'start:write_essay': <langgraph.channels.ephemeral_value.EphemeralValue at 0x7d05e2d8b280>}
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
console.log(graph.nodes);
|
||||
```
|
||||
|
||||
You will see something like this:
|
||||
|
||||
```console
|
||||
{
|
||||
__start__: PregelNode { ... },
|
||||
writeEssay: PregelNode { ... },
|
||||
scoreEssay: PregelNode { ... }
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
console.log(graph.channels);
|
||||
```
|
||||
|
||||
You should see something like this
|
||||
|
||||
```console
|
||||
{
|
||||
topic: LastValue { ... },
|
||||
content: LastValue { ... },
|
||||
score: LastValue { ... },
|
||||
__start__: EphemeralValue { ... },
|
||||
writeEssay: EphemeralValue { ... },
|
||||
scoreEssay: EphemeralValue { ... },
|
||||
'branch:__start__:__self__:writeEssay': EphemeralValue { ... },
|
||||
'branch:__start__:__self__:scoreEssay': EphemeralValue { ... },
|
||||
'branch:writeEssay:__self__:writeEssay': EphemeralValue { ... },
|
||||
'branch:writeEssay:__self__:scoreEssay': EphemeralValue { ... },
|
||||
'branch:scoreEssay:__self__:writeEssay': EphemeralValue { ... },
|
||||
'branch:scoreEssay:__self__:scoreEssay': EphemeralValue { ... },
|
||||
'start:writeEssay': EphemeralValue { ... }
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
=== "Functional API"
|
||||
|
||||
In the [Functional API](functional_api.md), you can use an [`entrypoint`][langgraph.func.entrypoint] to create
|
||||
a Pregel application. The `entrypoint` decorator allows you to define a function that takes input and returns output.
|
||||
:::python
|
||||
|
||||
In the [Functional API](functional_api.md), you can use an @[`entrypoint`][entrypoint] to create a Pregel application. The `entrypoint` decorator allows you to define a function that takes input and returns output.
|
||||
|
||||
```python
|
||||
from typing import TypedDict, Optional
|
||||
@@ -332,3 +629,47 @@ LangGraph provides two high-level APIs for creating a Pregel application: the [S
|
||||
Channels:
|
||||
{'__start__': <langgraph.channels.ephemeral_value.EphemeralValue object at 0x7d05e2c906c0>, '__end__': <langgraph.channels.last_value.LastValue object at 0x7d05e2c90c40>, '__previous__': <langgraph.channels.last_value.LastValue object at 0x7d05e1007280>}
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
In the [Functional API](functional_api.md), you can use an @[`entrypoint`][entrypoint] to create a Pregel application. The `entrypoint` decorator allows you to define a function that takes input and returns output.
|
||||
|
||||
```typescript
|
||||
import { MemorySaver } from "@langchain/langgraph";
|
||||
import { entrypoint } from "@langchain/langgraph/func";
|
||||
|
||||
interface Essay {
|
||||
topic: string;
|
||||
content?: string;
|
||||
score?: number;
|
||||
}
|
||||
|
||||
const checkpointer = new MemorySaver();
|
||||
|
||||
const writeEssay = entrypoint(
|
||||
{ checkpointer, name: "writeEssay" },
|
||||
async (essay: Essay) => {
|
||||
return {
|
||||
content: `Essay about ${essay.topic}`,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
console.log("Nodes: ");
|
||||
console.log(writeEssay.nodes);
|
||||
console.log("Channels: ");
|
||||
console.log(writeEssay.channels);
|
||||
```
|
||||
|
||||
```console
|
||||
Nodes:
|
||||
{ writeEssay: PregelNode { ... } }
|
||||
Channels:
|
||||
{
|
||||
__start__: EphemeralValue { ... },
|
||||
__end__: LastValue { ... },
|
||||
__previous__: LastValue { ... }
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
+25
-14
@@ -5,25 +5,20 @@ search:
|
||||
|
||||
# LangGraph SDK
|
||||
|
||||
LangGraph Platform provides both a Python SDK for interacting with [LangGraph Server](./langgraph_server.md).
|
||||
:::python
|
||||
LangGraph Platform provides a python SDK for interacting with [LangGraph Server](./langgraph_server.md).
|
||||
|
||||
!!! tip "Python SDK reference"
|
||||
|
||||
|
||||
For detailed information about the Python SDK, see [Python SDK reference docs](../cloud/reference/sdk/python_sdk_ref.md).
|
||||
|
||||
## Installation
|
||||
|
||||
You can install the packages using the appropriate package manager for your language:
|
||||
You can install the LangGraph SDK using the following command:
|
||||
|
||||
=== "Python"
|
||||
```bash
|
||||
pip install langgraph-sdk
|
||||
```
|
||||
|
||||
=== "JS"
|
||||
```bash
|
||||
yarn add @langchain/langgraph-sdk
|
||||
```
|
||||
```bash
|
||||
pip install langgraph-sdk
|
||||
```
|
||||
|
||||
## Python sync vs. async
|
||||
|
||||
@@ -39,6 +34,7 @@ The Python SDK provides both synchronous (`get_sync_client`) and asynchronous (`
|
||||
```
|
||||
|
||||
=== "Async"
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
|
||||
@@ -46,9 +42,24 @@ The Python SDK provides both synchronous (`get_sync_client`) and asynchronous (`
|
||||
await client.assistants.search()
|
||||
```
|
||||
|
||||
|
||||
## Learn more
|
||||
|
||||
- [Python SDK Reference](../cloud/reference/sdk/python_sdk_ref.md)
|
||||
- [LangGraph CLI API Reference](../cloud/reference/cli.md)
|
||||
- [JS/TS SDK Reference](../cloud/reference/sdk/js_ts_sdk_ref.md)
|
||||
:::
|
||||
|
||||
:::js
|
||||
LangGraph Platform provides a JS/TS SDK for interacting with [LangGraph Server](./langgraph_server.md).
|
||||
|
||||
## Installation
|
||||
|
||||
You can add the LangGraph SDK to your project using the following command:
|
||||
|
||||
```bash
|
||||
npm install @langchain/langgraph-sdk
|
||||
```
|
||||
|
||||
## Learn more
|
||||
|
||||
- [LangGraph CLI API Reference](../cloud/reference/cli.md)
|
||||
:::
|
||||
|
||||
+138
-136
@@ -8,7 +8,7 @@ hide:
|
||||
|
||||
# MCP endpoint in LangGraph Server
|
||||
|
||||
The [Model Context Protocol (MCP)](./mcp.md) is an open protocol for describing tools and data sources in a model-agnostic format, enabling LLMs to discover and use them via a structured API.
|
||||
The [Model Context Protocol (MCP)](./mcp.md) is an open protocol for describing tools and data sources in a model-agnostic format, enabling LLMs to discover and use them via a structured API.
|
||||
|
||||
[LangGraph Server](./langgraph_server.md) implements MCP using the [Streamable HTTP transport](https://spec.modelcontextprotocol.io/specification/2025-03-26/basic/transports/#streamable-http). This allows LangGraph **agents** to be exposed as **MCP tools**, making them usable with any MCP-compliant client supporting Streamable HTTP.
|
||||
|
||||
@@ -16,6 +16,7 @@ The MCP endpoint is available at `/mcp` on [LangGraph Server](./langgraph_server
|
||||
|
||||
## Requirements
|
||||
|
||||
:::python
|
||||
To use MCP, ensure you have the following dependencies installed:
|
||||
|
||||
- `langgraph-api >= 0.2.3`
|
||||
@@ -27,107 +28,18 @@ Install them with:
|
||||
pip install "langgraph-api>=0.2.3" "langgraph-sdk>=0.1.61"
|
||||
```
|
||||
|
||||
## Usage overview
|
||||
:::
|
||||
|
||||
To enable MCP:
|
||||
:::js
|
||||
To use MCP, ensure you have both the api and sdk packages installed.
|
||||
|
||||
- Upgrade to use langgraph-api>=0.2.3. If you are deploying LangGraph Platform, this will be done for you automatically if you create a new revision.
|
||||
- MCP tools (agents) will be automatically exposed.
|
||||
- Connect with any MCP-compliant client that supports Streamable HTTP.
|
||||
```bash
|
||||
npm install @langchain/langgraph-api @langchain/langgraph-sdk
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Client
|
||||
|
||||
Use an MCP-compliant client to connect to the LangGraph server. The following examples show how to connect using different programming languages.
|
||||
|
||||
=== "JavaScript/TypeScript"
|
||||
|
||||
```bash
|
||||
npm install @modelcontextprotocol/sdk
|
||||
```
|
||||
|
||||
> **Note**
|
||||
> Replace `serverUrl` with your LangGraph server URL and configure authentication headers as needed.
|
||||
|
||||
```js
|
||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
||||
|
||||
// Connects to the LangGraph MCP endpoint
|
||||
async function connectClient(url) {
|
||||
const baseUrl = new URL(url);
|
||||
const client = new Client({
|
||||
name: 'streamable-http-client',
|
||||
version: '1.0.0'
|
||||
});
|
||||
|
||||
const transport = new StreamableHTTPClientTransport(baseUrl);
|
||||
await client.connect(transport);
|
||||
|
||||
console.log("Connected using Streamable HTTP transport");
|
||||
console.log(JSON.stringify(await client.listTools(), null, 2));
|
||||
return client;
|
||||
}
|
||||
|
||||
const serverUrl = "http://localhost:2024/mcp";
|
||||
|
||||
connectClient(serverUrl)
|
||||
.then(() => {
|
||||
console.log("Client connected successfully");
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Failed to connect client:", error);
|
||||
});
|
||||
```
|
||||
|
||||
=== "Python"
|
||||
|
||||
|
||||
Install the adapter with:
|
||||
|
||||
```bash
|
||||
pip install langchain-mcp-adapters
|
||||
```
|
||||
|
||||
Here is an example of how to connect to a remote MCP endpoint and use an agent as a tool:
|
||||
|
||||
```python
|
||||
# Create server parameters for stdio connection
|
||||
from mcp import ClientSession
|
||||
from mcp.client.streamable_http import streamablehttp_client
|
||||
import asyncio
|
||||
|
||||
from langchain_mcp_adapters.tools import load_mcp_tools
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
server_params = {
|
||||
"url": "https://mcp-finance-agent.xxx.us.langgraph.app/mcp",
|
||||
"headers": {
|
||||
"X-Api-Key":"lsv2_pt_your_api_key"
|
||||
}
|
||||
}
|
||||
|
||||
async def main():
|
||||
async with streamablehttp_client(**server_params) as (read, write, _):
|
||||
async with ClientSession(read, write) as session:
|
||||
# Initialize the connection
|
||||
await session.initialize()
|
||||
|
||||
# Load the remote graph as if it was a tool
|
||||
tools = await load_mcp_tools(session)
|
||||
|
||||
# Create and run a react agent with the tools
|
||||
agent = create_react_agent("openai:gpt-4.1", tools)
|
||||
|
||||
# Invoke the agent with a message
|
||||
agent_response = await agent.ainvoke({"messages": "What can the finance agent do for me?"})
|
||||
print(agent_response)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## Expose an agent as MCP tool
|
||||
## Exposing an agent as MCP tool
|
||||
|
||||
When deployed, your agent will appear as a tool in the MCP endpoint
|
||||
with this configuration:
|
||||
@@ -136,22 +48,41 @@ with this configuration:
|
||||
- **Tool description**: The agent's description.
|
||||
- **Tool input schema**: The agent's input schema.
|
||||
|
||||
### Setting name and description
|
||||
### Setting name and description
|
||||
|
||||
You can set the name and description of your agent in `langgraph.json`:
|
||||
|
||||
:::python
|
||||
|
||||
```json
|
||||
{
|
||||
"graphs": {
|
||||
"my_agent": {
|
||||
"path": "./my_agent/agent.py:graph",
|
||||
"description": "A description of what the agent does"
|
||||
}
|
||||
},
|
||||
"env": ".env"
|
||||
"graphs": {
|
||||
"my_agent": {
|
||||
"path": "./my_agent/agent.py:graph",
|
||||
"description": "A description of what the agent does"
|
||||
}
|
||||
},
|
||||
"env": ".env"
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
:::js
|
||||
|
||||
```json
|
||||
{
|
||||
"graphs": {
|
||||
"my_agent": {
|
||||
"path": "./my_agent/agent.ts:graph",
|
||||
"description": "A description of what the agent does"
|
||||
}
|
||||
},
|
||||
"env": ".env"
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
After deployment, you can update the name and description using the LangGraph SDK.
|
||||
|
||||
### Schema
|
||||
@@ -198,45 +129,116 @@ print(graph.invoke({"question": "hi"}))
|
||||
|
||||
For more details, see the [low-level concepts guide](https://langchain-ai.github.io/langgraph/concepts/low_level/#state).
|
||||
|
||||
## Use user-scoped MCP tools in your deployment
|
||||
## Usage overview
|
||||
|
||||
!!! tip "Prerequisites"
|
||||
To enable MCP:
|
||||
|
||||
You have added your own [custom auth middleware](https://langchain-ai.github.io/langgraph/how-tos/auth/custom_auth/) that populates the `langgraph_auth_user` object, making it accessible through configurable context for every node in your graph.
|
||||
- Upgrade to use langgraph-api>=0.2.3. If you are deploying LangGraph Platform, this will be done for you automatically if you create a new revision.
|
||||
- MCP tools (agents) will be automatically exposed.
|
||||
- Connect with any MCP-compliant client that supports Streamable HTTP.
|
||||
|
||||
To make user-scoped tools available to your LangGraph Platform deployment, start with implementing a snippet like the following:
|
||||
### Client
|
||||
|
||||
```python
|
||||
from langchain_mcp_adapters.client import MultiServerMCPClient
|
||||
:::python
|
||||
Use an MCP-compliant client to connect to the LangGraph server. The following example shows how to connect using [langchain-mcp-adapters](https://github.com/langchain-ai/langchain-mcp-adapters).
|
||||
|
||||
def mcp_tools_node(state, config):
|
||||
user = config["configurable"].get("langgraph_auth_user")
|
||||
# e.g., user["github_token"], user["email"], etc.
|
||||
|
||||
client = MultiServerMCPClient({
|
||||
"github": {
|
||||
"transport": "streamable_http", # (1)
|
||||
"url": "https://my-github-mcp-server/mcp", # (2)
|
||||
"headers": {
|
||||
"Authorization": f"Bearer {user['github_token']}"
|
||||
}
|
||||
}
|
||||
})
|
||||
tools = await client.get_tools() # (3)
|
||||
|
||||
# Your tool-calling logic here
|
||||
|
||||
tool_messages = ...
|
||||
return {"messages": tool_messages}
|
||||
Install the adapter with:
|
||||
|
||||
```bash
|
||||
pip install langchain-mcp-adapters
|
||||
```
|
||||
|
||||
1. MCP only supports adding headers to requests made to `streamable_http` and `sse` `transport` servers.
|
||||
2. Your MCP server URL.
|
||||
3. Get available tools from your MCP server.
|
||||
Here is an example of how to connect to a remote MCP endpoint and use an agent as a tool:
|
||||
|
||||
_This can also be done by [rebuilding your graph at runtime](https://langchain-ai.github.io/langgraph/cloud/deployment/graph_rebuild/) to have a different configuration for a new run_
|
||||
```python
|
||||
# Create server parameters for stdio connection
|
||||
from mcp import ClientSession
|
||||
from mcp.client.streamable_http import streamablehttp_client
|
||||
import asyncio
|
||||
|
||||
## Session behavior
|
||||
from langchain_mcp_adapters.tools import load_mcp_tools
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
server_params = {
|
||||
"url": "https://mcp-finance-agent.xxx.us.langgraph.app/mcp",
|
||||
"headers": {
|
||||
"X-Api-Key":"lsv2_pt_your_api_key"
|
||||
}
|
||||
}
|
||||
|
||||
async def main():
|
||||
async with streamablehttp_client(**server_params) as (read, write, _):
|
||||
async with ClientSession(read, write) as session:
|
||||
# Initialize the connection
|
||||
await session.initialize()
|
||||
|
||||
# Load the remote graph as if it was a tool
|
||||
tools = await load_mcp_tools(session)
|
||||
|
||||
# Create and run a react agent with the tools
|
||||
agent = create_react_agent("openai:gpt-4.1", tools)
|
||||
|
||||
# Invoke the agent with a message
|
||||
agent_response = await agent.ainvoke({"messages": "What can the finance agent do for me?"})
|
||||
print(agent_response)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
Use an MCP-compliant client to connect to the LangGraph server. The following example shows how to connect using [`@langchain/mcp-adapters`](https://npmjs.com/package/@langchain/mcp-adapters).
|
||||
|
||||
```bash
|
||||
npm install @langchain/mcp-adapters
|
||||
```
|
||||
|
||||
Here is an example of how to connect to a remote MCP endpoint and use an agent as a tool:
|
||||
|
||||
```typescript
|
||||
import { MultiServerMCPClient } from "@langchain/mcp-adapters";
|
||||
import { createReactAgent } from "@langchain/langgraph";
|
||||
import { ChatOpenAI } from "@langchain/openai";
|
||||
|
||||
async function main() {
|
||||
const client = new MultiServerMCPClient({
|
||||
mcpServers: {
|
||||
"finance-agent": {
|
||||
url: "https://mcp-finance-agent.xxx.us.langgraph.app/mcp",
|
||||
headers: {
|
||||
"X-Api-Key": "lsv2_pt_your_api_key",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const tools = await client.getTools();
|
||||
|
||||
const model = new ChatOpenAI({
|
||||
model: "gpt-4o-mini",
|
||||
temperature: 0,
|
||||
});
|
||||
|
||||
const agent = createReactAgent({
|
||||
model,
|
||||
tools,
|
||||
});
|
||||
|
||||
const response = await agent.invoke({
|
||||
input: "What can the finance agent do for me?",
|
||||
});
|
||||
|
||||
console.log(response);
|
||||
}
|
||||
|
||||
main();
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Session behavior
|
||||
|
||||
The current LangGraph MCP implementation does not support sessions. Each `/mcp` request is stateless and independent.
|
||||
|
||||
|
||||
+134
-53
@@ -12,71 +12,152 @@ Some reasons for using subgraphs are:
|
||||
|
||||
The main question when adding subgraphs is how the parent graph and subgraph communicate, i.e. how they pass the [state](./low_level.md#state) between each other during the graph execution. There are two scenarios:
|
||||
|
||||
* parent and subgraph have **shared state keys** in their state [schemas](./low_level.md#state). In this case, you can [include the subgraph as a node in the parent graph](../how-tos/subgraph.md#shared-state-schemas)
|
||||
- parent and subgraph have **shared state keys** in their state [schemas](./low_level.md#state). In this case, you can [include the subgraph as a node in the parent graph](../how-tos/subgraph.ipynb#shared-state-schemas)
|
||||
|
||||
```python
|
||||
from langgraph.graph import StateGraph, MessagesState, START
|
||||
:::python
|
||||
|
||||
# Subgraph
|
||||
```python
|
||||
from langgraph.graph import StateGraph, MessagesState, START
|
||||
|
||||
def call_model(state: MessagesState):
|
||||
response = model.invoke(state["messages"])
|
||||
return {"messages": response}
|
||||
# Subgraph
|
||||
|
||||
subgraph_builder = StateGraph(State)
|
||||
subgraph_builder.add_node(call_model)
|
||||
...
|
||||
# highlight-next-line
|
||||
subgraph = subgraph_builder.compile()
|
||||
def call_model(state: MessagesState):
|
||||
response = model.invoke(state["messages"])
|
||||
return {"messages": response}
|
||||
|
||||
# Parent graph
|
||||
subgraph_builder = StateGraph(State)
|
||||
subgraph_builder.add_node(call_model)
|
||||
...
|
||||
# highlight-next-line
|
||||
subgraph = subgraph_builder.compile()
|
||||
|
||||
builder = StateGraph(State)
|
||||
# highlight-next-line
|
||||
builder.add_node("subgraph_node", subgraph)
|
||||
builder.add_edge(START, "subgraph_node")
|
||||
graph = builder.compile()
|
||||
...
|
||||
graph.invoke({"messages": [{"role": "user", "content": "hi!"}]})
|
||||
```
|
||||
# Parent graph
|
||||
|
||||
* parent graph and subgraph have **different schemas** (no shared state keys in their state [schemas](./low_level.md#state)). In this case, you have to [call the subgraph from inside a node in the parent graph](../how-tos/subgraph.md#different-state-schemas): this is useful when the parent graph and the subgraph have different state schemas and you need to transform state before or after calling the subgraph
|
||||
builder = StateGraph(State)
|
||||
# highlight-next-line
|
||||
builder.add_node("subgraph_node", subgraph)
|
||||
builder.add_edge(START, "subgraph_node")
|
||||
graph = builder.compile()
|
||||
...
|
||||
graph.invoke({"messages": [{"role": "user", "content": "hi!"}]})
|
||||
```
|
||||
|
||||
```python
|
||||
from typing_extensions import TypedDict, Annotated
|
||||
from langchain_core.messages import AnyMessage
|
||||
from langgraph.graph import StateGraph, MessagesState, START
|
||||
from langgraph.graph.message import add_messages
|
||||
:::
|
||||
|
||||
class SubgraphMessagesState(TypedDict):
|
||||
# highlight-next-line
|
||||
subgraph_messages: Annotated[list[AnyMessage], add_messages]
|
||||
:::js
|
||||
|
||||
# Subgraph
|
||||
```typescript
|
||||
import { StateGraph, MessagesZodState, START } from "@langchain/langgraph";
|
||||
|
||||
# highlight-next-line
|
||||
def call_model(state: SubgraphMessagesState):
|
||||
response = model.invoke(state["subgraph_messages"])
|
||||
return {"subgraph_messages": response}
|
||||
// Subgraph
|
||||
|
||||
subgraph_builder = StateGraph(SubgraphMessagesState)
|
||||
subgraph_builder.add_node("call_model_from_subgraph", call_model)
|
||||
subgraph_builder.add_edge(START, "call_model_from_subgraph")
|
||||
...
|
||||
# highlight-next-line
|
||||
subgraph = subgraph_builder.compile()
|
||||
const subgraphBuilder = new StateGraph(MessagesZodState).addNode(
|
||||
"callModel",
|
||||
async (state) => {
|
||||
const response = await model.invoke(state.messages);
|
||||
return { messages: response };
|
||||
}
|
||||
);
|
||||
// ... other nodes and edges
|
||||
// highlight-next-line
|
||||
const subgraph = subgraphBuilder.compile();
|
||||
|
||||
# Parent graph
|
||||
// Parent graph
|
||||
|
||||
def call_subgraph(state: MessagesState):
|
||||
response = subgraph.invoke({"subgraph_messages": state["messages"]})
|
||||
return {"messages": response["subgraph_messages"]}
|
||||
const builder = new StateGraph(MessagesZodState)
|
||||
// highlight-next-line
|
||||
.addNode("subgraphNode", subgraph)
|
||||
.addEdge(START, "subgraphNode");
|
||||
const graph = builder.compile();
|
||||
// ...
|
||||
await graph.invoke({ messages: [{ role: "user", content: "hi!" }] });
|
||||
```
|
||||
|
||||
builder = StateGraph(State)
|
||||
# highlight-next-line
|
||||
builder.add_node("subgraph_node", call_subgraph)
|
||||
builder.add_edge(START, "subgraph_node")
|
||||
graph = builder.compile()
|
||||
...
|
||||
graph.invoke({"messages": [{"role": "user", "content": "hi!"}]})
|
||||
```
|
||||
:::
|
||||
|
||||
- parent graph and subgraph have **different schemas** (no shared state keys in their state [schemas](./low_level.md#state)). In this case, you have to [call the subgraph from inside a node in the parent graph](../how-tos/subgraph.ipynb#different-state-schemas): this is useful when the parent graph and the subgraph have different state schemas and you need to transform state before or after calling the subgraph
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
from typing_extensions import TypedDict, Annotated
|
||||
from langchain_core.messages import AnyMessage
|
||||
from langgraph.graph import StateGraph, MessagesState, START
|
||||
from langgraph.graph.message import add_messages
|
||||
|
||||
class SubgraphMessagesState(TypedDict):
|
||||
# highlight-next-line
|
||||
subgraph_messages: Annotated[list[AnyMessage], add_messages]
|
||||
|
||||
# Subgraph
|
||||
|
||||
# highlight-next-line
|
||||
def call_model(state: SubgraphMessagesState):
|
||||
response = model.invoke(state["subgraph_messages"])
|
||||
return {"subgraph_messages": response}
|
||||
|
||||
subgraph_builder = StateGraph(SubgraphMessagesState)
|
||||
subgraph_builder.add_node("call_model_from_subgraph", call_model)
|
||||
subgraph_builder.add_edge(START, "call_model_from_subgraph")
|
||||
...
|
||||
# highlight-next-line
|
||||
subgraph = subgraph_builder.compile()
|
||||
|
||||
# Parent graph
|
||||
|
||||
def call_subgraph(state: MessagesState):
|
||||
response = subgraph.invoke({"subgraph_messages": state["messages"]})
|
||||
return {"messages": response["subgraph_messages"]}
|
||||
|
||||
builder = StateGraph(State)
|
||||
# highlight-next-line
|
||||
builder.add_node("subgraph_node", call_subgraph)
|
||||
builder.add_edge(START, "subgraph_node")
|
||||
graph = builder.compile()
|
||||
...
|
||||
graph.invoke({"messages": [{"role": "user", "content": "hi!"}]})
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
import { StateGraph, MessagesZodState, START } from "@langchain/langgraph";
|
||||
import { z } from "zod";
|
||||
|
||||
const SubgraphState = z.object({
|
||||
// highlight-next-line
|
||||
subgraphMessages: MessagesZodState.shape.messages,
|
||||
});
|
||||
|
||||
// Subgraph
|
||||
|
||||
const subgraphBuilder = new StateGraph(SubgraphState)
|
||||
// highlight-next-line
|
||||
.addNode("callModelFromSubgraph", async (state) => {
|
||||
const response = await model.invoke(state.subgraphMessages);
|
||||
return { subgraphMessages: response };
|
||||
})
|
||||
.addEdge(START, "callModelFromSubgraph");
|
||||
// ...
|
||||
// highlight-next-line
|
||||
const subgraph = subgraphBuilder.compile();
|
||||
|
||||
// Parent graph
|
||||
|
||||
const builder = new StateGraph(MessagesZodState)
|
||||
// highlight-next-line
|
||||
.addNode("subgraphNode", async (state) => {
|
||||
const response = await subgraph.invoke({
|
||||
subgraphMessages: state.messages,
|
||||
});
|
||||
return { messages: response.subgraphMessages };
|
||||
})
|
||||
.addEdge(START, "subgraphNode");
|
||||
const graph = builder.compile();
|
||||
// ...
|
||||
await graph.invoke({ messages: [{ role: "user", content: "hi!" }] });
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
@@ -9,6 +9,7 @@ Templates are open source reference applications designed to help you get starte
|
||||
|
||||
You can create an application from a template using the LangGraph CLI.
|
||||
|
||||
:::python
|
||||
!!! info "Requirements"
|
||||
|
||||
- Python >= 3.11
|
||||
@@ -16,56 +17,74 @@ You can create an application from a template using the LangGraph CLI.
|
||||
|
||||
## Install the LangGraph CLI
|
||||
|
||||
=== "Python"
|
||||
```bash
|
||||
pip install "langgraph-cli[inmem]" --upgrade
|
||||
```
|
||||
|
||||
```bash
|
||||
pip install "langgraph-cli[inmem]" --upgrade
|
||||
```
|
||||
Or via [`uv`](https://docs.astral.sh/uv/getting-started/installation/) (recommended):
|
||||
|
||||
Or via [`uv`](https://docs.astral.sh/uv/getting-started/installation/) (recommended):
|
||||
```bash
|
||||
uvx --from "langgraph-cli[inmem]" langgraph dev --help
|
||||
```
|
||||
|
||||
```bash
|
||||
uvx --from "langgraph-cli[inmem]" langgraph dev --help
|
||||
```
|
||||
:::
|
||||
|
||||
=== "JS"
|
||||
:::js
|
||||
|
||||
```bash
|
||||
npx @langchain/langgraph-cli --help
|
||||
```
|
||||
```bash
|
||||
npx @langchain/langgraph-cli --help
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Available Templates
|
||||
|
||||
| Template | Description | Python | JS/TS |
|
||||
|---------------------------|------------------------------------------------------------------------------------------|------------------------------------------------------------------|---------------------------------------------------------------------|
|
||||
| **New LangGraph Project** | A simple, minimal chatbot with memory. | [Repo](https://github.com/langchain-ai/new-langgraph-project) | [Repo](https://github.com/langchain-ai/new-langgraphjs-project) |
|
||||
| **ReAct Agent** | A simple agent that can be flexibly extended to many tools. | [Repo](https://github.com/langchain-ai/react-agent) | [Repo](https://github.com/langchain-ai/react-agent-js) |
|
||||
| **Memory Agent** | A ReAct-style agent with an additional tool to store memories for use across threads. | [Repo](https://github.com/langchain-ai/memory-agent) | [Repo](https://github.com/langchain-ai/memory-agent-js) |
|
||||
| **Retrieval Agent** | An agent that includes a retrieval-based question-answering system. | [Repo](https://github.com/langchain-ai/retrieval-agent-template) | [Repo](https://github.com/langchain-ai/retrieval-agent-template-js) |
|
||||
| **Data-Enrichment Agent** | An agent that performs web searches and organizes its findings into a structured format. | [Repo](https://github.com/langchain-ai/data-enrichment) | [Repo](https://github.com/langchain-ai/data-enrichment-js) |
|
||||
:::python
|
||||
| Template | Description | Link |
|
||||
| -------- | ----------- | ------ |
|
||||
| **New LangGraph Project** | A simple, minimal chatbot with memory. | [Repo](https://github.com/langchain-ai/new-langgraph-project) |
|
||||
| **ReAct Agent** | A simple agent that can be flexibly extended to many tools. | [Repo](https://github.com/langchain-ai/react-agent) |
|
||||
| **Memory Agent** | A ReAct-style agent with an additional tool to store memories for use across threads. | [Repo](https://github.com/langchain-ai/memory-agent) |
|
||||
| **Retrieval Agent** | An agent that includes a retrieval-based question-answering system. | [Repo](https://github.com/langchain-ai/retrieval-agent-template) |
|
||||
| **Data-Enrichment Agent** | An agent that performs web searches and organizes its findings into a structured format. | [Repo](https://github.com/langchain-ai/data-enrichment) |
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
| Template | Description | Link |
|
||||
| -------- | ----------- | ------ |
|
||||
| **New LangGraph Project** | A simple, minimal chatbot with memory. | [Repo](https://github.com/langchain-ai/new-langgraphjs-project) |
|
||||
| **ReAct Agent** | A simple agent that can be flexibly extended to many tools. | [Repo](https://github.com/langchain-ai/react-agent-js) |
|
||||
| **Memory Agent** | A ReAct-style agent with an additional tool to store memories for use across threads. | [Repo](https://github.com/langchain-ai/memory-agent-js) |
|
||||
| **Retrieval Agent** | An agent that includes a retrieval-based question-answering system. | [Repo](https://github.com/langchain-ai/retrieval-agent-template-js) |
|
||||
| **Data-Enrichment Agent** | An agent that performs web searches and organizes its findings into a structured format. | [Repo](https://github.com/langchain-ai/data-enrichment-js) |
|
||||
:::
|
||||
|
||||
## 🌱 Create a LangGraph App
|
||||
|
||||
To create a new app from a template, use the `langgraph new` command.
|
||||
|
||||
=== "Python"
|
||||
:::python
|
||||
|
||||
```bash
|
||||
langgraph new
|
||||
```
|
||||
```bash
|
||||
langgraph new
|
||||
```
|
||||
|
||||
Or via [`uv`](https://docs.astral.sh/uv/getting-started/installation/) (recommended):
|
||||
Or via [`uv`](https://docs.astral.sh/uv/getting-started/installation/) (recommended):
|
||||
|
||||
```bash
|
||||
uvx --from "langgraph-cli[inmem]" langgraph new
|
||||
```
|
||||
```bash
|
||||
uvx --from "langgraph-cli[inmem]" langgraph new
|
||||
```
|
||||
|
||||
=== "JS"
|
||||
:::
|
||||
|
||||
```bash
|
||||
npm create langgraph@latest
|
||||
```
|
||||
:::js
|
||||
|
||||
```bash
|
||||
npm create langgraph
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Next Steps
|
||||
|
||||
@@ -73,26 +92,31 @@ Review the `README.md` file in the root of your new LangGraph app for more infor
|
||||
|
||||
After configuring the app properly and adding your API keys, you can start the app using the LangGraph CLI:
|
||||
|
||||
=== "Python"
|
||||
:::python
|
||||
|
||||
```bash
|
||||
langgraph dev
|
||||
```
|
||||
```bash
|
||||
langgraph dev
|
||||
```
|
||||
|
||||
Or via [`uv`](https://docs.astral.sh/uv/getting-started/installation/) (recommended):
|
||||
Or via [`uv`](https://docs.astral.sh/uv/getting-started/installation/) (recommended):
|
||||
|
||||
```bash
|
||||
uvx --from "langgraph-cli[inmem]" --with-editable . langgraph dev
|
||||
```
|
||||
```bash
|
||||
uvx --from "langgraph-cli[inmem]" --with-editable . langgraph dev
|
||||
```
|
||||
|
||||
??? info "Missing Local Package?"
|
||||
If you are not using `uv` and run into a "`ModuleNotFoundError`" or "`ImportError`", even after installing the local package (`pip install -e .`), it is likely the case that you need to install the CLI into your local virtual environment to make the CLI "aware" of the local package. You can do this by running `python -m pip install "langgraph-cli[inmem]"` and re-activating your virtual environment before running `langgraph dev`.
|
||||
!!! info "Missing Local Package?"
|
||||
|
||||
=== "JS"
|
||||
If you are not using `uv` and run into a "`ModuleNotFoundError`" or "`ImportError`", even after installing the local package (`pip install -e .`), it is likely the case that you need to install the CLI into your local virtual environment to make the CLI "aware" of the local package. You can do this by running `python -m pip install "langgraph-cli[inmem]"` and re-activating your virtual environment before running `langgraph dev`.
|
||||
|
||||
```bash
|
||||
npx @langchain/langgraph-cli dev
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```bash
|
||||
npx @langchain/langgraph-cli dev
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
See the following guides for more information on how to deploy your app:
|
||||
|
||||
|
||||
+101
-7
@@ -2,7 +2,13 @@
|
||||
|
||||
Many AI applications interact with users via natural language. However, some use cases require models to interface directly with external systems—such as APIs, databases, or file systems—using structured input. In these scenarios, [tool calling](../how-tos/tool-calling.md) enables models to generate requests that conform to a specified input schema.
|
||||
|
||||
:::python
|
||||
**Tools** encapsulate a callable function and its input schema. These can be passed to compatible [chat models](https://python.langchain.com/docs/concepts/chat_models), allowing the model to decide whether to invoke a tool and with what arguments.
|
||||
:::
|
||||
|
||||
:::js
|
||||
**Tools** encapsulate a callable function and its input schema. These can be passed to compatible [chat models](https://js.langchain.com/docs/concepts/chat_models), allowing the model to decide whether to invoke a tool and with what arguments.
|
||||
:::
|
||||
|
||||
## Tool calling
|
||||
|
||||
@@ -10,17 +16,63 @@ Many AI applications interact with users via natural language. However, some use
|
||||
|
||||
Tool calling is typically **conditional**. Based on the user input and available tools, the model may choose to issue a tool call request. This request is returned in an `AIMessage` object, which includes a `tool_calls` field that specifies the tool name and input arguments:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
llm_with_tools.invoke("What is 2 multiplied by 3?")
|
||||
# -> AIMessage(tool_calls=[{'name': 'multiply', 'args': {'a': 2, 'b': 3}, ...}])
|
||||
```
|
||||
|
||||
```
|
||||
AIMessage(
|
||||
tool_calls=[
|
||||
ToolCall(name="multiply", args={"a": 2, "b": 3}),
|
||||
...
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
await llmWithTools.invoke("What is 2 multiplied by 3?");
|
||||
```
|
||||
|
||||
```
|
||||
AIMessage {
|
||||
tool_calls: [
|
||||
ToolCall {
|
||||
name: "multiply",
|
||||
args: { a: 2, b: 3 },
|
||||
...
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
If the input is unrelated to any tool, the model returns only a natural language message:
|
||||
|
||||
:::python
|
||||
|
||||
```python
|
||||
llm_with_tools.invoke("Hello world!") # -> AIMessage(content="Hello!")
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
```typescript
|
||||
await llmWithTools.invoke("Hello world!"); // { content: "Hello!" }
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Importantly, the model does not execute the tool—it only generates a request. A separate executor (such as a runtime or agent) is responsible for handling the tool call and returning the result.
|
||||
|
||||
See the [tool calling guide](../how-tos/tool-calling.md) for more details.
|
||||
@@ -29,18 +81,25 @@ See the [tool calling guide](../how-tos/tool-calling.md) for more details.
|
||||
|
||||
LangChain provides prebuilt tool integrations for common external systems including APIs, databases, file systems, and web data.
|
||||
|
||||
:::python
|
||||
Browse the [integrations directory](https://python.langchain.com/docs/integrations/tools/) for available tools.
|
||||
:::
|
||||
|
||||
:::js
|
||||
Browse the [integrations directory](https://js.langchain.com/docs/integrations/tools/) for available tools.
|
||||
:::
|
||||
|
||||
Common categories:
|
||||
|
||||
* **Search**: Bing, SerpAPI, Tavily
|
||||
* **Code execution**: Python REPL, Node.js REPL
|
||||
* **Databases**: SQL, MongoDB, Redis
|
||||
* **Web data**: Scraping and browsing
|
||||
* **APIs**: OpenWeatherMap, NewsAPI, etc.
|
||||
- **Search**: Bing, SerpAPI, Tavily
|
||||
- **Code execution**: Python REPL, Node.js REPL
|
||||
- **Databases**: SQL, MongoDB, Redis
|
||||
- **Web data**: Scraping and browsing
|
||||
- **APIs**: OpenWeatherMap, NewsAPI, etc.
|
||||
|
||||
## Custom tools
|
||||
|
||||
:::python
|
||||
You can define custom tools using the `@tool` decorator or plain Python functions. For example:
|
||||
|
||||
```python
|
||||
@@ -52,6 +111,32 @@ def multiply(a: int, b: int) -> int:
|
||||
return a * b
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
:::js
|
||||
You can define custom tools using the `tool` function. For example:
|
||||
|
||||
```typescript
|
||||
import { tool } from "@langchain/core/tools";
|
||||
import { z } from "zod";
|
||||
|
||||
const multiply = tool(
|
||||
(input) => {
|
||||
return input.a * input.b;
|
||||
},
|
||||
{
|
||||
name: "multiply",
|
||||
description: "Multiply two numbers.",
|
||||
schema: z.object({
|
||||
a: z.number(),
|
||||
b: z.number(),
|
||||
}),
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
See the [tool calling guide](../how-tos/tool-calling.md) for more details.
|
||||
|
||||
## Tool execution
|
||||
@@ -60,5 +145,14 @@ While the model determines when to call a tool, execution of the tool call must
|
||||
|
||||
LangGraph provides prebuilt components for this:
|
||||
|
||||
* [`ToolNode`][langgraph.prebuilt.tool_node.ToolNode]: A prebuilt node that executes tools.
|
||||
* [`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent]: Constructs a full agent that manages tool calling automatically.
|
||||
:::python
|
||||
|
||||
- @[`ToolNode`][ToolNode]: A prebuilt node that executes tools.
|
||||
- @[`create_react_agent`][create_react_agent]: Constructs a full agent that manages tool calling automatically.
|
||||
:::
|
||||
|
||||
:::js
|
||||
|
||||
- @[ToolNode]: A prebuilt node that executes tools.
|
||||
- @[`createReactAgent`][create_react_agent]: Constructs a full agent that manages tool calling automatically.
|
||||
:::
|
||||
|
||||
Reference in New Issue
Block a user