mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-25 17:12:26 +02:00
Merge branch 'main' into brace/filter-status-js
This commit is contained in:
@@ -82,7 +82,6 @@ jobs:
|
||||
--check-links-ignore "https://github\.com/.*" \
|
||||
--check-links-ignore "/.*\.(ipynb|html)$" \
|
||||
--check-links-ignore "https://python\.langchain\.com/.*" \
|
||||
--check-links-ignore "https://www.langchain\.com/.*" \
|
||||
--check-links-ignore "https://openai.com/index/memory-and-new-controls-for-chatgpt/" \
|
||||
--check-links $(find docs/site -name "index.html" | grep -v 'storm/index.html')
|
||||
|
||||
@@ -97,7 +96,6 @@ jobs:
|
||||
poetry run pytest -v \
|
||||
--check-links-ignore "https://(api|web|docs)\.smith\.langchain\.com/.*" \
|
||||
--check-links-ignore "https://x.com/.*" \
|
||||
--check-links-ignore "https://www\.langchain\.com/.*" \
|
||||
--check-links-ignore "https://github\.com/.*" \
|
||||
--check-links-ignore "/.*\.(ipynb|html)$" \
|
||||
--check-links ${CHANGED_FILES} \
|
||||
|
||||
@@ -1,216 +0,0 @@
|
||||
# API Concepts
|
||||
|
||||
This page describes the high-level concepts of the LangGraph Cloud API. The conceptual guide of LangGraph (Python library) is [here](../../concepts/high_level.md).
|
||||
|
||||
## Data Models
|
||||
|
||||
The LangGraph Cloud API consists of a few core data models: [Assistants](#assistants), [Threads](#threads), [Runs](#runs), and [Cron Jobs](#cron-jobs).
|
||||
|
||||
### Assistants
|
||||
|
||||
When building agents, it is fairly common to make rapid changes that *do not* alter the graph logic. For example, simply changing prompts or the LLM selection can have significant impacts on the behavior of the agents. Assistants offer an easy way to make and save these types of changes to agent configuration. This can have at least two use-cases:
|
||||
|
||||
* Assistants give developers a quick and easy way to modify and version graph version for experimentation.
|
||||
* Assistants can be modified via LangGraph Studio, offering a no-code way to configure agents (e.g., for business users).
|
||||
|
||||
#### Configuring Assistants
|
||||
|
||||
In practice, an assistant is just an *instance* of a graph with a specific configuration. Because of this, multiple assistants can reference the same graph but can contain different configurations, such as prompts, models, and other graph configuration options. The LangGraph Cloud API provides several endpoints for creating and managing assistants. See the [API reference](../reference/api/api_ref.html#tag/assistantscreate) and [this how-to](../how-tos/configuration_cloud.md) for more details on how to create assistants.
|
||||
|
||||
#### Versioning Assistants
|
||||
|
||||

|
||||
|
||||
Once you've created an assistant, you can save and version it to track changes to the configuration over time. You can think about this at three levels:
|
||||
|
||||
1) The graph lays out the general agent application logic
|
||||
2) The agent configuration options represent parameters that can be changed
|
||||
3) Assistant versions save and track specific settings of the agent configuration options
|
||||
|
||||
For example, if you have an agent that helps for planning trips, you can create a new assistant *for each user* that passes specific user preferences (e.g., desired airline and car service). As each user interacts with their own assistant, assistant versions can be saved that track the specific desires of the user. Read [this how-to](../how-tos/assistant_versioning.md) to learn how you can use assistant versioning through both the [Studio](../how-tos/index.md/#langgraph-studio) and the SDK.
|
||||
|
||||
### Threads
|
||||
|
||||
A thread contains the accumulated state of a group of runs. If a run is executed on a thread, then the [state][state] of the underlying graph of the assistant will be persisted to the thread. A thread's current and historical state can be retrieved. To persist state, a thread must be created prior to executing a run.
|
||||
|
||||
The state of a thread at a particular point in time is called a checkpoint.
|
||||
|
||||
For more on threads and checkpoints, see this section of the [LangGraph conceptual guide](../../concepts/low_level.md#persistence).
|
||||
|
||||
The LangGraph Cloud API provides several endpoints for creating and managing threads and thread state. See the [API reference](../reference/api/api_ref.html#tag/threadscreate) for more details.
|
||||
|
||||
### Runs
|
||||
|
||||
A run is an invocation of an assistant. Each run may have its own input, configuration, and metadata, which may affect execution and output of the underlying graph. A run can optionally be executed on a thread.
|
||||
|
||||
The LangGraph Cloud API provides several endpoints for creating and managing runs. See the [API reference](../reference/api/api_ref.html#tag/runscreate) for more details.
|
||||
|
||||
### Cron Jobs
|
||||
|
||||
It's often useful to run graphs on some schedule. LangGraph Cloud supports cron jobs, which run on a user defined schedule. The user specifies a schedule, an assistant, and some input. After than, on the specified schedule LangGraph cloud will:
|
||||
|
||||
- Create a new thread with the specified assistant
|
||||
- Send the specified input to that thread
|
||||
|
||||
Note that this sends the same input to the thread every time. See the [how-to guide](../how-tos/cron_jobs.md) for creating cron jobs.
|
||||
|
||||
The LangGraph Cloud API provides several endpoints for creating and managing cron jobs. See the [API reference](../reference/api/api_ref.html#tag/runscreate/POST/threads/{thread_id}/runs/crons) for more details.
|
||||
|
||||
## Features
|
||||
|
||||
The LangGraph Cloud API offers several features to support complex agent architectures.
|
||||
|
||||
### Streaming
|
||||
|
||||
Streaming is critical for making LLM applications feel responsive to end users. When creating a streaming run, the streaming mode determines what data is streamed back to the API client. The LangGraph Cloud API supports five streaming modes.
|
||||
|
||||
- `values`: Stream the full state of the graph after each [super-step](https://langchain-ai.github.io/langgraph/concepts/low_level/#graphs) is executed. See the [how-to guide](../how-tos/stream_values.md) for streaming values.
|
||||
- `messages`: Stream complete messages (at the end of node execution) as well as tokens for any messages generated inside a node. This mode is primarily meant for powering chat applications. This is only an option if your graph contains a `messages` key. See the [how-to guide](../how-tos/stream_messages.md) for streaming messages.
|
||||
- `updates`: Streams updates to the state of the graph after each node is executed. See the [how-to guide](../how-tos/stream_updates.md) for streaming updates.
|
||||
- `events`: Stream all events (including the state of the graph) that occur during graph execution. See the [how-to guide](../how-tos/stream_events.md) for streaming events. This can be used to do token-by-token streaming for LLMs.
|
||||
- `debug`: Stream debug events throughout graph execution. See the [how-to guide](../how-tos/stream_debug.md) for streaming debug events.
|
||||
|
||||
You can also specify multiple streaming modes at the same time. See the [how-to guide](../how-tos/stream_multiple.md) for configuring multiple streaming modes at the same time.
|
||||
|
||||
See the [API reference](../reference/api/api_ref.html#tag/runscreate/POST/threads/{thread_id}/runs/stream) for how to create streaming runs.
|
||||
|
||||
Streaming modes `values`, `updates`, and `debug` are very similar to modes available in the LangGraph library - for a deeper conceptual explanation of those, you can see the LangGraph library documentation [here](../../concepts/low_level.md#streaming).
|
||||
|
||||
Streaming mode `events` is the same as using `.astream_events` in the LangGraph library - for a deeper conceptual explanation of this, you can see the LangGraph library documentation [here](../../concepts/low_level.md#streaming).
|
||||
|
||||
#### `mode="messages"`
|
||||
Streaming mode `messages` is a new streaming mode, currently only available in the API. What does this mode enable?
|
||||
|
||||
This mode is focused on streaming back messages. It currently assumes that you have a `messages` key in your graph that is a list of messages. Assuming we have a simple react agent deployed, what does this stream look like?
|
||||
|
||||
All events emitted have two attributes:
|
||||
|
||||
- `event`: This is the name of the event
|
||||
- `data`: This is data associated with the event
|
||||
|
||||
Let's run it on a question that should trigger a tool call:
|
||||
|
||||
```python
|
||||
thread = await client.threads.create()
|
||||
input = {"messages": [{"role": "user", "content": "what's the weather in sf?"}]}
|
||||
|
||||
events = []
|
||||
async for event in client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistant_id="agent", # This may need to change depending on the graph you deployed
|
||||
input=input,
|
||||
stream_mode="messages",
|
||||
):
|
||||
print(event.event)
|
||||
```
|
||||
```shell
|
||||
metadata
|
||||
messages/complete
|
||||
messages/metadata
|
||||
messages/partial
|
||||
...
|
||||
messages/partial
|
||||
messages/complete
|
||||
messages/complete
|
||||
messages/metadata
|
||||
messages/partial
|
||||
...
|
||||
messages/partial
|
||||
messages/complete
|
||||
end
|
||||
```
|
||||
|
||||
We first get some `metadata` - this is metadata about the run.
|
||||
|
||||
```python
|
||||
StreamPart(event='metadata', data={'run_id': '1ef657cf-ae55-6f65-97d4-f4ed1dbdabc6'})
|
||||
```
|
||||
|
||||
We then get a `messages/complete` event - this a fully formed message getting emitted. In this case,
|
||||
this was the just the input message we sent in.
|
||||
|
||||
```python
|
||||
StreamPart(event='messages/complete', data=[{'content': 'hi!', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'human', 'name': None, 'id': '833c09a3-bb19-46c9-81d9-1e5954ec5f92', 'example': False}])
|
||||
```
|
||||
|
||||
We then get a `messages/metadata` - this is just letting us know that a new message is starting.
|
||||
|
||||
```python
|
||||
StreamPart(event='messages/metadata', data={'run-985c0f14-9f43-40d4-a505-4637fc58e333': {'metadata': {'created_by': 'system', 'run_id': '1ef657de-7594-66df-8eb2-31518e4a1ee2', 'graph_id': 'agent', 'thread_id': 'c178eab5-e293-423c-8e7d-1d113ffe7cd9', 'model_name': 'openai', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.0}}})
|
||||
```
|
||||
|
||||
We then get a BUNCH of `messages/partial` events - these are the individual tokens from the LLM! In the case below, we can see the START of a tool call.
|
||||
|
||||
```python
|
||||
StreamPart(event='messages/partial', data=[{'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_w8Hr8dHGuZCPgRfd5FqRBArs', 'function': {'arguments': '', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-985c0f14-9f43-40d4-a505-4637fc58e333', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [{'name': 'tavily_search_results_json', 'args': '', 'id': 'call_w8Hr8dHGuZCPgRfd5FqRBArs', 'error': None}], 'usage_metadata': None}])
|
||||
```
|
||||
|
||||
After that, we get a `messages/complete` event - this is the AIMessage finishing. It's now a complete tool call:
|
||||
|
||||
```python
|
||||
StreamPart(event='messages/complete', data=[{'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_w8Hr8dHGuZCPgRfd5FqRBArs', 'function': {'arguments': '{"query":"current weather in San Francisco"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'type': 'ai', 'name': None, 'id': 'run-985c0f14-9f43-40d4-a505-4637fc58e333', 'example': False, 'tool_calls': [{'name': 'tavily_search_results_json', 'args': {'query': 'current weather in San Francisco'}, 'id': 'call_w8Hr8dHGuZCPgRfd5FqRBArs'}], 'invalid_tool_calls': [], 'usage_metadata': None}])
|
||||
```
|
||||
|
||||
After that, we get ANOTHER `messages/complete` event. This is a tool message - our agent has called a tool, gotten a response, and now inserting it into the state in the form of a tool message.
|
||||
|
||||
```python
|
||||
StreamPart(event='messages/complete', data=[{'content': '[{"url": "https://www.weatherapi.com/", "content": "{\'location\': {\'name\': \'San Francisco\', \'region\': \'California\', \'country\': \'United States of America\', \'lat\': 37.78, \'lon\': -122.42, \'tz_id\': \'America/Los_Angeles\', \'localtime_epoch\': 1724877689, \'localtime\': \'2024-08-28 13:41\'}, \'current\': {\'last_updated_epoch\': 1724877000, \'last_updated\': \'2024-08-28 13:30\', \'temp_c\': 23.3, \'temp_f\': 73.9, \'is_day\': 1, \'condition\': {\'text\': \'Partly cloudy\', \'icon\': \'//cdn.weatherapi.com/weather/64x64/day/116.png\', \'code\': 1003}, \'wind_mph\': 15.0, \'wind_kph\': 24.1, \'wind_degree\': 310, \'wind_dir\': \'NW\', \'pressure_mb\': 1014.0, \'pressure_in\': 29.93, \'precip_mm\': 0.0, \'precip_in\': 0.0, \'humidity\': 57, \'cloud\': 25, \'feelslike_c\': 25.0, \'feelslike_f\': 77.1, \'windchill_c\': 20.9, \'windchill_f\': 69.6, \'heatindex_c\': 23.3, \'heatindex_f\': 74.0, \'dewpoint_c\': 12.9, \'dewpoint_f\': 55.2, \'vis_km\': 16.0, \'vis_miles\': 9.0, \'uv\': 6.0, \'gust_mph\': 19.5, \'gust_kph\': 31.3}}"}]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'tavily_search_results_json', 'id': '0112eba5-7660-4375-9f24-c7a1d6777b97', 'tool_call_id': 'call_w8Hr8dHGuZCPgRfd5FqRBArs'}])
|
||||
```
|
||||
|
||||
After that, we see the agent doing another LLM call and streaming back a response. We then get an `end` event:
|
||||
|
||||
```python
|
||||
StreamPart(event='end', data=None)
|
||||
```
|
||||
|
||||
And that's it! This is more focused streaming mode specifically focused on streaming back messages. See this [how-to guide](../how-tos/stream_messages.md) for more information.
|
||||
|
||||
|
||||
### Human-in-the-Loop
|
||||
|
||||
There are many occasions where the graph cannot run completely autonomously. For instance, the user might need to input some additional arguments to a function call, or select the next edge for the graph to continue on. In these instances, we need to insert some human in the loop interaction, which you can learn about in the [human in the loop how-tos](../how-tos/index.md#human-in-the-loop).
|
||||
|
||||
### Double Texting
|
||||
|
||||
Many times users might interact with your graph in unintended ways. For instance, a user may send one message and before the graph has finished running send a second message. To solve this issue of "double-texting" (i.e. prompting the graph a second time before the first run has finished), LangGraph has provided four different solutions, all of which are covered in the [Double Texting how-tos](../how-tos/index.md#double-texting). These options are:
|
||||
|
||||
- `reject`: This is the simplest option, this just rejects any follow up runs and does not allow double texting. See the [how-to guide](../how-tos/reject_concurrent.md) for configuring the reject double text option.
|
||||
- `enqueue`: This is a relatively simple option which continues the first run until it completes the whole run, then sends the new input as a separate run. See the [how-to guide](../how-tos/enqueue_concurrent.md) for configuring the enqueue double text option.
|
||||
- `interrupt`: This option interrupts the current execution but saves all the work done up until that point. It then inserts the user input and continues from there. If you enable this option, your graph should be able to handle weird edge cases that may arise. See the [how-to guide](../how-tos/interrupt_concurrent.md) for configuring the interrupt double text option.
|
||||
- `rollback`: This option rolls back all work done up until that point. It then sends the user input in, basically as if it just followed the original run input. See the [how-to guide](../how-tos/rollback_concurrent.md) for configuring the rollback double text option.
|
||||
|
||||
### Stateless Runs
|
||||
|
||||
All runs use the built-in checkpointer to store checkpoints for runs. However, it can often be useful to just kick off a run without worrying about explicitly creating a thread and without wanting to keep those checkpointers around. Stateless runs allow you to do this by exposing an endpoint that:
|
||||
|
||||
- Takes in user input
|
||||
- Under the hood, creates a thread
|
||||
- Runs the agent but skips all checkpointing steps
|
||||
- Cleans up the thread afterwards
|
||||
|
||||
Stateless runs are still retried as regular retries are per node, while everything still in memory, so doesn't use checkpoints.
|
||||
|
||||
The only difference is in stateless background runs, if the task worker dies halfway (not because the run itself failed, for some external reason) then the whole run will be retried like any background run, but
|
||||
|
||||
- whereas a stateful background run would retry from the last successful checkpoint
|
||||
- a stateless background run would retry from the beginning
|
||||
|
||||
See the [how-to guide](../how-tos/stateless_runs.md) for creating stateless runs.
|
||||
|
||||
### Webhooks
|
||||
|
||||
For all types of runs, langgraph cloud supports completion webhooks. When you create the run you can pass a webhook URL to be called when the completes (successfully or not). This is especially useful for background runs and cron jobs, as the webhook can give you an indication the run has completed and you can perform further actions for your appilcation.
|
||||
|
||||
See this [how-to guide](../how-tos/webhooks.md) to learn about how to use webhooks with LangGraph Cloud.
|
||||
|
||||
## Deployment
|
||||
|
||||
The LangGraph Cloud offers several features to support secure and robost deployments.
|
||||
|
||||
### Authentication
|
||||
|
||||
LangGraph applications deployed to LangGraph Cloud are automatically configured with LangSmith authentication. In order to call the API, a valid <a href="https://docs.smith.langchain.com/how_to_guides/setup/create_account_api_key#api-keys" target="_blank">LangSmith API key</a> is required.
|
||||
|
||||
### Local Testing
|
||||
|
||||
Before deploying your app in production to LangGraph Cloud, you may wish to test out your graph locally in order to ensure that everything is running as expected. Luckily, LangGraph makes this easy for you through use of the LangGraph CLI. Read more in this [how-to guide](../deployment/test_locally.md) or look at the [CLI reference](../reference/cli.md) to learn more.
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 257 KiB |
@@ -1,28 +0,0 @@
|
||||
# Cloud Concepts
|
||||
|
||||
This page describes the high-level concepts of the LangGraph Cloud deployment.
|
||||
|
||||
## Deployment
|
||||
|
||||
A deployment is an instance of a LangGraph API. A single deployment can have many [revisions](#revision). When a deployment is created, all of the necessary infrastructure (e.g. database, containers, secrets store) are automatically provisioned. See the [architecture diagram](#architecture) below for more details.
|
||||
|
||||
See the [how-to guide](../deployment/cloud.md#create-new-deployment) for creating a new deployment.
|
||||
|
||||
## Revision
|
||||
|
||||
A revision is an iteration of a [deployment](#deployment). When a new deployment is created, an initial revision is automatically created. To deploy new code changes or update environment variable configurations for a deployment, a new revision must be created. When a revision is created, a new container image is built automatically.
|
||||
|
||||
See the [how-to guide](../deployment/cloud.md#create-new-revision) for creating a new revision.
|
||||
|
||||
## Asynchronous Deployment
|
||||
|
||||
Infrastructure for [deployments](#deployment) and [revisions](#revision) are provisioned and deployed asynchronously. They are not deployed immediately after submission. Currently, deployment can take up to several minutes.
|
||||
|
||||
## Architecture
|
||||
|
||||
!!! warning "Subject to Change"
|
||||
The LangGraph Cloud deployment architecture may change in the future.
|
||||
|
||||
A high-level diagram of a LangGraph Cloud deployment.
|
||||
|
||||

|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 157 KiB |
@@ -13,7 +13,7 @@ Install the proper packages:
|
||||
```bash
|
||||
pip install -U langgraph-cli
|
||||
```
|
||||
=== "Homebrew" (macOS only)
|
||||
=== "Homebrew (macOS only)"
|
||||
```bash
|
||||
brew install langgraph-cli
|
||||
```
|
||||
@@ -189,4 +189,4 @@ Now we can invoke our graph to ensure it is working. Make sure to change the inp
|
||||
'
|
||||
```
|
||||
|
||||
If your graph works correctly, you should see your graph output displayed in the console. Of course, there are many more ways you might need to test your graph, for a full list of commands you can send with the SDK, see the [Python](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/) and [JS/TS](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/) references.
|
||||
If your graph works correctly, you should see your graph output displayed in the console. Of course, there are many more ways you might need to test your graph, for a full list of commands you can send with the SDK, see the [Python](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/) and [JS/TS](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/) references.
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
# Studio FAQs
|
||||
|
||||
## Why is my project failing to start?
|
||||
|
||||
There are a few reasons that your project might fail to start, here are some of the most common ones.
|
||||
|
||||
### Docker issues
|
||||
|
||||
LangGraph Studio requires Docker Desktop version 4.24 or higher. Please make sure you have a version of Docker installed that satisfies that requirement and also make sure you have the Docker Desktop app up and running before trying to use LangGraph Studio. In addition, make sure you have docker-compose updated to version 2.22.0 or higher.
|
||||
|
||||
### Configuration or environment issues
|
||||
|
||||
Another reason your project might fail to start is because your configuration file is defined incorrectly, or you are missing required environment variables.
|
||||
|
||||
## How does interrupt work?
|
||||
|
||||
When you select the `Interrupts` dropdown and select a node to interrupt the graph will pause execution before and after (unless the node goes straight to `END`) that node has run. This means that you will be able to both edit the state before the node is ran and the state after the node has ran. This is intended to allow developers more fine-grained control over the behavior of a node and make it easier to observe how the node is behaving. You will not be able to edit the state after the node has ran if the node is the final node in the graph.
|
||||
|
||||
## How do I reload the app?
|
||||
|
||||
If you would like to reload the app, don't use Command+R as you might normally do. Instead, close and reopen the app for a full refresh.
|
||||
|
||||
## How does automatic rebuilding work?
|
||||
|
||||
One of the key features of LangGraph Studio is that it automatically rebuilds your image when you change the source code. This allows for a super fast development and testing cycle which makes it easy to iterate on your graph. There are two different ways that LangGraph rebuilds your image: either by editing the image or completely rebuilding it.
|
||||
|
||||
### Rebuilds from source code changes
|
||||
|
||||
If you modified the source code only (no configuration or dependency changes!) then the image does not require a full rebuild, and LangGraph Studio will only update the relevant parts. The UI status in the bottom left will switch from `Online` to `Stopping` temporarily while the image gets edited. The logs will be shown as this process is happening, and after the image has been edited the status will change back to `Online` and you will be able to run your graph with the modified code!
|
||||
|
||||
|
||||
### Rebuilds from configuration or dependency changes
|
||||
|
||||
If you edit your graph configuration file (`langgraph.json`) or the dependencies (either `pyproject.toml` or `requirements.txt`) then the entire image will be rebuilt. This will cause the UI to switch away from the graph view and start showing the logs of the new image building process. This can take a minute or two, and once it is done your updated image will be ready to use!
|
||||
|
||||
## Why is my graph taking so long to startup?
|
||||
|
||||
The LangGraph Studio interacts with a local LangGraph API server. To stay aligned with ongoing updates, the LangGraph API requires regular rebuilding. As a result, you may occasionally experience slight delays when starting up your project.
|
||||
|
||||
## Why are extra edges showing up in my graph?
|
||||
|
||||
If you don't define your conditional edges carefully, you might notice extra edges appearing in your graph. This is because without proper definition, LangGraph Studio assumes the conditional edge could access all other nodes. In order for this to not be the case, you need to be explicit about how you define the nodes the conditional edge routes to. There are two ways you can do this:
|
||||
|
||||
### Solution 1: Include a path map
|
||||
|
||||
The first way to solve this is to add path maps to your conditional edges. A path map is just a dictionary or array that maps the possible outputs of your router function with the names of the nodes that each output corresponds to. The path map is passed as the third argument to the `add_conditional_edges` function like so:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
graph.add_conditional_edges("node_a", routing_function, {True: "node_b", False: "node_c"})
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```ts
|
||||
graph.addConditionalEdges("node_a", routingFunction, { true: "node_b", false: "node_c" });
|
||||
```
|
||||
|
||||
In this case, the routing function returns either True or False, which map to `node_b` and `node_c` respectively.
|
||||
|
||||
### Solution 2: Update the typing of the router (Python only)
|
||||
|
||||
Instead of passing a path map, you can also be explicit about the typing of your routing function by specifying the nodes it can map to using the `Literal` python definition. Here is an example of how to define a routing function in that way:
|
||||
|
||||
```python
|
||||
def routing_function(state: GraphState) -> Literal["node_b","node_c"]:
|
||||
if state['some_condition'] == True:
|
||||
return "node_b"
|
||||
else:
|
||||
return "node_c"
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# How to version assistants
|
||||
|
||||
In this how-to guide we will walk through how you can create and manage different assistant versions. If you haven't already, you can read [this](../concepts/api.md/#versioning-assistants) conceptual guide to gain a better understanding of what assistant versioning is. This how-to assumes you have a graph that is configurable, which means you have defined a config schema and passed it to your graph as follows:
|
||||
In this how-to guide we will walk through how you can create and manage different assistant versions. If you haven't already, you can read [this](../../concepts/assistants.md#versioning-assistants) conceptual guide to gain a better understanding of what assistant versioning is. This how-to assumes you have a graph that is configurable, which means you have defined a config schema and passed it to your graph as follows:
|
||||
|
||||
=== "Python"
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ You may wish to copy (i.e. "fork") an existing thread in order to keep the exist
|
||||
|
||||
## Setup
|
||||
|
||||
This code assumes you already have a thread to copy. You can read about what a thread is [here](https://langchain-ai.github.io/langgraph/cloud/concepts/api/#threads) and learn how to stream a run on a thread in [these how-to guides](https://langchain-ai.github.io/langgraph/cloud/how-tos/#streaming).
|
||||
This code assumes you already have a thread to copy. You can read about what a thread is [here](../../concepts/langgraph_server.md#threads) and learn how to stream a run on a thread in [these how-to guides](../../how-tos/index.md#streaming_1).
|
||||
|
||||
### SDK initialization
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Enqueue
|
||||
|
||||
This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](../concepts/api.md#double-texting).
|
||||
This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](../../concepts/double_texting.md).
|
||||
|
||||
The guide covers the `enqueue` option for double texting, which adds the interruptions to a queue and executes them in the order they are received by the client. Below is a quick example of using the `enqueue` option.
|
||||
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
---
|
||||
hide:
|
||||
- toc
|
||||
---
|
||||
|
||||
# How-to Guides
|
||||
|
||||
Welcome to the LangGraph Cloud how-to guides! These guides provide practical, step-by-step instructions for accomplishing key tasks in LangGraph Cloud.
|
||||
|
||||
## Setup
|
||||
|
||||
LangGraph Cloud gives you best in class observability, testing, and hosting services. Learn how to setup your app for deployment to LangGraph Cloud in these how-to guides
|
||||
|
||||
- [How to set up app for deployment (requirements.txt)](../deployment/setup.md)
|
||||
- [How to set up app for deployment (pyproject.toml)](../deployment/setup_pyproject.md)
|
||||
- [How to set up app for deployment (JavaScript)](../deployment/setup_javascript.md)
|
||||
- [How to customize Dockerfile](../deployment/custom_docker.md)
|
||||
- [How to test locally](../deployment/test_locally.md)
|
||||
|
||||
## Deploy
|
||||
|
||||
Learn how to deploy your app to LangGraph Cloud in these how to guides:
|
||||
|
||||
- [How to deploy to LangGraph cloud](../deployment/cloud.md)
|
||||
- [How to interact with the deployment using RemoteGraph](../../how-tos/use-remote-graph.md)
|
||||
|
||||
|
||||
## Streaming
|
||||
|
||||
Streaming the results of your LLM application is vital for ensuring a good user experience, especially when your graph may call multiple models and take a long time to fully complete a run. Read about how to stream values from your graph in these how to guides:
|
||||
|
||||
- [How to stream values](./stream_values.md)
|
||||
- [How to stream updates](./stream_updates.md)
|
||||
- [How to stream messages](./stream_messages.md)
|
||||
- [How to stream events](./stream_events.md)
|
||||
- [How to stream in debug mode](./stream_debug.md)
|
||||
- [How to stream multiple modes](./stream_multiple.md)
|
||||
|
||||
## Double-texting
|
||||
|
||||
Graph execution can take a while, and sometimes users may change their mind about the input they wanted to send before their original input has finished running. For example, a user might notice a typo in their original request and will edit the prompt and resend it. Deciding what to do in these cases is important for ensuring a smooth user experience and preventing your graphs from behaving in unexpected ways. The following how-to guides provide information on the various options LangGraph Cloud gives you for dealing with double-texting:
|
||||
|
||||
- [How to use the interrupt option](./interrupt_concurrent.md)
|
||||
- [How to use the rollback option](./rollback_concurrent.md)
|
||||
- [How to use the reject option](./reject_concurrent.md)
|
||||
- [How to use the enqueue option](./enqueue_concurrent.md)
|
||||
|
||||
## Human-in-the-loop
|
||||
|
||||
When creating complex graphs, leaving every decision up to the LLM can be dangerous, especially when the decisions involve invoking certain tools or accessing specific documents. To remedy this, LangGraph allows you to insert human-in-the-loop behavior to ensure your graph does not have undesired outcomes. Read more about the different ways you can add human-in-the-loop capabilities to your LangGraph Cloud projects in these how-to guides:
|
||||
|
||||
- [How to add a breakpoint](./human_in_the_loop_breakpoint.md)
|
||||
- [How to wait for user input](./human_in_the_loop_user_input.md)
|
||||
- [How to edit graph state](./human_in_the_loop_edit_state.md)
|
||||
- [How to replay and branch from prior states](./human_in_the_loop_time_travel.md)
|
||||
- [How to review tool calls](./human_in_the_loop_review_tool_calls.md)
|
||||
|
||||
## LangGraph Studio
|
||||
|
||||
LangGraph Studio is a built-in UI for visualizing, testing, and debugging your agents.
|
||||
|
||||
- [How to enter LangGraph Studio](./test_deployment.md)
|
||||
- [How to enter LangGraph Studio for local deployment](./test_local_deployment.md)
|
||||
- [How to test your graph in LangGraph Studio](./invoke_studio.md)
|
||||
- [Interact with threads in LangGraph Studio](./threads_studio.md)
|
||||
|
||||
## Different Types of Runs:
|
||||
|
||||
LangGraph Cloud supports multiple types of runs besides streaming runs.
|
||||
|
||||
- [How to run an agent in the background](./background_run.md)
|
||||
- [How to run multiple agents in the same thread](./same-thread.md)
|
||||
- [How to create cron jobs](./cron_jobs.md)
|
||||
- [How to create stateless runs](./stateless_runs.md)
|
||||
|
||||
## Other
|
||||
|
||||
Other guides that may prove helpful!
|
||||
|
||||
- [How to configure agents](./configuration_cloud.md)
|
||||
- [How to version assistants](./assistant_versioning.md)
|
||||
- [How to convert LangGraph calls to LangGraph cloud calls](./langgraph_to_langgraph_cloud.ipynb)
|
||||
- [How to integrate webhooks](./webhooks.md)
|
||||
- [How to copy threads](./copy_threads.md)
|
||||
- [How to check status of your threads](./check_thread_status.md)
|
||||
@@ -1,6 +1,6 @@
|
||||
# Interrupt
|
||||
|
||||
This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](../concepts/api.md#double-texting).
|
||||
This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](../../concepts/double_texting.md).
|
||||
|
||||
The guide covers the `interrupt` option for double texting, which interrupts the prior run of the graph and starts a new one with the double-text. This option does not delete the first run, but rather keeps it in the database but sets its status to `interrupted`. Below is a quick example of using the `interrupt` option.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Reject
|
||||
|
||||
This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide][double-texting].
|
||||
This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](../../concepts/double_texting.md).
|
||||
|
||||
The guide covers the `reject` option for double texting, which rejects the new run of the graph by throwing an error and continues with the original run until completion. Below is a quick example of using the `reject` option.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Rollback
|
||||
|
||||
This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide][double-texting].
|
||||
This guide assumes knowledge of what double-texting is, which you can learn about in the [double-texting conceptual guide](../../concepts/double_texting.md).
|
||||
|
||||
The guide covers the `rollback` option for double texting, which interrupts the prior run of the graph and starts a new one with the double-text. This option is very similar to the `interrupt` option, but in this case the first run is completely deleted from the database and cannot be restarted. Below is a quick example of using the `rollback` option.
|
||||
|
||||
|
||||
@@ -292,131 +292,4 @@ Output:
|
||||
|
||||
|
||||
Receiving new event of type: end...
|
||||
None
|
||||
|
||||
|
||||
## Token-by-Token Streaming
|
||||
|
||||
Token-by-token streaming can be implemented with the `events` streaming mode. The `on_chat_model_stream` event type should be processed to stream LLM responses token-by-token.
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
llm_response = ""
|
||||
|
||||
# stream token-by-token
|
||||
async for chunk in client.runs.stream(
|
||||
thread_id=thread["thread_id"],
|
||||
assistant_id=assistant_id,
|
||||
input=input,
|
||||
stream_mode="events",
|
||||
):
|
||||
if (
|
||||
chunk.event == "events" and
|
||||
chunk.data["event"] == "on_chat_model_stream" and
|
||||
len(chunk.data["data"]["chunk"]["content"]) > 0 and
|
||||
'text' in chunk.data["data"]["chunk"]["content"][0]
|
||||
):
|
||||
llm_response += chunk.data["data"]["chunk"]["content"][0]['text']
|
||||
print(llm_response)
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
const llmResponse = "";
|
||||
// stream events
|
||||
const streamResponse = client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistantID,
|
||||
{
|
||||
input,
|
||||
streamMode: "events"
|
||||
}
|
||||
);
|
||||
for await (const chunk of streamResponse) {
|
||||
if (chunk.event === "events" && chunk.data.event === "on_chat_model_stream" && chunk.data.chunk.content.length > 0 && 'text' in chunk.data.chunk.content[0]) {
|
||||
llmResponse += chunk.data.data.chunk.content[0].text;
|
||||
console.log(llmResponse);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\",
|
||||
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"What's the weather in sf\"}]},
|
||||
\"stream_mode\": [
|
||||
\"events\"
|
||||
]
|
||||
}" | sed 's/\r$//' | awk '
|
||||
/^event:/ { event = $2 }
|
||||
/^data:/ {
|
||||
json_data = substr($0, index($0, $2))
|
||||
|
||||
if (event == "events") {
|
||||
print json_data
|
||||
}
|
||||
}' | jq -r '
|
||||
select(.event == "on_chat_model_stream") |
|
||||
.data.chunk.content[] | .text // empty
|
||||
' | awk '
|
||||
BEGIN { llm_response="" }
|
||||
$0 != "" && $0 != "null" {
|
||||
llm_response = llm_response $0
|
||||
print llm_response
|
||||
}'
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
The
|
||||
The search
|
||||
The search results provide
|
||||
The search results provide the current weather conditions
|
||||
The search results provide the current weather conditions in San Francisco.
|
||||
The search results provide the current weather conditions in San Francisco. According
|
||||
The search results provide the current weather conditions in San Francisco. According to the data,
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12,
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024,
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C).
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The win
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is bl
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 k
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph).
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70%
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles (10 km
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles (10 km).
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles (10 km). Overall
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles (10 km). Overall, it appears
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles (10 km). Overall, it appears to be a nice
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles (10 km). Overall, it appears to be a nice sunny day in San
|
||||
The search results provide the current weather conditions in San Francisco. According to the data, as of 3:19 PM on August 12, 2024, the weather in San Francisco is sunny with a temperature of 60.8°F (16°C). The wind is blowing from the west-southwest at 13.4 mph (21.6 kph). The humidity is 70% and visibility is 6 miles (10 km). Overall, it appears to be a nice sunny day in San Francisco.
|
||||
|
||||
|
||||
None
|
||||
@@ -3,9 +3,7 @@
|
||||
!!! info "Prerequisites"
|
||||
* [Streaming](../../concepts/streaming.md)
|
||||
|
||||
This guide covers how to stream messages from your graph. With `stream_mode="messages"`, messages from any chat model invocations inside your graph nodes will be streamed back.
|
||||
|
||||
Read more about how the `messages` streaming mode works [here](https://langchain-ai.github.io/langgraph/cloud/concepts/api/#modemessages)
|
||||
This guide covers how to stream messages from your graph. With `stream_mode="messages-tuple"`, messages (i.e. individual LLM tokens) from any chat model invocations inside your graph nodes will be streamed back.
|
||||
|
||||
## Setup
|
||||
|
||||
@@ -60,7 +58,7 @@ Output:
|
||||
|
||||
## Stream graph in messages mode
|
||||
|
||||
Now we can stream by messages, which will return complete messages (at the end of node execution) as well as tokens for any messages generated inside a node:
|
||||
Now we can stream LLM tokens for any messages generated inside a node in the form of tuples `(message, metadata)`. Metadata contains additional information that can be useful for filtering the streamed outputs to a specific node or LLM.
|
||||
|
||||
=== "Python"
|
||||
|
||||
@@ -73,7 +71,7 @@ Now we can stream by messages, which will return complete messages (at the end o
|
||||
assistant_id=assistant_id,
|
||||
input=input,
|
||||
config=config,
|
||||
stream_mode="messages",
|
||||
stream_mode="messages-tuple",
|
||||
):
|
||||
print(f"Receiving new event of type: {chunk.event}...")
|
||||
print(chunk.data)
|
||||
@@ -99,7 +97,7 @@ Now we can stream by messages, which will return complete messages (at the end o
|
||||
{
|
||||
input,
|
||||
config,
|
||||
streamMode: "messages"
|
||||
streamMode: "messages-tuple"
|
||||
}
|
||||
);
|
||||
for await (const chunk of streamResponse) {
|
||||
@@ -119,7 +117,7 @@ Now we can stream by messages, which will return complete messages (at the end o
|
||||
\"assistant_id\": \"agent\",
|
||||
\"input\": {\"messages\": [{\"role\": \"human\", \"content\": \"what's the weather in la\"}]},
|
||||
\"stream_mode\": [
|
||||
\"messages\"
|
||||
\"messages-tuple\"
|
||||
]
|
||||
}" | \
|
||||
sed 's/\r$//' | \
|
||||
@@ -150,117 +148,101 @@ Output:
|
||||
Receiving new event of type: metadata...
|
||||
{"run_id": "1ef971e0-9a84-6154-9047-247b4ce89c4d", "attempt": 1}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: messages/metadata...
|
||||
{
|
||||
"run-700157a5-df1a-4829-9e7c-1e07a1d934f7": {
|
||||
"metadata": {
|
||||
"graph_id": "agent",
|
||||
"langgraph_node": "agent",
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
Receiving new event of type: messages/partial...
|
||||
Receiving new event of type: messages...
|
||||
[
|
||||
{
|
||||
"type": "AIMessageChunk",
|
||||
"tool_calls": [
|
||||
{
|
||||
"name": "tavily_search_results_json",
|
||||
"args": {
|
||||
"query": "weather"
|
||||
"query": "weat"
|
||||
},
|
||||
"id": "toolu_01RJGmVJtTxccoHHixGkGqaC",
|
||||
"type": "tool_call"
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: messages/partial...
|
||||
[
|
||||
{
|
||||
"type": "ai",
|
||||
"tool_calls": [
|
||||
{
|
||||
"name": "tavily_search_results_json",
|
||||
"args": {
|
||||
"query": "weather in "
|
||||
},
|
||||
"id": "toolu_01RJGmVJtTxccoHHixGkGqaC",
|
||||
"id": "toolu_0114XKXdNtHQEa3ozmY1uDdM",
|
||||
"type": "tool_call"
|
||||
}
|
||||
],
|
||||
...
|
||||
}
|
||||
]
|
||||
|
||||
...
|
||||
|
||||
Receiving new event of type: messages/partial...
|
||||
[
|
||||
},
|
||||
{
|
||||
"type": "ai",
|
||||
"tool_calls": [
|
||||
{
|
||||
"name": "tavily_search_results_json",
|
||||
"args": {
|
||||
"query": "weather in san francisco"
|
||||
},
|
||||
"id": "toolu_01RJGmVJtTxccoHHixGkGqaC",
|
||||
"type": "tool_call"
|
||||
}
|
||||
],
|
||||
"graph_id": "agent",
|
||||
"langgraph_node": "agent",
|
||||
...
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: messages/metadata...
|
||||
{
|
||||
"aa162b98-433d-4e3c-b204-0d41a6694156": {
|
||||
"metadata": {
|
||||
"graph_id": "agent",
|
||||
"langgraph_node": "action",
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: messages/complete...
|
||||
Receiving new event of type: messages...
|
||||
[
|
||||
{
|
||||
"content": "[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{'location': {'name': 'San Francisco', 'region': 'California', 'country': 'United States of America', 'lat': 37.775, 'lon': -122.4183, 'tz_id': 'America/Los_Angeles', 'localtime_epoch': 1730334046, 'localtime': '2024-10-30 17:20'}, 'current': {'last_updated_epoch': 1730333700, 'last_updated': '2024-10-30 17:15', 'temp_c': 12.3, 'temp_f': 54.2, 'is_day': 1, 'condition': {'text': 'Partly Cloudy', 'icon': '//cdn.weatherapi.com/weather/64x64/day/116.png', 'code': 1003}, 'wind_mph': 9.6, 'wind_kph': 15.5, 'wind_degree': 238, 'wind_dir': 'WSW', 'pressure_mb': 1021.0, 'pressure_in': 30.15, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 93, 'cloud': 57, 'feelslike_c': 11.2, 'feelslike_f': 52.2, 'windchill_c': 11.2, 'windchill_f': 52.2, 'heatindex_c': 12.3, 'heatindex_f': 54.2, 'dewpoint_c': 11.2, 'dewpoint_f': 52.1, 'vis_km': 10.0, 'vis_miles': 6.0, 'uv': 0.5, 'gust_mph': 12.9, 'gust_kph': 20.8}}\"}]",
|
||||
"type": "AIMessageChunk",
|
||||
"tool_calls": [
|
||||
{
|
||||
"name": "tavily_search_results_json",
|
||||
"args": {
|
||||
"query": "her in san "
|
||||
},
|
||||
"id": "toolu_0114XKXdNtHQEa3ozmY1uDdM",
|
||||
"type": "tool_call"
|
||||
}
|
||||
],
|
||||
...
|
||||
},
|
||||
{
|
||||
"graph_id": "agent",
|
||||
"langgraph_node": "agent",
|
||||
...
|
||||
}
|
||||
]
|
||||
|
||||
...
|
||||
|
||||
Receiving new event of type: messages...
|
||||
[
|
||||
{
|
||||
"type": "AIMessageChunk",
|
||||
"tool_calls": [
|
||||
{
|
||||
"name": "tavily_search_results_json",
|
||||
"args": {
|
||||
"query": "francisco"
|
||||
},
|
||||
"id": "toolu_0114XKXdNtHQEa3ozmY1uDdM",
|
||||
"type": "tool_call"
|
||||
}
|
||||
],
|
||||
...
|
||||
},
|
||||
{
|
||||
"graph_id": "agent",
|
||||
"langgraph_node": "agent",
|
||||
...
|
||||
}
|
||||
]
|
||||
|
||||
...
|
||||
|
||||
Receiving new event of type: messages...
|
||||
[
|
||||
{
|
||||
"content": "[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{'location': {'name': 'San Francisco', 'region': 'California', 'country': 'United States of America', 'lat': 37.775, 'lon': -122.4183, 'tz_id': 'America/Los_Angeles', 'localtime_epoch': 1730475777, 'localtime': '2024-11-01 08:42'}, 'current': {'last_updated_epoch': 1730475000, 'last_updated': '2024-11-01 08:30', 'temp_c': 11.1, 'temp_f': 52.0, 'is_day': 1, 'condition': {'text': 'Partly cloudy', 'icon': '//cdn.weatherapi.com/weather/64x64/day/116.png', 'code': 1003}, 'wind_mph': 2.2, 'wind_kph': 3.6, 'wind_degree': 192, 'wind_dir': 'SSW', 'pressure_mb': 1018.0, 'pressure_in': 30.07, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 89, 'cloud': 75, 'feelslike_c': 11.5, 'feelslike_f': 52.6, 'windchill_c': 10.0, 'windchill_f': 50.1, 'heatindex_c': 10.4, 'heatindex_f': 50.7, 'dewpoint_c': 9.1, 'dewpoint_f': 48.5, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 3.0, 'gust_mph': 6.7, 'gust_kph': 10.8}}\"}]",
|
||||
"type": "tool",
|
||||
"name": "tavily_search_results_json",
|
||||
"tool_call_id": "toolu_01RJGmVJtTxccoHHixGkGqaC",
|
||||
"tool_call_id": "toolu_0114XKXdNtHQEa3ozmY1uDdM",
|
||||
...
|
||||
},
|
||||
{
|
||||
"graph_id": "agent",
|
||||
"langgraph_node": "action",
|
||||
...
|
||||
}
|
||||
]
|
||||
|
||||
...
|
||||
|
||||
Receiving new event of type: messages/metadata...
|
||||
{
|
||||
"run-f92646d2-6b13-4648-90c7-0280766bfaf2": {
|
||||
"metadata": {
|
||||
"graph_id": "agent",
|
||||
"langgraph_node": "agent",
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: messages/partial...
|
||||
Receiving new event of type: messages...
|
||||
[
|
||||
{
|
||||
"content": [
|
||||
@@ -270,41 +252,80 @@ Output:
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
"type": "ai",
|
||||
"type": "AIMessageChunk",
|
||||
...
|
||||
},
|
||||
{
|
||||
"graph_id": "agent",
|
||||
"langgraph_node": "agent",
|
||||
...
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: messages/partial...
|
||||
Receiving new event of type: messages...
|
||||
[
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"text": "\n\nThe search results provide",
|
||||
"text": " results provide",
|
||||
"type": "text",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
"type": "ai",
|
||||
"type": "AIMessageChunk",
|
||||
...
|
||||
},
|
||||
{
|
||||
"graph_id": "agent",
|
||||
"langgraph_node": "agent",
|
||||
...
|
||||
}
|
||||
]
|
||||
|
||||
...
|
||||
|
||||
Receiving new event of type: messages/partial...
|
||||
|
||||
Receiving new event of type: messages...
|
||||
[
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"text": "\n\nThe search results provide the current weather conditions in San Francisco. According to the data, as of 5:20pm on October 30, 2024, the weather in San Francisco is partly cloudy with a temperature of 54\\u00b0F (12\\u00b0C). The wind is blowing from the west-southwest at around 10 mph (15 km/h). The humidity is high at 93% and visibility is 6 miles (10 km). Overall, it seems to be a cool, partly cloudy day with moderate winds in San Francisco.",
|
||||
"text": " the current weather conditions",
|
||||
"type": "text",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
"type": "ai",
|
||||
"type": "AIMessageChunk",
|
||||
...
|
||||
},
|
||||
{
|
||||
"graph_id": "agent",
|
||||
"langgraph_node": "agent",
|
||||
...
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
|
||||
Receiving new event of type: messages...
|
||||
[
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"text": " in San Francisco.",
|
||||
"type": "text",
|
||||
"index": 0
|
||||
}
|
||||
],
|
||||
"type": "AIMessageChunk",
|
||||
...
|
||||
},
|
||||
{
|
||||
"graph_id": "agent",
|
||||
"langgraph_node": "agent",
|
||||
...
|
||||
}
|
||||
]
|
||||
|
||||
...
|
||||
@@ -76,7 +76,9 @@ Output:
|
||||
|
||||
## Use graph with a webhook
|
||||
|
||||
Now we can invoke a run 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`:
|
||||
|
||||
=== "Python"
|
||||
|
||||
@@ -89,7 +91,7 @@ Now we can invoke a run with a webhook:
|
||||
assistant_id=assistant_id,
|
||||
input=input,
|
||||
stream_mode="events",
|
||||
webhook="your-webhook"
|
||||
webhook="https://my-server.app/my-webhook-endpoint"
|
||||
):
|
||||
# Do something with the stream output
|
||||
pass
|
||||
@@ -107,7 +109,7 @@ Now we can invoke a run with a webhook:
|
||||
assistantID,
|
||||
{
|
||||
input: input,
|
||||
webhook: "your-webhook"
|
||||
webhook: "https://my-server.app/my-webhook-endpoint"
|
||||
}
|
||||
);
|
||||
for await (const chunk of streamResponse) {
|
||||
@@ -124,8 +126,17 @@ Now we can invoke a run with a webhook:
|
||||
--data '{
|
||||
"assistant_id": <ASSISTANT_ID>,
|
||||
"input" : {"messages":[{"role": "user", "content": "Hello!"}]},
|
||||
"webhook": <YOUR_WEBHOOK_URL>
|
||||
"webhook": "https://my-server.app/my-webhook-endpoint"
|
||||
}'
|
||||
```
|
||||
|
||||
And that's it! Now you can trigger your custom webhooks whenever you want in your LangGraph applications!
|
||||
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=...
|
||||
```
|
||||
|
||||
The server should then extract the token from the request's parameters and validate it before processing the payload.
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
# LangGraph Cloud (beta)
|
||||
|
||||
!!! tip
|
||||
- LangGraph is an MIT-licensed open-source library, which we are committed to maintaining and growing for the community.
|
||||
- LangGraph Cloud is an optional managed hosting service for LangGraph, which provides additional features geared towards production deployments.
|
||||
- We are actively contributing improvements back to LangGraph informed by our work on LangGraph Cloud.
|
||||
- You can always deploy LangGraph applications on your own infrastructure using the open-source LangGraph project.
|
||||
|
||||
!!! warning "Under Construction"
|
||||
LangGraph Cloud documentation is under construction. Contents may change until general availability.
|
||||
|
||||
|
||||
<video controls preload="auto" allowfullscreen="true" poster="how-tos/img/studio_forks_poster.png">
|
||||
<source src="how-tos/img/studio_forks.mp4" type="video/mp4">
|
||||
</video>
|
||||
|
||||
|
||||
## Overview
|
||||
|
||||
LangGraph Cloud is a managed service for deploying and hosting LangGraph applications. Deploying applications with LangGraph Cloud shortens the time-to-market for developers. With one click, deploy a production-ready API with built-in persistence for your LangGraph application. LangGraph Cloud APIs are horizontally scalable and deployed with durable storage.
|
||||
|
||||
The LangGraph Cloud API exposes functionality of your LangGraph application through [Assistants](./concepts/api.md#assistants). An assistant abstracts the cognitive architecture of your graph. Invoke an assistant by calling the pre-built [API endpoints](./reference/api/api_ref.md).
|
||||
|
||||
LangGraph Cloud is seamlessly integrated with [LangSmith](https://www.langchain.com/langsmith) and is accessible from within the LangSmith UI.
|
||||
|
||||
LangGraph Cloud applications can be tested and debugged using the [LangGraph Studio Desktop](https://github.com/langchain-ai/langgraph-studio).
|
||||
|
||||
## Key Features
|
||||
|
||||
The LangGraph Cloud API supports key LangGraph features in addition to new functionality for enabling complex, agentic workflows.
|
||||
|
||||
- **Assistants and Threads**: Assistants abstract the cognitive architecture of graphs and threads track the state/history of graphs.
|
||||
- **Streaming**: API support for [LangGraph streaming modes](../concepts/low_level.md#streaming) including setting multiple streaming modes at the same time.
|
||||
- **Human-in-the-Loop**: API support for [LangGraph human-in-the-loop features](../concepts/agentic_concepts.md#human-in-the-loop).
|
||||
- **Double Texting**: Configure how assistants respond when new input is received while processing a previous input. Interrupt, rollback, reject, or enqueue.
|
||||
- **Background Runs/Cron Jobs**: A built-in task queue enables background runs and scheduled cron jobs.
|
||||
- **Stateless Runs**: For simpler use cases, invoke an assistant without needing to create a thread.
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Tutorials](./quick_start.md): Learn to build and deploy applications for LangGraph Cloud.
|
||||
- [How-to Guides](./how-tos/index.md): Learn how to set up a LangGraph application for deployment and implement features of the LangGraph Cloud API such as streaming tokens, configuring double texting, and creating cron jobs. Go here if you want to copy and run a specific code snippet.
|
||||
- [Conceptual Guides](./concepts/api.md): In-depth explanations of the core data models (e.g. assistants), key features of the LangGraph Cloud API (e.g. double texting), and the architecture of a LangGraph Cloud deployment.
|
||||
- [Reference](./reference/api/api_ref.md): References for the LangGraph Cloud API, the corresponding Python and JS/TS SDKs, the LangGraph CLI, and deployment environment variables.
|
||||
@@ -304,10 +304,19 @@
|
||||
"description": "The ID of the assistant.",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"title": "Assistant ID",
|
||||
"description": "The ID of the assistant."
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"title": "Assistant ID",
|
||||
"description": "The ID of the assistant."
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"title": "Graph ID",
|
||||
"description": "The ID of the graph."
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "assistant_id",
|
||||
"in": "path"
|
||||
|
||||
@@ -25,7 +25,7 @@ Once you've created an assistant, you can save and version it to track changes t
|
||||
2) The agent configuration options represent parameters that can be changed
|
||||
3) Assistant versions save and track specific settings of the agent configuration options
|
||||
|
||||
For example, let's imagine you have a general writing agent. You have created a general graph architecture that works well for writing. However, there are different types of writing, e.g. blogs vs tweets. In order to get the best performance on each use case, you need to make some minor changes to the models and prompts used. In this setup, you could create an assistant for each use case - one for blog writing and one for tweeting. These would share the same graph structure, but they may use different models and different prompts. Read [this how-to](../cloud/how-tos/assistant_versioning.md) to learn how you can use assistant versioning through both the [Studio](../cloud/how-tos/index.md/#langgraph-studio) and the SDK.
|
||||
For example, let's imagine you have a general writing agent. You have created a general graph architecture that works well for writing. However, there are different types of writing, e.g. blogs vs tweets. In order to get the best performance on each use case, you need to make some minor changes to the models and prompts used. In this setup, you could create an assistant for each use case - one for blog writing and one for tweeting. These would share the same graph structure, but they may use different models and different prompts. Read [this how-to](../cloud/how-tos/assistant_versioning.md) to learn how you can use assistant versioning through both the [Studio](../concepts/langgraph_studio.md) and the SDK.
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -2,9 +2,58 @@
|
||||
|
||||
Common questions and their answers!
|
||||
|
||||
## Do I need to use LangChain in order to use LangGraph?
|
||||
## Do I need to use LangChain to use LangGraph? What’s the difference?
|
||||
|
||||
No! LangGraph is a general-purpose framework - the nodes and edges are nothing more than Python functions. You can use LangChain, raw HTTP requests, or even other frameworks inside these nodes and edges.
|
||||
No. LangGraph is an orchestration framework for complex agentic systems and is more low-level and controllable than LangChain agents. LangChain provides a standard interface to interact with models and other components, useful for straight-forward chains and retrieval flows.
|
||||
|
||||
## How is LangGraph different from other agent frameworks?
|
||||
|
||||
Other agentic frameworks can work for simple, generic tasks but fall short for complex tasks bespoke to a company’s needs. LangGraph provides a more expressive framework to handle companies’ unique tasks without restricting users to a single black-box cognitive architecture.
|
||||
|
||||
## Does LangGraph impact the performance of my app?
|
||||
|
||||
LangGraph will not add any overhead to your code and is specifically designed with streaming workflows in mind.
|
||||
|
||||
## Is LangGraph open source? Is it free?
|
||||
|
||||
Yes. LangGraph is an MIT-licensed open-source library and is free to use.
|
||||
|
||||
## How are LangGraph and LangGraph Platform different?
|
||||
|
||||
LangGraph is a stateful, orchestration framework that brings added control to agent workflows. LangGraph Platform is a service for deploying and scaling LangGraph applications, with an opinionated API for building agent UXs, plus an integrated developer studio.
|
||||
|
||||
| Features | LangGraph (open source) | LangGraph Platform |
|
||||
|----------|------------------------|-------------------|
|
||||
| Description | Stateful orchestration framework for agentic applications | Scalable infrastructure for deploying LangGraph applications |
|
||||
| SDKs | Python and JavaScript | Python and JavaScript |
|
||||
| HTTP APIs | None | Yes - useful for retrieving & updating state or long-term memory, or creating a configurable assistant |
|
||||
| Streaming | Basic | Dedicated mode for token-by-token messages |
|
||||
| Checkpointer | Community contributed | Supported out-of-the-box |
|
||||
| Persistence Layer | Self-managed | Managed Postgres with efficient storage |
|
||||
| Deployment | Self-managed | • Cloud SaaS <br> • Free self-hosted <br> • Enterprise (BYOC or paid self-hosted) |
|
||||
| Scalability | Self-managed | Auto-scaling of task queues and servers |
|
||||
| Fault-tolerance | Self-managed | Automated retries |
|
||||
| Concurrency Control | Simple threading | Supports double-texting |
|
||||
| Scheduling | None | Cron scheduling |
|
||||
| Monitoring | None | Integrated with LangSmith for observability |
|
||||
| IDE integration | LangGraph Studio for Desktop | LangGraph Studio for Desktop & Cloud |
|
||||
|
||||
## What are my deployment options for LangGraph Platform?
|
||||
|
||||
We currently have the following deployment options for LangGraph applications:
|
||||
|
||||
- [Self-Hosted Lite](./deployment_options.md#self-hosted-lite): A free (up to 1M nodes executed), limited version of LangGraph Platform that you can run locally or in a self-hosted manner. This version requires a LangSmith API key and logs all usage to LangSmith. Fewer features are available than in paid plans.
|
||||
- [Cloud SaaS](./deployment_options.md#cloud-saas): Fully managed and hosted as part of LangSmith, with automatic updates and zero maintenance.
|
||||
- [Bring Your Own Cloud (BYOC)](./deployment_options.md#bring-your-own-cloud): Deploy LangGraph Platform within your VPC, provisioned and run as a service. Keep data in your environment while outsourcing the management of the service.
|
||||
- [Self-Hosted Enterprise](./deployment_options.md#self-hosted-enterprise): Deploy LangGraph entirely on your own infrastructure.
|
||||
|
||||
## Is LangGraph Platform open source?
|
||||
|
||||
No. LangGraph Platform is proprietary software.
|
||||
|
||||
There is a free, self-hosted version of LangGraph Platform with access to basic features. The Cloud SaaS deployment option is free while in beta, but will eventually be a paid service. We will always give ample notice before charging for a service and reward our early adopters with preferential pricing. The Bring Your Own Cloud (BYOC) and Self-Hosted Enterprise options are also paid services. [Contact our sales team](https://www.langchain.com/contact-sales) to learn more.
|
||||
|
||||
For more information, see our [LangGraph Platform pricing page](https://www.langchain.com/pricing-langgraph-platform).
|
||||
|
||||
## Does LangGraph work with LLMs that don't support tool calling?
|
||||
|
||||
|
||||
@@ -55,4 +55,4 @@ Once you've built a graph, you often want to test and debug it. [LangGraph Studi
|
||||
|
||||
## Deployment
|
||||
|
||||
Once you have confidence in your LangGraph application, many developers want an easy path to deployment. [LangGraph Cloud](../cloud/index.md) is an opinionated, simple way to deploy LangGraph objects from the LangChain team. Of course, you can also use services like [FastAPI](https://fastapi.tiangolo.com/) and call your graph from inside the FastAPI server as you see fit.
|
||||
Once you have confidence in your LangGraph application, many developers want an easy path to deployment. [LangGraph Platform](../concepts/index.md#langgraph-platform) offers a range of options for deploying LangGraph graphs.
|
||||
@@ -1,12 +1,10 @@
|
||||
# Cloud SaaS
|
||||
|
||||
!!! info "Prerequisites"
|
||||
- [LangGraph Platform](./langgraph_platform.md)
|
||||
- [LangGraph Server](./langgraph_server.md)
|
||||
!!! info "Prerequisites" - [LangGraph Platform](./langgraph_platform.md) - [LangGraph Server](./langgraph_server.md)
|
||||
|
||||
## Overview
|
||||
|
||||
LangGraph's Cloud SaaS is a managed service that provides a scalable and secure environment for deploying LangGraph APIs. It is designed to work seamlessly with your LangGraph API regardless of how it is defined, what tools it uses, or any dependencies. Cloud SaaS provides a simple way to deploy and manage your LangGraph API in the cloud.
|
||||
LangGraph's Cloud SaaS is a managed service for deploying LangGraph APIs, regardless of its definition or dependencies. The service offers managed implementations of checkpointers and stores, allowing you to focus on building the right cognitive architecture for your use case. By handling scalable & secure infrastructure, LangGraph Cloud offers the fastest path to getting your LangGraph API deployed to production.
|
||||
|
||||
## Deployment
|
||||
|
||||
@@ -35,4 +33,4 @@ A high-level diagram of a Cloud SaaS deployment.
|
||||
|
||||
## Related
|
||||
|
||||
- [Deployment Options](./deployment_options.md)
|
||||
- [Deployment Options](./deployment_options.md)
|
||||
|
||||
@@ -153,7 +153,7 @@ guide for that [here](../how-tos/streaming-tokens.ipynb).
|
||||
Streaming is critical for making LLM applications feel responsive to end users. When creating a streaming run, the streaming mode determines what data is streamed back to the API client. LangGraph Platform supports five streaming modes:
|
||||
|
||||
- `values`: Stream the full state of the graph after each [super-step](https://langchain-ai.github.io/langgraph/concepts/low_level/#graphs) is executed. See the [how-to guide](../cloud/how-tos/stream_values.md) for streaming values.
|
||||
- `messages`: Stream complete messages (at the end of node execution) as well as tokens for any messages generated inside a node. This mode is primarily meant for powering chat applications. This is only an option if your graph contains a `messages` key. See the [how-to guide](../cloud/how-tos/stream_messages.md) for streaming messages.
|
||||
- `messages-tuple`: Stream LLM tokens for any messages generated inside a node. This mode is primarily meant for powering chat applications. See the [how-to guide](../cloud/how-tos/stream_messages.md) for streaming messages.
|
||||
- `updates`: Streams updates to the state of the graph after each node is executed. See the [how-to guide](../cloud/how-tos/stream_updates.md) for streaming updates.
|
||||
- `events`: Stream all events (including the state of the graph) that occur during graph execution. See the [how-to guide](../cloud/how-tos/stream_events.md) for streaming events. This can be used to do token-by-token streaming for LLMs.
|
||||
- `debug`: Stream debug events throughout graph execution. See the [how-to guide](../cloud/how-tos/stream_debug.md) for streaming debug events.
|
||||
@@ -162,90 +162,11 @@ You can also specify multiple streaming modes at the same time. See the [how-to
|
||||
|
||||
See the [API reference](../cloud/reference/api/api_ref.html#tag/threads-runs/POST/threads/{thread_id}/runs/stream) for how to create streaming runs.
|
||||
|
||||
Streaming modes `values`, `updates`, and `debug` are very similar to modes available in the LangGraph library - for a deeper conceptual explanation of those, you can see the [previous section](#streaming-graph-outputs-stream-and-astream).
|
||||
Streaming modes `values`, `updates`, `messages-tuple` and `debug` are very similar to modes available in the LangGraph library - for a deeper conceptual explanation of those, you can see the [previous section](#streaming-graph-outputs-stream-and-astream).
|
||||
|
||||
Streaming mode `events` is the same as using `.astream_events` in the LangGraph library - for a deeper conceptual explanation of this, you can see the [previous section](#streaming-graph-outputs-stream-and-astream).
|
||||
|
||||
### `stream_mode="messages"`
|
||||
|
||||
Streaming mode `messages` is for streaming back messages from the LLM. Assuming we have a simple [ReAct](./agentic_concepts.md#react-implementation)-style agent deployed, what does this stream look like?
|
||||
|
||||
All events emitted have two attributes:
|
||||
|
||||
- `event`: This is the name of the event
|
||||
- `data`: This is data associated with the event
|
||||
|
||||
!!! note
|
||||
Streaming mode `messages` is different from the one in the LangGraph library:
|
||||
|
||||
- LangGraph Server streams event objects with messages in the `data` field, while LangGraph library streams tuples (`AIMessageChunk`, metadata).
|
||||
- In LangGraph Server, metadata is streamed only once per message (`messages/metadata`), before the individual tokens are streamed (`messages/partial`), while in LangGraph library it's streamed with every `AIMessageChunk` (for each LLM token).
|
||||
- LangGraph Server also streams additional events (`metadata`, `messages/complete`, see below for more details).
|
||||
|
||||
Let's run it on a question that should trigger a tool call:
|
||||
|
||||
```python
|
||||
thread = await client.threads.create()
|
||||
input = {"messages": [{"role": "user", "content": "what's the weather in sf?"}]}
|
||||
|
||||
events = []
|
||||
async for event in client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistant_id="agent", # This may need to change depending on the graph you deployed
|
||||
input=input,
|
||||
stream_mode="messages",
|
||||
):
|
||||
print(event.event)
|
||||
```
|
||||
```shell
|
||||
metadata
|
||||
messages/metadata
|
||||
messages/partial
|
||||
...
|
||||
messages/partial
|
||||
messages/metadata
|
||||
messages/complete
|
||||
messages/metadata
|
||||
messages/partial
|
||||
...
|
||||
messages/partial
|
||||
end
|
||||
```
|
||||
|
||||
We first get some `metadata` - this is metadata about the run.
|
||||
|
||||
```python
|
||||
StreamPart(event='metadata', data={'run_id': '1ef657cf-ae55-6f65-97d4-f4ed1dbdabc6'})
|
||||
```
|
||||
|
||||
We then get a `messages/metadata` - this is letting us know that a new message is starting and provides additional information about the LLM as well as the node where the LLM is invoked.
|
||||
|
||||
```python
|
||||
StreamPart(event='messages/metadata', data={'run-985c0f14-9f43-40d4-a505-4637fc58e333': {'metadata': {'created_by': 'system', 'run_id': '1ef657de-7594-66df-8eb2-31518e4a1ee2', 'graph_id': 'agent', 'thread_id': 'c178eab5-e293-423c-8e7d-1d113ffe7cd9', 'model_name': 'openai', 'assistant_id': 'fe096781-5601-53d2-b2f6-0d3403f7e9ca', 'langgraph_step': 1, 'langgraph_node': 'agent', 'langgraph_triggers': ['start:agent'], 'langgraph_task_idx': 0, 'ls_provider': 'openai', 'ls_model_name': 'gpt-4o', 'ls_model_type': 'chat', 'ls_temperature': 0.0}}})
|
||||
```
|
||||
|
||||
We then get a BUNCH of `messages/partial` events - these are the individual tokens from the LLM! In the case below, we can see the START of a tool call.
|
||||
|
||||
```python
|
||||
StreamPart(event='messages/partial', data=[{'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_w8Hr8dHGuZCPgRfd5FqRBArs', 'function': {'arguments': '', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, 'response_metadata': {}, 'type': 'ai', 'name': None, 'id': 'run-985c0f14-9f43-40d4-a505-4637fc58e333', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [{'name': 'tavily_search_results_json', 'args': '', 'id': 'call_w8Hr8dHGuZCPgRfd5FqRBArs', 'error': None}], 'usage_metadata': None}])
|
||||
```
|
||||
|
||||
The last `messages/partial` event for a given message will contain all of the tokens streamed for that message. In our case, it is now a complete tool call:
|
||||
|
||||
```python
|
||||
StreamPart(event='messages/partial', data=[{'content': '', 'additional_kwargs': {'tool_calls': [{'index': 0, 'id': 'call_w8Hr8dHGuZCPgRfd5FqRBArs', 'function': {'arguments': '{"query":"current weather in San Francisco"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, 'response_metadata': {'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'type': 'ai', 'name': None, 'id': 'run-985c0f14-9f43-40d4-a505-4637fc58e333', 'example': False, 'tool_calls': [{'name': 'tavily_search_results_json', 'args': {'query': 'current weather in San Francisco'}, 'id': 'call_w8Hr8dHGuZCPgRfd5FqRBArs'}], 'invalid_tool_calls': [], 'usage_metadata': None}])
|
||||
```
|
||||
|
||||
After that, we get another `messages/metadata`, now followed by a `messages/complete` event. This event is emitted for a tool message - our agent has called a tool, gotten a response, and now inserting it into the state in the form of a tool message.
|
||||
|
||||
```python
|
||||
StreamPart(event='messages/complete', data=[{'content': '[{"url": "https://www.weatherapi.com/", "content": "{\'location\': {\'name\': \'San Francisco\', \'region\': \'California\', \'country\': \'United States of America\', \'lat\': 37.78, \'lon\': -122.42, \'tz_id\': \'America/Los_Angeles\', \'localtime_epoch\': 1724877689, \'localtime\': \'2024-08-28 13:41\'}, \'current\': {\'last_updated_epoch\': 1724877000, \'last_updated\': \'2024-08-28 13:30\', \'temp_c\': 23.3, \'temp_f\': 73.9, \'is_day\': 1, \'condition\': {\'text\': \'Partly cloudy\', \'icon\': \'//cdn.weatherapi.com/weather/64x64/day/116.png\', \'code\': 1003}, \'wind_mph\': 15.0, \'wind_kph\': 24.1, \'wind_degree\': 310, \'wind_dir\': \'NW\', \'pressure_mb\': 1014.0, \'pressure_in\': 29.93, \'precip_mm\': 0.0, \'precip_in\': 0.0, \'humidity\': 57, \'cloud\': 25, \'feelslike_c\': 25.0, \'feelslike_f\': 77.1, \'windchill_c\': 20.9, \'windchill_f\': 69.6, \'heatindex_c\': 23.3, \'heatindex_f\': 74.0, \'dewpoint_c\': 12.9, \'dewpoint_f\': 55.2, \'vis_km\': 16.0, \'vis_miles\': 9.0, \'uv\': 6.0, \'gust_mph\': 19.5, \'gust_kph\': 31.3}}"}]', 'additional_kwargs': {}, 'response_metadata': {}, 'type': 'tool', 'name': 'tavily_search_results_json', 'id': '0112eba5-7660-4375-9f24-c7a1d6777b97', 'tool_call_id': 'call_w8Hr8dHGuZCPgRfd5FqRBArs'}])
|
||||
```
|
||||
|
||||
After that, we see the agent doing another LLM call and streaming back a response. We then get an `end` event:
|
||||
|
||||
```python
|
||||
StreamPart(event='end', data=None)
|
||||
```
|
||||
|
||||
And that's it! This is more focused streaming mode specifically focused on streaming back messages. See this [how-to guide](../cloud/how-tos/stream_messages.md) for more information.
|
||||
- `data`: This is data associated with the event
|
||||
@@ -14,7 +14,7 @@ With the self-hosted deployment option, you are responsible for managing the inf
|
||||
You will need to do the following:
|
||||
|
||||
1. Deploy Redis and Postgres instances on your own infrastructure.
|
||||
2. Build a docker image with the [LangGraph Sever](../concepts/langgraph_server.md) using the [LangGraph CLI](../concepts/langgraph_cli.md).
|
||||
2. Build a docker image with the [LangGraph Server](../concepts/langgraph_server.md) using the [LangGraph CLI](../concepts/langgraph_cli.md).
|
||||
3. Deploy a web server that will run the docker image and pass in the necessary environment variables.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
@@ -90,9 +90,9 @@ These how-to guides show common patterns for tool calling with LangGraph:
|
||||
|
||||
### State Management
|
||||
|
||||
- [Use Pydantic model as state](state-model.ipynb)
|
||||
- [Have a separate input and output schema](input_output_schema.ipynb)
|
||||
- [Pass private state between nodes inside the graph](pass_private_state.ipynb)
|
||||
- [How to use Pydantic model as state](state-model.ipynb)
|
||||
- [How to define input/output schema for your graph](input_output_schema.ipynb)
|
||||
- [How to pass private state between nodes inside the graph](pass_private_state.ipynb)
|
||||
|
||||
### Other
|
||||
|
||||
@@ -141,7 +141,8 @@ Learn how to set up your app for deployment to LangGraph Platform:
|
||||
- [How to set up app for deployment (JavaScript)](../cloud/deployment/setup_javascript.md)
|
||||
- [How to customize Dockerfile](../cloud/deployment/custom_docker.md)
|
||||
- [How to test locally](../cloud/deployment/test_locally.md)
|
||||
|
||||
- [How to rebuild graph at runtime](../cloud/deployment/graph_rebuild.md)
|
||||
|
||||
### Deployment
|
||||
|
||||
LangGraph applications can be deployed using LangGraph Cloud, which provides a range of services to help you deploy, manage, and scale your applications.
|
||||
@@ -219,11 +220,12 @@ LangGraph Studio is a built-in UI for visualizing, testing, and debugging your a
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
The [Error Reference](../troubleshooting/errors/index.md) page contains guides around resolving common errors you may find while building with LangGraph. Errors referenced below will have an `lc_error_code` property corresponding to one of the below codes when they are thrown in code.
|
||||
These are the guides for resolving common errors you may find while building with LangGraph. Errors referenced below will have an `lc_error_code` property corresponding to one of the below codes when they are thrown in code.
|
||||
|
||||
- [GRAPH_RECURSION_LIMIT](../troubleshooting/errors/GRAPH_RECURSION_LIMIT.md)
|
||||
- [INVALID_CONCURRENT_GRAPH_UPDATE](../troubleshooting/errors/INVALID_CONCURRENT_GRAPH_UPDATE.md)
|
||||
- [INVALID_GRAPH_NODE_RETURN_VALUE](../troubleshooting/errors/INVALID_GRAPH_NODE_RETURN_VALUE.md)
|
||||
- [MULTIPLE_SUBGRAPHS](../troubleshooting/errors/MULTIPLE_SUBGRAPHS.md)
|
||||
- [INVALID_CHAT_HISTORY](../troubleshooting/errors/INVALID_CHAT_HISTORY.md)
|
||||
|
||||
|
||||
|
||||
@@ -217,6 +217,12 @@ Since the `RemoteGraph` behaves the same way as a regular `CompiledGraph`, it ca
|
||||
"messages": [{"role": "user", "content": "what's the weather in sf"}]
|
||||
})
|
||||
print(result)
|
||||
|
||||
# stream outputs from both the parent graph and subgraph
|
||||
for chunk in graph.stream({
|
||||
"messages": [{"role": "user", "content": "what's the weather in sf"}]
|
||||
}, subgraphs=True):
|
||||
print(chunk)
|
||||
```
|
||||
|
||||
=== "JavaScript"
|
||||
@@ -240,4 +246,11 @@ Since the `RemoteGraph` behaves the same way as a regular `CompiledGraph`, it ca
|
||||
messages: [{ role: "user", content: "what's the weather in sf" }]
|
||||
});
|
||||
console.log(result);
|
||||
|
||||
// stream outputs from both the parent graph and subgraph
|
||||
for await (const chunk of await graph.stream({
|
||||
messages: [{ role: "user", content: "what's the weather in la" }]
|
||||
}, { subgraphs: true })) {
|
||||
console.log(chunk);
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,30 @@
|
||||
# INVALID_CHAT_HISTORY
|
||||
|
||||
This error is raised in the prebuilt [create_react_agent][langgraph.prebuilt.chat_agent_executor.create_react_agent] when the `call_model` graph node receives a malformed list of messages. Specifically, it is malformed when there are `AIMessages` with `tool_calls` (LLM requesting to call a tool) that do not have a corresponding `ToolMessage` (result of a tool invocation to return to the LLM).
|
||||
|
||||
There could be a few reasons you're seeing this error:
|
||||
|
||||
1. You manually passed a malformed list of messages when invoking the graph, e.g. `graph.invoke({'messages': [AIMessage(..., tool_calls=[...])]})`
|
||||
2. The graph was interrupted before receiving updates from the `tools` node (i.e. a list of ToolMessages)
|
||||
and you invoked it with a an input that is not None or a ToolMessage,
|
||||
e.g. `graph.invoke({'messages': [HumanMessage(...)]}, config)`.
|
||||
This interrupt could have been triggered in one of the following ways:
|
||||
- You manually set `interrupt_before = ['tools']` in `create_react_agent`
|
||||
- One of the tools raised an error that wasn't handled by the [ToolNode][langgraph.prebuilt.tool_node.ToolNode] (`"tools"`)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
To resolve this, you can do one of the following:
|
||||
|
||||
1. Don't invoke the graph with a malformed list of messages
|
||||
2. In case of an interrupt (manual or due to an error) you can:
|
||||
|
||||
- provide ToolMessages that match existing tool calls and call `graph.invoke({'messages': [ToolMessage(...)]})`.
|
||||
**NOTE**: this will append the messages to the history and run the graph from the START node.
|
||||
- manually update the state and resume the graph from the interrupt:
|
||||
|
||||
1. get the list of most recent messages from the graph state with `graph.get_state(config)`
|
||||
2. modify the list of messages to either remove unanswered tool calls from AIMessages
|
||||
or add ToolMessages with tool_call_ids that match unanswered tool calls
|
||||
3. call `graph.update_state(config, {'messages': ...})` with the modified list of messages
|
||||
4. resume the graph, e.g. call `graph.invoke(None, config)`
|
||||
@@ -7,3 +7,4 @@ Errors referenced below will have an `lc_error_code` property corresponding to o
|
||||
- [INVALID_CONCURRENT_GRAPH_UPDATE](./INVALID_CONCURRENT_GRAPH_UPDATE.md)
|
||||
- [INVALID_GRAPH_NODE_RETURN_VALUE](./INVALID_GRAPH_NODE_RETURN_VALUE.md)
|
||||
- [MULTIPLE_SUBGRAPHS](./MULTIPLE_SUBGRAPHS.md)
|
||||
- [INVALID_CHAT_HISTORY](./INVALID_CHAT_HISTORY.md)
|
||||
|
||||
@@ -1077,7 +1077,7 @@
|
||||
" ),\n",
|
||||
" (\"placeholder\", \"{messages}\"),\n",
|
||||
" ]\n",
|
||||
").partial(time=datetime.now())\n",
|
||||
").partial(time=datetime.now)\n",
|
||||
"\n",
|
||||
"part_1_tools = [\n",
|
||||
" TavilySearchResults(max_results=1),\n",
|
||||
@@ -1893,7 +1893,7 @@
|
||||
" ),\n",
|
||||
" (\"placeholder\", \"{messages}\"),\n",
|
||||
" ]\n",
|
||||
").partial(time=datetime.now())\n",
|
||||
").partial(time=datetime.now)\n",
|
||||
"\n",
|
||||
"part_2_tools = [\n",
|
||||
" TavilySearchResults(max_results=1),\n",
|
||||
@@ -2472,7 +2472,7 @@
|
||||
" ),\n",
|
||||
" (\"placeholder\", \"{messages}\"),\n",
|
||||
" ]\n",
|
||||
").partial(time=datetime.now())\n",
|
||||
").partial(time=datetime.now)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# \"Read\"-only tools (such as retrievers) don't need a user confirmation to use\n",
|
||||
@@ -3183,7 +3183,7 @@
|
||||
" ),\n",
|
||||
" (\"placeholder\", \"{messages}\"),\n",
|
||||
" ]\n",
|
||||
").partial(time=datetime.now())\n",
|
||||
").partial(time=datetime.now)\n",
|
||||
"\n",
|
||||
"update_flight_safe_tools = [search_flights]\n",
|
||||
"update_flight_sensitive_tools = [update_ticket_to_new_flight, cancel_ticket]\n",
|
||||
@@ -3215,7 +3215,7 @@
|
||||
" ),\n",
|
||||
" (\"placeholder\", \"{messages}\"),\n",
|
||||
" ]\n",
|
||||
").partial(time=datetime.now())\n",
|
||||
").partial(time=datetime.now)\n",
|
||||
"\n",
|
||||
"book_hotel_safe_tools = [search_hotels]\n",
|
||||
"book_hotel_sensitive_tools = [book_hotel, update_hotel, cancel_hotel]\n",
|
||||
@@ -3247,7 +3247,7 @@
|
||||
" ),\n",
|
||||
" (\"placeholder\", \"{messages}\"),\n",
|
||||
" ]\n",
|
||||
").partial(time=datetime.now())\n",
|
||||
").partial(time=datetime.now)\n",
|
||||
"\n",
|
||||
"book_car_rental_safe_tools = [search_car_rentals]\n",
|
||||
"book_car_rental_sensitive_tools = [\n",
|
||||
@@ -3282,7 +3282,7 @@
|
||||
" ),\n",
|
||||
" (\"placeholder\", \"{messages}\"),\n",
|
||||
" ]\n",
|
||||
").partial(time=datetime.now())\n",
|
||||
").partial(time=datetime.now)\n",
|
||||
"\n",
|
||||
"book_excursion_safe_tools = [search_trip_recommendations]\n",
|
||||
"book_excursion_sensitive_tools = [book_excursion, update_excursion, cancel_excursion]\n",
|
||||
@@ -3389,7 +3389,7 @@
|
||||
" ),\n",
|
||||
" (\"placeholder\", \"{messages}\"),\n",
|
||||
" ]\n",
|
||||
").partial(time=datetime.now())\n",
|
||||
").partial(time=datetime.now)\n",
|
||||
"primary_assistant_tools = [\n",
|
||||
" TavilySearchResults(max_results=1),\n",
|
||||
" search_flights,\n",
|
||||
|
||||
@@ -56,7 +56,8 @@ Learn from example implementations of graphs designed for specific scenarios and
|
||||
|
||||
- [Basic Reflection](reflection/reflection.ipynb): Prompt the agent to reflect on and revise its outputs
|
||||
- [Reflexion](reflexion/reflexion.ipynb): Critique missing and superfluous details to guide next steps
|
||||
- [Language Agent Tree Search](lats/lats.ipynb): Use reflection and rewards to drive a tree search over agents
|
||||
- [Tree of Thoughts](tot/tot.ipynb): Search over candidate solutions to a problem using a scored tree
|
||||
- [Language Agent Tree Search](lats/lats.ipynb): Use reflection and rewards to drive a monte-carlo tree search over agents
|
||||
- [Self-Discover Agent](self-discover/self-discover.ipynb): Analyze an agent that learns about its own capabilities
|
||||
|
||||
### Evaluation
|
||||
|
||||
@@ -127,7 +127,7 @@
|
||||
"<div class=\"admonition tip\">\n",
|
||||
" <p class=\"admonition-title\">Note</p>\n",
|
||||
" <p>\n",
|
||||
" The first thing you do when you define a graph is define the <code>State</code> of the graph. The <code>State</code> consists of the schema of the graph as well as reducer functions which specify how to apply updates to the state. In our example <code>State</code> is a <code>TypedDict</code> with a single key: <code>messages</code>. The <code>messages</code> key is annotated with the <a href=\"https://langchain-ai.github.io/langgraph/reference/graphs/?h=add+messages#add_messages\"><code>add_messages</code></a> reducer function, which tells LangGraph to append new messages to the existing list, rather than overwriting it. State keys without an annotation will be overwritten by each update, storing the most recent value. Check out <a href=\"https://langchain-ai.github.io/langgraph/reference/graphs/?h=add+messages#add_messages\">this conceptual guide</a> to learn more about state, reducers and other low-level concepts.\n",
|
||||
" The first thing you do when you define a graph is define the <code>State</code> of the graph. The <code>State</code> consists of the schema of the graph as well as <a href=\"https://langchain-ai.github.io/langgraph/concepts/low_level/#reducers\">reducer functions</a> which specify how to apply updates to the state. In our example <code>State</code> is a <code>TypedDict</code> with a single key: <code>messages</code>. The <code>messages</code> key is annotated with the <a href=\"https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.message.add_messages\"><code>add_messages</code></a> reducer function, which tells LangGraph to append new messages to the existing list, rather than overwriting it. State keys without an annotation will be overwritten by each update, storing the most recent value. Check out <a href=\"https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.message.add_messages\">this conceptual guide</a> to learn more about state, reducers and other low-level concepts.\n",
|
||||
" </p>\n",
|
||||
"</div>"
|
||||
]
|
||||
|
||||
@@ -46,8 +46,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%pip install -U --quiet langgraph langchain_anthropic\n",
|
||||
"%pip install -U --quiet tavily-python"
|
||||
"%pip install -U --quiet langgraph langchain_anthropic tavily-python"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -189,7 +188,7 @@
|
||||
" self.runnable = runnable\n",
|
||||
" self.validator = validator\n",
|
||||
"\n",
|
||||
" def respond(self, state: list):\n",
|
||||
" def respond(self, state: dict):\n",
|
||||
" response = []\n",
|
||||
" for attempt in range(3):\n",
|
||||
" response = self.runnable.invoke(\n",
|
||||
@@ -622,12 +621,6 @@
|
||||
"2. The 'reflections' can be paired with additional external feedback (such as validators), to further guide the actor.\n",
|
||||
"3. In the paper, 1 environment (AlfWorld) uses external memory. It does this by storing summaries of the reflections to an external store and using them in subsequent trials/invocations."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "39e44dd6",
|
||||
"metadata": {},
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
@@ -646,7 +639,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.9"
|
||||
"version": "3.12.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
File diff suppressed because one or more lines are too long
+19
-10
@@ -22,6 +22,7 @@ theme:
|
||||
- navigation.footer
|
||||
- navigation.indexes
|
||||
- navigation.instant
|
||||
- navigation.sections
|
||||
- navigation.instant.prefetch
|
||||
- navigation.instant.progress
|
||||
- navigation.prune
|
||||
@@ -53,6 +54,13 @@ plugins:
|
||||
- search:
|
||||
separator: '[\s\u200b\-_,:!=\[\]()"`/]+|\.(?!\d)|&[lg]t;|(?!\b)(?=[A-Z][a-z])'
|
||||
- autorefs
|
||||
- redirects:
|
||||
redirect_maps:
|
||||
'cloud/index.md': 'concepts/index.md#langgraph-platform'
|
||||
'cloud/how-tos/index.md': 'how-tos/index.md#langgraph-platform'
|
||||
'cloud/concepts/api.md': 'concepts/langgraph_server.md'
|
||||
'cloud/concepts/cloud.md': 'concepts/langgraph_cloud.md'
|
||||
'cloud/faq/studio.md': 'concepts/langgraph_studio.md#studio-faqs'
|
||||
- mkdocstrings:
|
||||
handlers:
|
||||
python:
|
||||
@@ -87,20 +95,21 @@ nav:
|
||||
- "How-to Guides": "how-tos/index.md"
|
||||
- Reference:
|
||||
- "reference/index.md"
|
||||
- Graphs: reference/graphs.md
|
||||
- RemoteGraph: reference/remote_graph.md
|
||||
- Checkpointing: reference/checkpoints.md
|
||||
- Storage: reference/store.md
|
||||
- Prebuilt Components: reference/prebuilt.md
|
||||
- Channels: reference/channels.md
|
||||
- Errors: reference/errors.md
|
||||
- Types: reference/types.md
|
||||
- Constants: reference/constants.md
|
||||
- Library:
|
||||
- Graphs: reference/graphs.md
|
||||
- Checkpointing: reference/checkpoints.md
|
||||
- Storage: reference/store.md
|
||||
- Prebuilt Components: reference/prebuilt.md
|
||||
- Channels: reference/channels.md
|
||||
- Errors: reference/errors.md
|
||||
- Types: reference/types.md
|
||||
- Constants: reference/constants.md
|
||||
- LangGraph Platform:
|
||||
- Server API: "cloud/reference/api/api_ref.md"
|
||||
- CLI: "cloud/reference/cli.md"
|
||||
- SDK (Python): "cloud/reference/sdk/python_sdk_ref.md"
|
||||
- SDK (JS/TS): "cloud/reference/sdk/js_ts_sdk_ref.md"
|
||||
- CLI: "cloud/reference/cli.md"
|
||||
- RemoteGraph: reference/remote_graph.md
|
||||
- Environment Variables: "cloud/reference/env_var.md"
|
||||
|
||||
markdown_extensions:
|
||||
|
||||
@@ -84,7 +84,7 @@ select
|
||||
and cw.checkpoint_id = checkpoints.checkpoint_id
|
||||
) as pending_writes,
|
||||
(
|
||||
select array_agg(array[cw.type::bytea, cw.blob] order by cw.idx)
|
||||
select array_agg(array[cw.type::bytea, cw.blob] order by cw.task_id, cw.idx)
|
||||
from checkpoint_writes cw
|
||||
where cw.thread_id = checkpoints.thread_id
|
||||
and cw.checkpoint_ns = checkpoints.checkpoint_ns
|
||||
|
||||
@@ -25,7 +25,7 @@ from langchain_core.load.serializable import Serializable
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from langgraph.checkpoint.serde.base import SerializerProtocol
|
||||
from langgraph.checkpoint.serde.types import SendProtocol
|
||||
from langgraph.checkpoint.serde.types import ControlProtocol, SendProtocol
|
||||
from langgraph.store.base import Item
|
||||
|
||||
LC_REVIVER = Reviver()
|
||||
@@ -402,6 +402,21 @@ def _msgpack_default(obj: Any) -> Union[str, msgpack.ExtType]:
|
||||
(obj.__class__.__module__, obj.__class__.__name__, (obj.node, obj.arg)),
|
||||
),
|
||||
)
|
||||
elif isinstance(obj, ControlProtocol):
|
||||
return msgpack.ExtType(
|
||||
EXT_CONSTRUCTOR_KW_ARGS,
|
||||
_msgpack_enc(
|
||||
(
|
||||
obj.__class__.__module__,
|
||||
obj.__class__.__name__,
|
||||
{
|
||||
"update_state": obj.update_state,
|
||||
"trigger": obj.trigger,
|
||||
"send": obj.send,
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
elif dataclasses.is_dataclass(obj):
|
||||
# doesn't use dataclasses.asdict to avoid deepcopy and recursion
|
||||
return msgpack.ExtType(
|
||||
|
||||
@@ -4,6 +4,7 @@ from typing import (
|
||||
Protocol,
|
||||
Sequence,
|
||||
TypeVar,
|
||||
Union,
|
||||
runtime_checkable,
|
||||
)
|
||||
|
||||
@@ -48,3 +49,13 @@ class SendProtocol(Protocol):
|
||||
def __repr__(self) -> str: ...
|
||||
|
||||
def __eq__(self, value: object) -> bool: ...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class ControlProtocol(Protocol):
|
||||
# Mirrors langgraph.constants.Control
|
||||
update_state: Optional[dict[str, Any]]
|
||||
trigger: Union[str, Sequence[str]]
|
||||
send: Union[Any, Sequence[Any]]
|
||||
|
||||
def __repr__(self) -> str: ...
|
||||
|
||||
@@ -239,7 +239,7 @@ For production use, requires a license key in env var LANGGRAPH_CLOUD_LICENSE_KE
|
||||
f"""Ready!
|
||||
- API: http://localhost:{port}
|
||||
- Docs: http://localhost:{port}/docs
|
||||
- Debugger: {debugger_origin}/studio/?baseUrl={debugger_base_url_query}
|
||||
- LangGraph Studio: {debugger_origin}/studio/?baseUrl={debugger_base_url_query}
|
||||
"""
|
||||
)
|
||||
sys.stdout.flush()
|
||||
|
||||
@@ -289,17 +289,41 @@ ENV LANGSERVE_GRAPHS='{json.dumps(config["graphs"])}'
|
||||
def node_config_to_docker(config_path: pathlib.Path, config: Config, base_image: str):
|
||||
faux_path = f"/deps/{config_path.parent.name}"
|
||||
|
||||
def test_file(file_name):
|
||||
full_path = config_path.parent / file_name
|
||||
try:
|
||||
return full_path.is_file()
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
npm, yarn, pnpm = [
|
||||
test_file("package-lock.json"),
|
||||
test_file("yarn.lock"),
|
||||
test_file("pnpm-lock.yaml"),
|
||||
]
|
||||
|
||||
if yarn:
|
||||
install_cmd = "yarn install --frozen-lockfile"
|
||||
elif pnpm:
|
||||
install_cmd = "pnpm i --frozen-lockfile"
|
||||
elif npm:
|
||||
install_cmd = "npm ci"
|
||||
else:
|
||||
install_cmd = "npm i"
|
||||
|
||||
return f"""FROM {base_image}:{config['node_version']}
|
||||
|
||||
{os.linesep.join(config["dockerfile_lines"])}
|
||||
|
||||
ADD . {faux_path}
|
||||
|
||||
RUN cd {faux_path} && yarn install --frozen-lockfile
|
||||
RUN cd {faux_path} && {install_cmd}
|
||||
|
||||
ENV LANGSERVE_GRAPHS='{json.dumps(config["graphs"])}'
|
||||
|
||||
WORKDIR {faux_path}"""
|
||||
WORKDIR {faux_path}
|
||||
|
||||
RUN (test ! -f /api/langgraph_api/js/build.mts && echo "Prebuild script not found, skipping") || tsx /api/langgraph_api/js/build.mts"""
|
||||
|
||||
|
||||
def config_to_docker(config_path: pathlib.Path, config: Config, base_image: str):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "langgraph-cli"
|
||||
version = "0.1.52"
|
||||
version = "0.1.53"
|
||||
description = "CLI for interacting with LangGraph API"
|
||||
authors = []
|
||||
license = "MIT"
|
||||
|
||||
@@ -250,9 +250,10 @@ def test_config_to_docker_nodejs():
|
||||
ARG meow
|
||||
ARG foo
|
||||
ADD . /deps/unit_tests
|
||||
RUN cd /deps/unit_tests && yarn install --frozen-lockfile
|
||||
RUN cd /deps/unit_tests && npm i
|
||||
ENV LANGSERVE_GRAPHS='{"agent": "./graphs/agent.js:graph"}'
|
||||
WORKDIR /deps/unit_tests"""
|
||||
WORKDIR /deps/unit_tests
|
||||
RUN (test ! -f /api/langgraph_api/js/build.mts && echo "Prebuild script not found, skipping") || tsx /api/langgraph_api/js/build.mts"""
|
||||
|
||||
assert clean_empty_lines(actual_docker_stdin) == expected_docker_stdin
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ START = sys.intern("__start__")
|
||||
"""The first (maybe virtual) node in graph-style Pregel."""
|
||||
END = sys.intern("__end__")
|
||||
"""The last (maybe virtual) node in graph-style Pregel."""
|
||||
SELF = sys.intern("__self__")
|
||||
"""The implicit branch that handles each node's Control values."""
|
||||
|
||||
# --- Reserved write keys ---
|
||||
INPUT = sys.intern("__input__")
|
||||
|
||||
@@ -12,6 +12,7 @@ class ErrorCode(Enum):
|
||||
INVALID_CONCURRENT_GRAPH_UPDATE = "INVALID_CONCURRENT_GRAPH_UPDATE"
|
||||
INVALID_GRAPH_NODE_RETURN_VALUE = "INVALID_GRAPH_NODE_RETURN_VALUE"
|
||||
MULTIPLE_SUBGRAPHS = "MULTIPLE_SUBGRAPHS"
|
||||
INVALID_CHAT_HISTORY = "INVALID_CHAT_HISTORY"
|
||||
|
||||
|
||||
def create_error_message(*, message: str, error_code: ErrorCode) -> str:
|
||||
|
||||
@@ -27,6 +27,7 @@ from typing_extensions import Self
|
||||
|
||||
from langgraph.channels.ephemeral_value import EphemeralValue
|
||||
from langgraph.constants import (
|
||||
EMPTY_SEQ,
|
||||
END,
|
||||
NS_END,
|
||||
NS_SEP,
|
||||
@@ -47,6 +48,7 @@ logger = logging.getLogger(__name__)
|
||||
class NodeSpec(NamedTuple):
|
||||
runnable: Runnable
|
||||
metadata: Optional[dict[str, Any]] = None
|
||||
ends: Optional[tuple[str, ...]] = EMPTY_SEQ
|
||||
|
||||
|
||||
class Branch(NamedTuple):
|
||||
@@ -123,7 +125,7 @@ class Branch(NamedTuple):
|
||||
result: Any,
|
||||
config: RunnableConfig,
|
||||
) -> Union[Runnable, Any]:
|
||||
if not isinstance(result, list):
|
||||
if not isinstance(result, (list, tuple)):
|
||||
result = [result]
|
||||
if self.ends:
|
||||
destinations: Sequence[Union[Send, str]] = [
|
||||
@@ -364,6 +366,9 @@ class Graph:
|
||||
for node in self.nodes:
|
||||
if node != start and node != branch.then:
|
||||
all_sources.add(node)
|
||||
for name, spec in self.nodes.items():
|
||||
if spec.ends:
|
||||
all_sources.add(name)
|
||||
# validate sources
|
||||
for source in all_sources:
|
||||
if source not in self.nodes and source != START:
|
||||
@@ -387,6 +392,9 @@ class Graph:
|
||||
for node in self.nodes:
|
||||
if node != start and node != branch.then:
|
||||
all_targets.add(node)
|
||||
for name, spec in self.nodes.items():
|
||||
if spec.ends:
|
||||
all_targets.update(spec.ends)
|
||||
# validate targets
|
||||
for node in self.nodes:
|
||||
if node not in all_targets:
|
||||
@@ -620,5 +628,9 @@ class CompiledGraph(Pregel):
|
||||
)
|
||||
if branch.then is not None:
|
||||
add_edge(end, branch.then)
|
||||
for key, n in self.builder.nodes.items():
|
||||
if n.ends:
|
||||
for end in n.ends:
|
||||
add_edge(key, end, conditional=True)
|
||||
|
||||
return graph
|
||||
|
||||
@@ -4,6 +4,7 @@ import typing
|
||||
import warnings
|
||||
from functools import partial
|
||||
from inspect import isclass, isfunction, ismethod, signature
|
||||
from types import FunctionType
|
||||
from typing import (
|
||||
Any,
|
||||
Callable,
|
||||
@@ -14,6 +15,7 @@ from typing import (
|
||||
Type,
|
||||
Union,
|
||||
cast,
|
||||
get_args,
|
||||
get_origin,
|
||||
get_type_hints,
|
||||
overload,
|
||||
@@ -32,7 +34,7 @@ from langgraph.channels.dynamic_barrier_value import DynamicBarrierValue, WaitFo
|
||||
from langgraph.channels.ephemeral_value import EphemeralValue
|
||||
from langgraph.channels.last_value import LastValue
|
||||
from langgraph.channels.named_barrier_value import NamedBarrierValue
|
||||
from langgraph.constants import NS_END, NS_SEP, TAG_HIDDEN
|
||||
from langgraph.constants import EMPTY_SEQ, NS_END, NS_SEP, SELF, TAG_HIDDEN
|
||||
from langgraph.errors import ErrorCode, InvalidUpdateError, create_error_message
|
||||
from langgraph.graph.graph import END, START, Branch, CompiledGraph, Graph, Send
|
||||
from langgraph.managed.base import (
|
||||
@@ -46,10 +48,10 @@ from langgraph.managed.base import (
|
||||
from langgraph.pregel.read import ChannelRead, PregelNode
|
||||
from langgraph.pregel.write import SKIP_WRITE, ChannelWrite, ChannelWriteEntry
|
||||
from langgraph.store.base import BaseStore
|
||||
from langgraph.types import All, Checkpointer, RetryPolicy
|
||||
from langgraph.types import All, Checkpointer, Control, RetryPolicy
|
||||
from langgraph.utils.fields import get_field_default
|
||||
from langgraph.utils.pydantic import create_model
|
||||
from langgraph.utils.runnable import coerce_to_runnable
|
||||
from langgraph.utils.runnable import RunnableCallable, coerce_to_runnable
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -71,6 +73,7 @@ class StateNodeSpec(NamedTuple):
|
||||
metadata: Optional[dict[str, Any]]
|
||||
input: Type[Any]
|
||||
retry_policy: Optional[RetryPolicy]
|
||||
ends: Optional[tuple[str, ...]] = EMPTY_SEQ
|
||||
|
||||
|
||||
class StateGraph(Graph):
|
||||
@@ -338,8 +341,33 @@ class StateGraph(Graph):
|
||||
f"'{character}' is a reserved character and is not allowed in the node names."
|
||||
)
|
||||
|
||||
if input is None:
|
||||
input = _get_input_schema_from_type_hint(action)
|
||||
ends = EMPTY_SEQ
|
||||
try:
|
||||
if (isfunction(action) or ismethod(getattr(action, "__call__", None))) and (
|
||||
hints := get_type_hints(getattr(action, "__call__"))
|
||||
or get_type_hints(action)
|
||||
):
|
||||
if input is None:
|
||||
first_parameter_name = next(
|
||||
iter(
|
||||
inspect.signature(
|
||||
cast(FunctionType, action)
|
||||
).parameters.keys()
|
||||
)
|
||||
)
|
||||
if input_hint := hints.get(first_parameter_name):
|
||||
if isinstance(input_hint, type) and get_type_hints(input_hint):
|
||||
input = input_hint
|
||||
if (
|
||||
(rtn := hints.get("return"))
|
||||
and get_origin(rtn) is Control
|
||||
and (rargs := get_args(rtn))
|
||||
and get_origin(rargs[0]) is Literal
|
||||
and (vals := get_args(rargs[0]))
|
||||
):
|
||||
ends = vals
|
||||
except (TypeError, StopIteration):
|
||||
pass
|
||||
if input is not None:
|
||||
self._add_schema(input)
|
||||
self.nodes[cast(str, node)] = StateNodeSpec(
|
||||
@@ -347,6 +375,7 @@ class StateGraph(Graph):
|
||||
metadata,
|
||||
input=input or self.schema,
|
||||
retry_policy=retry,
|
||||
ends=ends,
|
||||
)
|
||||
return self
|
||||
|
||||
@@ -401,9 +430,11 @@ class StateGraph(Graph):
|
||||
streamed, batched, and run asynchronously.
|
||||
|
||||
Args:
|
||||
checkpointer (Checkpointer): An optional checkpoint saver object.
|
||||
This serves as a fully versioned "memory" for the graph, allowing
|
||||
the graph to be paused and resumed, and replayed from any point.
|
||||
checkpointer (Optional[Union[Checkpointer, Literal[False]]]): A checkpoint saver object or flag.
|
||||
If provided, this Checkpointer serves as a fully versioned "short-term memory" for the graph,
|
||||
allowing it to be paused, resumed, and replayed from any point.
|
||||
If None, it may inherit the parent graph's checkpointer when used as a subgraph.
|
||||
If False, it will not use or inherit any checkpointer.
|
||||
interrupt_before (Optional[Sequence[str]]): An optional list of node names to interrupt before.
|
||||
interrupt_after (Optional[Sequence[str]]): An optional list of node names to interrupt after.
|
||||
debug (bool): A flag indicating whether to enable debug mode.
|
||||
@@ -468,6 +499,9 @@ class StateGraph(Graph):
|
||||
for key, node in self.nodes.items():
|
||||
compiled.attach_node(key, node)
|
||||
|
||||
for key, node in self.nodes.items():
|
||||
compiled.attach_branch(key, SELF, CONTROL_BRANCH, with_reader=False)
|
||||
|
||||
for start, end in self.edges:
|
||||
compiled.attach_edge(start, end)
|
||||
|
||||
@@ -518,11 +552,23 @@ class CompiledStateGraph(CompiledGraph):
|
||||
if is_writable_managed_value(v)
|
||||
]
|
||||
|
||||
def _get_root(input: Any) -> Any:
|
||||
if isinstance(input, Control):
|
||||
return input.update_state
|
||||
else:
|
||||
return input
|
||||
|
||||
def _get_state_key(input: Union[None, dict, Any], *, key: str) -> Any:
|
||||
if input is None:
|
||||
return SKIP_WRITE
|
||||
elif isinstance(input, dict):
|
||||
if all(k not in output_keys for k in input):
|
||||
raise InvalidUpdateError(
|
||||
f"Expected node {key} to update at least one of {output_keys}, got {input}"
|
||||
)
|
||||
return input.get(key, SKIP_WRITE)
|
||||
elif isinstance(input, Control):
|
||||
return _get_state_key(input.update_state, key=key)
|
||||
elif get_type_hints(type(input)):
|
||||
value = getattr(input, key, SKIP_WRITE)
|
||||
return value if value is not None else SKIP_WRITE
|
||||
@@ -535,7 +581,7 @@ class CompiledStateGraph(CompiledGraph):
|
||||
|
||||
# state updaters
|
||||
write_entries = (
|
||||
[ChannelWriteEntry("__root__", skip_none=True)]
|
||||
[ChannelWriteEntry("__root__", skip_none=True, mapper=_get_root)]
|
||||
if output_keys == ["__root__"]
|
||||
else [
|
||||
ChannelWriteEntry(key, mapper=partial(_get_state_key, key=key))
|
||||
@@ -578,7 +624,6 @@ class CompiledStateGraph(CompiledGraph):
|
||||
ChannelWrite(
|
||||
[ChannelWriteEntry(key, key)] + write_entries,
|
||||
tags=[TAG_HIDDEN],
|
||||
require_at_least_one_of=output_keys,
|
||||
),
|
||||
],
|
||||
metadata=node.metadata,
|
||||
@@ -615,7 +660,9 @@ class CompiledStateGraph(CompiledGraph):
|
||||
[ChannelWriteEntry(channel_name, start)], tags=[TAG_HIDDEN]
|
||||
)
|
||||
|
||||
def attach_branch(self, start: str, name: str, branch: Branch) -> None:
|
||||
def attach_branch(
|
||||
self, start: str, name: str, branch: Branch, *, with_reader: bool = True
|
||||
) -> None:
|
||||
def branch_writer(
|
||||
packets: Sequence[Union[str, Send]], config: RunnableConfig
|
||||
) -> None:
|
||||
@@ -648,7 +695,8 @@ class CompiledStateGraph(CompiledGraph):
|
||||
else self.builder.schema
|
||||
)
|
||||
self.nodes[start] |= branch.run(
|
||||
branch_writer, _get_state_reader(self.builder, schema)
|
||||
branch_writer,
|
||||
_get_state_reader(self.builder, schema) if with_reader else None,
|
||||
)
|
||||
|
||||
# attach branch subscribers
|
||||
@@ -697,6 +745,42 @@ def _coerce_state(schema: Type[Any], input: dict[str, Any]) -> dict[str, Any]:
|
||||
return schema(**input)
|
||||
|
||||
|
||||
def _control_branch(value: Any) -> Sequence[Union[str, Send]]:
|
||||
if not isinstance(value, Control):
|
||||
return EMPTY_SEQ
|
||||
rtn: list[Union[str, Send]] = []
|
||||
if isinstance(value.trigger, str):
|
||||
rtn.append(value.trigger)
|
||||
else:
|
||||
rtn.extend(value.trigger)
|
||||
if isinstance(value.send, Send):
|
||||
rtn.append(value.send)
|
||||
else:
|
||||
rtn.extend(value.send)
|
||||
return rtn
|
||||
|
||||
|
||||
async def _acontrol_branch(value: Any) -> None:
|
||||
if not isinstance(value, Control):
|
||||
return EMPTY_SEQ
|
||||
rtn: list[Union[str, Send]] = []
|
||||
if isinstance(value.trigger, str):
|
||||
rtn.append(value.trigger)
|
||||
else:
|
||||
rtn.extend(value.trigger)
|
||||
if isinstance(value.send, Send):
|
||||
rtn.append(value.send)
|
||||
else:
|
||||
rtn.extend(value.send)
|
||||
return rtn
|
||||
|
||||
|
||||
CONTROL_BRANCH_PATH = RunnableCallable(
|
||||
_control_branch, _acontrol_branch, tags=[TAG_HIDDEN], trace=False, recurse=False
|
||||
)
|
||||
CONTROL_BRANCH = Branch(CONTROL_BRANCH_PATH, None)
|
||||
|
||||
|
||||
def _get_channels(
|
||||
schema: Type[dict],
|
||||
) -> tuple[dict[str, BaseChannel], dict[str, ManagedValueSpec]]:
|
||||
@@ -823,21 +907,3 @@ def _get_schema(
|
||||
if k in channels and isinstance(channels[k], BaseChannel)
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _get_input_schema_from_type_hint(
|
||||
action: Optional[RunnableLike],
|
||||
) -> Optional[Type[Any]]:
|
||||
if not isfunction(action) and not ismethod(getattr(action, "__call__", None)):
|
||||
return None
|
||||
action = cast(Callable, action)
|
||||
|
||||
try:
|
||||
hints = get_type_hints(getattr(action, "__call__")) or get_type_hints(action)
|
||||
first_parameter_name = next(iter(inspect.signature(action).parameters.keys()))
|
||||
input_hint = hints.get(first_parameter_name)
|
||||
if isinstance(input_hint, type) and get_type_hints(input_hint):
|
||||
return input_hint
|
||||
except (TypeError, StopIteration):
|
||||
pass
|
||||
return None
|
||||
|
||||
@@ -11,6 +11,7 @@ from langchain_core.tools import BaseTool
|
||||
from typing_extensions import Annotated, TypedDict
|
||||
|
||||
from langgraph._api.deprecation import deprecated_parameter
|
||||
from langgraph.errors import ErrorCode, create_error_message
|
||||
from langgraph.graph import StateGraph
|
||||
from langgraph.graph.graph import CompiledGraph
|
||||
from langgraph.graph.message import add_messages
|
||||
@@ -161,6 +162,37 @@ def _should_bind_tools(model: LanguageModelLike, tools: Sequence[BaseTool]) -> b
|
||||
return False
|
||||
|
||||
|
||||
def _validate_chat_history(
|
||||
messages: Sequence[BaseMessage],
|
||||
) -> None:
|
||||
"""Validate that all tool calls in AIMessages have a corresponding ToolMessage."""
|
||||
all_tool_calls = [
|
||||
tool_call
|
||||
for message in messages
|
||||
if isinstance(message, AIMessage)
|
||||
for tool_call in message.tool_calls
|
||||
]
|
||||
tool_call_ids_with_results = {
|
||||
message.tool_call_id for message in messages if isinstance(message, ToolMessage)
|
||||
}
|
||||
tool_calls_without_results = [
|
||||
tool_call
|
||||
for tool_call in all_tool_calls
|
||||
if tool_call["id"] not in tool_call_ids_with_results
|
||||
]
|
||||
if not tool_calls_without_results:
|
||||
return
|
||||
|
||||
error_message = create_error_message(
|
||||
message="Found AIMessages with tool_calls that do not have a corresponding ToolMessage. "
|
||||
f"Here are the first few of those tool calls: {tool_calls_without_results[:3]}.\n\n"
|
||||
"Every tool call (LLM requesting to call a tool) in the message history MUST have a corresponding ToolMessage "
|
||||
"(result of a tool invocation to return to the LLM) - this is required by most LLM providers.",
|
||||
error_code=ErrorCode.INVALID_CHAT_HISTORY,
|
||||
)
|
||||
raise ValueError(error_message)
|
||||
|
||||
|
||||
@deprecated_parameter("messages_modifier", "0.1.9", "state_modifier", removal="0.3.0")
|
||||
def create_react_agent(
|
||||
model: LanguageModelLike,
|
||||
@@ -530,6 +562,7 @@ def create_react_agent(
|
||||
|
||||
# Define the function that calls the model
|
||||
def call_model(state: AgentState, config: RunnableConfig) -> AgentState:
|
||||
_validate_chat_history(state["messages"])
|
||||
response = model_runnable.invoke(state, config)
|
||||
has_tool_calls = isinstance(response, AIMessage) and response.tool_calls
|
||||
all_tools_return_direct = (
|
||||
@@ -566,6 +599,7 @@ def create_react_agent(
|
||||
return {"messages": [response]}
|
||||
|
||||
async def acall_model(state: AgentState, config: RunnableConfig) -> AgentState:
|
||||
_validate_chat_history(state["messages"])
|
||||
response = await model_runnable.ainvoke(state, config)
|
||||
has_tool_calls = isinstance(response, AIMessage) and response.tool_calls
|
||||
all_tools_return_direct = (
|
||||
|
||||
@@ -54,6 +54,7 @@ from langgraph.checkpoint.base import (
|
||||
)
|
||||
from langgraph.constants import (
|
||||
CONF,
|
||||
CONFIG_KEY_CHECKPOINT_ID,
|
||||
CONFIG_KEY_CHECKPOINT_NS,
|
||||
CONFIG_KEY_CHECKPOINTER,
|
||||
CONFIG_KEY_NODE_FINISHED,
|
||||
@@ -64,9 +65,11 @@ from langgraph.constants import (
|
||||
CONFIG_KEY_STREAM,
|
||||
CONFIG_KEY_STREAM_WRITER,
|
||||
CONFIG_KEY_TASK_ID,
|
||||
ERROR,
|
||||
INTERRUPT,
|
||||
NS_END,
|
||||
NS_SEP,
|
||||
SCHEDULED,
|
||||
)
|
||||
from langgraph.errors import (
|
||||
ErrorCode,
|
||||
@@ -439,6 +442,7 @@ class Pregel(PregelProtocol):
|
||||
config: RunnableConfig,
|
||||
saved: Optional[CheckpointTuple],
|
||||
recurse: Optional[BaseCheckpointSaver] = None,
|
||||
apply_pending_writes: bool = False,
|
||||
) -> StateSnapshot:
|
||||
if not saved:
|
||||
return StateSnapshot(
|
||||
@@ -469,7 +473,10 @@ class Pregel(PregelProtocol):
|
||||
managed,
|
||||
saved.config,
|
||||
saved.metadata.get("step", -1) + 1,
|
||||
for_execution=False,
|
||||
for_execution=True,
|
||||
store=self.store,
|
||||
checkpointer=self.checkpointer or None,
|
||||
manager=None,
|
||||
)
|
||||
# get the subgraphs
|
||||
subgraphs = dict(self.get_subgraphs())
|
||||
@@ -503,10 +510,20 @@ class Pregel(PregelProtocol):
|
||||
task_states[task.id] = subgraphs[task.name].get_state(
|
||||
config, subgraphs=True
|
||||
)
|
||||
# apply pending writes
|
||||
if apply_pending_writes and saved.pending_writes:
|
||||
for tid, k, v in saved.pending_writes:
|
||||
if k in (ERROR, INTERRUPT, SCHEDULED):
|
||||
continue
|
||||
if tid not in next_tasks:
|
||||
continue
|
||||
next_tasks[tid].writes.append((k, v))
|
||||
if tasks := [t for t in next_tasks.values() if t.writes]:
|
||||
apply_writes(saved.checkpoint, channels, tasks, None)
|
||||
# assemble the state snapshot
|
||||
return StateSnapshot(
|
||||
read_channels(channels, self.stream_channels_asis),
|
||||
tuple(t.name for t in next_tasks.values()),
|
||||
tuple(t.name for t in next_tasks.values() if not t.writes),
|
||||
patch_checkpoint_map(saved.config, saved.metadata),
|
||||
saved.metadata,
|
||||
saved.checkpoint["ts"],
|
||||
@@ -524,6 +541,7 @@ class Pregel(PregelProtocol):
|
||||
config: RunnableConfig,
|
||||
saved: Optional[CheckpointTuple],
|
||||
recurse: Optional[BaseCheckpointSaver] = None,
|
||||
apply_pending_writes: bool = False,
|
||||
) -> StateSnapshot:
|
||||
if not saved:
|
||||
return StateSnapshot(
|
||||
@@ -557,7 +575,10 @@ class Pregel(PregelProtocol):
|
||||
managed,
|
||||
saved.config,
|
||||
saved.metadata.get("step", -1) + 1,
|
||||
for_execution=False,
|
||||
for_execution=True,
|
||||
store=self.store,
|
||||
checkpointer=self.checkpointer or None,
|
||||
manager=None,
|
||||
)
|
||||
# get the subgraphs
|
||||
subgraphs = {n: g async for n, g in self.aget_subgraphs()}
|
||||
@@ -591,10 +612,20 @@ class Pregel(PregelProtocol):
|
||||
task_states[task.id] = await subgraphs[task.name].aget_state(
|
||||
config, subgraphs=True
|
||||
)
|
||||
# apply pending writes
|
||||
if apply_pending_writes and saved.pending_writes:
|
||||
for tid, k, v in saved.pending_writes:
|
||||
if k in (ERROR, INTERRUPT, SCHEDULED):
|
||||
continue
|
||||
if tid not in next_tasks:
|
||||
continue
|
||||
next_tasks[tid].writes.append((k, v))
|
||||
if tasks := [t for t in next_tasks.values() if t.writes]:
|
||||
apply_writes(saved.checkpoint, channels, tasks, None)
|
||||
# assemble the state snapshot
|
||||
return StateSnapshot(
|
||||
read_channels(channels, self.stream_channels_asis),
|
||||
tuple(t.name for t in next_tasks.values()),
|
||||
tuple(t.name for t in next_tasks.values() if not t.writes),
|
||||
patch_checkpoint_map(saved.config, saved.metadata),
|
||||
saved.metadata,
|
||||
saved.checkpoint["ts"],
|
||||
@@ -638,7 +669,10 @@ class Pregel(PregelProtocol):
|
||||
config = merge_configs(self.config, config) if self.config else config
|
||||
saved = checkpointer.get_tuple(config)
|
||||
return self._prepare_state_snapshot(
|
||||
config, saved, recurse=checkpointer if subgraphs else None
|
||||
config,
|
||||
saved,
|
||||
recurse=checkpointer if subgraphs else None,
|
||||
apply_pending_writes=CONFIG_KEY_CHECKPOINT_ID not in config[CONF],
|
||||
)
|
||||
|
||||
async def aget_state(
|
||||
@@ -672,7 +706,10 @@ class Pregel(PregelProtocol):
|
||||
config = merge_configs(self.config, config) if self.config else config
|
||||
saved = await checkpointer.aget_tuple(config)
|
||||
return await self._aprepare_state_snapshot(
|
||||
config, saved, recurse=checkpointer if subgraphs else None
|
||||
config,
|
||||
saved,
|
||||
recurse=checkpointer if subgraphs else None,
|
||||
apply_pending_writes=CONFIG_KEY_CHECKPOINT_ID not in config[CONF],
|
||||
)
|
||||
|
||||
def get_state_history(
|
||||
@@ -814,7 +851,7 @@ class Pregel(PregelProtocol):
|
||||
raise ValueError(f"Subgraph {recast_checkpoint_ns} not found")
|
||||
|
||||
# get last checkpoint
|
||||
config = merge_configs(self.config, config) if self.config else config
|
||||
config = ensure_config(self.config, config)
|
||||
saved = checkpointer.get_tuple(config)
|
||||
checkpoint = copy_checkpoint(saved.checkpoint) if saved else empty_checkpoint()
|
||||
checkpoint_previous_versions = (
|
||||
@@ -826,62 +863,91 @@ class Pregel(PregelProtocol):
|
||||
config,
|
||||
{CONFIG_KEY_CHECKPOINT_NS: config[CONF].get(CONFIG_KEY_CHECKPOINT_NS, "")},
|
||||
)
|
||||
checkpoint_metadata = config["metadata"]
|
||||
if saved:
|
||||
checkpoint_config = patch_configurable(config, saved.config[CONF])
|
||||
# find last node that updated the state, if not provided
|
||||
if values is None and as_node is None:
|
||||
next_config = checkpointer.put(
|
||||
checkpoint_config,
|
||||
create_checkpoint(checkpoint, None, step),
|
||||
{
|
||||
"source": "update",
|
||||
"step": step + 1,
|
||||
"writes": {},
|
||||
"parents": saved.metadata.get("parents", {}) if saved else {},
|
||||
},
|
||||
{},
|
||||
)
|
||||
return patch_checkpoint_map(next_config, saved.metadata if saved else None)
|
||||
elif as_node is None and not any(
|
||||
v for vv in checkpoint["versions_seen"].values() for v in vv.values()
|
||||
):
|
||||
if (
|
||||
isinstance(self.input_channels, str)
|
||||
and self.input_channels in self.nodes
|
||||
):
|
||||
as_node = self.input_channels
|
||||
elif as_node is None:
|
||||
last_seen_by_node = sorted(
|
||||
(v, n)
|
||||
for n, seen in checkpoint["versions_seen"].items()
|
||||
if n in self.nodes
|
||||
for v in seen.values()
|
||||
)
|
||||
# if two nodes updated the state at the same time, it's ambiguous
|
||||
if last_seen_by_node:
|
||||
if len(last_seen_by_node) == 1:
|
||||
as_node = last_seen_by_node[0][1]
|
||||
elif last_seen_by_node[-1][0] != last_seen_by_node[-2][0]:
|
||||
as_node = last_seen_by_node[-1][1]
|
||||
if as_node is None:
|
||||
raise InvalidUpdateError("Ambiguous update, specify as_node")
|
||||
if as_node not in self.nodes:
|
||||
raise InvalidUpdateError(f"Node {as_node} does not exist")
|
||||
# update channels
|
||||
checkpoint_metadata = {**saved.metadata, **checkpoint_metadata}
|
||||
with ChannelsManager(
|
||||
self.channels,
|
||||
checkpoint,
|
||||
LoopProtocol(config=config, step=step + 1, stop=step + 2),
|
||||
) as (
|
||||
channels,
|
||||
managed,
|
||||
):
|
||||
) as (channels, managed):
|
||||
# apply pending writes, if not on specific checkpoint
|
||||
if (
|
||||
CONFIG_KEY_CHECKPOINT_ID not in config[CONF]
|
||||
and saved is not None
|
||||
and saved.pending_writes
|
||||
):
|
||||
# tasks for this checkpoint
|
||||
next_tasks = prepare_next_tasks(
|
||||
checkpoint,
|
||||
self.nodes,
|
||||
channels,
|
||||
managed,
|
||||
saved.config,
|
||||
saved.metadata.get("step", -1) + 1,
|
||||
for_execution=True,
|
||||
store=self.store,
|
||||
checkpointer=self.checkpointer or None,
|
||||
manager=None,
|
||||
)
|
||||
# apply writes
|
||||
for tid, k, v in saved.pending_writes:
|
||||
if k in (ERROR, INTERRUPT, SCHEDULED):
|
||||
continue
|
||||
if tid not in next_tasks:
|
||||
continue
|
||||
next_tasks[tid].writes.append((k, v))
|
||||
if tasks := [t for t in next_tasks.values() if t.writes]:
|
||||
apply_writes(checkpoint, channels, tasks, None)
|
||||
# find last node that updated the state, if not provided
|
||||
if values is None and as_node is None:
|
||||
next_config = checkpointer.put(
|
||||
checkpoint_config,
|
||||
create_checkpoint(checkpoint, None, step),
|
||||
{
|
||||
**checkpoint_metadata,
|
||||
"source": "update",
|
||||
"step": step + 1,
|
||||
"writes": {},
|
||||
"parents": saved.metadata.get("parents", {}) if saved else {},
|
||||
},
|
||||
{},
|
||||
)
|
||||
return patch_checkpoint_map(
|
||||
next_config, saved.metadata if saved else None
|
||||
)
|
||||
elif as_node is None and not any(
|
||||
v for vv in checkpoint["versions_seen"].values() for v in vv.values()
|
||||
):
|
||||
if (
|
||||
isinstance(self.input_channels, str)
|
||||
and self.input_channels in self.nodes
|
||||
):
|
||||
as_node = self.input_channels
|
||||
elif as_node is None:
|
||||
last_seen_by_node = sorted(
|
||||
(v, n)
|
||||
for n, seen in checkpoint["versions_seen"].items()
|
||||
if n in self.nodes
|
||||
for v in seen.values()
|
||||
)
|
||||
# if two nodes updated the state at the same time, it's ambiguous
|
||||
if last_seen_by_node:
|
||||
if len(last_seen_by_node) == 1:
|
||||
as_node = last_seen_by_node[0][1]
|
||||
elif last_seen_by_node[-1][0] != last_seen_by_node[-2][0]:
|
||||
as_node = last_seen_by_node[-1][1]
|
||||
if as_node is None:
|
||||
raise InvalidUpdateError("Ambiguous update, specify as_node")
|
||||
if as_node not in self.nodes:
|
||||
raise InvalidUpdateError(f"Node {as_node} does not exist")
|
||||
# create task to run all writers of the chosen node
|
||||
writers = self.nodes[as_node].flat_writers
|
||||
if not writers:
|
||||
raise InvalidUpdateError(f"Node {as_node} has no writers")
|
||||
writes: deque[tuple[str, Any]] = deque()
|
||||
task = PregelTaskWrites(as_node, writes, [INTERRUPT])
|
||||
task = PregelTaskWrites((), as_node, writes, [INTERRUPT])
|
||||
task_id = str(uuid5(UUID(checkpoint["id"]), INTERRUPT))
|
||||
run = RunnableSequence(*writers) if len(writers) > 1 else writers[0]
|
||||
# execute task
|
||||
@@ -922,6 +988,7 @@ class Pregel(PregelProtocol):
|
||||
checkpoint_config,
|
||||
checkpoint,
|
||||
{
|
||||
**checkpoint_metadata,
|
||||
"source": "update",
|
||||
"step": step + 1,
|
||||
"writes": {as_node: values},
|
||||
@@ -966,7 +1033,7 @@ class Pregel(PregelProtocol):
|
||||
raise ValueError(f"Subgraph {recast_checkpoint_ns} not found")
|
||||
|
||||
# get last checkpoint
|
||||
config = merge_configs(self.config, config) if self.config else config
|
||||
config = ensure_config(self.config, config)
|
||||
saved = await checkpointer.aget_tuple(config)
|
||||
checkpoint = copy_checkpoint(saved.checkpoint) if saved else empty_checkpoint()
|
||||
checkpoint_previous_versions = (
|
||||
@@ -978,46 +1045,10 @@ class Pregel(PregelProtocol):
|
||||
config,
|
||||
{CONFIG_KEY_CHECKPOINT_NS: config[CONF].get(CONFIG_KEY_CHECKPOINT_NS, "")},
|
||||
)
|
||||
checkpoint_metadata = config["metadata"]
|
||||
if saved:
|
||||
checkpoint_config = patch_configurable(config, saved.config[CONF])
|
||||
# find last node that updated the state, if not provided
|
||||
if values is None and as_node is None:
|
||||
next_config = await checkpointer.aput(
|
||||
checkpoint_config,
|
||||
create_checkpoint(checkpoint, None, step),
|
||||
{
|
||||
"source": "update",
|
||||
"step": step + 1,
|
||||
"writes": {},
|
||||
"parents": saved.metadata.get("parents", {}) if saved else {},
|
||||
},
|
||||
{},
|
||||
)
|
||||
return patch_checkpoint_map(next_config, saved.metadata if saved else None)
|
||||
elif as_node is None and not saved:
|
||||
if (
|
||||
isinstance(self.input_channels, str)
|
||||
and self.input_channels in self.nodes
|
||||
):
|
||||
as_node = self.input_channels
|
||||
elif as_node is None:
|
||||
last_seen_by_node = sorted(
|
||||
(v, n)
|
||||
for n, seen in checkpoint["versions_seen"].items()
|
||||
if n in self.nodes
|
||||
for v in seen.values()
|
||||
)
|
||||
# if two nodes updated the state at the same time, it's ambiguous
|
||||
if last_seen_by_node:
|
||||
if len(last_seen_by_node) == 1:
|
||||
as_node = last_seen_by_node[0][1]
|
||||
elif last_seen_by_node[-1][0] != last_seen_by_node[-2][0]:
|
||||
as_node = last_seen_by_node[-1][1]
|
||||
if as_node is None:
|
||||
raise InvalidUpdateError("Ambiguous update, specify as_node")
|
||||
if as_node not in self.nodes:
|
||||
raise InvalidUpdateError(f"Node {as_node} does not exist")
|
||||
# update channels, acting as the chosen node
|
||||
checkpoint_metadata = {**saved.metadata, **checkpoint_metadata}
|
||||
async with AsyncChannelsManager(
|
||||
self.channels,
|
||||
checkpoint,
|
||||
@@ -1026,12 +1057,79 @@ class Pregel(PregelProtocol):
|
||||
channels,
|
||||
managed,
|
||||
):
|
||||
# apply pending writes, if not on specific checkpoint
|
||||
if (
|
||||
CONFIG_KEY_CHECKPOINT_ID not in config[CONF]
|
||||
and saved is not None
|
||||
and saved.pending_writes
|
||||
):
|
||||
# tasks for this checkpoint
|
||||
next_tasks = prepare_next_tasks(
|
||||
checkpoint,
|
||||
self.nodes,
|
||||
channels,
|
||||
managed,
|
||||
saved.config,
|
||||
saved.metadata.get("step", -1) + 1,
|
||||
for_execution=True,
|
||||
store=self.store,
|
||||
checkpointer=self.checkpointer or None,
|
||||
manager=None,
|
||||
)
|
||||
for tid, k, v in saved.pending_writes:
|
||||
if k in (ERROR, INTERRUPT, SCHEDULED):
|
||||
continue
|
||||
if tid not in next_tasks:
|
||||
continue
|
||||
next_tasks[tid].writes.append((k, v))
|
||||
if tasks := [t for t in next_tasks.values() if t.writes]:
|
||||
apply_writes(checkpoint, channels, tasks, None)
|
||||
# find last node that updated the state, if not provided
|
||||
if values is None and as_node is None:
|
||||
next_config = await checkpointer.aput(
|
||||
checkpoint_config,
|
||||
create_checkpoint(checkpoint, None, step),
|
||||
{
|
||||
**checkpoint_metadata,
|
||||
"source": "update",
|
||||
"step": step + 1,
|
||||
"writes": {},
|
||||
"parents": saved.metadata.get("parents", {}) if saved else {},
|
||||
},
|
||||
{},
|
||||
)
|
||||
return patch_checkpoint_map(
|
||||
next_config, saved.metadata if saved else None
|
||||
)
|
||||
elif as_node is None and not saved:
|
||||
if (
|
||||
isinstance(self.input_channels, str)
|
||||
and self.input_channels in self.nodes
|
||||
):
|
||||
as_node = self.input_channels
|
||||
elif as_node is None:
|
||||
last_seen_by_node = sorted(
|
||||
(v, n)
|
||||
for n, seen in checkpoint["versions_seen"].items()
|
||||
if n in self.nodes
|
||||
for v in seen.values()
|
||||
)
|
||||
# if two nodes updated the state at the same time, it's ambiguous
|
||||
if last_seen_by_node:
|
||||
if len(last_seen_by_node) == 1:
|
||||
as_node = last_seen_by_node[0][1]
|
||||
elif last_seen_by_node[-1][0] != last_seen_by_node[-2][0]:
|
||||
as_node = last_seen_by_node[-1][1]
|
||||
if as_node is None:
|
||||
raise InvalidUpdateError("Ambiguous update, specify as_node")
|
||||
if as_node not in self.nodes:
|
||||
raise InvalidUpdateError(f"Node {as_node} does not exist")
|
||||
# create task to run all writers of the chosen node
|
||||
writers = self.nodes[as_node].flat_writers
|
||||
if not writers:
|
||||
raise InvalidUpdateError(f"Node {as_node} has no writers")
|
||||
writes: deque[tuple[str, Any]] = deque()
|
||||
task = PregelTaskWrites(as_node, writes, [INTERRUPT])
|
||||
task = PregelTaskWrites((), as_node, writes, [INTERRUPT])
|
||||
task_id = str(uuid5(UUID(checkpoint["id"]), INTERRUPT))
|
||||
run = RunnableSequence(*writers) if len(writers) > 1 else writers[0]
|
||||
# execute task
|
||||
@@ -1072,6 +1170,7 @@ class Pregel(PregelProtocol):
|
||||
checkpoint_config,
|
||||
checkpoint,
|
||||
{
|
||||
**checkpoint_metadata,
|
||||
"source": "update",
|
||||
"step": step + 1,
|
||||
"writes": {as_node: values},
|
||||
|
||||
@@ -67,6 +67,9 @@ class WritesProtocol(Protocol):
|
||||
"""Protocol for objects containing writes to be applied to checkpoint.
|
||||
Implemented by PregelTaskWrites and PregelExecutableTask."""
|
||||
|
||||
@property
|
||||
def path(self) -> tuple[Union[str, int], ...]: ...
|
||||
|
||||
@property
|
||||
def name(self) -> str: ...
|
||||
|
||||
@@ -81,6 +84,7 @@ class PregelTaskWrites(NamedTuple):
|
||||
"""Simplest implementation of WritesProtocol, for usage with writes that
|
||||
don't originate from a runnable task, eg. graph input, update_state, etc."""
|
||||
|
||||
path: tuple[Union[str, int], ...]
|
||||
name: str
|
||||
writes: Sequence[tuple[str, Any]]
|
||||
triggers: Sequence[str]
|
||||
@@ -190,6 +194,9 @@ def apply_writes(
|
||||
"""Apply writes from a set of tasks (usually the tasks from a Pregel step)
|
||||
to the checkpoint and channels, and return managed values writes to be applied
|
||||
externally."""
|
||||
# sort tasks on path
|
||||
tasks = sorted(tasks, key=lambda t: t.path)
|
||||
|
||||
# update seen versions
|
||||
for task in tasks:
|
||||
checkpoint["versions_seen"].setdefault(task.name, {}).update(
|
||||
@@ -444,7 +451,9 @@ def prepare_single_task(
|
||||
checkpoint,
|
||||
channels,
|
||||
managed,
|
||||
PregelTaskWrites(packet.node, writes, triggers),
|
||||
PregelTaskWrites(
|
||||
task_path, packet.node, writes, triggers
|
||||
),
|
||||
config,
|
||||
),
|
||||
CONFIG_KEY_STORE: (
|
||||
@@ -552,7 +561,7 @@ def prepare_single_task(
|
||||
checkpoint,
|
||||
channels,
|
||||
managed,
|
||||
PregelTaskWrites(name, writes, triggers),
|
||||
PregelTaskWrites(task_path, name, writes, triggers),
|
||||
config,
|
||||
),
|
||||
CONFIG_KEY_STORE: (
|
||||
|
||||
@@ -399,6 +399,9 @@ class PregelLoop(LoopProtocol):
|
||||
for task in self.tasks.values():
|
||||
if task.writes:
|
||||
self._output_writes(task.id, task.writes, cached=True)
|
||||
elif not self.skip_done_tasks:
|
||||
# "not skip_done_tasks" only applies to first tick after resuming
|
||||
self.skip_done_tasks = True
|
||||
|
||||
# if all tasks have finished, re-tick
|
||||
if all(task.writes for task in self.tasks.values()):
|
||||
@@ -474,7 +477,10 @@ class PregelLoop(LoopProtocol):
|
||||
mv_writes = apply_writes(
|
||||
self.checkpoint,
|
||||
self.channels,
|
||||
[*discard_tasks.values(), PregelTaskWrites(INPUT, input_writes, [])],
|
||||
[
|
||||
*discard_tasks.values(),
|
||||
PregelTaskWrites((), INPUT, input_writes, []),
|
||||
],
|
||||
self.checkpointer_get_next_version,
|
||||
)
|
||||
assert not mv_writes, "Can't write to SharedValues in graph input"
|
||||
@@ -491,6 +497,8 @@ class PregelLoop(LoopProtocol):
|
||||
)
|
||||
|
||||
def _put_checkpoint(self, metadata: CheckpointMetadata) -> None:
|
||||
for k, v in self.config["metadata"].items():
|
||||
metadata.setdefault(k, v) # type: ignore
|
||||
# assign step and parents
|
||||
metadata["step"] = self.step
|
||||
metadata["parents"] = self.config[CONF].get(CONFIG_KEY_CHECKPOINT_MAP, {})
|
||||
|
||||
@@ -133,10 +133,20 @@ class RemoteGraph(PregelProtocol):
|
||||
nodes = {}
|
||||
for node in graph["nodes"]:
|
||||
node_id = str(node["id"])
|
||||
node_data = node.get("data", {})
|
||||
|
||||
# Get node name from node_data if available. If not, use node_id.
|
||||
node_name = node.get("name")
|
||||
if node_name is None:
|
||||
if isinstance(node_data, dict):
|
||||
node_name = node_data.get("name", node_id)
|
||||
else:
|
||||
node_name = node_id
|
||||
|
||||
nodes[node_id] = DrawableNode(
|
||||
id=node_id,
|
||||
name=node.get("name", ""),
|
||||
data=node.get("data", {}),
|
||||
name=node_name,
|
||||
data=node_data,
|
||||
metadata=node.get("metadata"),
|
||||
)
|
||||
return nodes
|
||||
@@ -538,6 +548,13 @@ class RemoteGraph(PregelProtocol):
|
||||
if "messages" in updated_stream_modes:
|
||||
updated_stream_modes.remove("messages")
|
||||
updated_stream_modes.append("messages-tuple")
|
||||
|
||||
# if requested "messages-tuple",
|
||||
# map to "messages" in requested_stream_modes
|
||||
if "messages-tuple" in requested_stream_modes:
|
||||
requested_stream_modes.remove("messages-tuple")
|
||||
requested_stream_modes.append("messages")
|
||||
|
||||
# add 'updates' mode if not present
|
||||
if "updates" not in updated_stream_modes:
|
||||
updated_stream_modes.append("updates")
|
||||
|
||||
@@ -4,11 +4,13 @@ from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
Generic,
|
||||
Literal,
|
||||
NamedTuple,
|
||||
Optional,
|
||||
Sequence,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
@@ -221,6 +223,34 @@ class Send:
|
||||
)
|
||||
|
||||
|
||||
N = TypeVar("N")
|
||||
|
||||
|
||||
class Control(Generic[N]):
|
||||
"""A control object to update the graph's state, trigger nodes, and send messages."""
|
||||
|
||||
__slots__ = ("update_state", "trigger", "send")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
update_state: Optional[dict[str, Any]] = None,
|
||||
trigger: Union[str, Sequence[str]] = (),
|
||||
send: Union[Send, Sequence[Send]] = (),
|
||||
) -> None:
|
||||
self.update_state = update_state
|
||||
self.trigger = trigger
|
||||
self.send = send
|
||||
|
||||
def __repr__(self) -> str:
|
||||
contents = ", ".join(
|
||||
f"{key}={value!r}"
|
||||
for key in self.__slots__
|
||||
if (value := getattr(self, key))
|
||||
)
|
||||
return f"Control({contents})"
|
||||
|
||||
|
||||
StreamChunk = tuple[tuple[str, ...], str, Any]
|
||||
|
||||
|
||||
|
||||
Generated
+108
-108
@@ -1336,13 +1336,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "langchain-core"
|
||||
version = "0.3.14"
|
||||
version = "0.3.15"
|
||||
description = "Building applications with LLMs through composability"
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.9"
|
||||
files = [
|
||||
{file = "langchain_core-0.3.14-py3-none-any.whl", hash = "sha256:3b1e031820ef60af7134ffb7fd15393c5be87031b4288cc1978130f4f468dca8"},
|
||||
{file = "langchain_core-0.3.14.tar.gz", hash = "sha256:cef68958d59d0970a89c778004dfa7d4559a89139abbf8b0e348e18511792e84"},
|
||||
{file = "langchain_core-0.3.15-py3-none-any.whl", hash = "sha256:3d4ca6dbb8ed396a6ee061063832a2451b0ce8c345570f7b086ffa7288e4fa29"},
|
||||
{file = "langchain_core-0.3.15.tar.gz", hash = "sha256:b1a29787a4ffb7ec2103b4e97d435287201da7809b369740dd1e32f176325aba"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -2829,114 +2829,114 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "rpds-py"
|
||||
version = "0.20.0"
|
||||
version = "0.20.1"
|
||||
description = "Python bindings to Rust's persistent data structures (rpds)"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "rpds_py-0.20.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3ad0fda1635f8439cde85c700f964b23ed5fc2d28016b32b9ee5fe30da5c84e2"},
|
||||
{file = "rpds_py-0.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9bb4a0d90fdb03437c109a17eade42dfbf6190408f29b2744114d11586611d6f"},
|
||||
{file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6377e647bbfd0a0b159fe557f2c6c602c159fc752fa316572f012fc0bf67150"},
|
||||
{file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb851b7df9dda52dc1415ebee12362047ce771fc36914586b2e9fcbd7d293b3e"},
|
||||
{file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e0f80b739e5a8f54837be5d5c924483996b603d5502bfff79bf33da06164ee2"},
|
||||
{file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a8c94dad2e45324fc74dce25e1645d4d14df9a4e54a30fa0ae8bad9a63928e3"},
|
||||
{file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e604fe73ba048c06085beaf51147eaec7df856824bfe7b98657cf436623daf"},
|
||||
{file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:df3de6b7726b52966edf29663e57306b23ef775faf0ac01a3e9f4012a24a4140"},
|
||||
{file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf258ede5bc22a45c8e726b29835b9303c285ab46fc7c3a4cc770736b5304c9f"},
|
||||
{file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:55fea87029cded5df854ca7e192ec7bdb7ecd1d9a3f63d5c4eb09148acf4a7ce"},
|
||||
{file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ae94bd0b2f02c28e199e9bc51485d0c5601f58780636185660f86bf80c89af94"},
|
||||
{file = "rpds_py-0.20.0-cp310-none-win32.whl", hash = "sha256:28527c685f237c05445efec62426d285e47a58fb05ba0090a4340b73ecda6dee"},
|
||||
{file = "rpds_py-0.20.0-cp310-none-win_amd64.whl", hash = "sha256:238a2d5b1cad28cdc6ed15faf93a998336eb041c4e440dd7f902528b8891b399"},
|
||||
{file = "rpds_py-0.20.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac2f4f7a98934c2ed6505aead07b979e6f999389f16b714448fb39bbaa86a489"},
|
||||
{file = "rpds_py-0.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:220002c1b846db9afd83371d08d239fdc865e8f8c5795bbaec20916a76db3318"},
|
||||
{file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d7919548df3f25374a1f5d01fbcd38dacab338ef5f33e044744b5c36729c8db"},
|
||||
{file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:758406267907b3781beee0f0edfe4a179fbd97c0be2e9b1154d7f0a1279cf8e5"},
|
||||
{file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d61339e9f84a3f0767b1995adfb171a0d00a1185192718a17af6e124728e0f5"},
|
||||
{file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1259c7b3705ac0a0bd38197565a5d603218591d3f6cee6e614e380b6ba61c6f6"},
|
||||
{file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c1dc0f53856b9cc9a0ccca0a7cc61d3d20a7088201c0937f3f4048c1718a209"},
|
||||
{file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7e60cb630f674a31f0368ed32b2a6b4331b8350d67de53c0359992444b116dd3"},
|
||||
{file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbe982f38565bb50cb7fb061ebf762c2f254ca3d8c20d4006878766e84266272"},
|
||||
{file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:514b3293b64187172bc77c8fb0cdae26981618021053b30d8371c3a902d4d5ad"},
|
||||
{file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d0a26ffe9d4dd35e4dfdd1e71f46401cff0181c75ac174711ccff0459135fa58"},
|
||||
{file = "rpds_py-0.20.0-cp311-none-win32.whl", hash = "sha256:89c19a494bf3ad08c1da49445cc5d13d8fefc265f48ee7e7556839acdacf69d0"},
|
||||
{file = "rpds_py-0.20.0-cp311-none-win_amd64.whl", hash = "sha256:c638144ce971df84650d3ed0096e2ae7af8e62ecbbb7b201c8935c370df00a2c"},
|
||||
{file = "rpds_py-0.20.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a84ab91cbe7aab97f7446652d0ed37d35b68a465aeef8fc41932a9d7eee2c1a6"},
|
||||
{file = "rpds_py-0.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:56e27147a5a4c2c21633ff8475d185734c0e4befd1c989b5b95a5d0db699b21b"},
|
||||
{file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2580b0c34583b85efec8c5c5ec9edf2dfe817330cc882ee972ae650e7b5ef739"},
|
||||
{file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b80d4a7900cf6b66bb9cee5c352b2d708e29e5a37fe9bf784fa97fc11504bf6c"},
|
||||
{file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50eccbf054e62a7b2209b28dc7a22d6254860209d6753e6b78cfaeb0075d7bee"},
|
||||
{file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:49a8063ea4296b3a7e81a5dfb8f7b2d73f0b1c20c2af401fb0cdf22e14711a96"},
|
||||
{file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea438162a9fcbee3ecf36c23e6c68237479f89f962f82dae83dc15feeceb37e4"},
|
||||
{file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:18d7585c463087bddcfa74c2ba267339f14f2515158ac4db30b1f9cbdb62c8ef"},
|
||||
{file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d4c7d1a051eeb39f5c9547e82ea27cbcc28338482242e3e0b7768033cb083821"},
|
||||
{file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4df1e3b3bec320790f699890d41c59d250f6beda159ea3c44c3f5bac1976940"},
|
||||
{file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2cf126d33a91ee6eedc7f3197b53e87a2acdac63602c0f03a02dd69e4b138174"},
|
||||
{file = "rpds_py-0.20.0-cp312-none-win32.whl", hash = "sha256:8bc7690f7caee50b04a79bf017a8d020c1f48c2a1077ffe172abec59870f1139"},
|
||||
{file = "rpds_py-0.20.0-cp312-none-win_amd64.whl", hash = "sha256:0e13e6952ef264c40587d510ad676a988df19adea20444c2b295e536457bc585"},
|
||||
{file = "rpds_py-0.20.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:aa9a0521aeca7d4941499a73ad7d4f8ffa3d1affc50b9ea11d992cd7eff18a29"},
|
||||
{file = "rpds_py-0.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1f1d51eccb7e6c32ae89243cb352389228ea62f89cd80823ea7dd1b98e0b91"},
|
||||
{file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a86a9b96070674fc88b6f9f71a97d2c1d3e5165574615d1f9168ecba4cecb24"},
|
||||
{file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c8ef2ebf76df43f5750b46851ed1cdf8f109d7787ca40035fe19fbdc1acc5a7"},
|
||||
{file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b74b25f024b421d5859d156750ea9a65651793d51b76a2e9238c05c9d5f203a9"},
|
||||
{file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57eb94a8c16ab08fef6404301c38318e2c5a32216bf5de453e2714c964c125c8"},
|
||||
{file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1940dae14e715e2e02dfd5b0f64a52e8374a517a1e531ad9412319dc3ac7879"},
|
||||
{file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d20277fd62e1b992a50c43f13fbe13277a31f8c9f70d59759c88f644d66c619f"},
|
||||
{file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:06db23d43f26478303e954c34c75182356ca9aa7797d22c5345b16871ab9c45c"},
|
||||
{file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b2a5db5397d82fa847e4c624b0c98fe59d2d9b7cf0ce6de09e4d2e80f8f5b3f2"},
|
||||
{file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a35df9f5548fd79cb2f52d27182108c3e6641a4feb0f39067911bf2adaa3e57"},
|
||||
{file = "rpds_py-0.20.0-cp313-none-win32.whl", hash = "sha256:fd2d84f40633bc475ef2d5490b9c19543fbf18596dcb1b291e3a12ea5d722f7a"},
|
||||
{file = "rpds_py-0.20.0-cp313-none-win_amd64.whl", hash = "sha256:9bc2d153989e3216b0559251b0c260cfd168ec78b1fac33dd485750a228db5a2"},
|
||||
{file = "rpds_py-0.20.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:f2fbf7db2012d4876fb0d66b5b9ba6591197b0f165db8d99371d976546472a24"},
|
||||
{file = "rpds_py-0.20.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1e5f3cd7397c8f86c8cc72d5a791071431c108edd79872cdd96e00abd8497d29"},
|
||||
{file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce9845054c13696f7af7f2b353e6b4f676dab1b4b215d7fe5e05c6f8bb06f965"},
|
||||
{file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c3e130fd0ec56cb76eb49ef52faead8ff09d13f4527e9b0c400307ff72b408e1"},
|
||||
{file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b16aa0107ecb512b568244ef461f27697164d9a68d8b35090e9b0c1c8b27752"},
|
||||
{file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa7f429242aae2947246587d2964fad750b79e8c233a2367f71b554e9447949c"},
|
||||
{file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af0fc424a5842a11e28956e69395fbbeab2c97c42253169d87e90aac2886d751"},
|
||||
{file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b8c00a3b1e70c1d3891f0db1b05292747f0dbcfb49c43f9244d04c70fbc40eb8"},
|
||||
{file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:40ce74fc86ee4645d0a225498d091d8bc61f39b709ebef8204cb8b5a464d3c0e"},
|
||||
{file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4fe84294c7019456e56d93e8ababdad5a329cd25975be749c3f5f558abb48253"},
|
||||
{file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:338ca4539aad4ce70a656e5187a3a31c5204f261aef9f6ab50e50bcdffaf050a"},
|
||||
{file = "rpds_py-0.20.0-cp38-none-win32.whl", hash = "sha256:54b43a2b07db18314669092bb2de584524d1ef414588780261e31e85846c26a5"},
|
||||
{file = "rpds_py-0.20.0-cp38-none-win_amd64.whl", hash = "sha256:a1862d2d7ce1674cffa6d186d53ca95c6e17ed2b06b3f4c476173565c862d232"},
|
||||
{file = "rpds_py-0.20.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3fde368e9140312b6e8b6c09fb9f8c8c2f00999d1823403ae90cc00480221b22"},
|
||||
{file = "rpds_py-0.20.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9824fb430c9cf9af743cf7aaf6707bf14323fb51ee74425c380f4c846ea70789"},
|
||||
{file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11ef6ce74616342888b69878d45e9f779b95d4bd48b382a229fe624a409b72c5"},
|
||||
{file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c52d3f2f82b763a24ef52f5d24358553e8403ce05f893b5347098014f2d9eff2"},
|
||||
{file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d35cef91e59ebbeaa45214861874bc6f19eb35de96db73e467a8358d701a96c"},
|
||||
{file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d72278a30111e5b5525c1dd96120d9e958464316f55adb030433ea905866f4de"},
|
||||
{file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4c29cbbba378759ac5786730d1c3cb4ec6f8ababf5c42a9ce303dc4b3d08cda"},
|
||||
{file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6632f2d04f15d1bd6fe0eedd3b86d9061b836ddca4c03d5cf5c7e9e6b7c14580"},
|
||||
{file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d0b67d87bb45ed1cd020e8fbf2307d449b68abc45402fe1a4ac9e46c3c8b192b"},
|
||||
{file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ec31a99ca63bf3cd7f1a5ac9fe95c5e2d060d3c768a09bc1d16e235840861420"},
|
||||
{file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22e6c9976e38f4d8c4a63bd8a8edac5307dffd3ee7e6026d97f3cc3a2dc02a0b"},
|
||||
{file = "rpds_py-0.20.0-cp39-none-win32.whl", hash = "sha256:569b3ea770c2717b730b61998b6c54996adee3cef69fc28d444f3e7920313cf7"},
|
||||
{file = "rpds_py-0.20.0-cp39-none-win_amd64.whl", hash = "sha256:e6900ecdd50ce0facf703f7a00df12374b74bbc8ad9fe0f6559947fb20f82364"},
|
||||
{file = "rpds_py-0.20.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:617c7357272c67696fd052811e352ac54ed1d9b49ab370261a80d3b6ce385045"},
|
||||
{file = "rpds_py-0.20.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9426133526f69fcaba6e42146b4e12d6bc6c839b8b555097020e2b78ce908dcc"},
|
||||
{file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deb62214c42a261cb3eb04d474f7155279c1a8a8c30ac89b7dcb1721d92c3c02"},
|
||||
{file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fcaeb7b57f1a1e071ebd748984359fef83ecb026325b9d4ca847c95bc7311c92"},
|
||||
{file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d454b8749b4bd70dd0a79f428731ee263fa6995f83ccb8bada706e8d1d3ff89d"},
|
||||
{file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d807dc2051abe041b6649681dce568f8e10668e3c1c6543ebae58f2d7e617855"},
|
||||
{file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3c20f0ddeb6e29126d45f89206b8291352b8c5b44384e78a6499d68b52ae511"},
|
||||
{file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7f19250ceef892adf27f0399b9e5afad019288e9be756d6919cb58892129f51"},
|
||||
{file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4f1ed4749a08379555cebf4650453f14452eaa9c43d0a95c49db50c18b7da075"},
|
||||
{file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:dcedf0b42bcb4cfff4101d7771a10532415a6106062f005ab97d1d0ab5681c60"},
|
||||
{file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:39ed0d010457a78f54090fafb5d108501b5aa5604cc22408fc1c0c77eac14344"},
|
||||
{file = "rpds_py-0.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bb273176be34a746bdac0b0d7e4e2c467323d13640b736c4c477881a3220a989"},
|
||||
{file = "rpds_py-0.20.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f918a1a130a6dfe1d7fe0f105064141342e7dd1611f2e6a21cd2f5c8cb1cfb3e"},
|
||||
{file = "rpds_py-0.20.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f60012a73aa396be721558caa3a6fd49b3dd0033d1675c6d59c4502e870fcf0c"},
|
||||
{file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d2b1ad682a3dfda2a4e8ad8572f3100f95fad98cb99faf37ff0ddfe9cbf9d03"},
|
||||
{file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:614fdafe9f5f19c63ea02817fa4861c606a59a604a77c8cdef5aa01d28b97921"},
|
||||
{file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa518bcd7600c584bf42e6617ee8132869e877db2f76bcdc281ec6a4113a53ab"},
|
||||
{file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0475242f447cc6cb8a9dd486d68b2ef7fbee84427124c232bff5f63b1fe11e5"},
|
||||
{file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f90a4cd061914a60bd51c68bcb4357086991bd0bb93d8aa66a6da7701370708f"},
|
||||
{file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:def7400461c3a3f26e49078302e1c1b38f6752342c77e3cf72ce91ca69fb1bc1"},
|
||||
{file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:65794e4048ee837494aea3c21a28ad5fc080994dfba5b036cf84de37f7ad5074"},
|
||||
{file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:faefcc78f53a88f3076b7f8be0a8f8d35133a3ecf7f3770895c25f8813460f08"},
|
||||
{file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:5b4f105deeffa28bbcdff6c49b34e74903139afa690e35d2d9e3c2c2fba18cec"},
|
||||
{file = "rpds_py-0.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fdfc3a892927458d98f3d55428ae46b921d1f7543b89382fdb483f5640daaec8"},
|
||||
{file = "rpds_py-0.20.0.tar.gz", hash = "sha256:d72a210824facfdaf8768cf2d7ca25a042c30320b3020de2fa04640920d4e121"},
|
||||
{file = "rpds_py-0.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a649dfd735fff086e8a9d0503a9f0c7d01b7912a333c7ae77e1515c08c146dad"},
|
||||
{file = "rpds_py-0.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f16bc1334853e91ddaaa1217045dd7be166170beec337576818461268a3de67f"},
|
||||
{file = "rpds_py-0.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14511a539afee6f9ab492b543060c7491c99924314977a55c98bfa2ee29ce78c"},
|
||||
{file = "rpds_py-0.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3ccb8ac2d3c71cda472b75af42818981bdacf48d2e21c36331b50b4f16930163"},
|
||||
{file = "rpds_py-0.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c142b88039b92e7e0cb2552e8967077e3179b22359e945574f5e2764c3953dcf"},
|
||||
{file = "rpds_py-0.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f19169781dddae7478a32301b499b2858bc52fc45a112955e798ee307e294977"},
|
||||
{file = "rpds_py-0.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13c56de6518e14b9bf6edde23c4c39dac5b48dcf04160ea7bce8fca8397cdf86"},
|
||||
{file = "rpds_py-0.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:925d176a549f4832c6f69fa6026071294ab5910e82a0fe6c6228fce17b0706bd"},
|
||||
{file = "rpds_py-0.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:78f0b6877bfce7a3d1ff150391354a410c55d3cdce386f862926a4958ad5ab7e"},
|
||||
{file = "rpds_py-0.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3dd645e2b0dcb0fd05bf58e2e54c13875847687d0b71941ad2e757e5d89d4356"},
|
||||
{file = "rpds_py-0.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4f676e21db2f8c72ff0936f895271e7a700aa1f8d31b40e4e43442ba94973899"},
|
||||
{file = "rpds_py-0.20.1-cp310-none-win32.whl", hash = "sha256:648386ddd1e19b4a6abab69139b002bc49ebf065b596119f8f37c38e9ecee8ff"},
|
||||
{file = "rpds_py-0.20.1-cp310-none-win_amd64.whl", hash = "sha256:d9ecb51120de61e4604650666d1f2b68444d46ae18fd492245a08f53ad2b7711"},
|
||||
{file = "rpds_py-0.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:762703bdd2b30983c1d9e62b4c88664df4a8a4d5ec0e9253b0231171f18f6d75"},
|
||||
{file = "rpds_py-0.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0b581f47257a9fce535c4567782a8976002d6b8afa2c39ff616edf87cbeff712"},
|
||||
{file = "rpds_py-0.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:842c19a6ce894493563c3bd00d81d5100e8e57d70209e84d5491940fdb8b9e3a"},
|
||||
{file = "rpds_py-0.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42cbde7789f5c0bcd6816cb29808e36c01b960fb5d29f11e052215aa85497c93"},
|
||||
{file = "rpds_py-0.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c8e9340ce5a52f95fa7d3b552b35c7e8f3874d74a03a8a69279fd5fca5dc751"},
|
||||
{file = "rpds_py-0.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ba6f89cac95c0900d932c9efb7f0fb6ca47f6687feec41abcb1bd5e2bd45535"},
|
||||
{file = "rpds_py-0.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a916087371afd9648e1962e67403c53f9c49ca47b9680adbeef79da3a7811b0"},
|
||||
{file = "rpds_py-0.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:200a23239781f46149e6a415f1e870c5ef1e712939fe8fa63035cd053ac2638e"},
|
||||
{file = "rpds_py-0.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:58b1d5dd591973d426cbb2da5e27ba0339209832b2f3315928c9790e13f159e8"},
|
||||
{file = "rpds_py-0.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6b73c67850ca7cae0f6c56f71e356d7e9fa25958d3e18a64927c2d930859b8e4"},
|
||||
{file = "rpds_py-0.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d8761c3c891cc51e90bc9926d6d2f59b27beaf86c74622c8979380a29cc23ac3"},
|
||||
{file = "rpds_py-0.20.1-cp311-none-win32.whl", hash = "sha256:cd945871335a639275eee904caef90041568ce3b42f402c6959b460d25ae8732"},
|
||||
{file = "rpds_py-0.20.1-cp311-none-win_amd64.whl", hash = "sha256:7e21b7031e17c6b0e445f42ccc77f79a97e2687023c5746bfb7a9e45e0921b84"},
|
||||
{file = "rpds_py-0.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:36785be22066966a27348444b40389f8444671630063edfb1a2eb04318721e17"},
|
||||
{file = "rpds_py-0.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:142c0a5124d9bd0e2976089484af5c74f47bd3298f2ed651ef54ea728d2ea42c"},
|
||||
{file = "rpds_py-0.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbddc10776ca7ebf2a299c41a4dde8ea0d8e3547bfd731cb87af2e8f5bf8962d"},
|
||||
{file = "rpds_py-0.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15a842bb369e00295392e7ce192de9dcbf136954614124a667f9f9f17d6a216f"},
|
||||
{file = "rpds_py-0.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be5ef2f1fc586a7372bfc355986226484e06d1dc4f9402539872c8bb99e34b01"},
|
||||
{file = "rpds_py-0.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbcf360c9e3399b056a238523146ea77eeb2a596ce263b8814c900263e46031a"},
|
||||
{file = "rpds_py-0.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecd27a66740ffd621d20b9a2f2b5ee4129a56e27bfb9458a3bcc2e45794c96cb"},
|
||||
{file = "rpds_py-0.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0b937b2a1988f184a3e9e577adaa8aede21ec0b38320d6009e02bd026db04fa"},
|
||||
{file = "rpds_py-0.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6889469bfdc1eddf489729b471303739bf04555bb151fe8875931f8564309afc"},
|
||||
{file = "rpds_py-0.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:19b73643c802f4eaf13d97f7855d0fb527fbc92ab7013c4ad0e13a6ae0ed23bd"},
|
||||
{file = "rpds_py-0.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3c6afcf2338e7f374e8edc765c79fbcb4061d02b15dd5f8f314a4af2bdc7feb5"},
|
||||
{file = "rpds_py-0.20.1-cp312-none-win32.whl", hash = "sha256:dc73505153798c6f74854aba69cc75953888cf9866465196889c7cdd351e720c"},
|
||||
{file = "rpds_py-0.20.1-cp312-none-win_amd64.whl", hash = "sha256:8bbe951244a838a51289ee53a6bae3a07f26d4e179b96fc7ddd3301caf0518eb"},
|
||||
{file = "rpds_py-0.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6ca91093a4a8da4afae7fe6a222c3b53ee4eef433ebfee4d54978a103435159e"},
|
||||
{file = "rpds_py-0.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b9c2fe36d1f758b28121bef29ed1dee9b7a2453e997528e7d1ac99b94892527c"},
|
||||
{file = "rpds_py-0.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f009c69bc8c53db5dfab72ac760895dc1f2bc1b62ab7408b253c8d1ec52459fc"},
|
||||
{file = "rpds_py-0.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6740a3e8d43a32629bb9b009017ea5b9e713b7210ba48ac8d4cb6d99d86c8ee8"},
|
||||
{file = "rpds_py-0.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:32b922e13d4c0080d03e7b62991ad7f5007d9cd74e239c4b16bc85ae8b70252d"},
|
||||
{file = "rpds_py-0.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe00a9057d100e69b4ae4a094203a708d65b0f345ed546fdef86498bf5390982"},
|
||||
{file = "rpds_py-0.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49fe9b04b6fa685bd39237d45fad89ba19e9163a1ccaa16611a812e682913496"},
|
||||
{file = "rpds_py-0.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:aa7ac11e294304e615b43f8c441fee5d40094275ed7311f3420d805fde9b07b4"},
|
||||
{file = "rpds_py-0.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aa97af1558a9bef4025f8f5d8c60d712e0a3b13a2fe875511defc6ee77a1ab7"},
|
||||
{file = "rpds_py-0.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:483b29f6f7ffa6af845107d4efe2e3fa8fb2693de8657bc1849f674296ff6a5a"},
|
||||
{file = "rpds_py-0.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:37fe0f12aebb6a0e3e17bb4cd356b1286d2d18d2e93b2d39fe647138458b4bcb"},
|
||||
{file = "rpds_py-0.20.1-cp313-none-win32.whl", hash = "sha256:a624cc00ef2158e04188df5e3016385b9353638139a06fb77057b3498f794782"},
|
||||
{file = "rpds_py-0.20.1-cp313-none-win_amd64.whl", hash = "sha256:b71b8666eeea69d6363248822078c075bac6ed135faa9216aa85f295ff009b1e"},
|
||||
{file = "rpds_py-0.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:5b48e790e0355865197ad0aca8cde3d8ede347831e1959e158369eb3493d2191"},
|
||||
{file = "rpds_py-0.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3e310838a5801795207c66c73ea903deda321e6146d6f282e85fa7e3e4854804"},
|
||||
{file = "rpds_py-0.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2249280b870e6a42c0d972339e9cc22ee98730a99cd7f2f727549af80dd5a963"},
|
||||
{file = "rpds_py-0.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e79059d67bea28b53d255c1437b25391653263f0e69cd7dec170d778fdbca95e"},
|
||||
{file = "rpds_py-0.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b431c777c9653e569986ecf69ff4a5dba281cded16043d348bf9ba505486f36"},
|
||||
{file = "rpds_py-0.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da584ff96ec95e97925174eb8237e32f626e7a1a97888cdd27ee2f1f24dd0ad8"},
|
||||
{file = "rpds_py-0.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a0629ec053fc013808a85178524e3cb63a61dbc35b22499870194a63578fb9"},
|
||||
{file = "rpds_py-0.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fbf15aff64a163db29a91ed0868af181d6f68ec1a3a7d5afcfe4501252840bad"},
|
||||
{file = "rpds_py-0.20.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:07924c1b938798797d60c6308fa8ad3b3f0201802f82e4a2c41bb3fafb44cc28"},
|
||||
{file = "rpds_py-0.20.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4a5a844f68776a7715ecb30843b453f07ac89bad393431efbf7accca3ef599c1"},
|
||||
{file = "rpds_py-0.20.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:518d2ca43c358929bf08f9079b617f1c2ca6e8848f83c1225c88caeac46e6cbc"},
|
||||
{file = "rpds_py-0.20.1-cp38-none-win32.whl", hash = "sha256:3aea7eed3e55119635a74bbeb80b35e776bafccb70d97e8ff838816c124539f1"},
|
||||
{file = "rpds_py-0.20.1-cp38-none-win_amd64.whl", hash = "sha256:7dca7081e9a0c3b6490a145593f6fe3173a94197f2cb9891183ef75e9d64c425"},
|
||||
{file = "rpds_py-0.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b41b6321805c472f66990c2849e152aff7bc359eb92f781e3f606609eac877ad"},
|
||||
{file = "rpds_py-0.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a90c373ea2975519b58dece25853dbcb9779b05cc46b4819cb1917e3b3215b6"},
|
||||
{file = "rpds_py-0.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16d4477bcb9fbbd7b5b0e4a5d9b493e42026c0bf1f06f723a9353f5153e75d30"},
|
||||
{file = "rpds_py-0.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:84b8382a90539910b53a6307f7c35697bc7e6ffb25d9c1d4e998a13e842a5e83"},
|
||||
{file = "rpds_py-0.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4888e117dd41b9d34194d9e31631af70d3d526efc363085e3089ab1a62c32ed1"},
|
||||
{file = "rpds_py-0.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5265505b3d61a0f56618c9b941dc54dc334dc6e660f1592d112cd103d914a6db"},
|
||||
{file = "rpds_py-0.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e75ba609dba23f2c95b776efb9dd3f0b78a76a151e96f96cc5b6b1b0004de66f"},
|
||||
{file = "rpds_py-0.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1791ff70bc975b098fe6ecf04356a10e9e2bd7dc21fa7351c1742fdeb9b4966f"},
|
||||
{file = "rpds_py-0.20.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d126b52e4a473d40232ec2052a8b232270ed1f8c9571aaf33f73a14cc298c24f"},
|
||||
{file = "rpds_py-0.20.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c14937af98c4cc362a1d4374806204dd51b1e12dded1ae30645c298e5a5c4cb1"},
|
||||
{file = "rpds_py-0.20.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3d089d0b88996df627693639d123c8158cff41c0651f646cd8fd292c7da90eaf"},
|
||||
{file = "rpds_py-0.20.1-cp39-none-win32.whl", hash = "sha256:653647b8838cf83b2e7e6a0364f49af96deec64d2a6578324db58380cff82aca"},
|
||||
{file = "rpds_py-0.20.1-cp39-none-win_amd64.whl", hash = "sha256:fa41a64ac5b08b292906e248549ab48b69c5428f3987b09689ab2441f267d04d"},
|
||||
{file = "rpds_py-0.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7a07ced2b22f0cf0b55a6a510078174c31b6d8544f3bc00c2bcee52b3d613f74"},
|
||||
{file = "rpds_py-0.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:68cb0a499f2c4a088fd2f521453e22ed3527154136a855c62e148b7883b99f9a"},
|
||||
{file = "rpds_py-0.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa3060d885657abc549b2a0f8e1b79699290e5d83845141717c6c90c2df38311"},
|
||||
{file = "rpds_py-0.20.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:95f3b65d2392e1c5cec27cff08fdc0080270d5a1a4b2ea1d51d5f4a2620ff08d"},
|
||||
{file = "rpds_py-0.20.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2cc3712a4b0b76a1d45a9302dd2f53ff339614b1c29603a911318f2357b04dd2"},
|
||||
{file = "rpds_py-0.20.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d4eea0761e37485c9b81400437adb11c40e13ef513375bbd6973e34100aeb06"},
|
||||
{file = "rpds_py-0.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f5179583d7a6cdb981151dd349786cbc318bab54963a192692d945dd3f6435d"},
|
||||
{file = "rpds_py-0.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2fbb0ffc754490aff6dabbf28064be47f0f9ca0b9755976f945214965b3ace7e"},
|
||||
{file = "rpds_py-0.20.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:a94e52537a0e0a85429eda9e49f272ada715506d3b2431f64b8a3e34eb5f3e75"},
|
||||
{file = "rpds_py-0.20.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:92b68b79c0da2a980b1c4197e56ac3dd0c8a149b4603747c4378914a68706979"},
|
||||
{file = "rpds_py-0.20.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:93da1d3db08a827eda74356f9f58884adb254e59b6664f64cc04cdff2cc19b0d"},
|
||||
{file = "rpds_py-0.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:754bbed1a4ca48479e9d4182a561d001bbf81543876cdded6f695ec3d465846b"},
|
||||
{file = "rpds_py-0.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ca449520e7484534a2a44faf629362cae62b660601432d04c482283c47eaebab"},
|
||||
{file = "rpds_py-0.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:9c4cb04a16b0f199a8c9bf807269b2f63b7b5b11425e4a6bd44bd6961d28282c"},
|
||||
{file = "rpds_py-0.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63804105143c7e24cee7db89e37cb3f3941f8e80c4379a0b355c52a52b6780"},
|
||||
{file = "rpds_py-0.20.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:55cd1fa4ecfa6d9f14fbd97ac24803e6f73e897c738f771a9fe038f2f11ff07c"},
|
||||
{file = "rpds_py-0.20.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f8f741b6292c86059ed175d80eefa80997125b7c478fb8769fd9ac8943a16c0"},
|
||||
{file = "rpds_py-0.20.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fc212779bf8411667234b3cdd34d53de6c2b8b8b958e1e12cb473a5f367c338"},
|
||||
{file = "rpds_py-0.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ad56edabcdb428c2e33bbf24f255fe2b43253b7d13a2cdbf05de955217313e6"},
|
||||
{file = "rpds_py-0.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0a3a1e9ee9728b2c1734f65d6a1d376c6f2f6fdcc13bb007a08cc4b1ff576dc5"},
|
||||
{file = "rpds_py-0.20.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:e13de156137b7095442b288e72f33503a469aa1980ed856b43c353ac86390519"},
|
||||
{file = "rpds_py-0.20.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:07f59760ef99f31422c49038964b31c4dfcfeb5d2384ebfc71058a7c9adae2d2"},
|
||||
{file = "rpds_py-0.20.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:59240685e7da61fb78f65a9f07f8108e36a83317c53f7b276b4175dc44151684"},
|
||||
{file = "rpds_py-0.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:83cba698cfb3c2c5a7c3c6bac12fe6c6a51aae69513726be6411076185a8b24a"},
|
||||
{file = "rpds_py-0.20.1.tar.gz", hash = "sha256:e1791c4aabd117653530dccd24108fa03cc6baf21f58b950d0a73c3b3b29a350"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3425,4 +3425,4 @@ type = ["pytest-mypy"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.9.0,<4.0"
|
||||
content-hash = "6ee10fe34b2f65b33184c95d3713962afe4ca7923fbcca9b85fe6168555cff43"
|
||||
content-hash = "f0b7800f90041227ba4d7b72482c70fabdbacc95e8a3a784fdebc88721728a91"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "langgraph"
|
||||
version = "0.2.42"
|
||||
version = "0.2.45"
|
||||
description = "Building stateful, multi-actor applications with LLMs"
|
||||
authors = []
|
||||
license = "MIT"
|
||||
@@ -9,7 +9,7 @@ repository = "https://www.github.com/langchain-ai/langgraph"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.9.0,<4.0"
|
||||
langchain-core = ">=0.2.42,<0.4.0,!=0.3.0,!=0.3.1,!=0.3.2,!=0.3.3,!=0.3.4,!=0.3.5,!=0.3.6,!=0.3.7,!=0.3.8,!=0.3.9,!=0.3.10,!=0.3.11,!=0.3.12,!=0.3.13"
|
||||
langchain-core = ">=0.2.43,<0.4.0,!=0.3.0,!=0.3.1,!=0.3.2,!=0.3.3,!=0.3.4,!=0.3.5,!=0.3.6,!=0.3.7,!=0.3.8,!=0.3.9,!=0.3.10,!=0.3.11,!=0.3.12,!=0.3.13,!=0.3.14"
|
||||
langgraph-checkpoint = "^2.0.0"
|
||||
langgraph-sdk = "^0.1.32"
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ from langgraph.prebuilt import (
|
||||
create_react_agent,
|
||||
tools_condition,
|
||||
)
|
||||
from langgraph.prebuilt.chat_agent_executor import _validate_chat_history
|
||||
from langgraph.prebuilt.tool_node import (
|
||||
TOOL_CALL_ERROR_TEMPLATE,
|
||||
InjectedState,
|
||||
@@ -157,6 +158,7 @@ def test_no_modifier(request: pytest.FixtureRequest, checkpointer_name: str) ->
|
||||
"source": "loop",
|
||||
"writes": {"agent": {"messages": [AIMessage(content="hi?", id="0")]}},
|
||||
"step": 1,
|
||||
"thread_id": "123",
|
||||
}
|
||||
assert saved.pending_writes == []
|
||||
|
||||
@@ -188,6 +190,7 @@ async def test_no_modifier_async(checkpointer_name: str) -> None:
|
||||
"source": "loop",
|
||||
"writes": {"agent": {"messages": [AIMessage(content="hi?", id="0")]}},
|
||||
"step": 1,
|
||||
"thread_id": "123",
|
||||
}
|
||||
assert saved.pending_writes == []
|
||||
|
||||
@@ -378,6 +381,71 @@ def test_model_with_tools(tool_style: str):
|
||||
create_react_agent(model.bind_tools([tool1]), [tool2])
|
||||
|
||||
|
||||
def test__validate_messages():
|
||||
# empty input
|
||||
_validate_chat_history([])
|
||||
|
||||
# single human message
|
||||
_validate_chat_history(
|
||||
[
|
||||
HumanMessage(content="What's the weather?"),
|
||||
]
|
||||
)
|
||||
|
||||
# human + AI
|
||||
_validate_chat_history(
|
||||
[
|
||||
HumanMessage(content="What's the weather?"),
|
||||
AIMessage(content="The weather is sunny and 75°F."),
|
||||
]
|
||||
)
|
||||
|
||||
# Answered tool calls
|
||||
_validate_chat_history(
|
||||
[
|
||||
HumanMessage(content="What's the weather?"),
|
||||
AIMessage(
|
||||
content="Let me check that for you.",
|
||||
tool_calls=[{"id": "call1", "name": "get_weather", "args": {}}],
|
||||
),
|
||||
ToolMessage(content="Sunny, 75°F", tool_call_id="call1"),
|
||||
AIMessage(content="The weather is sunny and 75°F."),
|
||||
]
|
||||
)
|
||||
|
||||
# Unanswered tool calls
|
||||
with pytest.raises(ValueError):
|
||||
_validate_chat_history(
|
||||
[
|
||||
AIMessage(
|
||||
content="I'll check that for you.",
|
||||
tool_calls=[
|
||||
{"id": "call1", "name": "get_weather", "args": {}},
|
||||
{"id": "call2", "name": "get_time", "args": {}},
|
||||
],
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
_validate_chat_history(
|
||||
[
|
||||
HumanMessage(content="What's the weather and time?"),
|
||||
AIMessage(
|
||||
content="I'll check that for you.",
|
||||
tool_calls=[
|
||||
{"id": "call1", "name": "get_weather", "args": {}},
|
||||
{"id": "call2", "name": "get_time", "args": {}},
|
||||
],
|
||||
),
|
||||
ToolMessage(content="Sunny, 75°F", tool_call_id="call1"),
|
||||
AIMessage(
|
||||
content="The weather is sunny and 75°F. Let me check the time."
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test__infer_handled_types() -> None:
|
||||
def handle(e): # type: ignore
|
||||
return ""
|
||||
|
||||
+1475
-31
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -54,7 +54,7 @@ def test_get_graph():
|
||||
"type": "runnable",
|
||||
"data": {
|
||||
"id": ["langgraph", "utils", "RunnableCallable"],
|
||||
"name": "agent",
|
||||
"name": "agent_1",
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -71,13 +71,15 @@ def test_get_graph():
|
||||
|
||||
assert drawable_graph.nodes == {
|
||||
"__start__": DrawableNode(
|
||||
id="__start__", name="", data="__start__", metadata=None
|
||||
id="__start__", name="__start__", data="__start__", metadata=None
|
||||
),
|
||||
"__end__": DrawableNode(
|
||||
id="__end__", name="__end__", data="__end__", metadata=None
|
||||
),
|
||||
"__end__": DrawableNode(id="__end__", name="", data="__end__", metadata=None),
|
||||
"agent": DrawableNode(
|
||||
id="agent",
|
||||
name="",
|
||||
data={"id": ["langgraph", "utils", "RunnableCallable"], "name": "agent"},
|
||||
name="agent_1",
|
||||
data={"id": ["langgraph", "utils", "RunnableCallable"], "name": "agent_1"},
|
||||
metadata=None,
|
||||
),
|
||||
}
|
||||
@@ -101,7 +103,7 @@ async def test_aget_graph():
|
||||
"type": "runnable",
|
||||
"data": {
|
||||
"id": ["langgraph", "utils", "RunnableCallable"],
|
||||
"name": "agent",
|
||||
"name": "agent_1",
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -118,13 +120,15 @@ async def test_aget_graph():
|
||||
|
||||
assert drawable_graph.nodes == {
|
||||
"__start__": DrawableNode(
|
||||
id="__start__", name="", data="__start__", metadata=None
|
||||
id="__start__", name="__start__", data="__start__", metadata=None
|
||||
),
|
||||
"__end__": DrawableNode(
|
||||
id="__end__", name="__end__", data="__end__", metadata=None
|
||||
),
|
||||
"__end__": DrawableNode(id="__end__", name="", data="__end__", metadata=None),
|
||||
"agent": DrawableNode(
|
||||
id="agent",
|
||||
name="",
|
||||
data={"id": ["langgraph", "utils", "RunnableCallable"], "name": "agent"},
|
||||
name="agent_1",
|
||||
data={"id": ["langgraph", "utils", "RunnableCallable"], "name": "agent_1"},
|
||||
metadata=None,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@langchain/langgraph-sdk",
|
||||
"version": "0.0.20",
|
||||
"version": "0.0.21",
|
||||
"description": "Client library for interacting with the LangGraph API",
|
||||
"type": "module",
|
||||
"packageManager": "yarn@1.22.19",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
Assistant,
|
||||
AssistantGraph,
|
||||
CancelAction,
|
||||
Config,
|
||||
DefaultValues,
|
||||
GraphSchema,
|
||||
@@ -935,17 +936,20 @@ export class RunsClient extends BaseClient {
|
||||
* @param threadId The ID of the thread.
|
||||
* @param runId The ID of the run.
|
||||
* @param wait Whether to block when canceling
|
||||
* @param action Action to take when cancelling the run. Possible values are `interrupt` or `rollback`. Default is `interrupt`.
|
||||
* @returns
|
||||
*/
|
||||
async cancel(
|
||||
threadId: string,
|
||||
runId: string,
|
||||
wait: boolean = false,
|
||||
action: CancelAction = "interrupt",
|
||||
): Promise<void> {
|
||||
return this.fetch<void>(`/threads/${threadId}/runs/${runId}/cancel`, {
|
||||
method: "POST",
|
||||
params: {
|
||||
wait: wait ? "1" : "0",
|
||||
action: action,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ export type ThreadStatus = "idle" | "busy" | "interrupted" | "error";
|
||||
|
||||
type MultitaskStrategy = "reject" | "interrupt" | "rollback" | "enqueue";
|
||||
|
||||
export type CancelAction = "interrupt" | "rollback";
|
||||
|
||||
export interface Config {
|
||||
/**
|
||||
* Tags for this call and any sub-calls (eg. a Chain calling an LLM).
|
||||
@@ -77,7 +79,17 @@ export interface GraphSchema {
|
||||
|
||||
export type Subgraphs = Record<string, GraphSchema>;
|
||||
|
||||
export type Metadata = Optional<Record<string, unknown>>;
|
||||
export type Metadata = Optional<{
|
||||
source?: "input" | "loop" | "update" | (string & {});
|
||||
|
||||
step?: number;
|
||||
|
||||
writes?: Record<string, unknown> | null;
|
||||
|
||||
parents?: Record<string, string>;
|
||||
|
||||
[key: string]: unknown;
|
||||
}>;
|
||||
|
||||
export interface AssistantBase {
|
||||
/** The ID of the assistant. */
|
||||
@@ -108,7 +120,22 @@ export interface Assistant extends AssistantBase {
|
||||
/** The name of the assistant */
|
||||
name: string;
|
||||
}
|
||||
export type AssistantGraph = Record<string, Array<Record<string, unknown>>>;
|
||||
|
||||
export interface AssistantGraph {
|
||||
nodes: Array<{
|
||||
id: string | number;
|
||||
name?: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
data?: Record<string, any> | string;
|
||||
metadata?: unknown;
|
||||
}>;
|
||||
edges: Array<{
|
||||
source: string;
|
||||
target: string;
|
||||
data?: string;
|
||||
conditional?: boolean;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface Thread<ValuesType = DefaultValues> {
|
||||
/** The ID of the thread. */
|
||||
@@ -182,7 +209,7 @@ export interface ThreadTask {
|
||||
id: string;
|
||||
name: string;
|
||||
error: Optional<string>;
|
||||
interrupts: Array<Record<string, unknown>>;
|
||||
interrupts: Array<{ value: unknown; when: "during" }>;
|
||||
checkpoint: Optional<Checkpoint>;
|
||||
state: Optional<ThreadState>;
|
||||
}
|
||||
|
||||
@@ -45,12 +45,12 @@ interface RunsInvokePayload {
|
||||
/**
|
||||
* Interrupt execution before entering these nodes.
|
||||
*/
|
||||
interruptBefore?: string[];
|
||||
interruptBefore?: "*" | string[];
|
||||
|
||||
/**
|
||||
* Interrupt execution after leaving these nodes.
|
||||
*/
|
||||
interruptAfter?: string[];
|
||||
interruptAfter?: "*" | string[];
|
||||
|
||||
/**
|
||||
* Strategy to handle concurrent runs on the same thread. Only relevant if
|
||||
|
||||
@@ -34,6 +34,7 @@ from langgraph_sdk.schema import (
|
||||
All,
|
||||
Assistant,
|
||||
AssistantVersion,
|
||||
CancelAction,
|
||||
Checkpoint,
|
||||
Config,
|
||||
Cron,
|
||||
@@ -1712,13 +1713,22 @@ class RunsClient:
|
||||
|
||||
return await self.http.get(f"/threads/{thread_id}/runs/{run_id}")
|
||||
|
||||
async def cancel(self, thread_id: str, run_id: str, *, wait: bool = False) -> None:
|
||||
async def cancel(
|
||||
self,
|
||||
thread_id: str,
|
||||
run_id: str,
|
||||
*,
|
||||
wait: bool = False,
|
||||
action: CancelAction = "interrupt",
|
||||
) -> None:
|
||||
"""Get a run.
|
||||
|
||||
Args:
|
||||
thread_id: The thread ID to cancel.
|
||||
run_id: The run ID to cancek.
|
||||
wait: Whether to wait until run has completed.
|
||||
action: Action to take when cancelling the run. Possible values
|
||||
are `interrupt` or `rollback`. Default is `interrupt`.
|
||||
|
||||
Returns:
|
||||
None
|
||||
@@ -1728,12 +1738,13 @@ class RunsClient:
|
||||
await client.runs.cancel(
|
||||
thread_id="thread_id_to_cancel",
|
||||
run_id="run_id_to_cancel",
|
||||
wait=True
|
||||
wait=True,
|
||||
action="interrupt"
|
||||
)
|
||||
|
||||
""" # noqa: E501
|
||||
return await self.http.post(
|
||||
f"/threads/{thread_id}/runs/{run_id}/cancel?wait={1 if wait else 0}",
|
||||
f"/threads/{thread_id}/runs/{run_id}/cancel?wait={1 if wait else 0}&action={action}",
|
||||
json=None,
|
||||
)
|
||||
|
||||
@@ -3792,13 +3803,22 @@ class SyncRunsClient:
|
||||
|
||||
return self.http.get(f"/threads/{thread_id}/runs/{run_id}")
|
||||
|
||||
def cancel(self, thread_id: str, run_id: str, *, wait: bool = False) -> None:
|
||||
def cancel(
|
||||
self,
|
||||
thread_id: str,
|
||||
run_id: str,
|
||||
*,
|
||||
wait: bool = False,
|
||||
action: CancelAction = "interrupt",
|
||||
) -> None:
|
||||
"""Get a run.
|
||||
|
||||
Args:
|
||||
thread_id: The thread ID to cancel.
|
||||
run_id: The run ID to cancek.
|
||||
wait: Whether to wait until run has completed.
|
||||
action: Action to take when cancelling the run. Possible values
|
||||
are `interrupt` or `rollback`. Default is `interrupt`.
|
||||
|
||||
Returns:
|
||||
None
|
||||
@@ -3808,12 +3828,13 @@ class SyncRunsClient:
|
||||
client.runs.cancel(
|
||||
thread_id="thread_id_to_cancel",
|
||||
run_id="run_id_to_cancel",
|
||||
wait=True
|
||||
wait=True,
|
||||
action="interrupt"
|
||||
)
|
||||
|
||||
""" # noqa: E501
|
||||
return self.http.post(
|
||||
f"/threads/{thread_id}/runs/{run_id}/cancel?wait={1 if wait else 0}",
|
||||
f"/threads/{thread_id}/runs/{run_id}/cancel?wait={1 if wait else 0}&action={action}",
|
||||
json=None,
|
||||
)
|
||||
|
||||
|
||||
@@ -78,6 +78,13 @@ Specifies behavior if the thread doesn't exist:
|
||||
- "reject": Reject the operation if the thread doesn't exist.
|
||||
"""
|
||||
|
||||
CancelAction = Literal["interrupt", "rollback"]
|
||||
"""
|
||||
Action to take when cancelling the run.
|
||||
- "interrupt": Simply cancel the run.
|
||||
- "rollback": Cancel the run. Then delete the run and associated checkpoints.
|
||||
"""
|
||||
|
||||
|
||||
class Config(TypedDict, total=False):
|
||||
"""Configuration options for a call."""
|
||||
|
||||
Generated
+4
-4
@@ -2810,13 +2810,13 @@ tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "langchain-core"
|
||||
version = "0.3.8"
|
||||
version = "0.3.15"
|
||||
description = "Building applications with LLMs through composability"
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.9"
|
||||
files = [
|
||||
{file = "langchain_core-0.3.8-py3-none-any.whl", hash = "sha256:07015f7b1d9f52eefe05130e8cafe4dcbdbbf72a8411c9edafe38422e4d11b5c"},
|
||||
{file = "langchain_core-0.3.8.tar.gz", hash = "sha256:7485904f7082f1df880d5ae470a488161616132f30d99f556a1877901fffd1cb"},
|
||||
{file = "langchain_core-0.3.15-py3-none-any.whl", hash = "sha256:3d4ca6dbb8ed396a6ee061063832a2451b0ce8c345570f7b086ffa7288e4fa29"},
|
||||
{file = "langchain_core-0.3.15.tar.gz", hash = "sha256:b1a29787a4ffb7ec2103b4e97d435287201da7809b369740dd1e32f176325aba"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -2828,7 +2828,7 @@ pydantic = [
|
||||
{version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""},
|
||||
]
|
||||
PyYAML = ">=5.3"
|
||||
tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0"
|
||||
tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10.0.0"
|
||||
typing-extensions = ">=4.7"
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in New Issue
Block a user