mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-09-10 03:37:51 +02:00
sample of docs translations
This commit is contained in:
@@ -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
|
||||
@@ -56,21 +58,22 @@ Below are examples of directory structures for Python and JavaScript application
|
||||
├── langgraph.json # configuration file for LangGraph
|
||||
└── 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 you 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 +91,65 @@ 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"
|
||||
:::js
|
||||
* 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"
|
||||
}
|
||||
```
|
||||
|
||||
=== "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.
|
||||
|
||||
```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).
|
||||
|
||||
@@ -147,4 +163,4 @@ You can specify one or more graphs in the configuration file. Each graph is iden
|
||||
|
||||
If you're working with a deployed LangGraph application locally, you can configure environment variables in the `env` key of the [LangGraph configuration file](#configuration-file-concepts).
|
||||
|
||||
For a production deployment, you will typically want to configure the environment variables in the deployment environment.
|
||||
For a production deployment, you will typically want to configure the environment variables in the deployment environment.
|
||||
+399
-14
@@ -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
|
||||
|
||||
@@ -84,15 +90,24 @@ 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`](../cloud/reference/sdk/typescript_sdk_ref.md#auth.authenticate) handler in LangGraph handles steps 4-6, while your [`@auth.on`](../cloud/reference/sdk/typescript_sdk_ref.md#auth.on) handlers implement step 7.
|
||||
:::
|
||||
|
||||
## Authentication
|
||||
|
||||
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:
|
||||
Authentication in LangGraph runs as middleware on every request. Your authentication 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
|
||||
2. Return user info containing the user's identity and user information if valid
|
||||
3. Raise an HTTP exception or throw an error if invalid
|
||||
|
||||
:::python
|
||||
Your [`@auth.authenticate`](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.Auth.authenticate) handler should return [user info](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.types.MinimalUserDict) and raise an [HTTP exception](../cloud/reference/sdk/python_sdk_ref.md#langgraph_sdk.auth.exceptions.HTTPException) or AssertionError if invalid.
|
||||
|
||||
```python
|
||||
from langgraph_sdk import Auth
|
||||
@@ -126,11 +141,58 @@ 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
|
||||
Your [`@auth.authenticate`](../cloud/reference/sdk/typescript_sdk_ref.md#auth.authenticate) handler should return [user info](../cloud/reference/sdk/typescript_sdk_ref.md#auth.types.MinimalUserDict) and throw an [HTTP exception](../cloud/reference/sdk/typescript_sdk_ref.md#auth.exceptions.HTTPException) or throw an error if invalid.
|
||||
|
||||
```typescript
|
||||
import { Auth } from "@langchain/langgraph-sdk";
|
||||
|
||||
const auth = new Auth();
|
||||
|
||||
auth.authenticate(async (headers: Record<string, string>) => {
|
||||
// Validate credentials (e.g., API key, JWT token)
|
||||
const apiKey = headers["x-api-key"];
|
||||
if (!apiKey || !isValidKey(apiKey)) {
|
||||
throw new Auth.exceptions.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 [`ctx.user`](../cloud/reference/sdk/typescript_sdk_ref.md#auth.types.AuthContext)
|
||||
- In your application via `config.configurable.langgraph_auth_user`
|
||||
:::
|
||||
|
||||
??? tip "Supported Parameters"
|
||||
|
||||
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:
|
||||
The authentication handler can accept any of the following parameters by name:
|
||||
|
||||
* request: The raw request object
|
||||
* body: The parsed request body
|
||||
* path: The request path
|
||||
* method: The HTTP method
|
||||
* query_params/queryParams: URL query parameters
|
||||
* headers: Request headers
|
||||
* authorization: The Authorization header value
|
||||
|
||||
:::python
|
||||
* request (Request): The raw ASGI request object
|
||||
* body (dict): The parsed request body
|
||||
* path (str): The request path, e.g., "/threads/abcd-1234-abcd-1234/runs/abcd-1234-abcd-1234/stream"
|
||||
@@ -139,19 +201,34 @@ 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
|
||||
* 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.
|
||||
|
||||
## 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).
|
||||
3. Raise an HTTP exception if access is denied.
|
||||
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 or throw an error 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
|
||||
@@ -192,10 +269,58 @@ async def add_owner(
|
||||
# to ensure users can only access their own resources
|
||||
return filters
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
Your [`@auth.on`](../cloud/reference/sdk/typescript_sdk_ref.md#auth.on) handlers control access by mutating the `value.metadata` object directly and returning a [filter object](#filter-operations).
|
||||
|
||||
```typescript
|
||||
auth.on(async (
|
||||
ctx: Auth.types.AuthContext,
|
||||
value: any // The payload being sent to this access method
|
||||
): Promise<any> => { // Returns a filter object that restricts access to resources
|
||||
/**
|
||||
* Authorize all access to threads, runs, crons, and assistants.
|
||||
*
|
||||
* This handler does two things:
|
||||
* - Adds a value to resource metadata (to persist with the resource so it can be filtered later)
|
||||
* - Returns a filter (to restrict access to existing resources)
|
||||
*
|
||||
* Args:
|
||||
* ctx: Authentication context containing user info, permissions, the path, and
|
||||
* value: The request payload sent to the endpoint. For creation
|
||||
* operations, this contains the resource parameters. For read
|
||||
* operations, this contains the resource being accessed.
|
||||
*
|
||||
* Returns:
|
||||
* A filter object that LangGraph uses to restrict access to resources.
|
||||
* See [Filter Operations](#filter-operations) for supported operators.
|
||||
*/
|
||||
// Create filter to restrict access to just this user's resources
|
||||
const filters = { owner: ctx.user.identity };
|
||||
|
||||
// Get or create the metadata object in the payload
|
||||
// This is where we store persistent info about the resource
|
||||
if (!value.metadata) {
|
||||
value.metadata = {};
|
||||
}
|
||||
|
||||
// Add owner to metadata - if this is a create or update operation,
|
||||
// this information will be saved with the resource
|
||||
// So we can filter by it later in read operations
|
||||
Object.assign(value.metadata, filters);
|
||||
|
||||
// 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
|
||||
@@ -206,6 +331,7 @@ 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
|
||||
@@ -289,12 +415,121 @@ async def on_assistant_create(
|
||||
detail="User lacks the required permissions."
|
||||
)
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
// Generic / global handler catches calls that aren't handled by more specific handlers
|
||||
auth.on(async (ctx: Auth.types.AuthContext, value: any) => {
|
||||
console.log(`Request to ${ctx.path} by ${ctx.user.identity}`);
|
||||
throw new Auth.exceptions.HTTPException(
|
||||
403,
|
||||
"Forbidden"
|
||||
);
|
||||
});
|
||||
|
||||
// Matches the "thread" resource and all actions - create, read, update, delete, search
|
||||
// Since this is **more specific** than the generic @auth.on handler, it will take precedence
|
||||
// over the generic handler for all actions on the "threads" resource
|
||||
auth.on.threads(async (
|
||||
ctx: Auth.types.AuthContext,
|
||||
value: Auth.types.threads.create.value
|
||||
) => {
|
||||
if (!ctx.permissions.includes("write")) {
|
||||
throw new Auth.exceptions.HTTPException(
|
||||
403,
|
||||
"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
|
||||
if (!value.metadata) {
|
||||
value.metadata = {};
|
||||
}
|
||||
value.metadata.owner = ctx.user.identity;
|
||||
return { owner: ctx.user.identity };
|
||||
});
|
||||
|
||||
// Thread creation. This will match only on thread create actions
|
||||
// Since this is **more specific** than both the generic @auth.on handler and the @auth.on.threads handler,
|
||||
// it will take precedence for any "create" actions on the "threads" resources
|
||||
auth.on.threads.create(async (
|
||||
ctx: Auth.types.AuthContext,
|
||||
value: Auth.types.threads.create.value
|
||||
) => {
|
||||
// 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
|
||||
if (!value.metadata) {
|
||||
value.metadata = {};
|
||||
}
|
||||
value.metadata.owner = ctx.user.identity;
|
||||
return { owner: ctx.user.identity };
|
||||
});
|
||||
|
||||
// Reading a thread. Since this is also more specific than the generic @auth.on handler, and the @auth.on.threads handler,
|
||||
// it will take precedence for any "read" actions on the "threads" resource
|
||||
auth.on.threads.read(async (
|
||||
ctx: Auth.types.AuthContext,
|
||||
value: Auth.types.threads.read.value
|
||||
) => {
|
||||
// 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: ctx.user.identity };
|
||||
});
|
||||
|
||||
// Run creation, streaming, updates, etc.
|
||||
// This takes precedence over the generic @auth.on handler and the @auth.on.threads handler
|
||||
auth.on.threads.createRun(async (
|
||||
ctx: Auth.types.AuthContext,
|
||||
value: Auth.types.threads.createRun.value
|
||||
) => {
|
||||
if (!value.metadata) {
|
||||
value.metadata = {};
|
||||
}
|
||||
value.metadata.owner = ctx.user.identity;
|
||||
// Inherit thread's access control
|
||||
return { owner: ctx.user.identity };
|
||||
});
|
||||
|
||||
// Assistant creation
|
||||
auth.on.assistants.create(async (
|
||||
ctx: Auth.types.AuthContext,
|
||||
value: Auth.types.assistants.create.value
|
||||
) => {
|
||||
if (!ctx.permissions.includes("assistants:create")) {
|
||||
throw new Auth.exceptions.HTTPException(
|
||||
403,
|
||||
"User lacks the required permissions."
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
:::
|
||||
|
||||
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.
|
||||
Authorization handlers can return different types of values:
|
||||
|
||||
- `None`/`null` and `True`/`true` mean "authorize access to all underling resources"
|
||||
- `False`/`false` means "deny access to all underling resources (raises a 403 exception)"
|
||||
- A metadata filter restricts access to resources
|
||||
|
||||
A filter is a collection with keys that match the resource metadata. It supports three operators:
|
||||
|
||||
- The default value is a shorthand for exact match, or "$eq", below
|
||||
- `$eq`: Exact match
|
||||
- `$contains`: List membership - The value here must be an element of the list. The metadata in the stored resource must be a list/container type.
|
||||
|
||||
A filter with multiple keys is treated using a logical `AND` filter.
|
||||
|
||||
:::python
|
||||
- `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
|
||||
@@ -307,6 +542,22 @@ 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
|
||||
- `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
|
||||
|
||||
@@ -316,6 +567,7 @@ 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):
|
||||
@@ -323,11 +575,25 @@ async def owner_only(ctx: Auth.types.AuthContext, value: dict):
|
||||
metadata["owner"] = ctx.user.identity
|
||||
return {"owner": ctx.user.identity}
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
auth.on(async (ctx: Auth.types.AuthContext, value: any) => {
|
||||
if (!value.metadata) {
|
||||
value.metadata = {};
|
||||
}
|
||||
value.metadata.owner = ctx.user.identity;
|
||||
return { owner: ctx.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
|
||||
@@ -363,20 +629,83 @@ async def rbac_create(ctx: Auth.types.AuthContext, value: dict):
|
||||
)
|
||||
return _default(ctx, value)
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
// In your auth handler:
|
||||
auth.authenticate(async (headers: Record<string, string>) => {
|
||||
// ...
|
||||
return {
|
||||
identity: "user-123",
|
||||
isAuthenticated: true,
|
||||
permissions: ["threads:write", "threads:read"] // Define permissions in auth
|
||||
};
|
||||
});
|
||||
|
||||
function _default(ctx: Auth.types.AuthContext, value: any) {
|
||||
if (!value.metadata) {
|
||||
value.metadata = {};
|
||||
}
|
||||
value.metadata.owner = ctx.user.identity;
|
||||
return { owner: ctx.user.identity };
|
||||
}
|
||||
|
||||
auth.on.threads.create(async (ctx: Auth.types.AuthContext, value: any) => {
|
||||
if (!ctx.permissions.includes("threads:write")) {
|
||||
throw new Auth.exceptions.HTTPException(
|
||||
403,
|
||||
"Unauthorized"
|
||||
);
|
||||
}
|
||||
return _default(ctx, value);
|
||||
});
|
||||
|
||||
auth.on.threads.read(async (ctx: Auth.types.AuthContext, value: any) => {
|
||||
if (!ctx.permissions.includes("threads:read") && !ctx.permissions.includes("threads:write")) {
|
||||
throw new Auth.exceptions.HTTPException(
|
||||
403,
|
||||
"Unauthorized"
|
||||
);
|
||||
}
|
||||
return _default(ctx, value);
|
||||
});
|
||||
```
|
||||
:::
|
||||
|
||||
## Supported Resources
|
||||
|
||||
LangGraph provides three levels of authorization handlers, from most general to most specific:
|
||||
|
||||
1. **Global Handler**: Matches all resources and actions
|
||||
2. **Resource Handler**: Matches all actions for a specific resource
|
||||
3. **Action Handler**: Matches a specific action on a specific resource
|
||||
|
||||
The most specific matching handler will be used. For example, a thread-specific create handler takes precedence over a general threads handler for thread creation.
|
||||
If a more specific handler is registered, the more general handler will not be called for that resource and action.
|
||||
|
||||
:::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** (`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.
|
||||
:::
|
||||
|
||||
???+ 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
|
||||
```python
|
||||
@auth.on.threads.create
|
||||
async def on_thread_create(
|
||||
@@ -399,11 +728,39 @@ If a more specific handler is registered, the more general handler will not be c
|
||||
):
|
||||
...
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
auth.on.threads.create(async (
|
||||
ctx: Auth.types.AuthContext,
|
||||
value: Auth.types.on.threads.create.value // Specific type for thread creation
|
||||
) => {
|
||||
// ...
|
||||
});
|
||||
|
||||
auth.on.threads(async (
|
||||
ctx: Auth.types.AuthContext,
|
||||
value: Auth.types.on.threads.value // Union type of all thread actions
|
||||
) => {
|
||||
// ...
|
||||
});
|
||||
|
||||
auth.on(async (
|
||||
ctx: Auth.types.AuthContext,
|
||||
value: any // Union type of all possible actions
|
||||
) => {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
:::
|
||||
|
||||
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) |
|
||||
@@ -422,16 +779,44 @@ 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 | Handler | Description | Value Type |
|
||||
|----------|---------|-------------|------------|
|
||||
| **Threads** | `auth.on.threads.create` | Thread creation | [`ThreadsCreate`](../cloud/reference/sdk/typescript_sdk_ref.md#auth.types.ThreadsCreate) |
|
||||
| | `auth.on.threads.read` | Thread retrieval | [`ThreadsRead`](../cloud/reference/sdk/typescript_sdk_ref.md#auth.types.ThreadsRead) |
|
||||
| | `auth.on.threads.update` | Thread updates | [`ThreadsUpdate`](../cloud/reference/sdk/typescript_sdk_ref.md#auth.types.ThreadsUpdate) |
|
||||
| | `auth.on.threads.delete` | Thread deletion | [`ThreadsDelete`](../cloud/reference/sdk/typescript_sdk_ref.md#auth.types.ThreadsDelete) |
|
||||
| | `auth.on.threads.search` | Listing threads | [`ThreadsSearch`](../cloud/reference/sdk/typescript_sdk_ref.md#auth.types.ThreadsSearch) |
|
||||
| | `auth.on.threads.createRun` | Creating or updating a run | [`RunsCreate`](../cloud/reference/sdk/typescript_sdk_ref.md#auth.types.RunsCreate) |
|
||||
| **Assistants** | `auth.on.assistants.create` | Assistant creation | [`AssistantsCreate`](../cloud/reference/sdk/typescript_sdk_ref.md#auth.types.AssistantsCreate) |
|
||||
| | `auth.on.assistants.read` | Assistant retrieval | [`AssistantsRead`](../cloud/reference/sdk/typescript_sdk_ref.md#auth.types.AssistantsRead) |
|
||||
| | `auth.on.assistants.update` | Assistant updates | [`AssistantsUpdate`](../cloud/reference/sdk/typescript_sdk_ref.md#auth.types.AssistantsUpdate) |
|
||||
| | `auth.on.assistants.delete` | Assistant deletion | [`AssistantsDelete`](../cloud/reference/sdk/typescript_sdk_ref.md#auth.types.AssistantsDelete) |
|
||||
| | `auth.on.assistants.search` | Listing assistants | [`AssistantsSearch`](../cloud/reference/sdk/typescript_sdk_ref.md#auth.types.AssistantsSearch) |
|
||||
| **Crons** | `auth.on.crons.create` | Cron job creation | [`CronsCreate`](../cloud/reference/sdk/typescript_sdk_ref.md#auth.types.CronsCreate) |
|
||||
| | `auth.on.crons.read` | Cron job retrieval | [`CronsRead`](../cloud/reference/sdk/typescript_sdk_ref.md#auth.types.CronsRead) |
|
||||
| | `auth.on.crons.update` | Cron job updates | [`CronsUpdate`](../cloud/reference/sdk/typescript_sdk_ref.md#auth.types.CronsUpdate) |
|
||||
| | `auth.on.crons.delete` | Cron job deletion | [`CronsDelete`](../cloud/reference/sdk/typescript_sdk_ref.md#auth.types.CronsDelete) |
|
||||
| | `auth.on.crons.search` | Listing cron jobs | [`CronsSearch`](../cloud/reference/sdk/typescript_sdk_ref.md#auth.types.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 `createRun` handler for creating new runs because it had more arguments that you can view in the handler.
|
||||
:::
|
||||
|
||||
## Next Steps
|
||||
|
||||
For implementation details:
|
||||
|
||||
- Check out the introductory tutorial on [setting up authentication](../tutorials/auth/getting_started.md)
|
||||
- See the how-to guide on implementing a [custom auth handlers](../how-tos/auth/custom_auth.md)
|
||||
- See the how-to guide on implementing a [custom auth handlers](../how-tos/auth/custom_auth.md)
|
||||
@@ -13,8 +13,15 @@ It is designed to integrate these features into existing code that may use stand
|
||||
|
||||
The Functional API uses two key building blocks:
|
||||
|
||||
:::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`** – 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.
|
||||
:::
|
||||
|
||||
This provides a minimal abstraction for building workflows with state management and streaming.
|
||||
|
||||
@@ -27,6 +34,7 @@ This provides a minimal abstraction for building workflows with state management
|
||||
|
||||
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 MemorySaver
|
||||
from langgraph.func import entrypoint, task
|
||||
@@ -58,13 +66,50 @@ def workflow(topic: str) -> dict:
|
||||
"is_approved": is_approved, # Response from HIL
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { MemorySaver } from "@langchain/langgraph";
|
||||
import { entrypoint, task } from "@langchain/langgraph";
|
||||
import { interrupt } from "@langchain/langgraph";
|
||||
|
||||
const writeEssay = task("writeEssay", async (topic: string): Promise<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() },
|
||||
async (topic: string): Promise<{ essay: string; isApproved: boolean }> => {
|
||||
const essay = await writeEssay("cat");
|
||||
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: 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: essay, // The essay that was generated
|
||||
isApproved: 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
|
||||
@@ -133,13 +178,94 @@ 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 } from "@langchain/langgraph";
|
||||
import { entrypoint, task } from "@langchain/langgraph";
|
||||
import { interrupt } from "@langchain/langgraph";
|
||||
|
||||
const writeEssay = task("writeEssay", async (topic: string): Promise<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() },
|
||||
async (topic: string): Promise<{ essay: string; isApproved: boolean }> => {
|
||||
const essay = await writeEssay("cat");
|
||||
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: 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: essay, // The essay that was generated
|
||||
isApproved: 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
|
||||
|
||||
:::python
|
||||
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).
|
||||
:::
|
||||
|
||||
:::js
|
||||
The `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
|
||||
|
||||
:::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.
|
||||
@@ -173,23 +299,71 @@ You will usually want to pass a **checkpointer** to the `@entrypoint` decorator
|
||||
...
|
||||
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 a dictionary 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 usually want to pass a **checkpointer** to the `entrypoint` function to enable persistence and use features like **human-in-the-loop**.
|
||||
|
||||
=== "Sync"
|
||||
|
||||
```typescript
|
||||
import { entrypoint } from "@langchain/langgraph";
|
||||
|
||||
const myWorkflow = entrypoint(
|
||||
{ checkpointer: checkpointer },
|
||||
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;
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
=== "Async"
|
||||
|
||||
```typescript
|
||||
import { entrypoint } from "@langchain/langgraph";
|
||||
|
||||
const myWorkflow = entrypoint(
|
||||
{ checkpointer: checkpointer },
|
||||
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.
|
||||
|
||||
|
||||
### Injectable parameters
|
||||
|
||||
When declaring an `entrypoint`, you can request access to additional parameters that will be injected automatically at run time. These parameters include:
|
||||
|
||||
|
||||
:::python
|
||||
| Parameter | Description |
|
||||
|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **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. |
|
||||
:::
|
||||
|
||||
:::js
|
||||
| Parameter | Description |
|
||||
|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **store** | An instance of BaseStore. Useful for [long-term memory](../how-tos/use-functional-api.md#long-term-memory). |
|
||||
| **config** | For accessing run time configuration. See [LangGraphRunnableConfig](../reference/types.md#langgraphrunnable-config) for information. |
|
||||
:::
|
||||
|
||||
!!! important
|
||||
|
||||
@@ -197,6 +371,7 @@ When declaring an `entrypoint`, you can request access to additional parameters
|
||||
|
||||
??? example "Requesting Injectable Parameters"
|
||||
|
||||
:::python
|
||||
```python
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.func import entrypoint
|
||||
@@ -218,9 +393,35 @@ When declaring an `entrypoint`, you can request access to additional parameters
|
||||
config: RunnableConfig # For accessing the configuration passed to the entrypoint
|
||||
) -> ...:
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { entrypoint } from "@langchain/langgraph";
|
||||
import { BaseStore, InMemoryStore } from "@langchain/langgraph";
|
||||
import { LangGraphRunnableConfig } from "@langchain/langgraph";
|
||||
|
||||
const inMemoryStore = new InMemoryStore(); // An instance of InMemoryStore for long-term memory
|
||||
|
||||
const myWorkflow = entrypoint(
|
||||
{
|
||||
checkpointer: checkpointer, // Specify the checkpointer
|
||||
store: inMemoryStore, // Specify the store
|
||||
name: "myWorkflow",
|
||||
},
|
||||
async (
|
||||
someInput: Record<string, any>, // The input (e.g., passed via `invoke`)
|
||||
config: LangGraphRunnableConfig // For accessing the configuration passed to the entrypoint
|
||||
): Promise<any> => {
|
||||
// Rest of workflow logic...
|
||||
}
|
||||
);
|
||||
```
|
||||
:::
|
||||
|
||||
### Executing
|
||||
|
||||
:::python
|
||||
Using the [`@entrypoint`](#entrypoint) yields a [`Pregel`][langgraph.pregel.Pregel.stream] object that can be executed using the `invoke`, `ainvoke`, `stream`, and `astream` methods.
|
||||
|
||||
=== "Invoke"
|
||||
@@ -270,9 +471,40 @@ Using the [`@entrypoint`](#entrypoint) yields a [`Pregel`][langgraph.pregel.Preg
|
||||
async for chunk in my_workflow.astream(some_input, config):
|
||||
print(chunk)
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
Using the `entrypoint` yields a workflow 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
|
||||
|
||||
:::python
|
||||
Resuming an execution after an [interrupt][langgraph.types.interrupt] can be done by passing a **resume** value to the [Command][langgraph.types.Command] primitive.
|
||||
|
||||
=== "Invoke"
|
||||
@@ -332,14 +564,49 @@ Resuming an execution after an [interrupt][langgraph.types.interrupt] can be don
|
||||
async for chunk in my_workflow.astream(Command(resume=some_resume_value), config):
|
||||
print(chunk)
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
Resuming an execution after an `interrupt` can be done by passing a **resume** value to the `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"
|
||||
}
|
||||
};
|
||||
|
||||
for await (const chunk of myWorkflow.stream(new Command({ resume: someResumeValue }), config)) {
|
||||
console.log(chunk);
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
**Resuming after an error**
|
||||
|
||||
|
||||
To resume after an error, run the `entrypoint` with a `None` and the same **thread id** (config).
|
||||
|
||||
This assumes that the underlying **error** has been resolved and execution can proceed successfully.
|
||||
|
||||
:::python
|
||||
=== "Invoke"
|
||||
|
||||
```python
|
||||
@@ -393,11 +660,41 @@ This assumes that the underlying **error** has been resolved and execution can p
|
||||
async for chunk in my_workflow.astream(None, config):
|
||||
print(chunk)
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
=== "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).
|
||||
|
||||
:::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.
|
||||
@@ -417,9 +714,38 @@ config = {
|
||||
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: checkpointer },
|
||||
async (number: number): Promise<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`
|
||||
|
||||
:::python
|
||||
[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**.
|
||||
|
||||
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]`.
|
||||
@@ -442,6 +768,40 @@ config = {
|
||||
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` 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: checkpointer },
|
||||
async (number: number): Promise<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
|
||||
|
||||
@@ -452,6 +812,7 @@ A **task** represents a discrete unit of work, such as an API call or data proce
|
||||
|
||||
### Definition
|
||||
|
||||
:::python
|
||||
Tasks are defined using the `@task` decorator, which wraps a regular Python function.
|
||||
|
||||
```python
|
||||
@@ -463,6 +824,20 @@ def slow_computation(input_value):
|
||||
...
|
||||
return result
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
Tasks are defined using the `task` function, which wraps a regular TypeScript function.
|
||||
|
||||
```typescript
|
||||
import { task } from "@langchain/langgraph";
|
||||
|
||||
const slowComputation = task("slowComputation", async (inputValue: any) => {
|
||||
// Simulate a long-running operation
|
||||
return result;
|
||||
});
|
||||
```
|
||||
:::
|
||||
|
||||
!!! important "Serialization"
|
||||
|
||||
@@ -474,11 +849,11 @@ def slow_computation(input_value):
|
||||
|
||||
Tasks *cannot* be called directly from the main application code.
|
||||
|
||||
:::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
|
||||
@@ -495,6 +870,20 @@ To obtain the result of a **task**, you can either wait for it synchronously (us
|
||||
async def my_workflow(some_input: int) -> int:
|
||||
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: checkpointer },
|
||||
async (someInput: number): Promise<number> => {
|
||||
return await slowComputation(someInput);
|
||||
}
|
||||
);
|
||||
```
|
||||
:::
|
||||
|
||||
## When to use a task
|
||||
|
||||
@@ -510,11 +899,16 @@ To obtain the result of a **task**, you can either wait for it synchronously (us
|
||||
|
||||
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 TypeScript 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,10 +930,19 @@ Idempotency ensures that running the same operation multiple times produces the
|
||||
|
||||
The **Functional API** and the [Graph APIs (StateGraph)](./low_level.md#stategraph) provide two different paradigms to create applications with LangGraph. Here are some key differences:
|
||||
|
||||
:::python
|
||||
- **Control flow**: The Functional API does not require thinking about graph structure. You can use standard Python constructs to define workflows. This will usually trim the amount of code you need to write.
|
||||
- **Short-term memory**: The **GraphAPI** requires declaring a [**State**](./low_level.md#state) and may require defining [**reducers**](./low_level.md#reducers) to manage updates to the graph state. `@entrypoint` and `@tasks` do not require explicit state management as their state is scoped to the function and is not shared across functions.
|
||||
- **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.
|
||||
:::
|
||||
|
||||
:::js
|
||||
- **Control flow**: The Functional API does not require thinking about graph structure. You can use standard TypeScript constructs to define workflows. This will usually trim the amount of code you need to write.
|
||||
- **Short-term memory**: The **GraphAPI** requires declaring a [**State**](./low_level.md#state) and may require defining [**reducers**](./low_level.md#reducers) to manage updates to the graph state. `entrypoint` and `task` do not require explicit state management as their state is scoped to the function and is not shared across functions.
|
||||
- **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.
|
||||
:::
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
@@ -551,6 +954,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:
|
||||
@@ -563,11 +967,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 * as fs from "fs";
|
||||
|
||||
const myWorkflow = entrypoint(
|
||||
{ checkpointer: checkpointer },
|
||||
async (inputs: Record<string, any>): Promise<number> => {
|
||||
// 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
|
||||
|
||||
@@ -585,6 +1009,28 @@ 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: checkpointer },
|
||||
async (inputs: Record<string, any>): Promise<number> => {
|
||||
// The side effect is now encapsulated in a task.
|
||||
await writeToFile();
|
||||
const value = interrupt("question");
|
||||
return value;
|
||||
}
|
||||
);
|
||||
```
|
||||
:::
|
||||
|
||||
### Non-deterministic control flow
|
||||
|
||||
@@ -593,9 +1039,13 @@ Operations that might give different results each time (like getting current tim
|
||||
* 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.
|
||||
|
||||
@@ -605,6 +1055,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
|
||||
|
||||
@@ -628,9 +1079,36 @@ Please read the section on [determinism](#determinism) for more details.
|
||||
"value": value
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
```typescript
|
||||
import { entrypoint, interrupt } from "@langchain/langgraph";
|
||||
|
||||
const myWorkflow = entrypoint(
|
||||
{ checkpointer: checkpointer },
|
||||
async (inputs: { t0: number }): Promise<any> => {
|
||||
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
|
||||
@@ -664,4 +1142,33 @@ Please read the section on [determinism](#determinism) for more details.
|
||||
"value": value
|
||||
}
|
||||
```
|
||||
:::
|
||||
|
||||
:::js
|
||||
In this example, the workflow uses a task to get the current time. This ensures deterministic behavior on resume.
|
||||
|
||||
```typescript
|
||||
import { entrypoint, task, interrupt } from "@langchain/langgraph";
|
||||
|
||||
const getTime = task("getTime", () => Date.now());
|
||||
|
||||
const myWorkflow = entrypoint(
|
||||
{ checkpointer: checkpointer },
|
||||
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 };
|
||||
}
|
||||
}
|
||||
);
|
||||
```
|
||||
:::
|
||||
Reference in New Issue
Block a user