mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-09-03 08:18:47 +02:00
Compare commits
44
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b45bbc1ba | ||
|
|
edf707be51 | ||
|
|
d3b9a96504 | ||
|
|
3257e5ae76 | ||
|
|
bedd0eb286 | ||
|
|
42f0c351fd | ||
|
|
a290984362 | ||
|
|
f1d6fd184f | ||
|
|
503f716104 | ||
|
|
515c34d1ce | ||
|
|
5ea0d49d4d | ||
|
|
d9f71ef8b3 | ||
|
|
8658a5dc0b | ||
|
|
2afee13d9e | ||
|
|
f7d9daa4eb | ||
|
|
fb28aa6d4b | ||
|
|
3488945cdf | ||
|
|
57e8081921 | ||
|
|
078b335448 | ||
|
|
39b2bb9c8f | ||
|
|
5eb793d7d8 | ||
|
|
3dac1894cc | ||
|
|
1500764b46 | ||
|
|
fbb89325f9 | ||
|
|
187c71a812 | ||
|
|
b09b33070e | ||
|
|
31a7bcf750 | ||
|
|
1a12b0309c | ||
|
|
660c15d072 | ||
|
|
4a59da7cfd | ||
|
|
aacc079eed | ||
|
|
3d70a4ed65 | ||
|
|
caad15f7ae | ||
|
|
1f4d4e7bfd | ||
|
|
5b0bf861ac | ||
|
|
a69ea47ac2 | ||
|
|
f83d18188f | ||
|
|
688efdea3d | ||
|
|
6641dcd3c9 | ||
|
|
ad14d92f5e | ||
|
|
c63fbbfaa6 | ||
|
|
577b4413a9 | ||
|
|
f613fdfcbc | ||
|
|
265466184c |
@@ -117,6 +117,7 @@ jobs:
|
||||
--check-links-ignore "https://(api|web|docs)\.smith\.langchain\.com/.*" \
|
||||
--check-links-ignore "https://academy\.langchain\.com/.*" \
|
||||
--check-links-ignore "https://x.com/.*" \
|
||||
--check-links-ignore "https://twitter.com/.*" \
|
||||
--check-links-ignore "https://github\.com/.*" \
|
||||
--check-links-ignore "http://localhost:8123/.*" \
|
||||
--check-links-ignore "http://localhost:2024.*" \
|
||||
@@ -143,6 +144,7 @@ jobs:
|
||||
--check-links-ignore "http://localhost:2024.*" \
|
||||
--check-links-ignore "http://127.0.0.1:.*" \
|
||||
--check-links-ignore "https://x.com/.*" \
|
||||
--check-links-ignore "https://twitter.com/.*" \
|
||||
--check-links-ignore "https://github\.com/.*" \
|
||||
--check-links-ignore "/.*\.(ipynb|html)$" \
|
||||
--check-links ${CHANGED_FILES} \
|
||||
|
||||
@@ -14,4 +14,7 @@ packages:
|
||||
description: "Build agents that learn and adapt from interactions over time."
|
||||
- name: "langchain-mcp-adapters"
|
||||
repo: "langchain-ai/langchain-mcp-adapters"
|
||||
description: "Make Anthropic Model Context Protocol (MCP) tools compatible with LangGraph agents."
|
||||
description: "Make Anthropic Model Context Protocol (MCP) tools compatible with LangGraph agents."
|
||||
- name: "open-deep-research"
|
||||
repo: "langchain-ai/open_deep_research"
|
||||
description: "Open source assistant for iterative web research and report writing."
|
||||
|
||||
@@ -12,6 +12,7 @@ This list of companies using LangGraph and their success stories is compiled fro
|
||||
| [Elastic](https://www.elastic.co/) | Software & Technology | Copilot for domain-specific task | [Blog post, 2025](https://www.elastic.co/blog/elastic-security-generative-ai-features) |
|
||||
| [GitLab](https://about.gitlab.com/) | Software & Technology | Code generation | [Duo workflow docs](https://handbook.gitlab.com/handbook/engineering/architecture/design-documents/duo_workflow/) |
|
||||
| [Infor](https://infor.com/) | Software & Technology | GenAI embedded product experiences; customer support; copilot | [Case study, 2025](https://blog.langchain.dev/customers-infor/) |
|
||||
| [Klarna](https://www.klarna.com/) | Fintech | Copilot for domain-specific task | [Case study, 2025](https://blog.langchain.dev/customers-klarna/) |
|
||||
| [Komodo Health](https://www.komodohealth.com/) | Healthcare | Copilot for domain-specific task | [Blog post](https://www.komodohealth.com/perspectives/new-gen-ai-assistant-empowers-the-enterprise/) |
|
||||
| [LinkedIn](https://www.linkedin.com/) | Social Media | Code generation; Search & discovery | [Blog post, 2025](https://www.linkedin.com/blog/engineering/ai/practical-text-to-sql-for-data-analytics); [Blog post, 2024](https://www.linkedin.com/blog/engineering/generative-ai/behind-the-platform-the-journey-to-create-the-linkedin-genai-application-tech-stack) |
|
||||
| [Minimal](https://gominimal.ai/) | E-commerce | Customer support | [Case study, 2025](https://blog.langchain.dev/how-minimal-built-a-multi-agent-customer-support-system-with-langgraph-langsmith/) |
|
||||
@@ -22,4 +23,4 @@ This list of companies using LangGraph and their success stories is compiled fro
|
||||
| [Tradestack](https://www.tradestack.uk/) | Software & Technology (GenAI Native) | Copilot for domain-specific task | [Case study, 2024](https://blog.langchain.dev/customers-tradestack/) |
|
||||
| [Uber](https://www.uber.com/) | Transportation | Developer productivity; Code generation | [Presentation, 2024](https://dpe.org/sessions/ty-smith-adam-huda/this-year-in-ubers-ai-driven-developer-productivity-revolution/); [Video, 2024](https://www.youtube.com/watch?v=8rkA5vWUE4Y) |
|
||||
| [Unify](https://www.unifygtm.com/) | Software & Technology (GenAI Native) | Copilot for domain-specific task | [Blog post, 2024](https://blog.langchain.dev/unify-launches-agents-for-account-qualification-using-langgraph-and-langsmith/) |
|
||||
| [Vizient](https://www.vizientinc.com/) | Healthcare | Copilot for domain-specific task | [Case study, 2025](https://blog.langchain.dev/p/3d2cd58c-13a5-4df9-bd84-7d54ed0ed82c/) |
|
||||
| [Vizient](https://www.vizientinc.com/) | Healthcare | Copilot for domain-specific task | [Case study, 2025](https://blog.langchain.dev/p/3d2cd58c-13a5-4df9-bd84-7d54ed0ed82c/) |
|
||||
|
||||
@@ -92,3 +92,28 @@ Starting from the `LangGraph Platform` view...
|
||||
1. Check/uncheck checkbox to `Automatically update deployment on push to branch`.
|
||||
1. Branch creation/deletion and tag creation/deletion events will not trigger an update. Only pushes to an existing branch will trigger an update.
|
||||
1. Pushes in quick succession to a branch will not trigger subsequent updates. In the future, this functionality may be changed/improved.
|
||||
|
||||
## Add or Remove GitHub Repositories
|
||||
|
||||
After installing and authorizing LangChain's `hosted-langserve` GitHub app, repository access for the app can be modified to add new repositories or remove existing repositories. If a new repository is created, it may need to be added explicitly.
|
||||
|
||||
1. From the GitHub profile, navigate to `Settings` > `Applications` > `hosted-langserve` > click `Configure`.
|
||||
1. Under `Repository access`, select `All repositories` or `Only select repositories`. If `Only select repositories` is selected, new repositories must be explicitly added.
|
||||
1. Click `Save`.
|
||||
1. When creating a new deployment, the list of GitHub repositories in the dropdown menu will be updated to reflect the repository access changes.
|
||||
|
||||
## Whitelisting IP Addresses
|
||||
|
||||
All traffic from `LangGraph Platform` deployments created after January 6th 2025 will come through a NAT gateway.
|
||||
This NAT gateway will have several static ip addresses depending on the region you are deploying in. Refer to the table below for the list of IP addresses to whitelist:
|
||||
|
||||
| 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 |
|
||||
| 34.169.88.30 | 34.91.238.184 |
|
||||
| 34.19.93.202 | 35.204.101.241 |
|
||||
| 34.19.34.50 | 35.204.48.32 |
|
||||
|
||||
@@ -9,19 +9,18 @@ The `useStream()` React hook provides a seamless way to integrate LangGraph into
|
||||
Key features:
|
||||
|
||||
- Messages streaming: Handle a stream of message chunks to form a complete message
|
||||
- Automatic state management for messages, loading states, and errors
|
||||
- Automatic state management for messages, interrupts, loading states, and errors
|
||||
- Conversation branching: Create alternate conversation paths from any point in the chat history
|
||||
- UI-agnostic design - bring your own components and styling
|
||||
- UI-agnostic design: bring your own components and styling
|
||||
|
||||
Let's explore how to use `useStream()` in your React application.
|
||||
|
||||
The `useStream()` provides a solid foundation for creating bespoke chat experiences. For pre-built chat components and interfaces, we recommend checking out [CopilotKit](https://docs.copilotkit.ai/coagents/quickstart/langgraph) and [assistant-ui](https://www.assistant-ui.com/docs/runtimes/langgraph).
|
||||
|
||||
The `useStream()` provides a solid foundation for creating bespoke chat experiences. For pre-built chat components and interfaces, we also recommend checking out [CopilotKit](https://docs.copilotkit.ai/coagents/quickstart/langgraph) and [assistant-ui](https://www.assistant-ui.com/docs/runtimes/langgraph).
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @langchain/langgraph-sdk @langchain/langchain-core react
|
||||
npm install @langchain/langgraph-sdk @langchain/core
|
||||
```
|
||||
|
||||
## Example
|
||||
@@ -65,9 +64,7 @@ export default function App() {
|
||||
Stop
|
||||
</button>
|
||||
) : (
|
||||
<button key="submit" type="submit">
|
||||
Send
|
||||
</button>
|
||||
<button keytype="submit">Send</button>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
@@ -81,6 +78,7 @@ The `useStream()` hook takes care of all the complex state management behind the
|
||||
|
||||
- Thread state management
|
||||
- Loading and error states
|
||||
- Interrupts
|
||||
- Message handling and updates
|
||||
- Branching support
|
||||
|
||||
@@ -134,9 +132,9 @@ We recommend storing the `threadId` in your URL's query parameters to let users
|
||||
|
||||
### Messages Handling
|
||||
|
||||
To enable messages handling, you need to pass the `messagesKey` option to the `useStream()` hook.
|
||||
The `useStream()` hook will keep track of the message chunks received from the server and concatenate them together to form a complete message. The completed message chunks can be retrieved via the `messages` property.
|
||||
|
||||
When enabled, the `useStream()` hook will keep track of the message chunks received from the server and concatenate them together to form a complete message. The completed message chunks can be retrieved via the `messages` property.
|
||||
By default, the `messagesKey` is set to `messages`, where it will append the new messages chunks to `values["messages"]`. If you store messages in a different key, you can change the value of `messagesKey`.
|
||||
|
||||
```tsx
|
||||
import type { Message } from "@langchain/langgraph-sdk";
|
||||
@@ -159,9 +157,49 @@ export default function HomePage() {
|
||||
}
|
||||
```
|
||||
|
||||
### Branching Support
|
||||
Under the hood, the `useStream()` hook will use the `streamMode: "messages-key"` to receive a stream of messages (i.e. individual LLM tokens) from any LangChain chat model invocations inside your graph nodes. Learn more about messages streaming in the [How to stream messages from your graph](./stream_messages.md) guide.
|
||||
|
||||
To enable branching, you need to enable messages handling. Pass the `messagesKey` option to the `useStream()` hook. For each message, you can use `getMessagesMetadata()` to get the first checkpoint from which the message has been first seen. You can then create a new run from the checkpoint preceding the first seen checkpoint to create a new branch in a thread.
|
||||
### Interrupts
|
||||
|
||||
The `useStream()` hook exposes the `interrupt` property, which will be filled with the last interrupt from the thread. You can use interrupts to:
|
||||
|
||||
- Render a confirmation UI before executing a node
|
||||
- Wait for human input, allowing agent to ask the user with clarifying questions
|
||||
|
||||
Learn more about interrupts in the [How to handle interrupts](../../how-tos/human_in_the_loop/wait-user-input.ipynb) guide.
|
||||
|
||||
```tsx
|
||||
const thread = useStream<
|
||||
{ messages: Message[] },
|
||||
{ InterruptType: string }
|
||||
>({
|
||||
apiUrl: "http://localhost:2024",
|
||||
assistantId: "agent",
|
||||
messagesKey: "messages",
|
||||
});
|
||||
|
||||
if (thread.interrupt) {
|
||||
return (
|
||||
<div>
|
||||
Interrupted! {thread.interrupt.value}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
// `resume` can be any value that the agent accepts
|
||||
thread.submit(undefined, { command: { resume: true } });
|
||||
}}
|
||||
>
|
||||
Resume
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Branching
|
||||
|
||||
For each message, you can use `getMessagesMetadata()` to get the first checkpoint from which the message has been first seen. You can then create a new run from the checkpoint preceding the first seen checkpoint to create a new branch in a thread.
|
||||
|
||||
A branch can be created in following ways:
|
||||
|
||||
@@ -169,23 +207,12 @@ A branch can be created in following ways:
|
||||
2. Request a regeneration of a previous assistant message.
|
||||
|
||||
```tsx
|
||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||
"use client";
|
||||
|
||||
import type { Message } from "@langchain/langgraph-sdk";
|
||||
import { useStream } from "@langchain/langgraph-sdk/react";
|
||||
import {
|
||||
Annotation,
|
||||
MessagesAnnotation,
|
||||
type StateType,
|
||||
type UpdateType,
|
||||
} from "@langchain/langgraph/web";
|
||||
import { useState } from "react";
|
||||
|
||||
const AgentState = Annotation.Root({
|
||||
...MessagesAnnotation.spec,
|
||||
});
|
||||
|
||||
function BranchSwitcher({
|
||||
branch,
|
||||
branchOptions,
|
||||
@@ -263,10 +290,7 @@ function EditMessage({
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
const thread = useStream<
|
||||
StateType<typeof AgentState.spec>,
|
||||
UpdateType<typeof AgentState.spec>
|
||||
>({
|
||||
const thread = useStream({
|
||||
apiUrl: "http://localhost:2024",
|
||||
assistantId: "agent",
|
||||
messagesKey: "messages",
|
||||
@@ -289,7 +313,7 @@ export default function App() {
|
||||
onEdit={(message) =>
|
||||
thread.submit(
|
||||
{ messages: [message] },
|
||||
{ checkpoint: parentCheckpoint }
|
||||
{ checkpoint: parentCheckpoint },
|
||||
)
|
||||
}
|
||||
/>
|
||||
@@ -344,13 +368,11 @@ export default function App() {
|
||||
}
|
||||
```
|
||||
|
||||
For advanced use cases you can use the `experimental_branchTree` property to get the tree representation of the thread, which can be used to render branching controls for non-message based graphs.
|
||||
|
||||
### TypeScript
|
||||
|
||||
The `useStream()` hook is fully typed to help catch errors early and provide better IDE support. You can specify types for:
|
||||
|
||||
- State shape
|
||||
- Update format
|
||||
- Custom events
|
||||
The `useStream()` hook is friendly for apps written in TypeScript and you can specify types for the state to get better type safety and IDE support.
|
||||
|
||||
```tsx
|
||||
// Define your types
|
||||
@@ -359,25 +381,44 @@ type State = {
|
||||
context?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
type Update = {
|
||||
messages: Message[] | Message;
|
||||
context?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
type CustomEvent = {
|
||||
type: "progress" | "debug";
|
||||
payload: unknown;
|
||||
};
|
||||
|
||||
// Use them with the hook
|
||||
const thread = useStream<State, Update, CustomEvent>({
|
||||
const thread = useStream<State>({
|
||||
apiUrl: "http://localhost:2024",
|
||||
assistantId: "agent",
|
||||
messagesKey: "messages",
|
||||
});
|
||||
```
|
||||
|
||||
If you're using LangGraph.js, you can reuse your graph's annotation types:
|
||||
You can also optionally specify types for different scenarios, such as:
|
||||
|
||||
- `ConfigurableType`: Type for the `config.configurable` property (default: `Record<string, unknown>`)
|
||||
- `InterruptType`: Type for the interrupt value - i.e. contents of `interrupt(...)` function (default: `unknown`)
|
||||
- `CustomEventType`: Type for the custom events (default: `unknown`)
|
||||
- `UpdateType`: Type for the submit function (default: `Partial<State>`)
|
||||
|
||||
```tsx
|
||||
|
||||
const thread = useStream<State, {
|
||||
UpdateType: {
|
||||
messages: Message[] | Message;
|
||||
context?: Record<string, unknown>;
|
||||
};
|
||||
InterruptType: string;
|
||||
CustomEventType: {
|
||||
type: "progress" | "debug";
|
||||
payload: unknown;
|
||||
};
|
||||
ConfigurableType: {
|
||||
model: string;
|
||||
};
|
||||
}>({
|
||||
apiUrl: "http://localhost:2024",
|
||||
assistantId: "agent",
|
||||
messagesKey: "messages",
|
||||
});
|
||||
```
|
||||
|
||||
If you're using LangGraph.js, you can also reuse your graph's annotation types. However, make sure to only import the types of the annotation schema in order to avoid importing the entire LangGraph.js runtime (i.e. via `import type { ... }` directive).
|
||||
|
||||
```tsx
|
||||
import {
|
||||
@@ -394,7 +435,7 @@ const AgentState = Annotation.Root({
|
||||
|
||||
const thread = useStream<
|
||||
StateType<typeof AgentState.spec>,
|
||||
UpdateType<typeof AgentState.spec>
|
||||
{ UpdateType: UpdateType<typeof AgentState.spec> }
|
||||
>({
|
||||
apiUrl: "http://localhost:2024",
|
||||
assistantId: "agent",
|
||||
@@ -410,7 +451,7 @@ The `useStream()` hook provides several callback options to help you respond to
|
||||
- `onFinish`: Called when the stream is finished.
|
||||
- `onUpdateEvent`: Called when an update event is received.
|
||||
- `onCustomEvent`: Called when a custom event is received. See [Custom events](../../concepts/streaming.md#custom) to learn how to stream custom events.
|
||||
- `onMetadataEvent`: Called when a metadata event is received.
|
||||
- `onMetadataEvent`: Called when a metadata event is received, which contains the Run ID and Thread ID.
|
||||
|
||||
## Learn More
|
||||
|
||||
|
||||
+119
-114
@@ -1,142 +1,147 @@
|
||||
# Use Webhooks
|
||||
# Using Webhooks
|
||||
|
||||
You may wish to use webhooks in your client, especially when using async streams in case you want to update something in your service once the API call to LangGraph Cloud has finished running. To do so, you will need to expose an endpoint that can accept POST requests, and then pass it to your API request in the "webhook" parameter.
|
||||
When working with LangGraph Cloud, you may want to use webhooks to receive updates after an API call completes. Webhooks are useful for triggering actions in your service once a run has finished processing. To implement this, you need to expose an endpoint that can accept `POST` requests and pass this endpoint as a `webhook` parameter in your API request.
|
||||
|
||||
Currently, the SDK has not exposed this endpoint but you can access it through curl commands as follows.
|
||||
Currently, the SDK does not provide built-in support for defining webhook endpoints, but you can specify them manually using API requests.
|
||||
|
||||
The following endpoints accept `webhook` as a parameter:
|
||||
## Supported Endpoints
|
||||
|
||||
- Create Run -> POST /thread/{thread_id}/runs
|
||||
- Create Thread Cron -> POST /thread/{thread_id}/runs/crons
|
||||
- Stream Run -> POST /thread/{thread_id}/runs/stream
|
||||
- Wait Run -> POST /thread/{thread_id}/runs/wait
|
||||
- Create Cron -> POST /runs/crons
|
||||
- Stream Run Stateless -> POST /runs/stream
|
||||
- Wait Run Stateless -> POST /runs/wait
|
||||
The following API endpoints accept a `webhook` parameter:
|
||||
|
||||
In this example, we will show calling a webhook after streaming a run.
|
||||
| Operation | HTTP Method | Endpoint |
|
||||
|-----------|------------|----------|
|
||||
| Create Run | `POST` | `/thread/{thread_id}/runs` |
|
||||
| Create Thread Cron | `POST` | `/thread/{thread_id}/runs/crons` |
|
||||
| Stream Run | `POST` | `/thread/{thread_id}/runs/stream` |
|
||||
| Wait Run | `POST` | `/thread/{thread_id}/runs/wait` |
|
||||
| Create Cron | `POST` | `/runs/crons` |
|
||||
| Stream Run Stateless | `POST` | `/runs/stream` |
|
||||
| Wait Run Stateless | `POST` | `/runs/wait` |
|
||||
|
||||
## Setup
|
||||
In this guide, we’ll show how to trigger a webhook after streaming a run.
|
||||
|
||||
First, let's setup our assistant and thread:
|
||||
## Setting Up Your Assistant and Thread
|
||||
|
||||
Before making API calls, set up your assistant and thread.
|
||||
|
||||
=== "Python"
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
client = get_client(url=<DEPLOYMENT_URL>)
|
||||
assistant_id = "agent"
|
||||
thread = await client.threads.create()
|
||||
print(thread)
|
||||
```
|
||||
|
||||
client = get_client(url=<DEPLOYMENT_URL>)
|
||||
# Using the graph deployed with the name "agent"
|
||||
assistant_id = "agent"
|
||||
# create thread
|
||||
thread = await client.threads.create()
|
||||
print(thread)
|
||||
```
|
||||
=== "JavaScript"
|
||||
```js
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
|
||||
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
|
||||
// Using the graph deployed with the name "agent"
|
||||
const assistantID = "agent";
|
||||
// create thread
|
||||
const thread = await client.threads.create();
|
||||
console.log(thread);
|
||||
```
|
||||
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
|
||||
const assistantID = "agent";
|
||||
const thread = await client.threads.create();
|
||||
console.log(thread);
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/assistants/search \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{ "limit": 10, "offset": 0 }' | jq -c 'map(select(.config == null or .config == {})) | .[0]' && \
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{}'
|
||||
```
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/assistants/search \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"limit": 10,
|
||||
"offset": 0
|
||||
}' | jq -c 'map(select(.config == null or .config == {})) | .[0]' && \
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{}'
|
||||
```
|
||||
### Example Response
|
||||
```json
|
||||
{
|
||||
"thread_id": "9dde5490-2b67-47c8-aa14-4bfec88af217",
|
||||
"created_at": "2024-08-30T23:07:38.242730+00:00",
|
||||
"updated_at": "2024-08-30T23:07:38.242730+00:00",
|
||||
"metadata": {},
|
||||
"status": "idle",
|
||||
"config": {},
|
||||
"values": null
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
## Using a Webhook with a Graph Run
|
||||
|
||||
{
|
||||
'thread_id': '9dde5490-2b67-47c8-aa14-4bfec88af217',
|
||||
'created_at': '2024-08-30T23:07:38.242730+00:00',
|
||||
'updated_at': '2024-08-30T23:07:38.242730+00:00',
|
||||
'metadata': {},
|
||||
'status': 'idle',
|
||||
'config': {},
|
||||
'values': None
|
||||
}
|
||||
To use a webhook, specify the `webhook` parameter in your API request. When the run completes, LangGraph Cloud sends a `POST` request to the specified webhook URL.
|
||||
|
||||
## Use graph with a webhook
|
||||
|
||||
To invoke a run with a webhook, we specify the `webhook` parameter with the desired endpoint when creating a run. Webhook requests are triggered by the end of a run.
|
||||
|
||||
For example, if we can receive requests at `https://my-server.app/my-webhook-endpoint`, we can pass this to `stream`:
|
||||
For example, if your server listens for webhook events at `https://my-server.app/my-webhook-endpoint`, include this in your request:
|
||||
|
||||
=== "Python"
|
||||
```python
|
||||
input = { "messages": [{ "role": "user", "content": "Hello!" }] }
|
||||
|
||||
```python
|
||||
# create input
|
||||
input = { "messages": [{ "role": "user", "content": "Hello!" }] }
|
||||
async for chunk in client.runs.stream(
|
||||
thread_id=thread["thread_id"],
|
||||
assistant_id=assistant_id,
|
||||
input=input,
|
||||
stream_mode="events",
|
||||
webhook="https://my-server.app/my-webhook-endpoint"
|
||||
):
|
||||
pass
|
||||
```
|
||||
|
||||
async for chunk in client.runs.stream(
|
||||
thread_id=thread["thread_id"],
|
||||
assistant_id=assistant_id,
|
||||
input=input,
|
||||
stream_mode="events",
|
||||
webhook="https://my-server.app/my-webhook-endpoint"
|
||||
):
|
||||
# Do something with the stream output
|
||||
pass
|
||||
```
|
||||
=== "JavaScript"
|
||||
```js
|
||||
const input = { messages: [{ role: "human", content: "Hello!" }] };
|
||||
|
||||
=== "Javascript"
|
||||
const streamResponse = client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistantID,
|
||||
{
|
||||
input: input,
|
||||
webhook: "https://my-server.app/my-webhook-endpoint"
|
||||
}
|
||||
);
|
||||
|
||||
```js
|
||||
// create input
|
||||
const input = { messages: [{ role: "human", content: "Hello!" }] };
|
||||
|
||||
// stream events
|
||||
const streamResponse = client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistantID,
|
||||
{
|
||||
input: input,
|
||||
webhook: "https://my-server.app/my-webhook-endpoint"
|
||||
}
|
||||
);
|
||||
for await (const chunk of streamResponse) {
|
||||
// Do something with the stream output
|
||||
}
|
||||
```
|
||||
for await (const chunk of streamResponse) {
|
||||
// Handle stream output
|
||||
}
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"assistant_id": <ASSISTANT_ID>,
|
||||
"input" : {"messages":[{"role": "user", "content": "Hello!"}]},
|
||||
"webhook": "https://my-server.app/my-webhook-endpoint"
|
||||
}'
|
||||
```
|
||||
|
||||
The schema for the payload sent to `my-webhook-endpoint` is that of a [run](../../concepts/langgraph_server.md/#runs). See [API Reference](https://langchain-ai.github.io/langgraph/cloud/reference/api/api_ref.html#model/run) for more detail. Note that the run input, configuration, etc. are included in the `kwargs` field.
|
||||
|
||||
### Signing webhook requests
|
||||
|
||||
To sign the webhook requests, we can specify a token parameter in the webhook URL, e.g.,
|
||||
```
|
||||
https://my-server.app/my-webhook-endpoint?token=...
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"assistant_id": <ASSISTANT_ID>,
|
||||
"input": {"messages": [{"role": "user", "content": "Hello!"}]},
|
||||
"webhook": "https://my-server.app/my-webhook-endpoint"
|
||||
}'
|
||||
```
|
||||
|
||||
The server should then extract the token from the request's parameters and validate it before processing the payload.
|
||||
## Webhook Payload
|
||||
|
||||
LangGraph Cloud sends webhook notifications in the format of a [Run](../../concepts/langgraph_server.md/#runs). See the [API Reference](https://langchain-ai.github.io/langgraph/cloud/reference/api/api_ref.html#model/run) for details. The request payload includes run input, configuration, and other metadata in the `kwargs` field.
|
||||
|
||||
## Securing Webhooks
|
||||
|
||||
To ensure only authorized requests hit your webhook endpoint, consider adding a security token as a query parameter:
|
||||
|
||||
```
|
||||
https://my-server.app/my-webhook-endpoint?token=YOUR_SECRET_TOKEN
|
||||
```
|
||||
|
||||
Your server should extract and validate this token before processing requests.
|
||||
|
||||
## Testing Webhooks
|
||||
|
||||
You can test your webhook using online services like:
|
||||
|
||||
- **[Beeceptor](https://beeceptor.com/)** – Quickly create a test endpoint and inspect incoming webhook payloads.
|
||||
- **[Webhook.site](https://webhook.site/)** – View, debug, and log incoming webhook requests in real time.
|
||||
|
||||
These tools help you verify that LangGraph Cloud is correctly triggering and sending webhooks to your service.
|
||||
|
||||
---
|
||||
|
||||
By following these steps, you can integrate webhooks into your LangGraph Cloud workflow, automating actions based on completed runs.
|
||||
|
||||
@@ -51,6 +51,7 @@ The LangGraph CLI requires a JSON configuration file with the following keys:
|
||||
| <span style="white-space: nowrap;">`node_version`</span> | Specify `node_version: 20` to use LangGraph.js. |
|
||||
| <span style="white-space: nowrap;">`pip_config_file`</span> | Path to `pip` config file. |
|
||||
| <span style="white-space: nowrap;">`dockerfile_lines`</span> | Array of additional lines to add to Dockerfile following the import from parent image. |
|
||||
| <span style="white-space: nowrap;">`http`</span> | HTTP server configuration with the following fields: <ul><li>`app`: Path to custom Starlette/FastAPI app (e.g., `"./src/agent/webapp.py:app"`). See [custom routes guide](../../how-tos/http/custom_routes.md).</li><li>`disable_assistants`: Disable `/assistants` routes</li><li>`disable_threads`: Disable `/threads` routes</li><li>`disable_runs`: Disable `/runs` routes</li><li>`disable_store`: Disable `/store` routes</li><li>`disable_meta`: Disable `/ok`, `/info`, `/metrics`, and `/docs` routes</li><li>`cors`: CORS configuration with fields for `allow_origins`, `allow_methods`, `allow_headers`, etc.</li></ul> |
|
||||
|
||||
=== "JS"
|
||||
|
||||
|
||||
@@ -647,19 +647,15 @@ def node_in_parent_graph(state: State):
|
||||
This will print out
|
||||
|
||||
```pycon
|
||||
--- First invocation ---
|
||||
In parent node: {'foo': 'bar'}
|
||||
Entered `parent_node` a total of 1 times
|
||||
Entered `node_in_subgraph` a total of 1 times
|
||||
Entered human_node in sub-graph a total of 1 times
|
||||
{'__interrupt__': (Interrupt(value='what is your name?', resumable=True, ns=['parent_node:0b23d72f-aaba-0329-1a59-ca4f3c8bad3b', 'human_node:25df717c-cb80-57b0-7410-44e20aac8f3c'], when='during'),)}
|
||||
|
||||
{'__interrupt__': (Interrupt(value='what is your name?', resumable=True, ns=['parent_node:4c3a0248-21f0-1287-eacf-3002bc304db4', 'human_node:2fe86d52-6f70-2a3f-6b2f-b1eededd6348'], when='during'),)}
|
||||
--- Resuming ---
|
||||
In parent node: {'foo': 'bar'}
|
||||
Entered `parent_node` a total of 2 times
|
||||
Entered human_node in sub-graph a total of 2 times
|
||||
Got an answer of 35
|
||||
{'parent_node': None}
|
||||
{'parent_node': {'state_counter': 1}}
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ The conceptual guide does not cover step-by-step instructions or specific implem
|
||||
- [Streaming](streaming.md): Streaming is crucial for enhancing the responsiveness of applications built on LLMs. By displaying output progressively, even before a complete response is ready, streaming significantly improves user experience (UX), particularly when dealing with the latency of LLMs.
|
||||
- [Functional API](functional_api.md): `@entrypoint` and `@task` decorators that allow you to add LangGraph functionality to an existing codebase.
|
||||
- [Durable Execution](durable_execution.md): 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.
|
||||
- [Pregel](pregel.md): Pregel is LangGraph's runtime, which is responsible for managing the execution of LangGraph applications.
|
||||
- [FAQ](faq.md): Frequently asked questions about LangGraph.
|
||||
|
||||
## LangGraph Platform
|
||||
@@ -47,6 +48,7 @@ The LangGraph Platform offers a few different deployment options described in th
|
||||
|
||||
- [Why LangGraph Platform?](./langgraph_platform.md): The LangGraph platform is an opinionated way to deploy and manage LangGraph applications. This guide provides an overview of the key features and concepts behind LangGraph Platform.
|
||||
- [Platform Architecture](./platform_architecture.md): A high-level overview of the architecture of the LangGraph Platform.
|
||||
- [Scalability and Resilience](./scalability_and_resilience.md): LangGraph Platform is designed to be scalable and resilient. This document explains how the platform achieves this.
|
||||
- [Deployment Options](./deployment_options.md): LangGraph Platform offers four deployment options: [Self-Hosted Lite](./self_hosted.md#self-hosted-lite), [Self-Hosted Enterprise](./self_hosted.md#self-hosted-enterprise), [bring your own cloud (BYOC)](./bring_your_own_cloud.md), and [Cloud SaaS](./langgraph_cloud.md). This guide explains the differences between these options, and which Plans they are available on.
|
||||
- [Plans](./plans.md): LangGraph Platforms offer three different plans: Developer, Plus, Enterprise. This guide explains the differences between these options, what deployment options are available for each, and how to sign up for each one.
|
||||
- [Template Applications](./template_applications.md): Reference applications designed to help you get started quickly when building with LangGraph.
|
||||
|
||||
@@ -80,6 +80,22 @@ A high-level diagram of a Cloud SaaS deployment.
|
||||
|
||||

|
||||
|
||||
## Whitelisting IP Addresses
|
||||
|
||||
All traffic from `LangGraph Platform` deployments created after January 6th 2025 will come through a NAT gateway.
|
||||
This NAT gateway will have several static ip addresses depending on the region you are deploying in. Refer to the table below for the list of IP addresses to whitelist:
|
||||
|
||||
| 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 |
|
||||
| 34.169.88.30 | 34.91.238.184 |
|
||||
| 34.19.93.202 | 35.204.101.241 |
|
||||
| 34.19.34.50 | 35.204.48.32 |
|
||||
|
||||
## Related
|
||||
|
||||
- [Deployment Options](./deployment_options.md)
|
||||
|
||||
@@ -112,6 +112,7 @@ In this architecture, agents are defined as graph nodes. Each agent can communic
|
||||
```python
|
||||
from typing import Literal
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langgraph.types import Command
|
||||
from langgraph.graph import StateGraph, MessagesState, START, END
|
||||
|
||||
model = ChatOpenAI()
|
||||
@@ -158,6 +159,7 @@ In this architecture, we define agents as nodes and add a supervisor node (LLM)
|
||||
```python
|
||||
from typing import Literal
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langgraph.types import Command
|
||||
from langgraph.graph import StateGraph, MessagesState, START, END
|
||||
|
||||
model = ChatOpenAI()
|
||||
|
||||
@@ -0,0 +1,347 @@
|
||||
# LangGraph's Runtime (Pregel)
|
||||
|
||||
[Pregel][langgraph.pregel.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.
|
||||
|
||||
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.
|
||||
|
||||
## Overview
|
||||
|
||||
In LangGraph, Pregel combines [**actors**](https://en.wikipedia.org/wiki/Actor_model) and **channels** into a single application. **Actors** read data from channels and write data to channels. Pregel organizes the execution of the application into multiple steps, following the **Pregel Algorithm**/**Bulk Synchronous Parallel** model.
|
||||
|
||||
Each step consists of three phases:
|
||||
|
||||
- **Plan**: Determine which **actors** to execute in this step. For example, in the first step, select the **actors** that subscribe to the special **input** channels; in subsequent steps, select the **actors** that subscribe to channels updated in the previous step.
|
||||
- **Execution**: Execute all selected **actors** in parallel, until all complete, or one fails, or a timeout is reached. During this phase, channel updates are invisible to actors until the next step.
|
||||
- **Update**: Update the channels with the values written by the **actors** in this step.
|
||||
|
||||
Repeat until no **actors** are selected for execution, or a maximum number of steps is reached.
|
||||
|
||||
## Actors
|
||||
|
||||
An **actor** is a [PregelNode][langgraph.pregel.read.PregelNode]. It subscribes to channels, reads data from them, and writes data to them. It can be thought of as an **actor** in the Pregel algorithm. [PregelNodes][langgraph.pregel.read.PregelNode] implement LangChain's Runnable interface.
|
||||
|
||||
## Channels
|
||||
|
||||
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:
|
||||
|
||||
### Basic channels: LastValue and Topic
|
||||
|
||||
- [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.
|
||||
|
||||
### Advanced channels: Context and BinaryOperatorAggregate
|
||||
|
||||
- `Context`: exposes the value of a context manager, managing its lifecycle. Useful for accessing external resources that require setup and/or teardown; e.g., `client = Context(httpx.Client)`.
|
||||
- [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)`
|
||||
|
||||
## 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.
|
||||
|
||||
Below are a few different examples to give you a sense of the Pregel API.
|
||||
|
||||
=== "Single node"
|
||||
|
||||
```python
|
||||
|
||||
from langgraph.channels import EphemeralValue
|
||||
from langgraph.pregel import Pregel, Channel
|
||||
|
||||
node1 = (
|
||||
Channel.subscribe_to("a")
|
||||
| (lambda x: x + x)
|
||||
| Channel.write_to("b")
|
||||
)
|
||||
|
||||
app = Pregel(
|
||||
nodes={"node1": node1},
|
||||
channels={
|
||||
"a": EphemeralValue(str),
|
||||
"b": EphemeralValue(str),
|
||||
},
|
||||
input_channels=["a"],
|
||||
output_channels=["b"],
|
||||
)
|
||||
|
||||
app.invoke({"a": "foo"})
|
||||
```
|
||||
|
||||
```con
|
||||
{'b': 'foofoo'}
|
||||
```
|
||||
|
||||
=== "Multiple nodes"
|
||||
|
||||
```python
|
||||
from langgraph.channels import LastValue, EphemeralValue
|
||||
from langgraph.pregel import Pregel, Channel
|
||||
|
||||
node1 = (
|
||||
Channel.subscribe_to("a")
|
||||
| (lambda x: x + x)
|
||||
| Channel.write_to("b")
|
||||
)
|
||||
|
||||
node2 = (
|
||||
Channel.subscribe_to("b")
|
||||
| (lambda x: x + x)
|
||||
| Channel.write_to("c")
|
||||
)
|
||||
|
||||
|
||||
app = Pregel(
|
||||
nodes={"node1": node1, "node2": node2},
|
||||
channels={
|
||||
"a": EphemeralValue(str),
|
||||
"b": LastValue(str),
|
||||
"c": EphemeralValue(str),
|
||||
},
|
||||
input_channels=["a"],
|
||||
output_channels=["b", "c"],
|
||||
)
|
||||
|
||||
app.invoke({"a": "foo"})
|
||||
```
|
||||
|
||||
```con
|
||||
{'b': 'foofoo', 'c': 'foofoofoofoo'}
|
||||
```
|
||||
|
||||
=== "Topic"
|
||||
|
||||
```python
|
||||
from langgraph.channels import EphemeralValue, Topic
|
||||
from langgraph.pregel import Pregel, Channel
|
||||
|
||||
node1 = (
|
||||
Channel.subscribe_to("a")
|
||||
| (lambda x: x + x)
|
||||
| {
|
||||
"b": Channel.write_to("b"),
|
||||
"c": Channel.write_to("c")
|
||||
}
|
||||
)
|
||||
|
||||
node2 = (
|
||||
Channel.subscribe_to("b")
|
||||
| (lambda x: x + x)
|
||||
| {
|
||||
"c": Channel.write_to("c"),
|
||||
}
|
||||
)
|
||||
|
||||
app = Pregel(
|
||||
nodes={"node1": node1, "node2": node2},
|
||||
channels={
|
||||
"a": EphemeralValue(str),
|
||||
"b": EphemeralValue(str),
|
||||
"c": Topic(str, accumulate=True),
|
||||
},
|
||||
input_channels=["a"],
|
||||
output_channels=["c"],
|
||||
)
|
||||
|
||||
app.invoke({"a": "foo"})
|
||||
```
|
||||
|
||||
```pycon
|
||||
{'c': ['foofoo', 'foofoofoofoo']}
|
||||
```
|
||||
|
||||
=== "BinaryOperatorAggregate"
|
||||
|
||||
This examples demonstrates how to use the BinaryOperatorAggregate channel to implement a reducer.
|
||||
|
||||
```python
|
||||
from langgraph.channels import EphemeralValue, BinaryOperatorAggregate
|
||||
from langgraph.pregel import Pregel, Channel
|
||||
|
||||
|
||||
node1 = (
|
||||
Channel.subscribe_to("a")
|
||||
| (lambda x: x + x)
|
||||
| {
|
||||
"b": Channel.write_to("b"),
|
||||
"c": Channel.write_to("c")
|
||||
}
|
||||
)
|
||||
|
||||
node2 = (
|
||||
Channel.subscribe_to("b")
|
||||
| (lambda x: x + x)
|
||||
| {
|
||||
"c": Channel.write_to("c"),
|
||||
}
|
||||
)
|
||||
|
||||
def reducer(current, update):
|
||||
if current:
|
||||
return current + " | " + "update"
|
||||
else:
|
||||
return update
|
||||
|
||||
app = Pregel(
|
||||
nodes={"node1": node1, "node2": node2},
|
||||
channels={
|
||||
"a": EphemeralValue(str),
|
||||
"b": EphemeralValue(str),
|
||||
"c": BinaryOperatorAggregate(str, operator=reducer),
|
||||
},
|
||||
input_channels=["a"],
|
||||
output_channels=["c"],
|
||||
)
|
||||
|
||||
app.invoke({"a": "foo"})
|
||||
```
|
||||
|
||||
|
||||
=== "Cycle"
|
||||
|
||||
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.
|
||||
|
||||
```python
|
||||
from langgraph.channels import EphemeralValue
|
||||
from langgraph.pregel import Pregel, Channel, ChannelWrite, ChannelWriteEntry
|
||||
|
||||
example_node = (
|
||||
Channel.subscribe_to("value")
|
||||
| (lambda x: x + x if len(x) < 10 else None)
|
||||
| ChannelWrite(writes=[ChannelWriteEntry(channel="value", skip_none=True)])
|
||||
)
|
||||
|
||||
app = Pregel(
|
||||
nodes={"example_node": example_node},
|
||||
channels={
|
||||
"value": EphemeralValue(str),
|
||||
},
|
||||
input_channels=["value"],
|
||||
output_channels=["value"],
|
||||
)
|
||||
|
||||
app.invoke({"value": "a"})
|
||||
```
|
||||
|
||||
```pycon
|
||||
{'value': 'aaaaaaaaaaaaaaaa'}
|
||||
```
|
||||
|
||||
## High-level API
|
||||
|
||||
LangGraph provides two high-level APIs for creating a Pregel application: the [StateGraph (Graph API)](./low_level.md) and the [Functional API](functional_api.md).
|
||||
|
||||
|
||||
=== "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
|
||||
from typing import TypedDict, Optional
|
||||
|
||||
from langgraph.constants import START
|
||||
from langgraph.graph import StateGraph
|
||||
|
||||
class Essay(TypedDict):
|
||||
topic: str
|
||||
content: Optional[str]
|
||||
score: Optional[float]
|
||||
|
||||
def write_essay(essay: Essay):
|
||||
return {
|
||||
"content": f"Essay about {essay['topic']}",
|
||||
}
|
||||
|
||||
def score_essay(essay: Essay):
|
||||
return {
|
||||
"score": 10
|
||||
}
|
||||
|
||||
builder = StateGraph(Essay)
|
||||
builder.add_node(write_essay)
|
||||
builder.add_node(score_essay)
|
||||
builder.add_edge(START, "write_essay")
|
||||
|
||||
# Compile the graph.
|
||||
# This will return a Pregel instance.
|
||||
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
|
||||
print(graph.nodes)
|
||||
```
|
||||
|
||||
You will see something like this:
|
||||
|
||||
```pycon
|
||||
{'__start__': <langgraph.pregel.read.PregelNode at 0x7d05e3ba1810>,
|
||||
'write_essay': <langgraph.pregel.read.PregelNode at 0x7d05e3ba14d0>,
|
||||
'score_essay': <langgraph.pregel.read.PregelNode at 0x7d05e3ba1710>}
|
||||
```
|
||||
|
||||
```python
|
||||
print(graph.channels)
|
||||
```
|
||||
|
||||
You should see something like this
|
||||
|
||||
```pycon
|
||||
{'topic': <langgraph.channels.last_value.LastValue at 0x7d05e3294d80>,
|
||||
'content': <langgraph.channels.last_value.LastValue at 0x7d05e3295040>,
|
||||
'score': <langgraph.channels.last_value.LastValue at 0x7d05e3295980>,
|
||||
'__start__': <langgraph.channels.ephemeral_value.EphemeralValue at 0x7d05e3297e00>,
|
||||
'write_essay': <langgraph.channels.ephemeral_value.EphemeralValue at 0x7d05e32960c0>,
|
||||
'score_essay': <langgraph.channels.ephemeral_value.EphemeralValue at 0x7d05e2d8ab80>,
|
||||
'branch:__start__:__self__:write_essay': <langgraph.channels.ephemeral_value.EphemeralValue at 0x7d05e32941c0>,
|
||||
'branch:__start__:__self__:score_essay': <langgraph.channels.ephemeral_value.EphemeralValue at 0x7d05e2d88800>,
|
||||
'branch:write_essay:__self__:write_essay': <langgraph.channels.ephemeral_value.EphemeralValue at 0x7d05e3295ec0>,
|
||||
'branch:write_essay:__self__:score_essay': <langgraph.channels.ephemeral_value.EphemeralValue at 0x7d05e2d8ac00>,
|
||||
'branch:score_essay:__self__:write_essay': <langgraph.channels.ephemeral_value.EphemeralValue at 0x7d05e2d89700>,
|
||||
'branch:score_essay:__self__:score_essay': <langgraph.channels.ephemeral_value.EphemeralValue at 0x7d05e2d8b400>,
|
||||
'start:write_essay': <langgraph.channels.ephemeral_value.EphemeralValue at 0x7d05e2d8b280>}
|
||||
```
|
||||
|
||||
=== "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
|
||||
from typing import TypedDict, Optional
|
||||
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
from langgraph.func import entrypoint
|
||||
|
||||
class Essay(TypedDict):
|
||||
topic: str
|
||||
content: Optional[str]
|
||||
score: Optional[float]
|
||||
|
||||
|
||||
checkpointer = InMemorySaver()
|
||||
|
||||
@entrypoint(checkpointer=checkpointer)
|
||||
def write_essay(essay: Essay):
|
||||
return {
|
||||
"content": f"Essay about {essay['topic']}",
|
||||
}
|
||||
|
||||
print("Nodes: ")
|
||||
print(write_essay.nodes)
|
||||
print("Channels: ")
|
||||
print(write_essay.channels)
|
||||
```
|
||||
|
||||
```pycon
|
||||
Nodes:
|
||||
{'write_essay': <langgraph.pregel.read.PregelNode object at 0x7d05e2f9aad0>}
|
||||
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>}
|
||||
```
|
||||
@@ -0,0 +1,35 @@
|
||||
# LangGraph Platform: Scalability & Resilience
|
||||
|
||||
LangGraph Platform is designed to scale horizontally with your workload. Each instance of the service is stateless, and keeps no resources in memory. The service is designed to gracefully handle new instances being added or removed, including hard shutdown cases.
|
||||
|
||||
## Server scalability
|
||||
|
||||
As you add more instances to a service, they will share the HTTP load as long as an appropriate load balancer mechanism is placed in front of them. In most deployment modalities we configure a load balancer for the service automatically. In the “self-hosted without control plane” modality it’s your responsibility to add a load balancer. Since the instances are stateless any load balancing strategy will work, no session stickiness is needed, or recommended. Any instance of the server can communicate with any queue instance (through Redis PubSub), meaning that requests to cancel or stream an in-progress run can be handled by any arbitrary instance.
|
||||
|
||||
## Queue scalability
|
||||
|
||||
As you add more instances to a service, they will increase run throughput linearly, as each instance is configured to handle a set number of concurrent runs (by default 10). Each attempt for each run will be handled by a single instance, with exactly-once semantics enforced through Postgres’s MVCC model (refer to section below for crash resilience details). Attempts that fail due to transient database errors are retried up to 3 times. We do not make use of long-lived transactions or locks, this enables us to make more efficient use of Postgres resources.
|
||||
|
||||
## Resilience
|
||||
|
||||
While a run is being handled by a queue instance, a periodic heartbeat timestamp will be recorded in Redis by that queue worker.
|
||||
|
||||
When a graceful shutdown request is received (SIGINT) an instance enters shutdown mode, which
|
||||
|
||||
- stops accepting new HTTP requests
|
||||
- gives any in-progress runs a limited number of seconds to finish (if not finished it will be put back in the queue)
|
||||
- stops the instance from picking up more runs from the queue
|
||||
|
||||
If a hard shutdown occurs, eg. due to a server crash, or an infra failure, any runs that were in progress will be picked up by a periodic sweeper task that looks for in-progress runs that have breached their heartbeat window, which will put them back in the queue for another instance to pick them up.
|
||||
|
||||
## Postgres resilience
|
||||
|
||||
For deployment modalities where we manage the Postgres database we have periodic backups, continuously replicated standby replicas for automatic failover. Optionally, on request, we can also setup read replicas as well as other advanced failover capabilities.
|
||||
|
||||
All communication with Postgres implements retries for retry-able errors. If Postgres is momentarily unavailable, such as during a database restart, most/all traffic should continue to succeed. Prolonged failure of the Postgres instance will switch traffic to the failover replica. If the failover replica also fails before the primary is brought back online the service would become unavailable.
|
||||
|
||||
## Redis resilience
|
||||
|
||||
All data that requires durable storage is stored in Postgres, not Redis. Redis is used only for ephemeral metadata, and communication between instances. Refer to the [architecture](./platform_architecture.md) page for more details on how we use Redis. Therefore we place no durability requirements on Redis.
|
||||
|
||||
All communication with Redis implements retries for retry-able errors. If Redis is momentarily unavailable, such as during a database restart, most/all traffic should continue to succeed. Prolonged failure of Redis will render the LGP service unavailable.
|
||||
@@ -0,0 +1,82 @@
|
||||
# How to add custom lifespan events
|
||||
|
||||
When deploying agents on the LangGraph platform, you often need to initialize resources like database connections when your server starts up, and ensure they're properly closed when it shuts down. Lifespan events let you hook into your server's startup and shutdown sequence to handle these critical setup and teardown tasks.
|
||||
|
||||
This works the same way as [adding custom routes](./custom_routes.md) - you just need to provide your own [`Starlette`](https://www.starlette.io/applications/) app (including [`FastAPI`](https://fastapi.tiangolo.com/), [`FastHTML`](https://fastht.ml/) and other compatible apps).
|
||||
|
||||
Below is an example using FastAPI.
|
||||
|
||||
???+ note "Python only"
|
||||
|
||||
We currently only support custom lifespan events in Python deployments with `langgraph-api>=0.0.26`.
|
||||
|
||||
## Create app
|
||||
|
||||
Starting from an **existing** LangGraph Platform application, add the following lifespan code to your `webapp.py` file. If you are starting from scratch, you can create a new app from a template using the CLI.
|
||||
|
||||
```bash
|
||||
langgraph new --template=new-langgraph-project-python my_new_project
|
||||
```
|
||||
|
||||
Once you have a LangGraph project, add the following app code:
|
||||
|
||||
```python
|
||||
# ./src/agent/webapp.py
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
# for example...
|
||||
engine = create_async_engine("postgresql+asyncpg://user:pass@localhost/db")
|
||||
# Create reusable session factory
|
||||
async_session = sessionmaker(engine, class_=AsyncSession)
|
||||
# Store in app state
|
||||
app.state.db_session = async_session
|
||||
yield
|
||||
# Clean up connections
|
||||
await engine.dispose()
|
||||
|
||||
# highlight-next-line
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
|
||||
# ... can add custom routes if needed.
|
||||
```
|
||||
|
||||
## Configure `langgraph.json`
|
||||
|
||||
Add the following to your `langgraph.json` file. Make sure the path points to the `webapp.py` file you created above.
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": ["."],
|
||||
"graphs": {
|
||||
"agent": "./src/agent/graph.py:graph"
|
||||
},
|
||||
"env": ".env",
|
||||
"http": {
|
||||
"app": "./src/agent/webapp.py:app"
|
||||
}
|
||||
// Other configuration options like auth, store, etc.
|
||||
}
|
||||
```
|
||||
|
||||
## Start server
|
||||
|
||||
Test the server out locally:
|
||||
|
||||
```bash
|
||||
langgraph dev --no-browser
|
||||
```
|
||||
|
||||
You should see your startup message printed when the server starts, and your cleanup message when you stop it with Ctrl+C.
|
||||
|
||||
## Deploying
|
||||
|
||||
You can deploy your app as-is to the managed langgraph cloud or to your self-hosted platform.
|
||||
|
||||
## Next steps
|
||||
|
||||
Now that you've added lifespan events to your deployment, you can use similar techniques to add [custom routes](./custom_routes.md) or [custom middleware](./custom_middleware.md) to further customize your server's behavior.
|
||||
@@ -0,0 +1,75 @@
|
||||
# How to add custom middleware
|
||||
|
||||
When deploying agents on the LangGraph platform, you can add custom middleware to your server to handle cross-cutting concerns like logging request metrics, injecting or checking headers, and enforcing security policies without modifying core server logic. This works the same way as [adding custom routes](./custom_routes.md) - you just need to provide your own [`Starlette`](https://www.starlette.io/applications/) app (including [`FastAPI`](https://fastapi.tiangolo.com/), [`FastHTML`](https://fastht.ml/) and other compatible apps).
|
||||
|
||||
Adding middleware lets you intercept and modify requests and responses globally across your deployment, whether they're hitting your custom endpoints or the built-in LangGraph Platform APIs.
|
||||
|
||||
Below is an example using FastAPI.
|
||||
|
||||
???+ note "Python only"
|
||||
|
||||
We currently only support custom middleware in Python deployments with `langgraph-api>=0.0.26`.
|
||||
|
||||
## Create app
|
||||
|
||||
Starting from an **existing** LangGraph Platform application, add the following middleware code to your `webapp.py` file. If you are starting from scratch, you can create a new app from a template using the CLI.
|
||||
|
||||
```bash
|
||||
langgraph new --template=new-langgraph-project-python my_new_project
|
||||
```
|
||||
|
||||
Once you have a LangGraph project, add the following app code:
|
||||
|
||||
```python
|
||||
# ./src/agent/webapp.py
|
||||
from fastapi import FastAPI, Request
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
|
||||
# highlight-next-line
|
||||
app = FastAPI()
|
||||
|
||||
class CustomHeaderMiddleware(BaseHTTPMiddleware):
|
||||
async def dispatch(self, request: Request, call_next):
|
||||
response = await call_next(request)
|
||||
response.headers['X-Custom-Header'] = 'Hello from middleware!'
|
||||
return response
|
||||
|
||||
# Add the middleware to the app
|
||||
app.add_middleware(CustomHeaderMiddleware)
|
||||
```
|
||||
|
||||
## Configure `langgraph.json`
|
||||
|
||||
Add the following to your `langgraph.json` file. Make sure the path points to the `webapp.py` file you created above.
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": ["."],
|
||||
"graphs": {
|
||||
"agent": "./src/agent/graph.py:graph"
|
||||
},
|
||||
"env": ".env",
|
||||
"http": {
|
||||
"app": "./src/agent/webapp.py:app"
|
||||
}
|
||||
// Other configuration options like auth, store, etc.
|
||||
}
|
||||
```
|
||||
|
||||
## Start server
|
||||
|
||||
Test the server out locally:
|
||||
|
||||
```bash
|
||||
langgraph dev --no-browser
|
||||
```
|
||||
|
||||
Now any request to your server will include the custom header `X-Custom-Header` in its response.
|
||||
|
||||
## Deploying
|
||||
|
||||
You can deploy this app as-is to the managed langgraph cloud or to your self-hosted platform.
|
||||
|
||||
## Next steps
|
||||
|
||||
Now that you've added custom middleware to your deployment, you can use similar techniques to add [custom routes](./custom_routes.md) or define [custom lifespan events](./custom_lifespan.md) to further customize your server's behavior.
|
||||
@@ -0,0 +1,78 @@
|
||||
# How to add custom routes
|
||||
|
||||
When deploying agents on the LangGraph platform, your server automatically exposes routes for creating runs and threads, interacting with the long-term memory store, managing configurable assistants, and other core functionality ([see all default API endpoints](../../cloud/reference/api/api_ref.md)).
|
||||
|
||||
You can add custom routes by providing your own [`Starlette`](https://www.starlette.io/applications/) app (including [`FastAPI`](https://fastapi.tiangolo.com/), [`FastHTML`](https://fastht.ml/) and other compatible apps). You make LangGraph Platform aware of this by providing a path to the app in your `langgraph.json` configuration file. (`"http": {"app": "path/to/app.py:app"}`).
|
||||
|
||||
Defining a custom app object lets you add any routes you'd like, so you can do anything from adding a `/login` endpoint to writing an entire full-stack web-app, all deployed in a single LangGraph deployment.
|
||||
|
||||
Below is an example using FastAPI.
|
||||
|
||||
???+ note "Python only"
|
||||
|
||||
We currently only support custom authentication and authorization in Python deployments with `langgraph-api>=0.0.26`.
|
||||
|
||||
## Create app
|
||||
|
||||
Starting from an **existing** LangGraph Platform application, add the following custom route code to your `webapp.py` file. If you are starting from scratch, you can create a new app from a template using the CLI.
|
||||
|
||||
```bash
|
||||
langgraph new --template=new-langgraph-project-python my_new_project
|
||||
```
|
||||
|
||||
Once you have a LangGraph project, add the following app code:
|
||||
|
||||
```python
|
||||
# ./src/agent/webapp.py
|
||||
from fastapi import FastAPI
|
||||
|
||||
# highlight-next-line
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/hello")
|
||||
def read_root():
|
||||
return {"Hello": "World"}
|
||||
|
||||
```
|
||||
|
||||
## Configure `langgraph.json`
|
||||
|
||||
Add the following to your `langgraph.json` file. Make sure the path points to the `app.py` file you created above.
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": ["."],
|
||||
"graphs": {
|
||||
"agent": "./src/agent/graph.py:graph"
|
||||
},
|
||||
"env": ".env",
|
||||
"http": {
|
||||
"app": "./src/agent/webapp.py:app"
|
||||
}
|
||||
// Other configuration options like auth, store, etc.
|
||||
}
|
||||
```
|
||||
|
||||
## Start server
|
||||
|
||||
Test the server out locally:
|
||||
|
||||
```bash
|
||||
langgraph dev --no-browser
|
||||
```
|
||||
|
||||
If you navigate to `localhost:2024/hello` in your browser (2024 is the default development port), you should see the `hello` endpoint returning `{"Hello": "World"}`.
|
||||
|
||||
|
||||
!!! note "Shadowing default endpoints"
|
||||
|
||||
The routes you create in the app are given priority over the system defaults, meaning you can shadow and redefine the behavior of any default endpoint.
|
||||
|
||||
## Deploying
|
||||
|
||||
You can deploy this app as-is to the managed langgraph cloud or to your self-hsoted platform.
|
||||
|
||||
## Next steps
|
||||
|
||||
Now that you've added a custom route to your deployment, you can use this same technique to further customize how your server behaves, such as defining custom [custom middleware](./custom_middleware.md) and [custom lifespan events](./custom_lifespan.md).
|
||||
@@ -215,6 +215,12 @@ LangGraph applications can be deployed using LangGraph Cloud, which provides a r
|
||||
- [How to add custom authentication](./auth/custom_auth.md)
|
||||
- [How to update the security schema of your OpenAPI spec](./auth/openapi_security.md)
|
||||
|
||||
### Modifying the API
|
||||
|
||||
- [How to add custom routes](./http/custom_routes.md)
|
||||
- [How to add custom middleware](./http/custom_middleware.md)
|
||||
- [How to add custom lifespan events](./http/custom_lifespan.md)
|
||||
|
||||
### Assistants
|
||||
|
||||
[Assistants](../concepts/assistants.md) is a configured instance of a template.
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
::: langgraph.pregel.Pregel
|
||||
# Pregel
|
||||
|
||||
::: langgraph.pregel
|
||||
options:
|
||||
members:
|
||||
- stream
|
||||
- astream
|
||||
- invoke
|
||||
- ainvoke
|
||||
- update_state
|
||||
- aupdate_state
|
||||
- Pregel
|
||||
- PregelNode
|
||||
@@ -271,6 +271,7 @@ nav:
|
||||
- concepts/streaming.md
|
||||
- concepts/functional_api.md
|
||||
- concepts/durable_execution.md
|
||||
- concepts/pregel.md
|
||||
- LangGraph Platform:
|
||||
- LangGraph Platform: concepts#langgraph-platform
|
||||
- High Level:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# LangGraph Checkpoint
|
||||
|
||||
This library defines the base interface for LangGraph checkpointers. Checkpointers provide persistence layer for LangGraph. They allow you to interact with and manage the graph's state. When you use a graph with a checkpointer, the checkpointer saves a _checkpoint_ of the graph state at every superstep, enabling several powerful capabilities like human-in-the-loop, "memory" between interactions and more.
|
||||
This library defines the base interface for LangGraph checkpointers. Checkpointers provide a persistence layer for LangGraph. They allow you to interact with and manage the graph's state. When you use a graph with a checkpointer, the checkpointer saves a _checkpoint_ of the graph state at every superstep, enabling several powerful capabilities like human-in-the-loop, "memory" between interactions and more.
|
||||
|
||||
## Key concepts
|
||||
|
||||
@@ -12,8 +12,8 @@ Checkpoint is a snapshot of the graph state at a given point in time. Checkpoint
|
||||
|
||||
Threads enable the checkpointing of multiple different runs, making them essential for multi-tenant chat applications and other scenarios where maintaining separate states is necessary. A thread is a unique ID assigned to a series of checkpoints saved by a checkpointer. When using a checkpointer, you must specify a `thread_id` and optionally `checkpoint_id` when running the graph.
|
||||
|
||||
- `thread_id` is simply the ID of a thread. This is always required
|
||||
- `checkpoint_id` can optionally be passed. This identifier refers to a specific checkpoint within a thread. This can be used to kick of a run of a graph from some point halfway through a thread.
|
||||
- `thread_id` is simply the ID of a thread. This is always required.
|
||||
- `checkpoint_id` can optionally be passed. This identifier refers to a specific checkpoint within a thread. This can be used to kick off a run of a graph from some point halfway through a thread.
|
||||
|
||||
You must pass these when invoking the graph as part of the configurable part of the config, e.g.
|
||||
|
||||
|
||||
Generated
+12
-12
@@ -502,13 +502,13 @@ tests = ["flask (>=2.2.5)", "hypothesis (>=6.79.4)", "pytest (>=7.4.4)"]
|
||||
|
||||
[[package]]
|
||||
name = "langchain-core"
|
||||
version = "0.3.36"
|
||||
version = "0.3.37"
|
||||
description = "Building applications with LLMs through composability"
|
||||
optional = true
|
||||
python-versions = "<4.0,>=3.9"
|
||||
files = [
|
||||
{file = "langchain_core-0.3.36-py3-none-any.whl", hash = "sha256:8410311862c7c674e4f3f120cfd8d1f3d003d6e7d8cb8f934746e222f7e865d9"},
|
||||
{file = "langchain_core-0.3.36.tar.gz", hash = "sha256:dffdce8a554905f53f33c1d6a40633a45a8d47c17c5792753891dd73941cd57a"},
|
||||
{file = "langchain_core-0.3.37-py3-none-any.whl", hash = "sha256:8202fd6506ce139a3a1b1c4c3006216b1c7fffa40bdd1779f7d2c67f75eb5f79"},
|
||||
{file = "langchain_core-0.3.37.tar.gz", hash = "sha256:cda8786e616caa2f68f7cc9e811b9b50e3b63fb2094333318b348e5961a7ea01"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -541,13 +541,13 @@ langgraph-sdk = ">=0.1.42,<0.2.0"
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-api"
|
||||
version = "0.0.24"
|
||||
version = "0.0.26"
|
||||
description = ""
|
||||
optional = true
|
||||
python-versions = "<4.0,>=3.11.0"
|
||||
files = [
|
||||
{file = "langgraph_api-0.0.24-py3-none-any.whl", hash = "sha256:26c4fb7aeefa3ac2ffadcc4cd6ff7e7afcc7d607e92b0c645cc541f89ad23af5"},
|
||||
{file = "langgraph_api-0.0.24.tar.gz", hash = "sha256:53eac22cf7d2bc436ed77e0790027a939534174001a27db88cd0e20af2161bcd"},
|
||||
{file = "langgraph_api-0.0.26-py3-none-any.whl", hash = "sha256:ecec9f0378f73dc0f0a30c43e1b920fe1f00821c82056efeb0de0ad81cdf4305"},
|
||||
{file = "langgraph_api-0.0.26.tar.gz", hash = "sha256:2a7606d6a8cf82774f4c5603d291835dec4fbb1dd93f3bba842cf5932c042da7"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -556,8 +556,8 @@ httpx = ">=0.27.0"
|
||||
jsonschema-rs = ">=0.25.0,<0.26.0"
|
||||
langchain-core = ">=0.2.38,<0.4.0"
|
||||
langgraph = ">=0.2.56,<0.3.0"
|
||||
langgraph-checkpoint = ">=2.0.7,<3.0"
|
||||
langgraph-sdk = ">=0.1.51,<0.2.0"
|
||||
langgraph-checkpoint = ">=2.0.15,<3.0"
|
||||
langgraph-sdk = ">=0.1.53,<0.2.0"
|
||||
langsmith = ">=0.1.63,<0.4.0"
|
||||
orjson = ">=3.10.1"
|
||||
pyjwt = ">=2.9.0,<3.0.0"
|
||||
@@ -585,13 +585,13 @@ msgpack = ">=1.1.0,<2.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-sdk"
|
||||
version = "0.1.51"
|
||||
version = "0.1.53"
|
||||
description = "SDK for interacting with LangGraph API"
|
||||
optional = true
|
||||
python-versions = "<4.0.0,>=3.9.0"
|
||||
files = [
|
||||
{file = "langgraph_sdk-0.1.51-py3-none-any.whl", hash = "sha256:ce2b58466d1700d06149782ed113157a8694a6d7932c801f316cd13fab315fe4"},
|
||||
{file = "langgraph_sdk-0.1.51.tar.gz", hash = "sha256:dea1363e72562cb1e82a2d156be8d5b1a69ff3fe8815eee0e1e7a2f423242ec1"},
|
||||
{file = "langgraph_sdk-0.1.53-py3-none-any.whl", hash = "sha256:4fab62caad73661ffe4c3ababedcd0d7bfaaba986bee4416b9c28948458a3af5"},
|
||||
{file = "langgraph_sdk-0.1.53.tar.gz", hash = "sha256:12906ed965905fa27e0c28d9fa07dc6fd89e6895ff321ff049fdf3965d057cc4"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1655,4 +1655,4 @@ inmem = ["langgraph-api", "python-dotenv"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.9.0,<4.0"
|
||||
content-hash = "efd4f33499434405e0d130f7f561f9ef454b8c7906c2ef9d119d3638bfd3cc33"
|
||||
content-hash = "48e374a559e6d8339c82b5271dea910f8ddfb6baf8436153ca54faefb8b2b220"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "langgraph-cli"
|
||||
version = "0.1.72"
|
||||
version = "0.1.73"
|
||||
description = "CLI for interacting with LangGraph API"
|
||||
authors = []
|
||||
license = "MIT"
|
||||
@@ -14,7 +14,7 @@ langgraph = "langgraph_cli.cli:cli"
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.9.0,<4.0"
|
||||
click = "^8.1.7"
|
||||
langgraph-api = { version = ">=0.0.24,<0.1.0", optional = true, python = ">=3.11,<4.0" }
|
||||
langgraph-api = { version = ">=0.0.26,<0.1.0", optional = true, python = ">=3.11,<4.0" }
|
||||
python-dotenv = { version = ">=0.8.0", optional = true }
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
|
||||
@@ -675,7 +675,9 @@ class CompiledStateGraph(CompiledGraph):
|
||||
elif isinstance(input, Command):
|
||||
if input.graph == Command.PARENT:
|
||||
return None
|
||||
return input._update_as_tuples()
|
||||
return [
|
||||
(k, v) for k, v in input._update_as_tuples() if k in output_keys
|
||||
]
|
||||
elif (
|
||||
isinstance(input, (list, tuple))
|
||||
and input
|
||||
@@ -686,7 +688,9 @@ class CompiledStateGraph(CompiledGraph):
|
||||
if isinstance(i, Command):
|
||||
if i.graph == Command.PARENT:
|
||||
continue
|
||||
updates.extend(i._update_as_tuples())
|
||||
updates.extend(
|
||||
(k, v) for k, v in i._update_as_tuples() if k in output_keys
|
||||
)
|
||||
else:
|
||||
updates.extend(_get_updates(i) or ())
|
||||
return updates
|
||||
|
||||
@@ -246,11 +246,12 @@ def create_react_agent(
|
||||
model: Union[str, LanguageModelLike],
|
||||
tools: Union[ToolExecutor, Sequence[BaseTool], ToolNode],
|
||||
*,
|
||||
state_schema: Optional[StateSchemaType] = None,
|
||||
prompt: Optional[Prompt] = None,
|
||||
response_format: Optional[
|
||||
Union[StructuredResponseSchema, tuple[str, StructuredResponseSchema]]
|
||||
] = None,
|
||||
state_schema: Optional[StateSchemaType] = None,
|
||||
config_schema: Optional[Type[Any]] = None,
|
||||
checkpointer: Optional[Checkpointer] = None,
|
||||
store: Optional[BaseStore] = None,
|
||||
interrupt_before: Optional[list[str]] = None,
|
||||
@@ -265,9 +266,6 @@ def create_react_agent(
|
||||
model: The `LangChain` chat model that supports tool calling.
|
||||
tools: A list of tools, a ToolExecutor, or a ToolNode instance.
|
||||
If an empty list is provided, the agent will consist of a single LLM node without tool calling.
|
||||
state_schema: An optional state schema that defines graph state.
|
||||
Must have `messages` and `is_last_step` keys.
|
||||
Defaults to `AgentState` that defines those two keys.
|
||||
prompt: An optional prompt for the LLM. Can take a few different forms:
|
||||
|
||||
- str: This is converted to a SystemMessage and added to the beginning of the list of messages in state["messages"].
|
||||
@@ -296,6 +294,11 @@ def create_react_agent(
|
||||
!!! Note
|
||||
The graph will make a separate call to the LLM to generate the structured response after the agent loop is finished.
|
||||
This is not the only strategy to get structured responses, see more options in [this guide](https://langchain-ai.github.io/langgraph/how-tos/react-agent-structured-output/).
|
||||
state_schema: An optional state schema that defines graph state.
|
||||
Must have `messages` and `is_last_step` keys.
|
||||
Defaults to `AgentState` that defines those two keys.
|
||||
config_schema: An optional schema for configuration.
|
||||
Use this to expose configurable parameters via agent.config_specs.
|
||||
checkpointer: An optional checkpoint saver object. This is used for persisting
|
||||
the state of the graph (e.g., as chat memory) for a single thread (e.g., a single conversation).
|
||||
store: An optional store object. This is used for persisting data
|
||||
@@ -763,7 +766,7 @@ def create_react_agent(
|
||||
|
||||
if not tool_calling_enabled:
|
||||
# Define a new graph
|
||||
workflow = StateGraph(state_schema)
|
||||
workflow = StateGraph(state_schema, config_schema=config_schema)
|
||||
workflow.add_node("agent", RunnableCallable(call_model, acall_model))
|
||||
workflow.set_entry_point("agent")
|
||||
if response_format is not None:
|
||||
@@ -803,7 +806,7 @@ def create_react_agent(
|
||||
return [Send("tools", [tool_call]) for tool_call in tool_calls]
|
||||
|
||||
# Define a new graph
|
||||
workflow = StateGraph(state_schema or AgentState)
|
||||
workflow = StateGraph(state_schema or AgentState, config_schema=config_schema)
|
||||
|
||||
# Define the two nodes we will cycle between
|
||||
workflow.add_node("agent", RunnableCallable(call_model, acall_model))
|
||||
|
||||
@@ -200,10 +200,42 @@ class Channel:
|
||||
class Pregel(PregelProtocol):
|
||||
"""Pregel manages the runtime behavior for LangGraph applications.
|
||||
|
||||
## Overview
|
||||
|
||||
Pregel combines [**actors**](https://en.wikipedia.org/wiki/Actor_model)
|
||||
and **channels** into a single application.
|
||||
**Actors** read data from channels and write data to channels.
|
||||
Pregel organizes the execution of the application into multiple steps,
|
||||
following the **Pregel Algorithm**/**Bulk Synchronous Parallel** model.
|
||||
|
||||
Each step consists of three phases:
|
||||
|
||||
- **Plan**: Determine which **actors** to execute in this step. For example,
|
||||
in the first step, select the **actors** that subscribe to the special
|
||||
**input** channels; in subsequent steps,
|
||||
select the **actors** that subscribe to channels updated in the previous step.
|
||||
- **Execution**: Execute all selected **actors** in parallel,
|
||||
until all complete, or one fails, or a timeout is reached. During this
|
||||
phase, channel updates are invisible to actors until the next step.
|
||||
- **Update**: Update the channels with the values written by the **actors**
|
||||
in this step.
|
||||
|
||||
Repeat until no **actors** are selected for execution, or a maximum number of
|
||||
steps is reached.
|
||||
|
||||
## Actors
|
||||
|
||||
An **actor** is a [PregelNode][langgraph.pregel.read.PregelNode].
|
||||
It subscribes to channels, reads data from them, and writes data to them.
|
||||
It can be thought of as an **actor** in the Pregel algorithm.
|
||||
[PregelNodes][langgraph.pregel.read.PregelNode] implement LangChain's
|
||||
Runnable interface.
|
||||
|
||||
## Channels
|
||||
|
||||
Channels are used to communicate between chains. Each channel has a value type,
|
||||
an update type, and an update function – which takes a sequence of updates and
|
||||
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:
|
||||
@@ -213,7 +245,7 @@ class Pregel(PregelProtocol):
|
||||
- `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 chains, or for accumulating output. Can be configured to deduplicate
|
||||
between *actors*, or for accumulating output. Can be configured to deduplicate
|
||||
values, and/or to accumulate values over the course of multiple steps.
|
||||
|
||||
### Advanced channels: Context and BinaryOperatorAggregate
|
||||
@@ -226,30 +258,202 @@ class Pregel(PregelProtocol):
|
||||
sent to the channel, useful for computing aggregates over multiple steps. eg.
|
||||
`total = BinaryOperatorAggregate(int, operator.add)`
|
||||
|
||||
## Chains
|
||||
## Examples
|
||||
|
||||
Chains are LCEL Runnables which subscribe to one or more channels, and write to
|
||||
one or more channels. Any valid LCEL expression can be used as a chain. Chains
|
||||
can be combined into a Pregel application, which coordinates the execution of the
|
||||
chains across multiple steps.
|
||||
Most users will interact with Pregel via a
|
||||
[StateGraph (Graph API)][langgraph.graph.StateGraph] or via an
|
||||
[entrypoint (Functional API)][langgraph.func.entrypoint].
|
||||
|
||||
## Pregel
|
||||
However, for **advanced** use cases, Pregel can be used directly. If you're
|
||||
not sure whether you need to use Pregel directly, then the answer is probably no
|
||||
– you should use the Graph API or Functional API instead. These are higher-level
|
||||
interfaces that will compile down to Pregel under the hood.
|
||||
|
||||
Pregel combines multiple chains (or actors) into a single application. It
|
||||
coordinates the execution of the chains across multiple steps, following the
|
||||
Pregel/Bulk Synchronous Parallel model. Each step consists of three phases:
|
||||
Here are some examples to give you a sense of how it works:
|
||||
|
||||
- **Plan**: Determine which chains to execute in this step, ie. the chains that
|
||||
subscribe to channels updated in the previous step (or, in the first step,
|
||||
chains that subscribe to input channels)
|
||||
- **Execution**: Execute those chains in parallel, until all complete, or one fails,
|
||||
or a timeout is reached. Any channel updates are invisible to other
|
||||
chains until the next step.
|
||||
- **Update**: Update the channels with the values written by the
|
||||
chains in this step.
|
||||
Example: Single node application
|
||||
```python
|
||||
from langgraph.channels import EphemeralValue
|
||||
from langgraph.pregel import Pregel, Channel, ChannelWriteEntry
|
||||
|
||||
Repeat until no chains are planned for execution, or a maximum number of steps
|
||||
is reached.
|
||||
node1 = (
|
||||
Channel.subscribe_to("a")
|
||||
| (lambda x: x + x)
|
||||
| Channel.write_to("b")
|
||||
)
|
||||
|
||||
app = Pregel(
|
||||
nodes={"node1": node1},
|
||||
channels={
|
||||
"a": EphemeralValue(str),
|
||||
"b": EphemeralValue(str),
|
||||
},
|
||||
input_channels=["a"],
|
||||
output_channels=["b"],
|
||||
)
|
||||
|
||||
app.invoke({"a": "foo"})
|
||||
```
|
||||
|
||||
```con
|
||||
{'b': 'foofoo'}
|
||||
```
|
||||
|
||||
Example: Using multiple nodes and multiple output channels
|
||||
```python
|
||||
from langgraph.channels import LastValue, EphemeralValue
|
||||
from langgraph.pregel import Pregel, Channel, ChannelWriteEntry
|
||||
|
||||
node1 = (
|
||||
Channel.subscribe_to("a")
|
||||
| (lambda x: x + x)
|
||||
| Channel.write_to("b")
|
||||
)
|
||||
|
||||
node2 = (
|
||||
Channel.subscribe_to("b")
|
||||
| (lambda x: x + x)
|
||||
| Channel.write_to("c")
|
||||
)
|
||||
|
||||
|
||||
app = Pregel(
|
||||
nodes={"node1": node1, "node2": node2},
|
||||
channels={
|
||||
"a": EphemeralValue(str),
|
||||
"b": LastValue(str),
|
||||
"c": EphemeralValue(str),
|
||||
},
|
||||
input_channels=["a"],
|
||||
output_channels=["b", "c"],
|
||||
)
|
||||
|
||||
app.invoke({"a": "foo"})
|
||||
```
|
||||
|
||||
```con
|
||||
{'b': 'foofoo', 'c': 'foofoofoofoo'}
|
||||
```
|
||||
|
||||
Example: Using a Topic channel
|
||||
```python
|
||||
from langgraph.channels import LastValue, EphemeralValue, Topic
|
||||
from langgraph.pregel import Pregel, Channel, ChannelWriteEntry
|
||||
|
||||
node1 = (
|
||||
Channel.subscribe_to("a")
|
||||
| (lambda x: x + x)
|
||||
| {
|
||||
"b": Channel.write_to("b"),
|
||||
"c": Channel.write_to("c")
|
||||
}
|
||||
)
|
||||
|
||||
node2 = (
|
||||
Channel.subscribe_to("b")
|
||||
| (lambda x: x + x)
|
||||
| {
|
||||
"c": Channel.write_to("c"),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
app = Pregel(
|
||||
nodes={"node1": node1, "node2": node2},
|
||||
channels={
|
||||
"a": EphemeralValue(str),
|
||||
"b": EphemeralValue(str),
|
||||
"c": Topic(str, accumulate=True),
|
||||
},
|
||||
input_channels=["a"],
|
||||
output_channels=["c"],
|
||||
)
|
||||
|
||||
app.invoke({"a": "foo"})
|
||||
```
|
||||
|
||||
```pycon
|
||||
{'c': ['foofoo', 'foofoofoofoo']}
|
||||
```
|
||||
|
||||
Example: Using a BinaryOperatorAggregate channel
|
||||
```python
|
||||
from langgraph.channels import EphemeralValue, BinaryOperatorAggregate
|
||||
from langgraph.pregel import Pregel, Channel
|
||||
|
||||
|
||||
node1 = (
|
||||
Channel.subscribe_to("a")
|
||||
| (lambda x: x + x)
|
||||
| {
|
||||
"b": Channel.write_to("b"),
|
||||
"c": Channel.write_to("c")
|
||||
}
|
||||
)
|
||||
|
||||
node2 = (
|
||||
Channel.subscribe_to("b")
|
||||
| (lambda x: x + x)
|
||||
| {
|
||||
"c": Channel.write_to("c"),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def reducer(current, update):
|
||||
if current:
|
||||
return current + " | " + "update"
|
||||
else:
|
||||
return update
|
||||
|
||||
app = Pregel(
|
||||
nodes={"node1": node1, "node2": node2},
|
||||
channels={
|
||||
"a": EphemeralValue(str),
|
||||
"b": EphemeralValue(str),
|
||||
"c": BinaryOperatorAggregate(str, operator=reducer),
|
||||
},
|
||||
input_channels=["a"],
|
||||
output_channels=["c"]
|
||||
)
|
||||
|
||||
app.invoke({"a": "foo"})
|
||||
```
|
||||
|
||||
```con
|
||||
{'c': 'foofoo | foofoofoofoo'}
|
||||
```
|
||||
|
||||
Example: Introducing a cycle
|
||||
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.
|
||||
|
||||
```python
|
||||
from langgraph.channels import EphemeralValue
|
||||
from langgraph.pregel import Pregel, Channel, ChannelWrite, ChannelWriteEntry
|
||||
|
||||
example_node = (
|
||||
Channel.subscribe_to("value")
|
||||
| (lambda x: x + x if len(x) < 10 else None)
|
||||
| ChannelWrite(writes=[ChannelWriteEntry(channel="value", skip_none=True)])
|
||||
)
|
||||
|
||||
app = Pregel(
|
||||
nodes={"example_node": example_node},
|
||||
channels={
|
||||
"value": EphemeralValue(str),
|
||||
},
|
||||
input_channels=["value"],
|
||||
output_channels=["value"]
|
||||
)
|
||||
|
||||
app.invoke({"value": "a"})
|
||||
```
|
||||
|
||||
```con
|
||||
{'value': 'aaaaaaaaaaaaaaaa'}
|
||||
```
|
||||
"""
|
||||
|
||||
nodes: dict[str, PregelNode]
|
||||
|
||||
@@ -888,7 +888,11 @@ class SyncPregelLoop(PregelLoop, ContextManager):
|
||||
)
|
||||
|
||||
def _update_mv(self, key: str, values: Sequence[Any]) -> None:
|
||||
return self.submit(cast(WritableManagedValue, self.managed[key]).update, values)
|
||||
managed_value = self.managed.get(key)
|
||||
if managed_value is None:
|
||||
return
|
||||
|
||||
return self.submit(cast(WritableManagedValue, managed_value).update, values)
|
||||
|
||||
# context manager
|
||||
|
||||
@@ -1023,9 +1027,11 @@ class AsyncPregelLoop(PregelLoop, AsyncContextManager):
|
||||
)
|
||||
|
||||
def _update_mv(self, key: str, values: Sequence[Any]) -> None:
|
||||
return self.submit(
|
||||
cast(WritableManagedValue, self.managed[key]).aupdate, values
|
||||
)
|
||||
managed_value = self.managed.get(key)
|
||||
if managed_value is None:
|
||||
return
|
||||
|
||||
return self.submit(cast(WritableManagedValue, managed_value).aupdate, values)
|
||||
|
||||
# context manager
|
||||
|
||||
|
||||
@@ -6260,6 +6260,67 @@ def test_merging_updates_command_parent():
|
||||
]
|
||||
|
||||
|
||||
def test_merging_non_overlapping_updates_command_parent():
|
||||
# simple reducer
|
||||
def append_unique(left, right):
|
||||
combined = list(left)
|
||||
for item in right:
|
||||
if item in combined:
|
||||
continue
|
||||
else:
|
||||
combined.append(item)
|
||||
return combined
|
||||
|
||||
class State(TypedDict):
|
||||
foo: Annotated[list, append_unique]
|
||||
|
||||
# Define subgraph
|
||||
def subgraph_node_1(state: State):
|
||||
return Command(
|
||||
goto="subgraph_node_2",
|
||||
update={
|
||||
"foo": ["bar"],
|
||||
"bar": ["subgraph_node_1"],
|
||||
},
|
||||
)
|
||||
|
||||
def subgraph_node_2(state: State):
|
||||
return Command(
|
||||
goto="node_3",
|
||||
update={"bar": ["subgraph_node_2"]},
|
||||
graph=Command.PARENT,
|
||||
)
|
||||
|
||||
subgraph_builder = StateGraph(State)
|
||||
subgraph_builder.add_node(subgraph_node_1)
|
||||
subgraph_builder.add_node(subgraph_node_2)
|
||||
subgraph_builder.add_edge(START, "subgraph_node_1")
|
||||
|
||||
# Define main graph
|
||||
def node_1(state: State):
|
||||
return Command(
|
||||
goto="node_2",
|
||||
update={"foo": ["foo"]},
|
||||
)
|
||||
|
||||
def node_3(state: State, store):
|
||||
return Command(
|
||||
update={"foo": ["baz"]},
|
||||
)
|
||||
|
||||
main_builder = StateGraph(State)
|
||||
main_builder.add_node("node_1", node_1)
|
||||
main_builder.add_node("node_2", subgraph_builder.compile())
|
||||
main_builder.add_node("node_3", node_3)
|
||||
main_builder.add_edge(START, "node_1")
|
||||
main_builder.add_edge("node_2", "node_3")
|
||||
main_graph = main_builder.compile()
|
||||
|
||||
assert main_graph.invoke({"foo": []}) == {
|
||||
"foo": ["foo", "bar", "baz"],
|
||||
}
|
||||
|
||||
|
||||
def test_entrypoint_output_schema_with_return_and_save() -> None:
|
||||
"""Test output schema inference with entrypoint.final."""
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@langchain/langgraph-sdk",
|
||||
"version": "0.0.43",
|
||||
"version": "0.0.46",
|
||||
"description": "Client library for interacting with the LangGraph API",
|
||||
"type": "module",
|
||||
"packageManager": "yarn@1.22.19",
|
||||
|
||||
@@ -9,7 +9,13 @@ import type {
|
||||
OnCompletionBehavior,
|
||||
} from "../types.js";
|
||||
import type { Message } from "../types.messages.js";
|
||||
import type { Checkpoint, Config, Metadata, ThreadState } from "../schema.js";
|
||||
import type {
|
||||
Checkpoint,
|
||||
Config,
|
||||
Interrupt,
|
||||
Metadata,
|
||||
ThreadState,
|
||||
} from "../schema.js";
|
||||
import type {
|
||||
CustomStreamEvent,
|
||||
DebugStreamEvent,
|
||||
@@ -37,6 +43,7 @@ import {
|
||||
type BaseMessage,
|
||||
coerceMessageLikeToMessage,
|
||||
convertToChunk,
|
||||
isBaseMessageChunk,
|
||||
} from "@langchain/core/messages";
|
||||
|
||||
class StreamError extends Error {
|
||||
@@ -54,21 +61,53 @@ class StreamError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
function tryConvertToChunk(message: BaseMessage): BaseMessageChunk | null {
|
||||
try {
|
||||
return convertToChunk(message);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class MessageTupleManager {
|
||||
chunks: Record<string, { chunk?: BaseMessageChunk; index?: number }> = {};
|
||||
chunks: Record<
|
||||
string,
|
||||
{ chunk?: BaseMessageChunk | BaseMessage; index?: number }
|
||||
> = {};
|
||||
|
||||
constructor() {
|
||||
this.chunks = {};
|
||||
}
|
||||
|
||||
add(serialized: Message): string | null {
|
||||
const chunk = convertToChunk(coerceMessageLikeToMessage(serialized));
|
||||
// TODO: this is sometimes sent from the API
|
||||
// figure out how to prevent this or move this to LC.js
|
||||
if (serialized.type.endsWith("MessageChunk")) {
|
||||
serialized.type = serialized.type
|
||||
.slice(0, -"MessageChunk".length)
|
||||
.toLowerCase() as Message["type"];
|
||||
}
|
||||
|
||||
const id = chunk.id;
|
||||
if (!id) return null;
|
||||
const message = coerceMessageLikeToMessage(serialized);
|
||||
const chunk = tryConvertToChunk(message);
|
||||
|
||||
const id = (chunk ?? message).id;
|
||||
if (!id) {
|
||||
console.warn(
|
||||
"No message ID found for chunk, ignoring in state",
|
||||
serialized,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
this.chunks[id] ??= {};
|
||||
this.chunks[id].chunk = this.chunks[id]?.chunk?.concat(chunk) ?? chunk;
|
||||
if (chunk) {
|
||||
const prev = this.chunks[id].chunk;
|
||||
this.chunks[id].chunk =
|
||||
(isBaseMessageChunk(prev) ? prev : null)?.concat(chunk) ?? chunk;
|
||||
} else {
|
||||
this.chunks[id].chunk = message;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
@@ -334,10 +373,41 @@ const useControllableThreadId = (options?: {
|
||||
return [options.threadId, onThreadId];
|
||||
};
|
||||
|
||||
type BagTemplate = {
|
||||
ConfigurableType?: Record<string, unknown>;
|
||||
InterruptType?: unknown;
|
||||
CustomEventType?: unknown;
|
||||
UpdateType?: unknown;
|
||||
};
|
||||
|
||||
type GetUpdateType<
|
||||
Bag extends BagTemplate,
|
||||
StateType extends Record<string, unknown>,
|
||||
> = Bag extends { UpdateType: unknown }
|
||||
? Bag["UpdateType"]
|
||||
: Partial<StateType>;
|
||||
|
||||
type GetConfigurableType<Bag extends BagTemplate> = Bag extends {
|
||||
ConfigurableType: Record<string, unknown>;
|
||||
}
|
||||
? Bag["ConfigurableType"]
|
||||
: Record<string, unknown>;
|
||||
|
||||
type GetInterruptType<Bag extends BagTemplate> = Bag extends {
|
||||
InterruptType: unknown;
|
||||
}
|
||||
? Bag["InterruptType"]
|
||||
: unknown;
|
||||
|
||||
type GetCustomEventType<Bag extends BagTemplate> = Bag extends {
|
||||
CustomEventType: unknown;
|
||||
}
|
||||
? Bag["CustomEventType"]
|
||||
: unknown;
|
||||
|
||||
interface UseStreamOptions<
|
||||
StateType extends Record<string, unknown> = Record<string, unknown>,
|
||||
UpdateType extends Record<string, unknown> = Partial<StateType>,
|
||||
CustomType = unknown,
|
||||
Bag extends BagTemplate = BagTemplate,
|
||||
> {
|
||||
/**
|
||||
* The ID of the assistant to use.
|
||||
@@ -375,12 +445,16 @@ interface UseStreamOptions<
|
||||
/**
|
||||
* Callback that is called when an update event is received.
|
||||
*/
|
||||
onUpdateEvent?: (data: UpdatesStreamEvent<UpdateType>["data"]) => void;
|
||||
onUpdateEvent?: (
|
||||
data: UpdatesStreamEvent<GetUpdateType<Bag, StateType>>["data"],
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* Callback that is called when a custom event is received.
|
||||
*/
|
||||
onCustomEvent?: (data: CustomStreamEvent<CustomType>["data"]) => void;
|
||||
onCustomEvent?: (
|
||||
data: CustomStreamEvent<GetCustomEventType<Bag>>["data"],
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* Callback that is called when a metadata event is received.
|
||||
@@ -400,8 +474,7 @@ interface UseStreamOptions<
|
||||
|
||||
interface UseStream<
|
||||
StateType extends Record<string, unknown> = Record<string, unknown>,
|
||||
UpdateType extends Record<string, unknown> = Partial<StateType>,
|
||||
ConfigurableType extends Record<string, unknown> = Record<string, unknown>,
|
||||
Bag extends BagTemplate = BagTemplate,
|
||||
> {
|
||||
/**
|
||||
* The current values of the thread.
|
||||
@@ -427,8 +500,8 @@ interface UseStream<
|
||||
* Create and stream a run to the thread.
|
||||
*/
|
||||
submit: (
|
||||
values: UpdateType,
|
||||
options?: SubmitOptions<StateType, ConfigurableType>,
|
||||
values: GetUpdateType<Bag, StateType> | null | undefined,
|
||||
options?: SubmitOptions<StateType, GetConfigurableType<Bag>>,
|
||||
) => void;
|
||||
|
||||
/**
|
||||
@@ -452,6 +525,11 @@ interface UseStream<
|
||||
*/
|
||||
experimental_branchTree: Sequence<StateType>;
|
||||
|
||||
/**
|
||||
* Get the interrupt value for the stream if interrupted.
|
||||
*/
|
||||
interrupt: Interrupt<GetInterruptType<Bag>> | undefined;
|
||||
|
||||
/**
|
||||
* Messages inferred from the thread.
|
||||
* Will automatically update with incoming message chunks.
|
||||
@@ -497,12 +575,18 @@ interface SubmitOptions<
|
||||
|
||||
export function useStream<
|
||||
StateType extends Record<string, unknown> = Record<string, unknown>,
|
||||
UpdateType extends Record<string, unknown> = Partial<StateType>,
|
||||
ConfigurableType extends Record<string, unknown> = Record<string, unknown>,
|
||||
CustomType = unknown,
|
||||
>(
|
||||
options: UseStreamOptions<StateType, UpdateType, CustomType>,
|
||||
): UseStream<StateType, UpdateType, ConfigurableType> {
|
||||
Bag extends {
|
||||
ConfigurableType?: Record<string, unknown>;
|
||||
InterruptType?: unknown;
|
||||
CustomEventType?: unknown;
|
||||
UpdateType?: unknown;
|
||||
} = BagTemplate,
|
||||
>(options: UseStreamOptions<StateType, Bag>): UseStream<StateType, Bag> {
|
||||
type UpdateType = GetUpdateType<Bag, StateType>;
|
||||
type CustomType = GetCustomEventType<Bag>;
|
||||
type InterruptType = GetInterruptType<Bag>;
|
||||
type ConfigurableType = GetConfigurableType<Bag>;
|
||||
|
||||
type EventStreamEvent =
|
||||
| ValuesStreamEvent<StateType>
|
||||
| UpdatesStreamEvent<UpdateType>
|
||||
@@ -536,7 +620,7 @@ export function useStream<
|
||||
|
||||
const trackStreamModeRef = useRef<
|
||||
Array<"values" | "updates" | "events" | "custom" | "messages-tuple">
|
||||
>(["values", "messages-tuple"]);
|
||||
>([]);
|
||||
|
||||
const trackStreamMode = useCallback(
|
||||
(mode: Exclude<StreamMode, "debug" | "messages">) => {
|
||||
@@ -648,7 +732,7 @@ export function useStream<
|
||||
}, []);
|
||||
|
||||
const submit = async (
|
||||
values: UpdateType | undefined,
|
||||
values: UpdateType | null | undefined,
|
||||
submitOptions?: SubmitOptions<StateType, ConfigurableType>,
|
||||
) => {
|
||||
try {
|
||||
@@ -772,6 +856,7 @@ export function useStream<
|
||||
(error.name === "AbortError" || error.name === "TimeoutError")
|
||||
)
|
||||
) {
|
||||
console.error(error);
|
||||
setStreamError(error);
|
||||
onError?.(error);
|
||||
}
|
||||
@@ -785,7 +870,7 @@ export function useStream<
|
||||
}
|
||||
};
|
||||
|
||||
const error = isLoading ? streamError : historyError;
|
||||
const error = streamError ?? historyError;
|
||||
const values = streamValues ?? historyValues;
|
||||
|
||||
return {
|
||||
@@ -806,6 +891,22 @@ export function useStream<
|
||||
history: flatHistory,
|
||||
experimental_branchTree: rootSequence,
|
||||
|
||||
get interrupt() {
|
||||
// Don't show the interrupt if the stream is loading
|
||||
if (isLoading) return undefined;
|
||||
|
||||
const interrupts = threadHead?.tasks?.at(-1)?.interrupts;
|
||||
if (interrupts == null || interrupts.length === 0) {
|
||||
// check if there's a next task present
|
||||
const next = threadHead?.next ?? [];
|
||||
if (!next.length || error != null) return undefined;
|
||||
return { when: "breakpoint" };
|
||||
}
|
||||
|
||||
// Return only the current interrupt
|
||||
return interrupts.at(-1) as Interrupt<InterruptType> | undefined;
|
||||
},
|
||||
|
||||
get messages() {
|
||||
trackStreamMode("messages-tuple");
|
||||
return getMessages(values);
|
||||
|
||||
@@ -141,10 +141,10 @@ export interface AssistantGraph {
|
||||
/**
|
||||
* An interrupt thrown inside a thread.
|
||||
*/
|
||||
export interface Interrupt {
|
||||
value: unknown;
|
||||
when: "during";
|
||||
resumable: boolean;
|
||||
export interface Interrupt<TValue = unknown> {
|
||||
value?: TValue;
|
||||
when: "during" | (string & {});
|
||||
resumable?: boolean;
|
||||
ns?: string[];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user