Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36fa8e0097 | ||
|
|
bcb11640a5 | ||
|
|
b6c3a0d0fd | ||
|
|
8b14b6a9f0 | ||
|
|
8c63cc1778 | ||
|
|
6756e91ffc | ||
|
|
fc5d919aee | ||
|
|
9bd351b80f | ||
|
|
83c3f86159 | ||
|
|
3a41a2addc | ||
|
|
3eca363c23 | ||
|
|
2f1e864570 | ||
|
|
79a1ce6804 | ||
|
|
deb99a9acb | ||
|
|
781d0cf27a | ||
|
|
8a02ddd868 | ||
|
|
5f7dcbb07a | ||
|
|
9615580a66 | ||
|
|
d45eb0f9f2 | ||
|
|
4caa483478 | ||
|
|
006305f6a8 | ||
|
|
d9b7aaa5cc | ||
|
|
d2794eda0a | ||
|
|
9cbef9b542 | ||
|
|
343dc2d37a | ||
|
|
ca0ff1d334 | ||
|
|
f5663ffa49 | ||
|
|
e7477a9315 | ||
|
|
18c083b60a | ||
|
|
2f0e3c66d1 | ||
|
|
d9ec185e72 | ||
|
|
87fba0ecd0 | ||
|
|
30e6b5482f | ||
|
|
77b42e0867 | ||
|
|
cbe92e3e55 | ||
|
|
70f2efc9a1 | ||
|
|
dbf5b4920e | ||
|
|
9291ae8646 | ||
|
|
2713082707 | ||
|
|
89eb938b30 | ||
|
|
26e97492b7 | ||
|
|
4c3958f0be | ||
|
|
980b592631 | ||
|
|
e494869c72 | ||
|
|
da0aac7556 | ||
|
|
a200027cda | ||
|
|
8cd57f7457 | ||
|
|
26c1f1ee7a | ||
|
|
33af251a09 | ||
|
|
17dc588c81 | ||
|
|
258060593b | ||
|
|
fbe513835f | ||
|
|
f319b1e107 | ||
|
|
cbb7348998 | ||
|
|
2d8246e7c4 | ||
|
|
e5ea4f51c7 | ||
|
|
a031f8294e | ||
|
|
7d940a4a96 | ||
|
|
6ae0c83c83 | ||
|
|
083a14c2c5 | ||
|
|
e4db5c2ca4 | ||
|
|
1d2b50e438 | ||
|
|
5146c9fcdf | ||
|
|
e8a2f7ef92 | ||
|
|
0400c5236e | ||
|
|
67f96063e2 | ||
|
|
df1e48154a |
@@ -13,7 +13,7 @@ serve-clean-docs: clean-docs
|
||||
poetry run python -m mkdocs serve -c -f docs/mkdocs.yml --strict -w ./libs/langgraph
|
||||
|
||||
serve-docs: build-typedoc
|
||||
poetry run python -m mkdocs serve -f docs/mkdocs.yml -w ./libs/langgraph -w ./libs/checkpoint --dirty
|
||||
poetry run python -m mkdocs serve -f docs/mkdocs.yml -w ./libs/langgraph -w ./libs/checkpoint -w ./libs/sdk-py --dirty
|
||||
|
||||
clean-docs:
|
||||
find ./docs/docs -name "*.ipynb" -type f -delete
|
||||
|
||||
@@ -1 +1 @@
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
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
|
||||
@@ -1 +0,0 @@
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
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
|
||||
@@ -1 +0,0 @@
|
||||
eNrtmg1wFOUZx8OHiO1UMkKiBijHCWYas5fd+77EVMLl85LLXXKX5E6C597ee3eb7O1u9iPJJVImFOxQUDiKOH4hkC8IEKBYIYhoE21RGfmoBSOIdnAcNYMIQVHR0ncvF0mAtNKh05TuzeT2dt9nn/d5nt/7vDuZ/S9qrwUcTzL0mC0kLQAOJwR4wq9a1M6BGhHwwuK2EBCCjK/FbnM4m0WO7E0JCgLLp6el4SypYlhA46SKYEJptVgaEcSFNPibpUDUTYuX8YXfm362URkCPI8HAK9MV8xrVBIMnIsW4ImyDt6SzCuEIFDUARweOAVJK+gwoUxVKDmGApKRyANOuSBVMfROWqSoISY4z5O8gMMheFFgGMpD4BQVm08Is1Ejv0hH85NsSJ90RTLy2Ox4CeMQcrz5LEGrjXP9GpPGVCaUSWY/3JIOZ6fxUNRPAAieWLSSDc4FxBCMSppN2VipJEghXAl/VyphHpXKBcoFC+ZfEb2yQFCEyEBQUHiBgqAY0Re+Rt5SHsPy8fyYqK+cyjm8tsVu85VTq5TXrOSVjkYDKz1XrEZziy1GC87XuekKzpUdKq2yakc3qxGjvqlZ1dlMQaemxF7N5buLKWcRMKG5JeKD/OhmNWLUNzUrZ3kuUa024U6jjdBaHyRc5eUuh2hhRjerEaOWWcmsZFYyq+tilU1khex5BY56m1Gn5d1luFmoFRzawtHNasSoZVYyK5mVzEpmJbOSWcmsZFYyK5mVzOq/xqoaNTJUrbmh1pVrN5c9mG2y6gMuUEaPblYjRn1zsyrIqipyGwkxK8feUKBVU3a36GLnjvK+GjFqmZXM6npYzYdXQowPUNFysQKiZZAQSZOSpVRLDB55gQN4CJ74cYoHUpogxAIOF0RO8oSq0Fjq/4Tij8HjAzzBkWzMTFnGA5gCCfNgFNBySCp+hgvhklm0HizOQX8C4Pioc5ZjYHACCQZOJcbRH4AWpRzmKWN14P1KKfnBYGGSJB2AK0AqEagRSQ74ouZRB0MtGW8VIGDRpcXSHgS4D868oiXI8EKkc/ir4m04QQBYU0ATjA96j2wNNJBsqsIH/BQugA6IjAbRskQ6qgFgEZwia0HbwF2R7TjLUiQRTTStimfoLTHEiBTJ1cMdElEED0CLSFcWH6YJG4wkqyDNHhaCDK3AVFqjCt1ej8BFQ9IU4HmEwmFQbWx0/KWhAyxOVENPSOyleaRt4ObOoTYMH2m14oTNMcwlzhHBSCvOhfTanUOvcyItkCEQaTfbr54uNnh5Oo0Kw1SmHcMcSxlFtkYP6dFvktk1zAkQuDBCMNBXZD3aOVgsCtABIRhpMej1uo0c4FmG5sGv2+B9gsgvaoFkwIH97bH39htshYNIT8YltmRDSpGXnUExVaHWKxyAVahRtVaBadI1xnSdRpFndW4xx+ZxXhPKDieH07wfgskZXATtRFCkq4Gvw3xN/C9L+GE6UvywVxFQzzI8QGJRRba4kNIBxQJSkL1zYK0hDBfAabIhOm1kk0SVCMKyvBAbhi0huYSTIyE+0oJhWnVnbGiw4h0wMRTBUATFuqRWIOAqkyJnGU5AeECIHGyDSG9qCK+XllimBtNp9CiKZsB2JCjRBxyiN5sJwUn5DAXLAYrBfXvqEbhJAIoMkRBD9DsmtoArRwNvRndfbSEw1YDmI5swHTrw2TfUhgPSFFIilz2pTfCz99pWP3jTSkY6rWHPcDseDImpWR3id189HvOxAeW31A8aI6Qv0jsLnni8Pq9R5/fqMKAzmQxejdpv0GkMhBrV+o1GjcG/zZyLmHEiCBBHdMVF2rPdxVnWAnOHA/o2M0w1CVa9N2acx0P4Pd5QprHOpbEXmVG/v0hjLwxhvtLCPLMehIp9FgMjCHNZE2Ysqff6LC4rghnUBo3OYNJoEEyFqjAVhuSLlKuCcuLFNR4bbWEsAp5r9YSZhgBdU4CbtRpR5eVccP+pLkXzXPocvp6u9vhQC6gq4XDUas4P1gK7yVtK1plx2mLTUajF69JmQaRw781My1DA1Qh3Rj4z1hMI7AlE6ghtOjrYERkKX3QhZKqG74YZinxBYG00Fc6ArQRXFIBHuHM7SAFkFjM06F0NayDWkr5MisgKV+j95SrbXBUbwvIorZs0WfLqPMVlpfYAVWGsRwmsRhCtsE8uF8FkNCBorA56VGuMLp/Lof+bUb3oQoa2OGKLPqAgR5rhadLvb3MADnZRpCP6iIYbOwfaIPPSLHfkBSNh0uBGtR6Y4L8/aiNAcipKtw96+2FDaJGeClEJUVPbwGPo9TE9M5ZNjIt+xsG/S5eE0sOP/wWNf+RgheviiXVt1oJnDJ1jWzYsGj/xtpIp35ywfraWeOmr3JWbvz3TPiu0avOrk9b84dRT01dcmIZO4Cb9YlzNh7Msm7vrJp1z5xxf8cEDC77eu2Pzqd9l1tzZP8Pzc/3H9xUlPRq+f8O6qX2Poa1l+Uc+uT3xdhPenDR7p29ffnl/+Ker/Ss61ies6X53ces76metgRqjtTsYBjOWT/lkJnhl7J/uytjVeMpx5mxaC5XeVWY5v/bOieTCpRibq0v48GShrXJy0QT7XdaNU/v2lFaNX4mlTK7ctL5m19vN/fGJFSkZoX3PpXR9/OW51dSuBww75vWf6v/2+42e5PnHj9ofIg//Rtt9saHh2HLs0F6q65bSr241TT/0/uG7x33+245s62Oi8cyc/I66lZ/POea99c568dId3/V91zhlSc/ec5XzNXuOpi345M0pR5/KW/jkzI9KCxfun8Z9sA9MI9m+o181ffbXbU1i2wurI+G57+/vTNxcnPDoG0mHgjW3vBFfcU/5HZdWPHE6spt65cXn295uf+f1dejp0IvbbvE0dqvfalUdcicf72o64ed/uSyRaZ6tmrl7fFqU1Li4/ZO71+VAbDdSWzYjSdaWydoyWVsma8tkbZmsgZE1MDIrmZXMSn5PJb+nklnJrGRWMiuZlcxKZiWzklnJrGRWsrZM1pbJeiVZWyazuglZ1VgcNSJpxsPGGoPV6rZoOTNe1WDOGd2sRoxaFuHdGBHeybh4WYY3ymR4bURU4RTpPTfKBU7/AenR1RJEk159fRLEhH8hQdT+b0oQm3VGzf+nAlFjuuEKRD9Q+1CDH/iNfo1Oi+l8GhzTmABuRA0oimPYiArEG6Bs8/sIHXZ9yjb2SmXbykJbDxq/+Mw3Cfc9/0yK1bHqfPnK+InOmW+OWWz/6N5nXns9rXfeunV/r1t9/o2mZZP6Ty8PXEiOe3iD1/EweGuTs/zRnYVnt37+xdl73Nm7+juK6XsPLuu7+MiXczRvTXiuJ7XCdeSPutTHfn9HwV0v97Sai99tffyIcbrtyCPPnp/z1E9AoDp1ydLbnjikXN5svK1ZOw001lVnL11r/ZsuLu7rTz/obO/8Ypl1ZtK0KUtO9k/oPW8c/+qzkee6d1/onnzstQnJriTrxouf2ZDuZK7qcNMhdGHX2NlPr3l4qd2ys1Pfw7f3NJwOH3toW+uJvuNEApnUd+DImW9Kti1J3IdM4xjhwtZkwm3bNebVea+c+/Zg4oH05tQE38mSuqbwmjPn3v30SEA4uD/+yQC39vtASsosVcsDL009njBlj2l29p/XfP/0Aeb+9SlV66cKR5e+f3f1vOXh+5+uXK4+4Nr+s1+NGZChiUWN251j4+L+AcCRRVA=
|
||||
@@ -0,0 +1 @@
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
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
|
||||
|
After Width: | Height: | Size: 736 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 304 KiB |
|
After Width: | Height: | Size: 266 KiB |
|
After Width: | Height: | Size: 376 KiB |
|
After Width: | Height: | Size: 400 KiB |
|
After Width: | Height: | Size: 461 KiB |
|
After Width: | Height: | Size: 642 KiB |
@@ -0,0 +1,17 @@
|
||||
# Adding nodes as dataset examples in Studio
|
||||
|
||||
In LangGraph Studio you can create dataset examples from the thread history in the right-hand pane. This can be especially useful when you want to evaluate intermediate steps of the agent.
|
||||
|
||||
1. Click on the `Add to Dataset` button to enter the dataset mode.
|
||||
1. Select nodes which you want to add to dataset.
|
||||
1. Select the target dataset to create the example in.
|
||||
|
||||
You can edit the example payload before sending it to the dataset, which is useful if you need to make changes to conform the example to the dataset schema.
|
||||
|
||||
Finally, you can customise the target dataset by clicking on the `Settings` button.
|
||||
|
||||
See [Evaluating intermediate steps](https://docs.smith.langchain.com/evaluation/how_to_guides/langgraph#evaluating-intermediate-steps) for more details on how to evaluate intermediate steps.
|
||||
|
||||
<video controls allowfullscreen="true" poster="../img/studio_datasets.jpg">
|
||||
<source src="https://langgraph-docs-assets.pages.dev/studio_datasets.mp4" type="video/mp4">
|
||||
</video>
|
||||
|
After Width: | Height: | Size: 170 KiB |
@@ -1,462 +1,273 @@
|
||||
# LangGraph Cloud Quick Start
|
||||
# Quickstart: Deploy on LangGraph Cloud
|
||||
|
||||
In this tutorial you will build and deploy a simple chatbot agent that can look things up on the internet. You will be using [LangGraph Cloud](../concepts/langgraph_cloud.md), [LangGraph Studio](../concepts/langgraph_studio.md) to visualize and test it out, and [LangGraph SDK](./reference/sdk/python_sdk_ref.md) to interact with the deployed agent.
|
||||
!!! note "Prerequisites"
|
||||
|
||||
If you want to learn how to build an agent like this from scratch, take a look at the [LangGraph Quick Start tutorial](../tutorials/introduction.ipynb).
|
||||
Before you begin, ensure you have the following:
|
||||
|
||||
## Set up requirements
|
||||
- [GitHub account](https://github.com/)
|
||||
- [LangSmith account](https://smith.langchain.com/)
|
||||
|
||||
This tutorial will use:
|
||||
## Create a repository on GitHub
|
||||
|
||||
- Anthropic for the LLM - sign up and get an API key [here](https://console.anthropic.com/).
|
||||
- Tavily for the search engine - sign up and get an API key [here](https://app.tavily.com/).
|
||||
- LangSmith for hosting - sign up and get an API key [here](https://smith.langchain.com/).
|
||||
To deploy a LangGraph application to **LangGraph Cloud**, your application code must reside in a GitHub repository. Both public and private repositories are supported.
|
||||
|
||||
## Create and configure your app
|
||||
You can deploy any [LangGraph Application](../concepts/application_structure.md) to LangGraph Cloud.
|
||||
|
||||
First, let's set create all of the necessary files for our LangGraph application.
|
||||
For this guide, we'll use the pre-built Python [**ReAct Agent**](https://github.com/langchain-ai/react-agent) template.
|
||||
|
||||
1. __Create application directory and files__
|
||||
??? note "Get Required API Keys for the ReAct Agent template"
|
||||
|
||||
Create a new application `my-app` with the following file structure:
|
||||
This **ReAct Agent** application requires an API key from [Anthropic](https://console.anthropic.com/) and [Tavily](https://app.tavily.com/). You can get these API keys by signing up on their respective websites.
|
||||
|
||||
```shell
|
||||
mkdir my-app
|
||||
```
|
||||
**Alternative**: If you'd prefer a scaffold application that doesn't require API keys, use the [**New LangGraph Project**](https://github.com/langchain-ai/new-langgraph-project) template instead of the **ReAct Agent** template.
|
||||
|
||||
=== "Python"
|
||||
|
||||
my-app/
|
||||
|-- agent.py # code for your LangGraph agent
|
||||
|-- requirements.txt # Python packages required for your graph
|
||||
|-- langgraph.json # configuration file for LangGraph
|
||||
|-- .env # environment files with API keys
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
my-app/
|
||||
|-- agent.ts # code for your LangGraph agent
|
||||
|-- package.json # Javascript packages required for your graph
|
||||
|-- langgraph.json # configuration file for LangGraph
|
||||
|-- .env # environment files with API keys
|
||||
|
||||
|
||||
1. __Define your graph__
|
||||
|
||||
=== "Python"
|
||||
The `agent.py` file should contain code with your graph.
|
||||
|
||||
=== "Javascript"
|
||||
The `agent.ts` file should contain code with your graph.
|
||||
|
||||
The following code example is a simple chatbot agent (similar to the one in the [previous tutorial](../tutorials/introduction.ipynb)). Specifically, it uses [create_react_agent][langgraph.prebuilt.chat_agent_executor.create_react_agent], a prebuilt [ReAct](../concepts/agentic_concepts.md#react-implementation)-style agent.
|
||||
|
||||
The `agent` file needs to have a variable with a [CompiledGraph][langgraph.graph.graph.CompiledGraph] (in this case the `graph` variable).
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
# agent.py
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
from langchain_community.tools.tavily_search import TavilySearchResults
|
||||
from langgraph.prebuilt import create_react_agent
|
||||
|
||||
model = ChatAnthropic(model="claude-3-5-sonnet-20240620")
|
||||
|
||||
tools = [TavilySearchResults(max_results=2)]
|
||||
|
||||
# compiled graph
|
||||
graph = create_react_agent(model, tools)
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```ts
|
||||
// agent.ts
|
||||
import { ChatAnthropic } from "@langchain/anthropic";
|
||||
import { TavilySearchResults } from "@langchain/community/tools/tavily_search";
|
||||
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
||||
|
||||
const model = new ChatAnthropic({
|
||||
model: "claude-3-5-sonnet-20240620",
|
||||
});
|
||||
|
||||
const tools = [
|
||||
new TavilySearchResults({ maxResults: 3, }),
|
||||
];
|
||||
|
||||
// compiled graph
|
||||
export const graph = createReactAgent({ llm: model, tools });
|
||||
```
|
||||
|
||||
1. __Specify dependencies__
|
||||
|
||||
=== "Python"
|
||||
You should add dependencies for your graph(s) to `requirements.txt`.
|
||||
|
||||
=== "Javascript"
|
||||
You should add dependencies for your graph(s) to `package.json`.
|
||||
|
||||
In this case we only require four packages for our graph to run:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python
|
||||
langgraph
|
||||
langchain_anthropic
|
||||
tavily-python
|
||||
langchain_community
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
{
|
||||
"name": "my-app",
|
||||
"packageManager": "yarn@1.22.22",
|
||||
"dependencies": {
|
||||
"@langchain/community": "^0.3.11",
|
||||
"@langchain/core": "^0.3.16",
|
||||
"@langchain/langgraph": "0.2.18",
|
||||
"@langchain/anthropic": "^0.3.7"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
1. __Create LangGraph configuration file__
|
||||
|
||||
The [`langgraph.json`][langgraph.json] file is a configuration file that describes what graph(s) you are going to deploy. In this case we only have one graph: the compiled `graph` object from `agent.py` / `agent.ts`.
|
||||
|
||||
=== "Python"
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": ["."],
|
||||
"graphs": {
|
||||
"agent": "./agent.py:graph"
|
||||
},
|
||||
"env": ".env"
|
||||
}
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```json
|
||||
{
|
||||
"node_version": "20",
|
||||
"dockerfile_lines": [],
|
||||
"dependencies": ["."],
|
||||
"graphs": {
|
||||
"agent": "./src/agent.ts:graph"
|
||||
},
|
||||
"env": ".env"
|
||||
}
|
||||
```
|
||||
|
||||
Learn more about the LangGraph CLI configuration file [here](./reference/cli.md#configuration-file).
|
||||
|
||||
1. __Specify environment variables__
|
||||
|
||||
The `.env` file should have any environment variables needed to run your graph. This will only be used for local testing, so if you are not testing locally you can skip this step.
|
||||
|
||||
!!! warning
|
||||
The `.env` file should NOT be included with the rest of source code in your Github repository. When creating a deployment using LangGraph Cloud, you will be able to specify the environment variables manually.
|
||||
|
||||
For this graph, we need two environment variables:
|
||||
|
||||
```shell
|
||||
ANTHROPIC_API_KEY=...
|
||||
TAVILY_API_KEY=...
|
||||
```
|
||||
|
||||
!!! tip
|
||||
Learn more about different application structure options [here](../how-tos/index.md#application-structure).
|
||||
|
||||
Now that we have set everything up on our local file system, we are ready to test our graph locally.
|
||||
|
||||
## Test the app locally
|
||||
|
||||
To test the LangGraph app before deploying it using LangGraph Cloud, you can start the [LangGraph server](../concepts/langgraph_server.md) locally or use [LangGraph Studio](../concepts/langgraph_studio.md).
|
||||
|
||||
## Using local server
|
||||
|
||||
You can test your app by running [LangGraph server](../concepts/langgraph_server.md) locally. This is useful to make sure you have configured our [CLI configuration file][langgraph.json] correctly and can interact with your graph.
|
||||
|
||||
To run the server locally, you need to first install the LangGraph CLI:
|
||||
|
||||
```shell
|
||||
pip install langgraph-cli
|
||||
```
|
||||
|
||||
You can then test our API server locally. In order to run the server locally, you will need to add your `LANGSMITH_API_KEY` to the `.env` file.
|
||||
|
||||
```shell
|
||||
langgraph up
|
||||
```
|
||||
|
||||
This will start up the LangGraph API server locally. If this runs successfully, you should see something like:
|
||||
|
||||
```shell
|
||||
Ready!
|
||||
- API: http://localhost:8123
|
||||
```
|
||||
|
||||
First, let's verify that the server is running correctly by calling `/ok` endpoint:
|
||||
|
||||
```shell
|
||||
curl --request GET --url http://localhost:8123/ok
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
{"ok": "true"}
|
||||
```
|
||||
|
||||
Now we're ready to test the app with the real inputs!
|
||||
|
||||
```shell
|
||||
curl --request POST \
|
||||
--url http://localhost:8123/runs/stream \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"assistant_id": "agent",
|
||||
"input": {
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "What is the weather in NYC?"
|
||||
}
|
||||
]
|
||||
},
|
||||
"stream_mode": "updates"
|
||||
}'
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
...
|
||||
|
||||
data: {
|
||||
"agent": {
|
||||
"messages": [
|
||||
{
|
||||
"content": "The search results from Tavily provide the current weather conditions in New York City, including temperature, wind speed, precipitation, humidity, and cloud cover. According to the results, as of 3:00pm on October 30th, 2024, it is overcast in NYC with a temperature of around 66°F (19°C), light winds from the southwest around 8 mph (13 km/h), and 66% humidity.\n\nSo in summary, the current weather in NYC is overcast with mild temperatures in the mid 60sF and light winds, based on the search results. Let me know if you need any other details!",
|
||||
"type": "ai",
|
||||
...
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can see that our agent responds with the up-to-date search results!
|
||||
|
||||
### Using LangGraph Studio Desktop
|
||||
|
||||
You can also test your app locally with [LangGraph Studio](../concepts/langgraph_studio.md). LangGraph Studio offers a new way to develop LLM applications by providing a specialized agent IDE that enables visualization, interaction, and debugging of complex agentic applications.
|
||||
|
||||
With visual graphs and the ability to edit state, you can better understand agent workflows and iterate faster. LangGraph Studio integrates with LangSmith allowing you to collaborate with teammates to debug failure modes.
|
||||
|
||||
LangGraph Studio is available as a [desktop app](https://studio.langchain.com/) for MacOS users. Once you have installed the app, you can select `my-app` directory, which will automatically start the server locally and load the graph in the UI.
|
||||
|
||||
To interact with your chatbot agent in LangGraph Studio, you can add a new message in the `Input` section and press `Submit`.
|
||||
|
||||

|
||||
1. Go to the [ReAct Agent](https://github.com/langchain-ai/react-agent) repository.
|
||||
2. Fork the repository to your GitHub account by clicking the `Fork` button in the top right corner.
|
||||
|
||||
## Deploy to LangGraph Cloud
|
||||
|
||||
Once you've tested your graph locally and verified that it works as expected, you can deploy it to the LangGraph Cloud.
|
||||
??? note "1. Log in to [LangSmith](https://smith.langchain.com/)"
|
||||
|
||||
First, you'll need to turn the `my-app` directory into a GitHub repo and [push it to GitHub](https://docs.github.com/en/migrations/importing-source-code/using-the-command-line-to-import-source-code/adding-locally-hosted-code-to-github).
|
||||
<figure markdown="1">
|
||||
[{: style="max-height:300px"}](deployment/img/01_login.png)
|
||||
<figcaption>
|
||||
Go to [LangSmith](https://smith.langchain.com/) and log in. If you don't have an account, you can sign up for free.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
Once you have created your GitHub repository with a Python file containing your compiled graph as well as a `langgraph.json` with the configuration, you can head over to [LangSmith](https://smith.langchain.com/) and click on the graph icon (`LangGraph Cloud`) on the bottom of the left navbar. This will open the LangGraph deployments page. On this page, click the `+ New Deployment` button in the top right corner.
|
||||
|
||||

|
||||
??? note "2. Click on <em>LangGraph Platform</em> (the left sidebar)"
|
||||
|
||||
**_If you have not deployed to LangGraph Cloud before:_** there will be a button that shows up saying `Import from GitHub`. You’ll need to follow that flow to connect LangGraph Cloud to GitHub.
|
||||
<figure markdown="1">
|
||||
[{: style="max-height:300px"}](deployment/img/02_langgraph_platform.png)
|
||||
<figcaption>
|
||||
Select **LangGraph Platform** from the left sidebar.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
**_Once you have set up your GitHub connection:_** the new deployment page will look as follows:
|
||||
??? note "3. Click on + New Deployment (top right corner)"
|
||||
|
||||

|
||||
<figure markdown="1">
|
||||
[{: style="max-height:300px"}](deployment/img/03_deployments_page.png)
|
||||
<figcaption>
|
||||
Click on **+ New Deployment** to create a new deployment. This button is located in the top right corner.
|
||||
It'll open a new modal where you can fill out the required fields.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
To deploy your application, you should do the following:
|
||||
??? note "4. Click on Import from GitHub (first time users)"
|
||||
|
||||
1. Select your GitHub username or organization from the selector
|
||||
1. Search for your repo to deploy in the search bar and select it
|
||||
1. Choose a name for your deployment
|
||||
1. In the `Git Branch` field, you can specify either the branch for the code you want to deploy, or the exact commit SHA.
|
||||
1. In the `LangGraph API config file` field, enter the path to your `langgraph.json` file (which in this case is just `langgraph.json`)
|
||||
1. If your application needs environment variables, add those in the `Environment Variables` section. They will be propagated to the underlying server so your code can access them. In this case, we will need `ANTHROPIC_API_KEY` and `TAVILY_API_KEY`.
|
||||
<figure markdown="1">
|
||||
[](deployment/img/04_create_new_deployment.png)
|
||||
<figcaption>
|
||||
Click on **Import from GitHub** and follow the instructions to connect your GitHub account. This step is needed for **first-time users** or to add private repositories that haven't been connected before.</figcaption>
|
||||
</figure>
|
||||
|
||||
Hit `Submit` and your application will start deploying!
|
||||
??? note "5. Select the repository, configure ENV vars etc"
|
||||
|
||||
After your deployment is complete, your deployments page should look as follows:
|
||||
<figure markdown="1">
|
||||
[{: style="max-height:300px"}](deployment/img/05_configure_deployment.png)
|
||||
<figcaption>
|
||||
Select the <strong>repository</strong>, add env variables and secrets, and set other configuration options.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||

|
||||
- **Repository**: Select the repository you forked earlier (or any other repository you want to deploy).
|
||||
- Set the secrets and environment variables required by your application. For the **ReAct Agent** template, you need to set the following secrets:
|
||||
- **ANTHROPIC_API_KEY**: Get an API key from [Anthropic](https://console.anthropic.com/).
|
||||
- **TAVILY_API_KEY**: Get an API key on the [Tavily website](https://app.tavily.com/).
|
||||
|
||||
## Interact with your deployment
|
||||
??? note "6. Click Submit to Deploy!"
|
||||
|
||||
### Using LangGraph Studio (Cloud)
|
||||
|
||||
On the deployment page for your application,, you should see a button in the top right corner that says `LangGraph Studio`. Clicking on this button will take you to the web version of LangGraph Studio. This is the same UI that you interacted with when [testing the app locally](#using-langgraph-studio-recommended), but instead of using a local LangGraph server, it uses the one from your LangGraph Cloud deployment.
|
||||
<figure markdown="1">
|
||||
[{: style="max-height:300px"}](deployment/img/05_configure_deployment.png)
|
||||
<figcaption>
|
||||
Please note that this step may ~15 minutes to complete. You can check the status of your deployment in the **Deployments** view.
|
||||
Click the <strong>Submit</strong> button at the top right corner to deploy your application.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||

|
||||
|
||||
### Using LangGraph SDK
|
||||
## Lagraph Studio Web UI
|
||||
|
||||
You can also interact with your deployed LangGraph application programmatically, using [LangGraph SDK](./reference/sdk/python_sdk_ref.md).
|
||||
Once your application is deployed, you can test it in **LangGraph Studio**.
|
||||
|
||||
First, make sure you have the SDK installed:
|
||||
??? note "1. Click on an existing deployment"
|
||||
|
||||
=== "Python"
|
||||
<figure markdown="1">
|
||||
[{: style="max-height:300px"}](deployment/img/07_deployments_page.png)
|
||||
<figcaption>
|
||||
Click on the deployment you just created to view more details.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
```shell
|
||||
pip install langgraph_sdk
|
||||
```
|
||||
??? note "2. Click on LangGraph Studio"
|
||||
|
||||
=== "Javascript"
|
||||
<figure markdown="1">
|
||||
[{: style="max-height:300px"}](deployment/img/08_deployment_view.png)
|
||||
<figcaption>
|
||||
Click on the <strong>LangGraph Studio</strong> button to open LangGraph Studio.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
```shell
|
||||
yarn add @langchain/langgraph-sdk
|
||||
```
|
||||
<figure markdown="1">
|
||||
[{: style="max-height:400px"}](deployment/img/09_langgraph_studio.png)
|
||||
<figcaption>
|
||||
Sample graph run in LangGraph Studio.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
Before using, you need to get the URL of your LangGraph deployment. You can find this in the `Deployment` view. Click the URL to copy it to the clipboard.
|
||||
## Test the API
|
||||
|
||||
You also need to make sure you have set up your API key properly so you can authenticate with LangGraph Cloud.
|
||||
!!! note
|
||||
|
||||
The API calls below are for the **ReAct Agent** template. If you're deploying a different application, you may need to adjust the API calls accordingly.
|
||||
|
||||
Before using, you need to get the `URL` of your LangGraph deployment. You can find this in the `Deployment` view. Click the `URL` to copy it to the clipboard.
|
||||
|
||||
You also need to make sure you have set up your API key properly, so you can authenticate with LangGraph Cloud.
|
||||
|
||||
```shell
|
||||
export LANGSMITH_API_KEY=...
|
||||
```
|
||||
|
||||
The first thing to do when using the SDK is to setup our client, access our assistant, and create a thread to execute a run on:
|
||||
=== "Python SDK (Async)"
|
||||
|
||||
=== "Python"
|
||||
**Install the LangGraph Python SDK**
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
```shell
|
||||
pip install langgraph-sdk
|
||||
```
|
||||
|
||||
client = get_client(url=<DEPLOYMENT_URL>)
|
||||
# get default assistant
|
||||
assistants = await client.assistants.search(metadata={"created_by": "system"})
|
||||
assistant = assistants[0]
|
||||
# create thread
|
||||
thread = await client.threads.create()
|
||||
print(thread)
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
|
||||
```js
|
||||
import { Client } from "@langchain/langgraph-sdk";
|
||||
|
||||
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
|
||||
// get default assistant
|
||||
const assistants = await client.assistants.search({ metadata: {"created_by": "system"} })
|
||||
const assistant = assistants[0];
|
||||
// create thread
|
||||
const thread = await client.threads.create();
|
||||
console.log(thread)
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/assistants/search \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"limit": 10,
|
||||
"offset": 0,
|
||||
"metadata": {"created_by": "system"}
|
||||
}' &&
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{}'
|
||||
```
|
||||
|
||||
We can then execute a run on the thread:
|
||||
|
||||
=== "Python"
|
||||
**Send a message to the assistant (threadless run)**
|
||||
|
||||
```python
|
||||
input = {
|
||||
"messages": [{"role": "user", "content": "What is the weather in NYC?"}]
|
||||
}
|
||||
from langgraph_sdk import get_client
|
||||
|
||||
client = get_client(url="your-deployment-url", api_key="your-langsmith-api-key")
|
||||
|
||||
async for chunk in client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistant["assistant_id"],
|
||||
input=input,
|
||||
None, # Threadless run
|
||||
"agent", # Name of assistant. Defined in langgraph.json.
|
||||
input={
|
||||
"messages": [{
|
||||
"role": "human",
|
||||
"content": "What is LangGraph?",
|
||||
}],
|
||||
},
|
||||
stream_mode="updates",
|
||||
):
|
||||
if chunk.data:
|
||||
print(chunk.data)
|
||||
print(f"Receiving new event of type: {chunk.event}...")
|
||||
print(chunk.data)
|
||||
print("\n\n")
|
||||
```
|
||||
|
||||
=== "Javascript"
|
||||
=== "Python SDK (Sync)"
|
||||
|
||||
**Install the LangGraph Python SDK**
|
||||
|
||||
```shell
|
||||
pip install langgraph-sdk
|
||||
```
|
||||
|
||||
**Send a message to the assistant (threadless run)**
|
||||
|
||||
```python
|
||||
from langgraph_sdk import get_sync_client
|
||||
|
||||
client = get_sync_client(url="your-deployment-url", api_key="your-langsmith-api-key")
|
||||
|
||||
for chunk in client.runs.stream(
|
||||
None, # Threadless run
|
||||
"agent", # Name of assistant. Defined in langgraph.json.
|
||||
input={
|
||||
"messages": [{
|
||||
"role": "human",
|
||||
"content": "What is LangGraph?",
|
||||
}],
|
||||
},
|
||||
stream_mode="updates",
|
||||
):
|
||||
print(f"Receiving new event of type: {chunk.event}...")
|
||||
print(chunk.data)
|
||||
print("\n\n")
|
||||
```
|
||||
|
||||
=== "Javascript SDK"
|
||||
|
||||
**Install the LangGraph JS SDK**
|
||||
|
||||
```shell
|
||||
npm install @langchain/langgraph-sdk
|
||||
```
|
||||
|
||||
**Send a message to the assistant (threadless run)**
|
||||
|
||||
```js
|
||||
const input = { "messages": [{ "role": "user", "content": "What is the weather in NYC?" }] };
|
||||
const { Client } = await import("@langchain/langgraph-sdk");
|
||||
|
||||
// only set the apiUrl if you changed the default port when calling langgraph up
|
||||
const client = new Client({ apiUrl: "your-deployment-url", apiKey: "your-langsmith-api-key" });
|
||||
|
||||
const streamResponse = client.runs.stream(
|
||||
thread["thread_id"],
|
||||
assistant["assistant_id"],
|
||||
{
|
||||
input,
|
||||
streamMode: "updates"
|
||||
}
|
||||
null, // Threadless run
|
||||
"agent", // Assistant ID
|
||||
{
|
||||
input: {
|
||||
"messages": [
|
||||
{ "role": "user", "content": "What is LangGraph?"}
|
||||
]
|
||||
},
|
||||
streamMode: "messages",
|
||||
}
|
||||
);
|
||||
|
||||
for await (const chunk of streamResponse) {
|
||||
if (chunk.data) {
|
||||
console.log(chunk.data);
|
||||
}
|
||||
console.log(`Receiving new event of type: ${chunk.event}...`);
|
||||
console.log(JSON.stringify(chunk.data));
|
||||
console.log("\n\n");
|
||||
}
|
||||
```
|
||||
|
||||
=== "CURL"
|
||||
=== "Rest API"
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"assistant_id": <ASSISTANT_ID>,
|
||||
"input": {
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "What is the weather in NYC?"
|
||||
}
|
||||
]
|
||||
},
|
||||
"stream_mode": "updates"
|
||||
}'
|
||||
curl -s --request POST \
|
||||
--url <DEPLOYMENT_URL> \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\",
|
||||
\"input\": {
|
||||
\"messages\": [
|
||||
{
|
||||
\"role\": \"human\",
|
||||
\"content\": \"What is LangGraph?\"
|
||||
}
|
||||
]
|
||||
},
|
||||
\"stream_mode\": \"updates\"
|
||||
}"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
...
|
||||
|
||||
data: {
|
||||
"agent": {
|
||||
"messages": [
|
||||
{
|
||||
"content": "The search results from Tavily provide the current weather conditions in New York City, including temperature, wind speed, precipitation, humidity, and cloud cover. According to the results, as of 3:00pm on October 30th, 2024, it is overcast in NYC with a temperature of around 66°F (19°C), light winds from the southwest around 8 mph (13 km/h), and 66% humidity.\n\nSo in summary, the current weather in NYC is overcast with mild temperatures in the mid 60sF and light winds, based on the search results. Let me know if you need any other details!",
|
||||
"type": "ai",
|
||||
...
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Next steps
|
||||
## Next Steps
|
||||
|
||||
Congratulations! If you've worked your way through this tutorial you are well on your way to becoming a LangGraph Cloud expert. Here are some other resources to check out to help you out on the path to expertise:
|
||||
|
||||
* [LangGraph How-to guides](../how-tos/index.md)
|
||||
* [LangGraph Tutorials](../tutorials/index.md)
|
||||
### LangGraph Framework
|
||||
|
||||
- **[LangGraph Tutorial](../tutorials/introduction.ipynb)**: Get started with LangGraph framework.
|
||||
- **[LangGraph Concepts](../concepts/index.md)**: Learn the foundational concepts of LangGraph.
|
||||
- **[LangGraph How-to Guides](../how-tos/index.md)**: Guides for common tasks with LangGraph.
|
||||
|
||||
### 📚 Learn More about LangGraph Platform
|
||||
|
||||
Expand your knowledge with these resources:
|
||||
|
||||
- **[LangGraph Platform Concepts](../concepts/index.md#langgraph-platform)**: Understand the foundational concepts of the LangGraph Platform.
|
||||
- **[LangGraph Platform How-to Guides](../how-tos/index.md#langgraph-platform)**: Discover step-by-step guides to build and deploy applications.
|
||||
- **[Launch Local LangGraph Server](../tutorials/langgraph-platform/local-server.md)**: This quick start guide shows how to start a LangGraph Server locally for the **ReAct Agent** template. The steps are similar for other templates.
|
||||
|
||||
|
||||
|
||||
@@ -6,3 +6,25 @@
|
||||
|
||||
::: langgraph_sdk.schema
|
||||
handler: python
|
||||
|
||||
|
||||
::: langgraph_sdk.auth
|
||||
handler: python
|
||||
|
||||
::: langgraph_sdk.auth.types.Authenticator
|
||||
handler: python
|
||||
|
||||
::: langgraph_sdk.auth.types.Handler
|
||||
handler: python
|
||||
|
||||
::: langgraph_sdk.auth.types.HandlerResult
|
||||
handler: python
|
||||
|
||||
::: langgraph_sdk.auth.types.FilterType
|
||||
handler: python
|
||||
|
||||
::: langgraph_sdk.auth.types.AuthContext
|
||||
handler: python
|
||||
|
||||
::: langgraph_sdk.auth.exceptions
|
||||
handler: python
|
||||
@@ -28,11 +28,14 @@ def human_node(state: State):
|
||||
value = interrupt(
|
||||
# Any JSON serializable value to surface to the human.
|
||||
# For example, a question or a piece of text or a set of keys in the state
|
||||
some_data
|
||||
{
|
||||
"text_to_revise": state["some_text"]
|
||||
}
|
||||
)
|
||||
...
|
||||
# Update the state with the human's input or route the graph based on the input.
|
||||
...
|
||||
return {
|
||||
"some_text": value
|
||||
}
|
||||
|
||||
graph = graph_builder.compile(
|
||||
checkpointer=checkpointer # Required for `interrupt` to work
|
||||
@@ -46,6 +49,83 @@ graph.invoke(some_input, config=thread_config)
|
||||
graph.invoke(Command(resume=value_from_human), config=thread_config)
|
||||
```
|
||||
|
||||
```pycon
|
||||
{'some_text': 'Edited text'}
|
||||
```
|
||||
|
||||
!!! warning
|
||||
Interrupts are both powerful and ergonomic. However, while they may resemble Python's input() function in terms of developer experience, it's important to note that they do not automatically resume execution from the interruption point. Instead, they rerun the entire node where the interrupt was used.
|
||||
For this reason, interrupts are typically best placed at the start of a node or in a dedicated node. Please read the [resuming from an interrupt](#how-does-resuming-from-an-interrupt-work) section for more details.
|
||||
|
||||
??? "Full Code"
|
||||
|
||||
Here's a full example of how to use `interrupt` in a graph, if you'd like
|
||||
to see the code in action.
|
||||
|
||||
```python
|
||||
from typing import TypedDict
|
||||
import uuid
|
||||
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langgraph.constants import START
|
||||
from langgraph.graph import StateGraph
|
||||
from langgraph.types import interrupt, Command
|
||||
|
||||
class State(TypedDict):
|
||||
"""The graph state."""
|
||||
some_text: str
|
||||
|
||||
def human_node(state: State):
|
||||
value = interrupt(
|
||||
# Any JSON serializable value to surface to the human.
|
||||
# For example, a question or a piece of text or a set of keys in the state
|
||||
{
|
||||
"text_to_revise": state["some_text"]
|
||||
}
|
||||
)
|
||||
return {
|
||||
# Update the state with the human's input
|
||||
"some_text": value
|
||||
}
|
||||
|
||||
|
||||
# Build the graph
|
||||
graph_builder = StateGraph(State)
|
||||
# Add the human-node to the graph
|
||||
graph_builder.add_node("human_node", human_node)
|
||||
graph_builder.add_edge(START, "human_node")
|
||||
|
||||
# A checkpointer is required for `interrupt` to work.
|
||||
checkpointer = MemorySaver()
|
||||
graph = graph_builder.compile(
|
||||
checkpointer=checkpointer
|
||||
)
|
||||
|
||||
# Pass a thread ID to the graph to run it.
|
||||
thread_config = {"configurable": {"thread_id": uuid.uuid4()}}
|
||||
|
||||
# Using stream() to directly surface the `__interrupt__` information.
|
||||
for chunk in graph.stream({"some_text": "Original text"}, config=thread_config):
|
||||
print(chunk)
|
||||
|
||||
# Resume using Command
|
||||
for chunk in graph.stream(Command(resume="Edited text"), config=thread_config):
|
||||
print(chunk)
|
||||
```
|
||||
|
||||
```pycon
|
||||
{'__interrupt__': (
|
||||
Interrupt(
|
||||
value={'question': 'Please revise the text', 'some_text': 'Original text'},
|
||||
resumable=True,
|
||||
ns=['human_node:10fe492f-3688-c8c6-0d0a-ec61a43fecd6'],
|
||||
when='during'
|
||||
),
|
||||
)
|
||||
}
|
||||
{'human_node': {'some_text': 'Edited text'}}
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
To use `interrupt` in your graph, you need to:
|
||||
@@ -448,18 +528,141 @@ Place code with side effects, such as API calls, **after** the `interrupt` to av
|
||||
|
||||
### Subgraphs called as functions
|
||||
|
||||
When invoking a subgraph [as a function](low_level.md#as-a-function), the **parent graph** will resume execution from the **beginning of the node** where the subgraph was invoked (and where an `interrupt` was triggered). Similarly, the **subgraph**, will resume from the **beginning of the node** where the `interrupt()` function was called.
|
||||
|
||||
**Subgraphs**: If you're invoking a subgraph [as a function](low_level.md#as-a-function), the **parent** graph will be re-run from the **beginning of the node** where the subgraph was invoked.
|
||||
For example,
|
||||
|
||||
```python
|
||||
def some_node(state: State):
|
||||
some_code() # <-- This code will be re-executed when the subgraph is resumed.
|
||||
# Using a subgraph as a function.
|
||||
# The subgraph has an `interrupt` call
|
||||
def node_in_parent_graph(state: State):
|
||||
some_code() # <-- This will re-execute when the subgraph is resumed.
|
||||
# Invoke a subgraph as a function.
|
||||
# The subgraph contains an `interrupt` call.
|
||||
subgraph_result = subgraph.invoke(some_input)
|
||||
...
|
||||
```
|
||||
|
||||
??? "**Example: Parent and Subgraph Execution Flow**"
|
||||
|
||||
Say we have a parent graph with 3 nodes:
|
||||
|
||||
**Parent Graph**: `node_1` → `node_2` (subgraph call) → `node_3`
|
||||
|
||||
And the subgraph has 3 nodes, where the second node contains an `interrupt`:
|
||||
|
||||
**Subgraph**: `sub_node_1` → `sub_node_2` (`interrupt`) → `sub_node_3`
|
||||
|
||||
When resuming the graph, the execution will proceed as follows:
|
||||
|
||||
1. **Skip `node_1`** in the parent graph (already executed, graph state was saved in snapshot).
|
||||
2. **Re-execute `node_2`** in the parent graph from the start.
|
||||
3. **Skip `sub_node_1`** in the subgraph (already executed, graph state was saved in snapshot).
|
||||
4. **Re-execute `sub_node_2`** in the subgraph from the beginning.
|
||||
5. Continue with `sub_node_3` and subsequent nodes.
|
||||
|
||||
Here is abbreviated example code that you can use to understand how subgraphs work with interrupts.
|
||||
It counts the number of times each node is entered and prints the count.
|
||||
|
||||
```python
|
||||
import uuid
|
||||
from typing import TypedDict
|
||||
|
||||
from langgraph.graph import StateGraph
|
||||
from langgraph.constants import START
|
||||
from langgraph.types import interrupt, Command
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
|
||||
|
||||
class State(TypedDict):
|
||||
"""The graph state."""
|
||||
state_counter: int
|
||||
|
||||
|
||||
counter_node_in_subgraph = 0
|
||||
|
||||
def node_in_subgraph(state: State):
|
||||
"""A node in the sub-graph."""
|
||||
global counter_node_in_subgraph
|
||||
counter_node_in_subgraph += 1 # This code will **NOT** run again!
|
||||
print(f"Entered `node_in_subgraph` a total of {counter_node_in_subgraph} times")
|
||||
|
||||
counter_human_node = 0
|
||||
|
||||
def human_node(state: State):
|
||||
global counter_human_node
|
||||
counter_human_node += 1 # This code will run again!
|
||||
print(f"Entered human_node in sub-graph a total of {counter_human_node} times")
|
||||
answer = interrupt("what is your name?")
|
||||
print(f"Got an answer of {answer}")
|
||||
|
||||
|
||||
checkpointer = MemorySaver()
|
||||
|
||||
subgraph_builder = StateGraph(State)
|
||||
subgraph_builder.add_node("some_node", node_in_subgraph)
|
||||
subgraph_builder.add_node("human_node", human_node)
|
||||
subgraph_builder.add_edge(START, "some_node")
|
||||
subgraph_builder.add_edge("some_node", "human_node")
|
||||
subgraph = subgraph_builder.compile(checkpointer=checkpointer)
|
||||
|
||||
|
||||
counter_parent_node = 0
|
||||
|
||||
def parent_node(state: State):
|
||||
"""This parent node will invoke the subgraph."""
|
||||
global counter_parent_node
|
||||
|
||||
counter_parent_node += 1 # This code will run again on resuming!
|
||||
print(f"Entered `parent_node` a total of {counter_parent_node} times")
|
||||
|
||||
# Please note that we're intentionally incrementing the state counter
|
||||
# in the graph state as well to demonstrate that the subgraph update
|
||||
# of the same key will not conflict with the parent graph (until
|
||||
subgraph_state = subgraph.invoke(state)
|
||||
return subgraph_state
|
||||
|
||||
|
||||
builder = StateGraph(State)
|
||||
builder.add_node("parent_node", parent_node)
|
||||
builder.add_edge(START, "parent_node")
|
||||
|
||||
# A checkpointer must be enabled for interrupts to work!
|
||||
checkpointer = MemorySaver()
|
||||
graph = builder.compile(checkpointer=checkpointer)
|
||||
|
||||
config = {
|
||||
"configurable": {
|
||||
"thread_id": uuid.uuid4(),
|
||||
}
|
||||
}
|
||||
|
||||
for chunk in graph.stream({"state_counter": 1}, config):
|
||||
print(chunk)
|
||||
|
||||
print('--- Resuming ---')
|
||||
|
||||
for chunk in graph.stream(Command(resume="35"), config):
|
||||
print(chunk)
|
||||
```
|
||||
|
||||
This will print out
|
||||
|
||||
```pycon
|
||||
--- First invocation ---
|
||||
In parent node: {'foo': 'bar'}
|
||||
Entered `parent_node` a total of 1 times
|
||||
Entered `node_in_subgraph` a total of 1 times
|
||||
Entered human_node in sub-graph a total of 1 times
|
||||
{'__interrupt__': (Interrupt(value='what is your name?', resumable=True, ns=['parent_node:0b23d72f-aaba-0329-1a59-ca4f3c8bad3b', 'human_node:25df717c-cb80-57b0-7410-44e20aac8f3c'], when='during'),)}
|
||||
|
||||
--- Resuming ---
|
||||
In parent node: {'foo': 'bar'}
|
||||
Entered `parent_node` a total of 2 times
|
||||
Entered human_node in sub-graph a total of 2 times
|
||||
Got an answer of 35
|
||||
{'parent_node': None}
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Using multiple interrupts
|
||||
|
||||
|
||||
@@ -94,13 +94,13 @@ This is a special case of updating the graph state from tools where in addition
|
||||
!!! important
|
||||
|
||||
If you want to use tools that return `Command`, you can either use prebuilt [`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent] / [`ToolNode`][langgraph.prebuilt.tool_node.ToolNode] components, or implement your own tool-executing node that collects `Command` objects returned by the tools and returns a list of them, e.g.:
|
||||
|
||||
```python
|
||||
def call_tools(state):
|
||||
...
|
||||
commands = [tools_by_name[call["name"].invoke(call, config={"coerce_tool_content": False}) for tool_call in tool_calls]
|
||||
return commands
|
||||
```
|
||||
|
||||
```python
|
||||
def call_tools(state):
|
||||
...
|
||||
commands = [tools_by_name[tool_call["name"]].invoke(tool_call) for tool_call in tool_calls]
|
||||
return commands
|
||||
```
|
||||
|
||||
Let's now take a closer look at the different multi-agent architectures.
|
||||
|
||||
|
||||
@@ -168,7 +168,7 @@ Importantly, LangGraph knows whether a particular checkpoint has been executed p
|
||||
|
||||
### Update state
|
||||
|
||||
In addition to re-playing the graph from specific `checkpoints`, we can also *edit* the graph state. We do this using `graph.update_state()`. This method three different arguments:
|
||||
In addition to re-playing the graph from specific `checkpoints`, we can also *edit* the graph state. We do this using `graph.update_state()`. This method accepts three different arguments:
|
||||
|
||||
#### `config`
|
||||
|
||||
@@ -222,7 +222,7 @@ The final thing you can optionally specify when calling `update_state` is `as_no
|
||||
|
||||
A [state schema](low_level.md#schema) specifies a set of keys that are populated as a graph is executed. As discussed above, state can be written by a checkpointer to a thread at each graph step, enabling state persistence.
|
||||
|
||||
But, what if we want to retrain some information *across threads*? Consider the case of a chatbot where we want to retain specific information about the user across *all* chat conversations (e.g., threads) with that user!
|
||||
But, what if we want to retain some information *across threads*? Consider the case of a chatbot where we want to retain specific information about the user across *all* chat conversations (e.g., threads) with that user!
|
||||
|
||||
With checkpointers alone, we cannot share information across threads. This motivates the need for the [`Store`](../reference/store.md#langgraph.store.base.BaseStore) interface. As an illustration, we can define an `InMemoryStore` to store information about a user across threads. We simply compile our graph with a checkpointer, as before, and with our new `in_memory_store` variable.
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ You can create an application from a template using the LangGraph CLI.
|
||||
## Install the LangGraph CLI
|
||||
|
||||
```bash
|
||||
pip install "langgraph-cli[inmem]==0.1.58" python-dotenv
|
||||
pip install "langgraph-cli[inmem]" --upgrade
|
||||
```
|
||||
|
||||
## Available Templates
|
||||
|
||||
@@ -30,7 +30,7 @@ These how-to guides show how to achieve that controllability.
|
||||
- [How to add thread-level persistence to subgraphs](subgraph-persistence.ipynb)
|
||||
- [How to add cross-thread persistence to your graph](cross-thread-persistence.ipynb)
|
||||
- [How to use Postgres checkpointer for persistence](persistence_postgres.ipynb)
|
||||
- [How to create a custom checkpointer using MongoDB](persistence_mongodb.ipynb)
|
||||
- [How to use MongoDB checkpointer for persistence](persistence_mongodb.ipynb)
|
||||
- [How to create a custom checkpointer using Redis](persistence_redis.ipynb)
|
||||
|
||||
### Memory
|
||||
@@ -247,6 +247,7 @@ LangGraph Studio is a built-in UI for visualizing, testing, and debugging your a
|
||||
- [How to connect to a local deployment (Docker)](../cloud/how-tos/test_local_deployment.md)
|
||||
- [How to test your graph in LangGraph Studio (MacOS only)](../cloud/how-tos/invoke_studio.md)
|
||||
- [How to interact with threads in LangGraph Studio](../cloud/how-tos/threads_studio.md)
|
||||
- [How to add nodes as dataset examples in LangGraph Studio](../cloud/how-tos/datasets_studio.md)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
" \"content\": user_input,\n",
|
||||
" }]\n",
|
||||
" },\n",
|
||||
" goto=active_agent,\n",
|
||||
" goto=active_agent,)\n",
|
||||
"\n",
|
||||
"def agent(state) -> Command[Literal[\"agent\", \"another_agent\", \"human\"]]:\n",
|
||||
" # The condition for routing/halting can be anything, e.g. LLM tool call / structured output, etc.\n",
|
||||
@@ -139,71 +139,116 @@
|
||||
"from typing_extensions import TypedDict, Literal\n",
|
||||
"\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"from langchain_core.messages import HumanMessage\n",
|
||||
"from langchain_core.messages import AnyMessage\n",
|
||||
"from langgraph.graph import MessagesState, StateGraph, START, END\n",
|
||||
"from langgraph.types import Command, interrupt\n",
|
||||
"from langgraph.checkpoint.memory import MemorySaver\n",
|
||||
"from langgraph.prebuilt import create_react_agent\n",
|
||||
"\n",
|
||||
"model = ChatOpenAI(model=\"gpt-4o\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def make_agent_node(*, name: str, destinations: list[str], system_prompt: str):\n",
|
||||
" def agent_node(state: MessagesState) -> Command[Literal[*destinations, \"human\"]]:\n",
|
||||
" # define schema for the structured output:\n",
|
||||
" # - model's text response (`response`)\n",
|
||||
" # - name of the node to go to next (or 'finish')\n",
|
||||
" class Response(TypedDict):\n",
|
||||
" response: str\n",
|
||||
" goto: Literal[*destinations, \"finish\"]\n",
|
||||
"# Define a helper for each of the agent nodes to call\n",
|
||||
"def call_llm(messages: list[AnyMessage], target_agent_nodes: list[str]):\n",
|
||||
" \"\"\"Call LLM with structured output to get a natural language response as well as a target agent (node) to go to next.\n",
|
||||
"\n",
|
||||
" messages = [{\"role\": \"system\", \"content\": system_prompt}] + state[\"messages\"]\n",
|
||||
" response = model.with_structured_output(Response).invoke(messages)\n",
|
||||
" goto = response[\"goto\"]\n",
|
||||
" if goto == \"finish\":\n",
|
||||
" # When the agent is done, we should go to the\n",
|
||||
" goto = \"human\"\n",
|
||||
"\n",
|
||||
" # Handoff to another agent or halt\n",
|
||||
" ai_msg = {\"role\": \"ai\", \"content\": response[\"response\"], \"name\": name}\n",
|
||||
" return Command(goto=goto, update={\"messages\": [ai_msg]})\n",
|
||||
"\n",
|
||||
" return agent_node\n",
|
||||
" Args:\n",
|
||||
" messages: list of messages to pass to the LLM\n",
|
||||
" target_agents: list of the node names of the target agents to navigate to\n",
|
||||
" \"\"\"\n",
|
||||
" # define JSON schema for the structured output:\n",
|
||||
" # - model's text response (`response`)\n",
|
||||
" # - name of the node to go to next (or 'finish')\n",
|
||||
" # see more on structured output here https://python.langchain.com/docs/concepts/structured_outputs\n",
|
||||
" json_schema = {\n",
|
||||
" \"name\": \"Response\",\n",
|
||||
" \"parameters\": {\n",
|
||||
" \"type\": \"object\",\n",
|
||||
" \"properties\": {\n",
|
||||
" \"response\": {\n",
|
||||
" \"type\": \"string\",\n",
|
||||
" \"description\": \"A human readable response to the original question. Does not need to be a final response. Will be streamed back to the user.\",\n",
|
||||
" },\n",
|
||||
" \"goto\": {\n",
|
||||
" \"enum\": [*target_agent_nodes, \"finish\"],\n",
|
||||
" \"type\": \"string\",\n",
|
||||
" \"description\": \"The next agent to call, or 'finish' if the user's query has been resolved. Must be one of the specified values.\",\n",
|
||||
" },\n",
|
||||
" },\n",
|
||||
" \"required\": [\"response\", \"goto\"],\n",
|
||||
" },\n",
|
||||
" }\n",
|
||||
" response = model.with_structured_output(json_schema).invoke(messages)\n",
|
||||
" return response\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"travel_advisor = make_agent_node(\n",
|
||||
" name=\"travel_advisor\",\n",
|
||||
" destinations=[\"sightseeing_advisor\", \"hotel_advisor\", \"human\"],\n",
|
||||
" system_prompt=(\n",
|
||||
"def travel_advisor(\n",
|
||||
" state: MessagesState,\n",
|
||||
") -> Command[Literal[\"sightseeing_advisor\", \"hotel_advisor\", \"human\"]]:\n",
|
||||
" system_prompt = (\n",
|
||||
" \"You are a general travel expert that can recommend travel destinations (e.g. countries, cities, etc). \"\n",
|
||||
" \"If you need specific sightseeing recommendations, ask 'sightseeing_advisor' for help. \"\n",
|
||||
" \"If you need hotel recommendations, ask 'hotel_advisor' for help. \"\n",
|
||||
" \"If you have enough information to respond to the user, return 'finish'. \"\n",
|
||||
" \"Never mention other agents by name.\"\n",
|
||||
" ),\n",
|
||||
")\n",
|
||||
"sightseeing_advisor = make_agent_node(\n",
|
||||
" name=\"sightseeing_advisor\",\n",
|
||||
" destinations=[\"travel_advisor\", \"hotel_advisor\", \"human\"],\n",
|
||||
" system_prompt=(\n",
|
||||
" )\n",
|
||||
" messages = [{\"role\": \"system\", \"content\": system_prompt}] + state[\"messages\"]\n",
|
||||
" target_agent_nodes = [\"sightseeing_advisor\", \"hotel_advisor\"]\n",
|
||||
" response = call_llm(messages, target_agent_nodes)\n",
|
||||
" ai_msg = {\"role\": \"ai\", \"content\": response[\"response\"], \"name\": \"travel_advisor\"}\n",
|
||||
" # handoff to another agent or go to the human when agent is done\n",
|
||||
" goto = response[\"goto\"]\n",
|
||||
" if goto == \"finish\":\n",
|
||||
" goto = \"human\"\n",
|
||||
"\n",
|
||||
" return Command(goto=goto, update={\"messages\": [ai_msg]})\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def sightseeing_advisor(\n",
|
||||
" state: MessagesState,\n",
|
||||
") -> Command[Literal[\"travel_advisor\", \"hotel_advisor\", \"human\"]]:\n",
|
||||
" system_prompt = (\n",
|
||||
" \"You are a travel expert that can provide specific sightseeing recommendations for a given destination. \"\n",
|
||||
" \"If you need general travel help, go to 'travel_advisor' for help. \"\n",
|
||||
" \"If you need hotel recommendations, go to 'hotel_advisor' for help. \"\n",
|
||||
" \"If you have enough information to respond to the user, return 'finish'. \"\n",
|
||||
" \"Never mention other agents by name.\"\n",
|
||||
" ),\n",
|
||||
")\n",
|
||||
"hotel_advisor = make_agent_node(\n",
|
||||
" name=\"hotel_advisor\",\n",
|
||||
" destinations=[\"travel_advisor\", \"sightseeing_advisor\", \"human\"],\n",
|
||||
" system_prompt=(\n",
|
||||
" )\n",
|
||||
" messages = [{\"role\": \"system\", \"content\": system_prompt}] + state[\"messages\"]\n",
|
||||
" target_agent_nodes = [\"travel_advisor\", \"hotel_advisor\"]\n",
|
||||
" response = call_llm(messages, target_agent_nodes)\n",
|
||||
" ai_msg = {\n",
|
||||
" \"role\": \"ai\",\n",
|
||||
" \"content\": response[\"response\"],\n",
|
||||
" \"name\": \"sightseeing_advisor\",\n",
|
||||
" }\n",
|
||||
" # handoff to another agent or go to the human when agent is done\n",
|
||||
" goto = response[\"goto\"]\n",
|
||||
" if goto == \"finish\":\n",
|
||||
" goto = \"human\"\n",
|
||||
"\n",
|
||||
" return Command(goto=goto, update={\"messages\": [ai_msg]})\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def hotel_advisor(\n",
|
||||
" state: MessagesState,\n",
|
||||
") -> Command[Literal[\"travel_advisor\", \"sightseeing_advisor\", \"human\"]]:\n",
|
||||
" system_prompt = (\n",
|
||||
" \"You are a travel expert that can provide hotel recommendations for a given destination. \"\n",
|
||||
" \"If you need general travel help, ask 'travel_advisor' for help. \"\n",
|
||||
" \"If you need specific sightseeing recommendations, ask 'sightseeing_advisor' for help. \"\n",
|
||||
" \"If you have enough information to respond to the user, return 'finish'. \"\n",
|
||||
" \"Never mention other agents by name.\"\n",
|
||||
" ),\n",
|
||||
")\n",
|
||||
" )\n",
|
||||
" messages = [{\"role\": \"system\", \"content\": system_prompt}] + state[\"messages\"]\n",
|
||||
" target_agent_nodes = [\"travel_advisor\", \"sightseeing_advisor\"]\n",
|
||||
" response = call_llm(messages, target_agent_nodes)\n",
|
||||
" ai_msg = {\"role\": \"ai\", \"content\": response[\"response\"], \"name\": \"hotel_advisor\"}\n",
|
||||
" # handoff to another agent or go to the human when agent is done\n",
|
||||
" goto = response[\"goto\"]\n",
|
||||
" if goto == \"finish\":\n",
|
||||
" goto = \"human\"\n",
|
||||
"\n",
|
||||
" return Command(goto=goto, update={\"messages\": [ai_msg]})\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def human_node(\n",
|
||||
@@ -376,7 +421,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.4"
|
||||
"version": "3.12.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -133,74 +133,110 @@
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "aa4bdbff-9461-46cc-aee9-8a22d3c3d9ec",
|
||||
"id": "29e2a824-dbeb-4944-9df2-63fdbd883252",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing_extensions import TypedDict, Literal\n",
|
||||
"\n",
|
||||
"from langchain_openai import ChatOpenAI\n",
|
||||
"from langchain_core.messages import AnyMessage\n",
|
||||
"from langgraph.graph import MessagesState, StateGraph, START, END\n",
|
||||
"from langgraph.types import Command\n",
|
||||
"\n",
|
||||
"model = ChatOpenAI(model=\"gpt-4o\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def make_agent_node(*, name: str, destinations: list[str], system_prompt: str):\n",
|
||||
" def agent_node(state: MessagesState) -> Command[Literal[*destinations, END]]:\n",
|
||||
" # define schema for the structured output:\n",
|
||||
" # - model's text response (`response`)\n",
|
||||
" # - name of the node to go to next (or 'finish')\n",
|
||||
" class Response(TypedDict):\n",
|
||||
" response: str\n",
|
||||
" goto: Literal[*destinations, \"finish\"]\n",
|
||||
"# Define a helper for each of the agent nodes to call\n",
|
||||
"def call_llm(messages: list[AnyMessage], target_agent_nodes: list[str]):\n",
|
||||
" \"\"\"Call LLM with structured output to get a natural language response as well as a target agent (node) to go to next.\n",
|
||||
"\n",
|
||||
" messages = [{\"role\": \"system\", \"content\": system_prompt}] + state[\"messages\"]\n",
|
||||
" response = model.with_structured_output(Response).invoke(messages)\n",
|
||||
" goto = response[\"goto\"]\n",
|
||||
" if goto == \"finish\":\n",
|
||||
" goto = END\n",
|
||||
"\n",
|
||||
" # handoff to another agent or halt\n",
|
||||
" ai_msg = {\"role\": \"ai\", \"content\": response[\"response\"], \"name\": name}\n",
|
||||
" return Command(goto=goto, update={\"messages\": ai_msg})\n",
|
||||
"\n",
|
||||
" return agent_node\n",
|
||||
" Args:\n",
|
||||
" messages: list of messages to pass to the LLM\n",
|
||||
" target_agents: list of the node names of the target agents to navigate to\n",
|
||||
" \"\"\"\n",
|
||||
" # define JSON schema for the structured output:\n",
|
||||
" # - model's text response (`response`)\n",
|
||||
" # - name of the node to go to next (or 'finish')\n",
|
||||
" # see more on structured output here https://python.langchain.com/docs/concepts/structured_outputs\n",
|
||||
" json_schema = {\n",
|
||||
" \"name\": \"Response\",\n",
|
||||
" \"parameters\": {\n",
|
||||
" \"type\": \"object\",\n",
|
||||
" \"properties\": {\n",
|
||||
" \"response\": {\n",
|
||||
" \"type\": \"string\",\n",
|
||||
" \"description\": \"A human readable response to the original question. Does not need to be a final response. Will be streamed back to the user.\",\n",
|
||||
" },\n",
|
||||
" \"goto\": {\n",
|
||||
" \"enum\": [*target_agent_nodes, \"__end__\"],\n",
|
||||
" \"type\": \"string\",\n",
|
||||
" \"description\": \"The next agent to call, or __end__ if the user's query has been resolved. Must be one of the specified values.\",\n",
|
||||
" },\n",
|
||||
" },\n",
|
||||
" \"required\": [\"response\", \"goto\"],\n",
|
||||
" },\n",
|
||||
" }\n",
|
||||
" response = model.with_structured_output(json_schema).invoke(messages)\n",
|
||||
" return response\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"travel_advisor = make_agent_node(\n",
|
||||
" name=\"travel_advisor\",\n",
|
||||
" destinations=[\"sightseeing_advisor\", \"hotel_advisor\"],\n",
|
||||
" system_prompt=(\n",
|
||||
"def travel_advisor(\n",
|
||||
" state: MessagesState,\n",
|
||||
") -> Command[Literal[\"sightseeing_advisor\", \"hotel_advisor\", \"__end__\"]]:\n",
|
||||
" system_prompt = (\n",
|
||||
" \"You are a general travel expert that can recommend travel destinations (e.g. countries, cities, etc). \"\n",
|
||||
" \"If you need specific sightseeing recommendations, ask 'sightseeing_advisor' for help. \"\n",
|
||||
" \"If you need hotel recommendations, ask 'hotel_advisor' for help. \"\n",
|
||||
" \"If you have enough information to respond to the user, return 'finish'. \"\n",
|
||||
" \"Never mention other agents by name.\"\n",
|
||||
" ),\n",
|
||||
")\n",
|
||||
"sightseeing_advisor = make_agent_node(\n",
|
||||
" name=\"sightseeing_advisor\",\n",
|
||||
" destinations=[\"travel_advisor\", \"hotel_advisor\"],\n",
|
||||
" system_prompt=(\n",
|
||||
" )\n",
|
||||
" messages = [{\"role\": \"system\", \"content\": system_prompt}] + state[\"messages\"]\n",
|
||||
" target_agent_nodes = [\"sightseeing_advisor\", \"hotel_advisor\"]\n",
|
||||
" response = call_llm(messages, target_agent_nodes)\n",
|
||||
" ai_msg = {\"role\": \"ai\", \"content\": response[\"response\"], \"name\": \"travel_advisor\"}\n",
|
||||
" # handoff to another agent or halt\n",
|
||||
" return Command(goto=response[\"goto\"], update={\"messages\": ai_msg})\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def sightseeing_advisor(\n",
|
||||
" state: MessagesState,\n",
|
||||
") -> Command[Literal[\"travel_advisor\", \"hotel_advisor\", \"__end__\"]]:\n",
|
||||
" system_prompt = (\n",
|
||||
" \"You are a travel expert that can provide specific sightseeing recommendations for a given destination. \"\n",
|
||||
" \"If you need general travel help, go to 'travel_advisor' for help. \"\n",
|
||||
" \"If you need hotel recommendations, go to 'hotel_advisor' for help. \"\n",
|
||||
" \"If you have enough information to respond to the user, return 'finish'. \"\n",
|
||||
" \"Never mention other agents by name.\"\n",
|
||||
" ),\n",
|
||||
")\n",
|
||||
"hotel_advisor = make_agent_node(\n",
|
||||
" name=\"hotel_advisor\",\n",
|
||||
" destinations=[\"travel_advisor\", \"sightseeing_advisor\"],\n",
|
||||
" system_prompt=(\n",
|
||||
" )\n",
|
||||
" messages = [{\"role\": \"system\", \"content\": system_prompt}] + state[\"messages\"]\n",
|
||||
" target_agent_nodes = [\"travel_advisor\", \"hotel_advisor\"]\n",
|
||||
" response = call_llm(messages, target_agent_nodes)\n",
|
||||
" ai_msg = {\n",
|
||||
" \"role\": \"ai\",\n",
|
||||
" \"content\": response[\"response\"],\n",
|
||||
" \"name\": \"sightseeing_advisor\",\n",
|
||||
" }\n",
|
||||
" # handoff to another agent or halt\n",
|
||||
" return Command(goto=response[\"goto\"], update={\"messages\": ai_msg})\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def hotel_advisor(\n",
|
||||
" state: MessagesState,\n",
|
||||
") -> Command[Literal[\"travel_advisor\", \"sightseeing_advisor\", \"__end__\"]]:\n",
|
||||
" system_prompt = (\n",
|
||||
" \"You are a travel expert that can provide hotel recommendations for a given destination. \"\n",
|
||||
" \"If you need general travel help, ask 'travel_advisor' for help. \"\n",
|
||||
" \"If you need specific sightseeing recommendations, ask 'sightseeing_advisor' for help. \"\n",
|
||||
" \"If you have enough information to respond to the user, return 'finish'. \"\n",
|
||||
" \"Never mention other agents by name.\"\n",
|
||||
" ),\n",
|
||||
")\n",
|
||||
" )\n",
|
||||
" messages = [{\"role\": \"system\", \"content\": system_prompt}] + state[\"messages\"]\n",
|
||||
" target_agent_nodes = [\"travel_advisor\", \"sightseeing_advisor\"]\n",
|
||||
" response = call_llm(messages, target_agent_nodes)\n",
|
||||
" ai_msg = {\"role\": \"ai\", \"content\": response[\"response\"], \"name\": \"hotel_advisor\"}\n",
|
||||
" # handoff to another agent or halt\n",
|
||||
" return Command(goto=response[\"goto\"], update={\"messages\": ai_msg})\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"builder = StateGraph(MessagesState)\n",
|
||||
@@ -254,7 +290,7 @@
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'travel_advisor': {'messages': {'role': 'ai', 'content': 'The Caribbean offers many warm destinations perfect for a relaxing getaway. Consider visiting Jamaica for its beautiful beaches and vibrant culture, the Bahamas for its stunning islands and clear waters, or the Dominican Republic for its all-inclusive resorts and rich history. Let me know if you need more information on sightseeing or hotel recommendations!', 'name': 'travel_advisor'}}}\n",
|
||||
"{'travel_advisor': {'messages': {'role': 'ai', 'content': 'The Caribbean offers many warm and beautiful destinations. Some popular ones include:\\n\\n1. **Jamaica** - Known for its beautiful beaches, reggae music, and vibrant culture.\\n2. **Bahamas** - Offers stunning beaches and clear turquoise waters, perfect for relaxation and water activities.\\n3. **Barbados** - Known for its friendly locals, delicious cuisine, and beautiful beaches.\\n4. **Dominican Republic** - Offers a mix of beaches, mountains, and historical sites.\\n5. **Aruba** - Known for its dry climate, beautiful beaches, and outdoor activities.\\n\\nWould you like recommendations for sightseeing or hotels in any of these destinations?', 'name': 'travel_advisor'}}}\n",
|
||||
"\n",
|
||||
"\n"
|
||||
]
|
||||
@@ -286,13 +322,13 @@
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'travel_advisor': {'messages': {'role': 'ai', 'content': 'I recommend visiting Jamaica, a beautiful Caribbean island known for its warm climate, stunning beaches, and vibrant culture.', 'name': 'travel_advisor'}}}\n",
|
||||
"{'travel_advisor': {'messages': {'role': 'ai', 'content': 'I recommend visiting Jamaica, a vibrant and warm destination in the Caribbean known for its beautiful beaches, rich culture, and exciting activities.', 'name': 'travel_advisor'}}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"{'sightseeing_advisor': {'messages': {'role': 'ai', 'content': \"Jamaica is a fantastic choice for a warm Caribbean getaway. Here are some top things to do while you're there:\\n\\n1. **Dunn's River Falls**: Located near Ocho Rios, this is one of Jamaica's most famous waterfalls. You can climb the falls, swim in the refreshing pools, or simply enjoy the beautiful surroundings.\\n\\n2. **Seven Mile Beach**: Located in Negril, this is one of the most beautiful beaches in the Caribbean. It's perfect for sunbathing, swimming, and enjoying water sports.\\n\\n3. **Bob Marley Museum**: Situated in Kingston, this museum is dedicated to the life and legacy of the reggae legend Bob Marley and is a must-visit for music lovers.\\n\\n4. **Blue Mountains**: Go hiking or take a tour to explore the Blue Mountains, where you can enjoy breathtaking views and taste some of the world's best coffee.\\n\\n5. **Luminous Lagoon**: Experience the natural wonder of the Luminous Lagoon in Falmouth, where the water glows at night due to bioluminescent microorganisms.\\n\\nFor hotel recommendations, I suggest checking with a hotel advisor for the best options that suit your budget and preferences.\", 'name': 'sightseeing_advisor'}}}\n",
|
||||
"{'sightseeing_advisor': {'messages': {'role': 'ai', 'content': \"For a warm and vibrant Caribbean destination, I recommend Jamaica. It's renowned for its stunning beaches, lively culture, and exciting activities.\\n\\n### Things to Do in Jamaica:\\n1. **Explore Dunn’s River Falls**: A famous waterfall near Ocho Rios where you can climb the terraced steps and enjoy the natural pools.\\n2. **Visit Bob Marley Museum**: Located in Kingston, this museum offers a deep dive into the life of the reggae legend.\\n3. **Relax at Seven Mile Beach**: Known for its beautiful white sand and clear blue waters, it’s perfect for sunbathing and swimming.\\n4. **Experience Negril Cliffs**: Enjoy breathtaking views and adventurous cliff diving.\\n5. **Discover Blue Hole**: A hidden gem near Ocho Rios, offering a refreshing swim in stunning turquoise waters.\\n\\nI will now find some hotel recommendations for you in Jamaica.\", 'name': 'sightseeing_advisor'}}}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"{'hotel_advisor': {'messages': {'role': 'ai', 'content': 'For hotel recommendations in Jamaica, here are a few options across different areas: \\n\\n1. **Sandals Montego Bay** (Montego Bay): A luxurious all-inclusive resort ideal for couples, offering beautiful beachfront views and a variety of dining options.\\n\\n2. **Half Moon Resort** (Montego Bay): A family-friendly resort with a private beach, golf course, and various activities for all ages.\\n\\n3. **Jamaica Inn** (Ocho Rios): A charming boutique hotel known for its excellent service and tranquil atmosphere.\\n\\n4. **The Caves** (Negril): A unique and romantic cliff-side resort offering stunning ocean views and intimate dining experiences.\\n\\n5. **Trident Hotel** (Port Antonio): A luxurious and contemporary hotel offering privacy, elegance, and beautiful views of the Caribbean Sea.\\n\\nThese options cater to different tastes and budgets, ensuring a comfortable and enjoyable stay in Jamaica.', 'name': 'hotel_advisor'}}}\n",
|
||||
"{'hotel_advisor': {'messages': {'role': 'ai', 'content': 'For your stay in Jamaica, here are some hotel recommendations:\\n\\n1. **Sandals Montego Bay**: An all-inclusive resort offering luxury accommodations, private beaches, and various dining options.\\n\\n2. **Jamaica Inn**: Located in Ocho Rios, this boutique hotel offers a more intimate experience with stunning beach views and personalized service.\\n\\n3. **Half Moon Resort**: Situated in Montego Bay, this resort features a private beach, golf course, and a world-class spa.\\n\\n4. **The Caves Hotel**: In Negril, offering unique cliffside cottages and a romantic atmosphere.\\n\\n5. **Round Hill Hotel and Villas**: A classic resort near Montego Bay with luxurious villas and an elegant ambiance.\\n\\nEnjoy your trip to Jamaica, where warm weather and vibrant culture await!', 'name': 'hotel_advisor'}}}\n",
|
||||
"\n",
|
||||
"\n"
|
||||
]
|
||||
@@ -374,7 +410,7 @@
|
||||
" guard_on_duty: bool\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def villager(state: GameState) -> Command[Literal[\"villager\", END]]:\n",
|
||||
"def villager(state: GameState) -> Command[Literal[\"villager\", \"__end__\"]]:\n",
|
||||
" \"\"\"Villager NPC that gathers wood and food.\"\"\"\n",
|
||||
" current_resources = state[\"wood\"] + state[\"food\"]\n",
|
||||
" if current_resources < 15: # Continue gathering until we have enough resources\n",
|
||||
@@ -387,7 +423,7 @@
|
||||
" return Command(goto=END)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def guard(state: GameState) -> Command[Literal[\"guard\", END]]:\n",
|
||||
"def guard(state: GameState) -> Command[Literal[\"guard\", \"__end__\"]]:\n",
|
||||
" \"\"\"Guard NPC that protects gold and consumes food.\"\"\"\n",
|
||||
" if not state[\"guard_on_duty\"]:\n",
|
||||
" return Command(goto=END)\n",
|
||||
@@ -404,7 +440,7 @@
|
||||
" return Command(goto=END, update={\"guard_on_duty\": False}) # Leave to get food\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def merchant(state: GameState) -> Command[Literal[\"merchant\", END]]:\n",
|
||||
"def merchant(state: GameState) -> Command[Literal[\"merchant\", \"__end__\"]]:\n",
|
||||
" \"\"\"Merchant NPC that trades wood for gold.\"\"\"\n",
|
||||
" if state[\"wood\"] >= 5: # Trade wood for gold when available\n",
|
||||
" print(\"Merchant trading wood for gold.\")\n",
|
||||
@@ -413,7 +449,7 @@
|
||||
" return Command(goto=END)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def thief(state: GameState) -> Command[Literal[\"thief\", END]]:\n",
|
||||
"def thief(state: GameState) -> Command[Literal[\"thief\", \"__end__\"]]:\n",
|
||||
" \"\"\"Thief NPC that steals gold if the guard leaves to get food.\"\"\"\n",
|
||||
" if not state[\"guard_on_duty\"]:\n",
|
||||
" print(\"Thief stealing gold.\")\n",
|
||||
@@ -561,7 +597,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.9"
|
||||
"version": "3.12.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -151,6 +151,7 @@
|
||||
"from langchain_core.runnables import RunnableConfig\n",
|
||||
"\n",
|
||||
"from langgraph.checkpoint.base import (\n",
|
||||
" WRITES_IDX_MAP,\n",
|
||||
" BaseCheckpointSaver,\n",
|
||||
" ChannelVersions,\n",
|
||||
" Checkpoint,\n",
|
||||
@@ -163,7 +164,7 @@
|
||||
"from redis import Redis\n",
|
||||
"from redis.asyncio import Redis as AsyncRedis\n",
|
||||
"\n",
|
||||
"REDIS_KEY_SEPARATOR = \":\"\n",
|
||||
"REDIS_KEY_SEPARATOR = \"$\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Utilities shared by both RedisSaver and AsyncRedisSaver\n",
|
||||
@@ -246,17 +247,6 @@
|
||||
" return keys\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _dump_writes(serde: SerializerProtocol, writes: tuple[str, Any]) -> list[dict]:\n",
|
||||
" \"\"\"Serialize pending writes.\"\"\"\n",
|
||||
" serialized_writes = []\n",
|
||||
" for channel, value in writes:\n",
|
||||
" type_, serialized_value = serde.dumps_typed(value)\n",
|
||||
" serialized_writes.append(\n",
|
||||
" {\"channel\": channel, \"type\": type_, \"value\": serialized_value}\n",
|
||||
" )\n",
|
||||
" return serialized_writes\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _load_writes(\n",
|
||||
" serde: SerializerProtocol, task_id_to_data: dict[tuple[str, str], dict]\n",
|
||||
") -> list[PendingWrite]:\n",
|
||||
@@ -413,7 +403,7 @@
|
||||
" config: RunnableConfig,\n",
|
||||
" writes: List[Tuple[str, Any]],\n",
|
||||
" task_id: str,\n",
|
||||
" ) -> RunnableConfig:\n",
|
||||
" ) -> None:\n",
|
||||
" \"\"\"Store intermediate writes linked to a checkpoint.\n",
|
||||
"\n",
|
||||
" Args:\n",
|
||||
@@ -425,12 +415,23 @@
|
||||
" checkpoint_ns = config[\"configurable\"][\"checkpoint_ns\"]\n",
|
||||
" checkpoint_id = config[\"configurable\"][\"checkpoint_id\"]\n",
|
||||
"\n",
|
||||
" for idx, data in enumerate(_dump_writes(self.serde, writes)):\n",
|
||||
" for idx, (channel, value) in enumerate(writes):\n",
|
||||
" key = _make_redis_checkpoint_writes_key(\n",
|
||||
" thread_id, checkpoint_ns, checkpoint_id, task_id, idx\n",
|
||||
" thread_id,\n",
|
||||
" checkpoint_ns,\n",
|
||||
" checkpoint_id,\n",
|
||||
" task_id,\n",
|
||||
" WRITES_IDX_MAP.get(channel, idx),\n",
|
||||
" )\n",
|
||||
" self.conn.hset(key, mapping=data)\n",
|
||||
" return config\n",
|
||||
" type_, serialized_value = self.serde.dumps_typed(value)\n",
|
||||
" data = {\"channel\": channel, \"type\": type_, \"value\": serialized_value}\n",
|
||||
" if all(w[0] in WRITES_IDX_MAP for w in writes):\n",
|
||||
" # Use HSET which will overwrite existing values\n",
|
||||
" self.conn.hset(key, mapping=data)\n",
|
||||
" else:\n",
|
||||
" # Use HSETNX which will not overwrite existing values\n",
|
||||
" for field, value in data.items():\n",
|
||||
" self.conn.hsetnx(key, field, value)\n",
|
||||
"\n",
|
||||
" def get_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]:\n",
|
||||
" \"\"\"Get a checkpoint tuple from Redis.\n",
|
||||
@@ -463,21 +464,8 @@
|
||||
" checkpoint_id\n",
|
||||
" or _parse_redis_checkpoint_key(checkpoint_key)[\"checkpoint_id\"]\n",
|
||||
" )\n",
|
||||
" writes_key = _make_redis_checkpoint_writes_key(\n",
|
||||
" thread_id, checkpoint_ns, checkpoint_id, \"*\", None\n",
|
||||
" )\n",
|
||||
" matching_keys = self.conn.keys(pattern=writes_key)\n",
|
||||
" parsed_keys = [\n",
|
||||
" _parse_redis_checkpoint_writes_key(key.decode()) for key in matching_keys\n",
|
||||
" ]\n",
|
||||
" pending_writes = _load_writes(\n",
|
||||
" self.serde,\n",
|
||||
" {\n",
|
||||
" (parsed_key[\"task_id\"], parsed_key[\"idx\"]): self.conn.hgetall(key)\n",
|
||||
" for key, parsed_key in sorted(\n",
|
||||
" zip(matching_keys, parsed_keys), key=lambda x: x[1][\"idx\"]\n",
|
||||
" )\n",
|
||||
" },\n",
|
||||
" pending_writes = self._load_pending_writes(\n",
|
||||
" thread_id, checkpoint_ns, checkpoint_id\n",
|
||||
" )\n",
|
||||
" return _parse_redis_checkpoint_data(\n",
|
||||
" self.serde, checkpoint_key, checkpoint_data, pending_writes=pending_writes\n",
|
||||
@@ -514,7 +502,37 @@
|
||||
" for key in keys:\n",
|
||||
" data = self.conn.hgetall(key)\n",
|
||||
" if data and b\"checkpoint\" in data and b\"metadata\" in data:\n",
|
||||
" yield _parse_redis_checkpoint_data(self.serde, key.decode(), data)\n",
|
||||
" # load pending writes\n",
|
||||
" checkpoint_id = _parse_redis_checkpoint_key(key.decode())[\n",
|
||||
" \"checkpoint_id\"\n",
|
||||
" ]\n",
|
||||
" pending_writes = self._load_pending_writes(\n",
|
||||
" thread_id, checkpoint_ns, checkpoint_id\n",
|
||||
" )\n",
|
||||
" yield _parse_redis_checkpoint_data(\n",
|
||||
" self.serde, key.decode(), data, pending_writes=pending_writes\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" def _load_pending_writes(\n",
|
||||
" self, thread_id: str, checkpoint_ns: str, checkpoint_id: str\n",
|
||||
" ) -> List[PendingWrite]:\n",
|
||||
" writes_key = _make_redis_checkpoint_writes_key(\n",
|
||||
" thread_id, checkpoint_ns, checkpoint_id, \"*\", None\n",
|
||||
" )\n",
|
||||
" matching_keys = self.conn.keys(pattern=writes_key)\n",
|
||||
" parsed_keys = [\n",
|
||||
" _parse_redis_checkpoint_writes_key(key.decode()) for key in matching_keys\n",
|
||||
" ]\n",
|
||||
" pending_writes = _load_writes(\n",
|
||||
" self.serde,\n",
|
||||
" {\n",
|
||||
" (parsed_key[\"task_id\"], parsed_key[\"idx\"]): self.conn.hgetall(key)\n",
|
||||
" for key, parsed_key in sorted(\n",
|
||||
" zip(matching_keys, parsed_keys), key=lambda x: x[1][\"idx\"]\n",
|
||||
" )\n",
|
||||
" },\n",
|
||||
" )\n",
|
||||
" return pending_writes\n",
|
||||
"\n",
|
||||
" def _get_checkpoint_key(\n",
|
||||
" self, conn, thread_id: str, checkpoint_ns: str, checkpoint_id: Optional[str]\n",
|
||||
@@ -637,7 +655,7 @@
|
||||
" config: RunnableConfig,\n",
|
||||
" writes: List[Tuple[str, Any]],\n",
|
||||
" task_id: str,\n",
|
||||
" ) -> RunnableConfig:\n",
|
||||
" ) -> None:\n",
|
||||
" \"\"\"Store intermediate writes linked to a checkpoint asynchronously.\n",
|
||||
"\n",
|
||||
" This method saves intermediate writes associated with a checkpoint to the database.\n",
|
||||
@@ -651,12 +669,23 @@
|
||||
" checkpoint_ns = config[\"configurable\"][\"checkpoint_ns\"]\n",
|
||||
" checkpoint_id = config[\"configurable\"][\"checkpoint_id\"]\n",
|
||||
"\n",
|
||||
" for idx, data in enumerate(_dump_writes(self.serde, writes)):\n",
|
||||
" for idx, (channel, value) in enumerate(writes):\n",
|
||||
" key = _make_redis_checkpoint_writes_key(\n",
|
||||
" thread_id, checkpoint_ns, checkpoint_id, task_id, idx\n",
|
||||
" thread_id,\n",
|
||||
" checkpoint_ns,\n",
|
||||
" checkpoint_id,\n",
|
||||
" task_id,\n",
|
||||
" WRITES_IDX_MAP.get(channel, idx),\n",
|
||||
" )\n",
|
||||
" await self.conn.hset(key, mapping=data)\n",
|
||||
" return config\n",
|
||||
" type_, serialized_value = self.serde.dumps_typed(value)\n",
|
||||
" data = {\"channel\": channel, \"type\": type_, \"value\": serialized_value}\n",
|
||||
" if all(w[0] in WRITES_IDX_MAP for w in writes):\n",
|
||||
" # Use HSET which will overwrite existing values\n",
|
||||
" await self.conn.hset(key, mapping=data)\n",
|
||||
" else:\n",
|
||||
" # Use HSETNX which will not overwrite existing values\n",
|
||||
" for field, value in data.items():\n",
|
||||
" await self.conn.hsetnx(key, field, value)\n",
|
||||
"\n",
|
||||
" async def aget_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]:\n",
|
||||
" \"\"\"Get a checkpoint tuple from Redis asynchronously.\n",
|
||||
@@ -688,21 +717,8 @@
|
||||
" checkpoint_id\n",
|
||||
" or _parse_redis_checkpoint_key(checkpoint_key)[\"checkpoint_id\"]\n",
|
||||
" )\n",
|
||||
" writes_key = _make_redis_checkpoint_writes_key(\n",
|
||||
" thread_id, checkpoint_ns, checkpoint_id, \"*\", None\n",
|
||||
" )\n",
|
||||
" matching_keys = await self.conn.keys(pattern=writes_key)\n",
|
||||
" parsed_keys = [\n",
|
||||
" _parse_redis_checkpoint_writes_key(key.decode()) for key in matching_keys\n",
|
||||
" ]\n",
|
||||
" pending_writes = _load_writes(\n",
|
||||
" self.serde,\n",
|
||||
" {\n",
|
||||
" (parsed_key[\"task_id\"], parsed_key[\"idx\"]): await self.conn.hgetall(key)\n",
|
||||
" for key, parsed_key in sorted(\n",
|
||||
" zip(matching_keys, parsed_keys), key=lambda x: x[1][\"idx\"]\n",
|
||||
" )\n",
|
||||
" },\n",
|
||||
" pending_writes = await self._aload_pending_writes(\n",
|
||||
" thread_id, checkpoint_ns, checkpoint_id\n",
|
||||
" )\n",
|
||||
" return _parse_redis_checkpoint_data(\n",
|
||||
" self.serde, checkpoint_key, checkpoint_data, pending_writes=pending_writes\n",
|
||||
@@ -738,7 +754,36 @@
|
||||
" for key in keys:\n",
|
||||
" data = await self.conn.hgetall(key)\n",
|
||||
" if data and b\"checkpoint\" in data and b\"metadata\" in data:\n",
|
||||
" yield _parse_redis_checkpoint_data(self.serde, key.decode(), data)\n",
|
||||
" checkpoint_id = _parse_redis_checkpoint_key(key.decode())[\n",
|
||||
" \"checkpoint_id\"\n",
|
||||
" ]\n",
|
||||
" pending_writes = await self._aload_pending_writes(\n",
|
||||
" thread_id, checkpoint_ns, checkpoint_id\n",
|
||||
" )\n",
|
||||
" yield _parse_redis_checkpoint_data(\n",
|
||||
" self.serde, key.decode(), data, pending_writes=pending_writes\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" async def _aload_pending_writes(\n",
|
||||
" self, thread_id: str, checkpoint_ns: str, checkpoint_id: str\n",
|
||||
" ) -> List[PendingWrite]:\n",
|
||||
" writes_key = _make_redis_checkpoint_writes_key(\n",
|
||||
" thread_id, checkpoint_ns, checkpoint_id, \"*\", None\n",
|
||||
" )\n",
|
||||
" matching_keys = await self.conn.keys(pattern=writes_key)\n",
|
||||
" parsed_keys = [\n",
|
||||
" _parse_redis_checkpoint_writes_key(key.decode()) for key in matching_keys\n",
|
||||
" ]\n",
|
||||
" pending_writes = _load_writes(\n",
|
||||
" self.serde,\n",
|
||||
" {\n",
|
||||
" (parsed_key[\"task_id\"], parsed_key[\"idx\"]): await self.conn.hgetall(key)\n",
|
||||
" for key, parsed_key in sorted(\n",
|
||||
" zip(matching_keys, parsed_keys), key=lambda x: x[1][\"idx\"]\n",
|
||||
" )\n",
|
||||
" },\n",
|
||||
" )\n",
|
||||
" return pending_writes\n",
|
||||
"\n",
|
||||
" async def _aget_checkpoint_key(\n",
|
||||
" self, conn, thread_id: str, checkpoint_ns: str, checkpoint_id: Optional[str]\n",
|
||||
@@ -1042,7 +1087,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.4"
|
||||
"version": "3.12.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -225,9 +225,19 @@
|
||||
"# Define the function that responds to the user\n",
|
||||
"def respond(state: AgentState):\n",
|
||||
" # Construct the final answer from the arguments of the last tool call\n",
|
||||
" response = WeatherResponse(**state[\"messages\"][-1].tool_calls[0][\"args\"])\n",
|
||||
" weather_tool_call = state[\"messages\"][-1].tool_calls[0]\n",
|
||||
" response = WeatherResponse(**weather_tool_call[\"args\"])\n",
|
||||
" # Since we're using tool calling to return structured output,\n",
|
||||
" # we need to add a tool message corresponding to the WeatherResponse tool call,\n",
|
||||
" # This is due to LLM providers' requirement that AI messages with tool calls\n",
|
||||
" # need to be followed by a tool message for each tool call\n",
|
||||
" tool_message = {\n",
|
||||
" \"type\": \"tool\",\n",
|
||||
" \"content\": \"Here is your structured response\",\n",
|
||||
" \"tool_call_id\": weather_tool_call[\"id\"],\n",
|
||||
" }\n",
|
||||
" # We return the final answer\n",
|
||||
" return {\"final_response\": response}\n",
|
||||
" return {\"final_response\": response, \"messages\": [tool_message]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Define the function that determines whether to continue or not\n",
|
||||
@@ -466,7 +476,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.4"
|
||||
"version": "3.12.3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"source": [
|
||||
"# How to call tools using ToolNode\n",
|
||||
"\n",
|
||||
"This guide covers how to use LangGraph's prebuilt [`ToolNode`](https://langchain-ai.github.io/langgraph/reference/prebuilt/#toolnode) for tool calling.\n",
|
||||
"This guide covers how to use LangGraph's prebuilt [`ToolNode`](https://langchain-ai.github.io/langgraph/reference/prebuilt/#langgraph.prebuilt.tool_node.ToolNode) for tool calling.\n",
|
||||
"\n",
|
||||
"`ToolNode` is a LangChain Runnable that takes graph state (with a list of messages) as input and outputs state update with the result of tool calls. It is designed to work well out-of-box with LangGraph's prebuilt [ReAct agent](https://langchain-ai.github.io/langgraph/how-tos/create-react-agent/), but can also work with any `StateGraph` as long as its state has a `messages` key with an appropriate reducer (see [`MessagesState`](https://github.com/langchain-ai/langgraph/blob/e3ef9adac7395e5c0943c22bbc8a4a856b103aa3/libs/langgraph/langgraph/graph/message.py#L150))."
|
||||
]
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
" ```python\n",
|
||||
" def call_tools(state):\n",
|
||||
" ...\n",
|
||||
" commands = [tools_by_name[call[\"name\"].invoke(call, config={\"coerce_tool_content\": False}) for tool_call in tool_calls]\n",
|
||||
" commands = [tools_by_name[tool_call[\"name\"]].invoke(tool_call) for tool_call in tool_calls]\n",
|
||||
" return commands\n",
|
||||
" ```\n",
|
||||
"\n",
|
||||
@@ -51,7 +51,7 @@
|
||||
"\n",
|
||||
"!!! note\n",
|
||||
"\n",
|
||||
" Support for tools that return [`Command`][langgraph.types.Command] was added in LangGraph `v0.2.57`.\n",
|
||||
" Support for tools that return [`Command`][langgraph.types.Command] was added in LangGraph `v0.2.59`.\n",
|
||||
"\n",
|
||||
"## Setup\n",
|
||||
"\n",
|
||||
@@ -166,8 +166,6 @@
|
||||
"\n",
|
||||
"\n",
|
||||
"class State(AgentState):\n",
|
||||
" # user provided\n",
|
||||
" last_name: str\n",
|
||||
" # updated by the tool\n",
|
||||
" user_info: dict[str, Any]\n",
|
||||
"\n",
|
||||
|
||||
@@ -11,9 +11,9 @@ New to LangGraph or LLM app development? Read this material to get up and runnin
|
||||
## Get Started 🚀 {#quick-start}
|
||||
|
||||
- [LangGraph Quickstart](introduction.ipynb): Build a chatbot that can use tools and keep track of conversation history. Add human-in-the-loop capabilities and explore how time-travel works.
|
||||
- [LangGraph Server Quickstart](langgraph-platform/local-server.md): Launch a LangGraph server locally and interact with it using the REST API and LangGraph Studio Web UI.
|
||||
- [LangGraph Cloud QuickStart](../cloud/quick_start.md): Deploy a LangGraph app using LangGraph Cloud.
|
||||
- [LangGraph Template Quickstart](../concepts/template_applications.md): Quickly start building with LangGraph Platform using a template application.
|
||||
- [LangGraph Server Quickstart](langgraph-platform/local-server.md): Launch a LangGraph server locally and interact with it using REST API and LangGraph Studio Web UI.
|
||||
- [LangGraph Template Quickstart](../concepts/template_applications.md): Start building with LangGraph Platform using a template application.
|
||||
- [Deploy with LangGraph Cloud Quickstart](../cloud/quick_start.md): Deploy a LangGraph app using LangGraph Cloud.
|
||||
|
||||
## Use cases 🛠️
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Quick Start: Launch Local LangGraph Server
|
||||
# QuickStart: Launch Local LangGraph Server
|
||||
|
||||
This is a quick start guide to help you get a LangGraph app up and running locally.
|
||||
|
||||
@@ -10,7 +10,7 @@ This is a quick start guide to help you get a LangGraph app up and running local
|
||||
## Install the LangGraph CLI
|
||||
|
||||
```bash
|
||||
pip install -U "langgraph-cli[inmem]" python-dotenv
|
||||
pip install --upgrade "langgraph-cli[inmem]"
|
||||
```
|
||||
|
||||
## 🌱 Create a LangGraph App
|
||||
@@ -53,21 +53,12 @@ ANTHROPIC_API_KEY=sk-
|
||||
OPENAI_API_KEY=sk-...
|
||||
```
|
||||
|
||||
<details><summary>Get API Keys</summary>
|
||||
<ul>
|
||||
<li> <b>LANGSMITH_API_KEY</b>: Go to the <a href="https://smith.langchain.com/settings">LangSmith Settings page</a>. Then clck <b>Create API Key</b>.
|
||||
</li>
|
||||
<li>
|
||||
<b>ANTHROPIC_API_KEY</b>: Get an API key from <a href="https://console.anthropic.com/">Anthropic</a>.
|
||||
</li>
|
||||
<li>
|
||||
<b>OPENAI_API_KEY</b>: Get an API key from <a href="https://openai.com/">OpenAI</a>.
|
||||
</li>
|
||||
<li>
|
||||
<b>TAVILY_API_KEY</b>: Get an API key on the <a href="https://app.tavily.com/">Tavily website</a>.
|
||||
</li>
|
||||
</ul>
|
||||
</details>
|
||||
??? note "Get API Keys"
|
||||
|
||||
- **LANGSMITH_API_KEY**: Go to the [LangSmith Settings page](https://smith.langchain.com/settings). Then clck **Create API Key**.
|
||||
- **ANTHROPIC_API_KEY**: Get an API key from [Anthropic](https://console.anthropic.com/).
|
||||
- **OPENAI_API_KEY**: Get an API key from [OpenAI](https://openai.com/).
|
||||
- **TAVILY_API_KEY**: Get an API key on the [Tavily website](https://app.tavily.com/).
|
||||
|
||||
## 🚀 Launch LangGraph Server
|
||||
|
||||
@@ -79,11 +70,11 @@ This will start up the LangGraph API server locally. If this runs successfully,
|
||||
|
||||
> Ready!
|
||||
>
|
||||
> - API: [http://localhost:8123](http://localhost:8123/)
|
||||
> - API: [http://localhost:2024](http://localhost:2024/)
|
||||
>
|
||||
> - Docs: http://localhost:8123/docs
|
||||
> - Docs: http://localhost:2024/docs
|
||||
>
|
||||
> - LangGraph Studio Web UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:8123
|
||||
> - LangGraph Studio Web UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024
|
||||
|
||||
|
||||
!!! note "In-Memory Mode"
|
||||
@@ -95,9 +86,9 @@ This will start up the LangGraph API server locally. If this runs successfully,
|
||||
|
||||
## LangGraph Studio Web UI
|
||||
|
||||
Test your graph in the LangGraph Studio Web UI by visiting the URL provided in the output of the `langgraph up` command.
|
||||
Test your graph in the LangGraph Studio Web UI by visiting the URL provided in the output of the `langgraph dev` command.
|
||||
|
||||
> - LangGraph Studio Web UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:8123
|
||||
> - LangGraph Studio Web UI: https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:2024
|
||||
|
||||
!!! warning "Safari Compatibility"
|
||||
|
||||
@@ -118,7 +109,7 @@ Test your graph in the LangGraph Studio Web UI by visiting the URL provided in t
|
||||
```python
|
||||
from langgraph_sdk import get_client
|
||||
|
||||
client = get_client(url="http://localhost:8123")
|
||||
client = get_client(url="http://localhost:2024")
|
||||
|
||||
async for chunk in client.runs.stream(
|
||||
None, # Threadless run
|
||||
@@ -149,7 +140,7 @@ Test your graph in the LangGraph Studio Web UI by visiting the URL provided in t
|
||||
```python
|
||||
from langgraph_sdk import get_sync_client
|
||||
|
||||
client = get_sync_client(url="http://localhost:8123")
|
||||
client = get_sync_client(url="http://localhost:2024")
|
||||
|
||||
for chunk in client.runs.stream(
|
||||
None, # Threadless run
|
||||
@@ -181,7 +172,7 @@ Test your graph in the LangGraph Studio Web UI by visiting the URL provided in t
|
||||
const { Client } = await import("@langchain/langgraph-sdk");
|
||||
|
||||
// only set the apiUrl if you changed the default port when calling langgraph up
|
||||
const client = new Client({ apiUrl: "http://localhost:8123"});
|
||||
const client = new Client({ apiUrl: "http://localhost:2024"});
|
||||
|
||||
const streamResponse = client.runs.stream(
|
||||
null, // Threadless run
|
||||
@@ -207,7 +198,7 @@ Test your graph in the LangGraph Studio Web UI by visiting the URL provided in t
|
||||
|
||||
```bash
|
||||
curl -s --request POST \
|
||||
--url "http://localhost:8123/runs/stream" \
|
||||
--url "http://localhost:2024/runs/stream" \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"assistant_id\": \"agent\",
|
||||
@@ -235,7 +226,7 @@ Now that you have a LangGraph app running locally, take your journey further by
|
||||
|
||||
### 🌐 Deploy to LangGraph Cloud
|
||||
|
||||
- **[LangGraph Cloud QuickStart](../../cloud/quick_start.md)**: Deploy your LangGraph app using LangGraph Cloud.
|
||||
- **[LangGraph Cloud Quickstart](../../cloud/quick_start.md)**: Deploy your LangGraph app using LangGraph Cloud.
|
||||
|
||||
### 📚 Learn More about LangGraph Platform
|
||||
|
||||
|
||||
@@ -284,6 +284,7 @@ nav:
|
||||
- cloud/how-tos/test_local_deployment.md
|
||||
- cloud/how-tos/invoke_studio.md
|
||||
- cloud/how-tos/threads_studio.md
|
||||
- cloud/how-tos/datasets_studio.md
|
||||
- Troubleshooting:
|
||||
- Troubleshooting: how-tos#troubleshooting
|
||||
- troubleshooting/errors/index.md
|
||||
|
||||
@@ -438,28 +438,36 @@ def _msgpack_default(obj: Any) -> Union[str, msgpack.ExtType]:
|
||||
def _msgpack_ext_hook(code: int, data: bytes) -> Any:
|
||||
if code == EXT_CONSTRUCTOR_SINGLE_ARG:
|
||||
try:
|
||||
tup = msgpack.unpackb(data, ext_hook=_msgpack_ext_hook)
|
||||
tup = msgpack.unpackb(
|
||||
data, ext_hook=_msgpack_ext_hook, strict_map_key=False
|
||||
)
|
||||
# module, name, arg
|
||||
return getattr(importlib.import_module(tup[0]), tup[1])(tup[2])
|
||||
except Exception:
|
||||
return
|
||||
elif code == EXT_CONSTRUCTOR_POS_ARGS:
|
||||
try:
|
||||
tup = msgpack.unpackb(data, ext_hook=_msgpack_ext_hook)
|
||||
tup = msgpack.unpackb(
|
||||
data, ext_hook=_msgpack_ext_hook, strict_map_key=False
|
||||
)
|
||||
# module, name, args
|
||||
return getattr(importlib.import_module(tup[0]), tup[1])(*tup[2])
|
||||
except Exception:
|
||||
return
|
||||
elif code == EXT_CONSTRUCTOR_KW_ARGS:
|
||||
try:
|
||||
tup = msgpack.unpackb(data, ext_hook=_msgpack_ext_hook)
|
||||
tup = msgpack.unpackb(
|
||||
data, ext_hook=_msgpack_ext_hook, strict_map_key=False
|
||||
)
|
||||
# module, name, args
|
||||
return getattr(importlib.import_module(tup[0]), tup[1])(**tup[2])
|
||||
except Exception:
|
||||
return
|
||||
elif code == EXT_METHOD_SINGLE_ARG:
|
||||
try:
|
||||
tup = msgpack.unpackb(data, ext_hook=_msgpack_ext_hook)
|
||||
tup = msgpack.unpackb(
|
||||
data, ext_hook=_msgpack_ext_hook, strict_map_key=False
|
||||
)
|
||||
# module, name, arg, method
|
||||
return getattr(getattr(importlib.import_module(tup[0]), tup[1]), tup[3])(
|
||||
tup[2]
|
||||
@@ -468,7 +476,9 @@ def _msgpack_ext_hook(code: int, data: bytes) -> Any:
|
||||
return
|
||||
elif code == EXT_PYDANTIC_V1:
|
||||
try:
|
||||
tup = msgpack.unpackb(data, ext_hook=_msgpack_ext_hook)
|
||||
tup = msgpack.unpackb(
|
||||
data, ext_hook=_msgpack_ext_hook, strict_map_key=False
|
||||
)
|
||||
# module, name, kwargs
|
||||
cls = getattr(importlib.import_module(tup[0]), tup[1])
|
||||
try:
|
||||
@@ -479,7 +489,9 @@ def _msgpack_ext_hook(code: int, data: bytes) -> Any:
|
||||
return
|
||||
elif code == EXT_PYDANTIC_V2:
|
||||
try:
|
||||
tup = msgpack.unpackb(data, ext_hook=_msgpack_ext_hook)
|
||||
tup = msgpack.unpackb(
|
||||
data, ext_hook=_msgpack_ext_hook, strict_map_key=False
|
||||
)
|
||||
# module, name, kwargs, method
|
||||
cls = getattr(importlib.import_module(tup[0]), tup[1])
|
||||
try:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "langgraph-checkpoint"
|
||||
version = "2.0.8"
|
||||
version = "2.0.9"
|
||||
description = "Library with base interfaces for LangGraph checkpoint savers."
|
||||
authors = []
|
||||
license = "MIT"
|
||||
|
||||
@@ -576,16 +576,18 @@ def dev(
|
||||
from langgraph_api.cli import run_server
|
||||
except ImportError:
|
||||
try:
|
||||
import pkg_resources
|
||||
from importlib import util
|
||||
|
||||
pkg_resources.require("langgraph-api-inmem")
|
||||
except (ImportError, pkg_resources.DistributionNotFound):
|
||||
if not util.find_spec("langgraph_api"):
|
||||
raise click.UsageError(
|
||||
"Required package 'langgraph-api' is not installed.\n"
|
||||
"Please install it with:\n\n"
|
||||
' pip install -U "langgraph-cli[inmem]"\n\n'
|
||||
) from None
|
||||
except ImportError:
|
||||
raise click.UsageError(
|
||||
"Required package 'langgraph-api-inmem' is not installed.\n"
|
||||
"Please install it with:\n\n"
|
||||
' pip install -U "langgraph-cli[inmem]"\n\n'
|
||||
"If you're developing the langgraph-cli package locally, you can install in development mode:\n"
|
||||
" pip install -e ."
|
||||
"Could not verify package installation. Please ensure Python is up to date and\n"
|
||||
"langgraph-cli is installed with the 'inmem' extra: pip install -U \"langgraph-cli[inmem]\""
|
||||
) from None
|
||||
raise click.UsageError(
|
||||
"Could not import run_server. This likely means your installation is incomplete.\n"
|
||||
@@ -614,6 +616,7 @@ def dev(
|
||||
env=config_json.get("env"),
|
||||
store=config_json.get("store"),
|
||||
wait_for_client=wait_for_client,
|
||||
auth=config_json.get("auth"),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -47,6 +47,44 @@ class StoreConfig(TypedDict, total=False):
|
||||
"""Configuration for vector embeddings in store."""
|
||||
|
||||
|
||||
class SecurityConfig(TypedDict, total=False):
|
||||
securitySchemes: dict
|
||||
security: list
|
||||
# path => {method => security}
|
||||
paths: dict[str, dict[str, list]]
|
||||
|
||||
|
||||
class AuthConfig(TypedDict, total=False):
|
||||
path: str
|
||||
"""Path to the authentication function in a Python file."""
|
||||
disable_studio_auth: bool
|
||||
"""Whether to disable auth when connecting from the LangSmith Studio."""
|
||||
openapi: SecurityConfig
|
||||
"""The schema to use for updating the openapi spec.
|
||||
|
||||
Example:
|
||||
{
|
||||
"securitySchemes": {
|
||||
"OAuth2": {
|
||||
"type": "oauth2",
|
||||
"flows": {
|
||||
"password": {
|
||||
"tokenUrl": "/token",
|
||||
"scopes": {
|
||||
"me": "Read information about the current user",
|
||||
"items": "Access to create and manage items"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{"OAuth2": ["me"]} # Default security requirement for all endpoints
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
class Config(TypedDict, total=False):
|
||||
python_version: str
|
||||
node_version: Optional[str]
|
||||
@@ -56,6 +94,7 @@ class Config(TypedDict, total=False):
|
||||
graphs: dict[str, str]
|
||||
env: Union[dict[str, str], str]
|
||||
store: Optional[StoreConfig]
|
||||
auth: Optional[AuthConfig]
|
||||
|
||||
|
||||
def _parse_version(version_str: str) -> tuple[int, int]:
|
||||
@@ -85,9 +124,11 @@ def validate_config(config: Config) -> Config:
|
||||
{
|
||||
"node_version": config.get("node_version"),
|
||||
"dockerfile_lines": config.get("dockerfile_lines", []),
|
||||
"dependencies": config.get("dependencies", []),
|
||||
"graphs": config.get("graphs", {}),
|
||||
"env": config.get("env", {}),
|
||||
"store": config.get("store"),
|
||||
"auth": config.get("auth"),
|
||||
}
|
||||
if config.get("node_version")
|
||||
else {
|
||||
@@ -98,6 +139,7 @@ def validate_config(config: Config) -> Config:
|
||||
"graphs": config.get("graphs", {}),
|
||||
"env": config.get("env", {}),
|
||||
"store": config.get("store"),
|
||||
"auth": config.get("auth"),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -400,6 +442,10 @@ RUN set -ex && \\
|
||||
ENV LANGGRAPH_STORE='{json.dumps(store_config)}'
|
||||
"""
|
||||
)
|
||||
if (auth_config := config.get("auth")) is not None:
|
||||
env_additional_config += f"""
|
||||
ENV LANGGRAPH_AUTH='{json.dumps(auth_config)}'
|
||||
"""
|
||||
return f"""FROM {base_image}:{config['python_version']}
|
||||
|
||||
{os.linesep.join(config["dockerfile_lines"])}
|
||||
@@ -445,6 +491,10 @@ def node_config_to_docker(config_path: pathlib.Path, config: Config, base_image:
|
||||
ENV LANGGRAPH_STORE='{json.dumps(store_config)}'
|
||||
"""
|
||||
)
|
||||
if (auth_config := config.get("auth")) is not None:
|
||||
env_additional_config += f"""
|
||||
ENV LANGGRAPH_AUTH='{json.dumps(auth_config)}'
|
||||
"""
|
||||
return f"""FROM {base_image}:{config['node_version']}
|
||||
|
||||
{os.linesep.join(config["dockerfile_lines"])}
|
||||
@@ -479,8 +529,9 @@ def config_to_compose(
|
||||
f"env_file: {config['env']}" if isinstance(config["env"], str) else ""
|
||||
)
|
||||
if watch:
|
||||
dependencies = config.get("dependencies") or ["."]
|
||||
watch_paths = [config_path.name] + [
|
||||
dep for dep in config["dependencies"] if dep.startswith(".")
|
||||
dep for dep in dependencies if dep.startswith(".")
|
||||
]
|
||||
watch_actions = "\n".join(
|
||||
f"""- path: {path}
|
||||
|
||||
@@ -13,22 +13,23 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.6.2.post1"
|
||||
version = "4.7.0"
|
||||
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
||||
optional = true
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d"},
|
||||
{file = "anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c"},
|
||||
{file = "anyio-4.7.0-py3-none-any.whl", hash = "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352"},
|
||||
{file = "anyio-4.7.0.tar.gz", hash = "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
idna = ">=2.8"
|
||||
sniffio = ">=1.1"
|
||||
typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""}
|
||||
|
||||
[package.extras]
|
||||
doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
|
||||
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21.0b1)"]
|
||||
doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"]
|
||||
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"]
|
||||
trio = ["trio (>=0.26.1)"]
|
||||
|
||||
[[package]]
|
||||
@@ -384,13 +385,13 @@ trio = ["trio (>=0.22.0,<1.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.27.2"
|
||||
version = "0.28.1"
|
||||
description = "The next generation HTTP client."
|
||||
optional = true
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"},
|
||||
{file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"},
|
||||
{file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"},
|
||||
{file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -398,7 +399,6 @@ anyio = "*"
|
||||
certifi = "*"
|
||||
httpcore = "==1.*"
|
||||
idna = "*"
|
||||
sniffio = "*"
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotli", "brotlicffi"]
|
||||
@@ -407,17 +407,6 @@ http2 = ["h2 (>=3,<5)"]
|
||||
socks = ["socksio (==1.*)"]
|
||||
zstd = ["zstandard (>=0.18.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "httpx-sse"
|
||||
version = "0.4.0"
|
||||
description = "Consume Server-Sent Event (SSE) messages with HTTPX."
|
||||
optional = true
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721"},
|
||||
{file = "httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.10"
|
||||
@@ -526,18 +515,18 @@ tests = ["flask (>=2.2.5)", "hypothesis (>=6.79.4)", "pytest (>=7.4.4)"]
|
||||
|
||||
[[package]]
|
||||
name = "langchain-core"
|
||||
version = "0.3.21"
|
||||
version = "0.3.24"
|
||||
description = "Building applications with LLMs through composability"
|
||||
optional = true
|
||||
python-versions = "<4.0,>=3.9"
|
||||
files = [
|
||||
{file = "langchain_core-0.3.21-py3-none-any.whl", hash = "sha256:7e723dff80946a1198976c6876fea8326dc82566ef9bcb5f8d9188f738733665"},
|
||||
{file = "langchain_core-0.3.21.tar.gz", hash = "sha256:561b52b258ffa50a9fb11d7a1940ebfd915654d1ec95b35e81dfd5ee84143411"},
|
||||
{file = "langchain_core-0.3.24-py3-none-any.whl", hash = "sha256:97192552ef882a3dd6ae3b870a180a743801d0137a1159173f51ac555eeb7eec"},
|
||||
{file = "langchain_core-0.3.24.tar.gz", hash = "sha256:460851e8145327f70b70aad7dce2cdbd285e144d14af82b677256b941fc99656"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
jsonpatch = ">=1.33,<2.0"
|
||||
langsmith = ">=0.1.125,<0.2.0"
|
||||
langsmith = ">=0.1.125,<0.3"
|
||||
packaging = ">=23.2,<25"
|
||||
pydantic = [
|
||||
{version = ">=2.5.2,<3.0.0", markers = "python_full_version < \"3.12.4\""},
|
||||
@@ -549,29 +538,29 @@ typing-extensions = ">=4.7"
|
||||
|
||||
[[package]]
|
||||
name = "langgraph"
|
||||
version = "0.2.53"
|
||||
version = "0.2.59"
|
||||
description = "Building stateful, multi-actor applications with LLMs"
|
||||
optional = true
|
||||
python-versions = "<4.0,>=3.9.0"
|
||||
files = [
|
||||
{file = "langgraph-0.2.53-py3-none-any.whl", hash = "sha256:b34b67d0a12ae0ba6f03af97ad0f744bc609bd0328e8b734618cc039985cfdea"},
|
||||
{file = "langgraph-0.2.53.tar.gz", hash = "sha256:b83232a04f2b536cbeac542f9ad7e0265f41ac6b7c6706ba8e031e0e80cb13a6"},
|
||||
{file = "langgraph-0.2.59-py3-none-any.whl", hash = "sha256:9b2d1331bbdcea96cfffde8e88700776118f4583d15f6a3423fc4482fe84ae56"},
|
||||
{file = "langgraph-0.2.59.tar.gz", hash = "sha256:61cb5dd409878641fe8a12e9d7928a7524caa3e54ab12bc883feeaba4a5ba618"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
langchain-core = ">=0.2.43,<0.3.0 || >0.3.0,<0.3.1 || >0.3.1,<0.3.2 || >0.3.2,<0.3.3 || >0.3.3,<0.3.4 || >0.3.4,<0.3.5 || >0.3.5,<0.3.6 || >0.3.6,<0.3.7 || >0.3.7,<0.3.8 || >0.3.8,<0.3.9 || >0.3.9,<0.3.10 || >0.3.10,<0.3.11 || >0.3.11,<0.3.12 || >0.3.12,<0.3.13 || >0.3.13,<0.3.14 || >0.3.14,<0.4.0"
|
||||
langchain-core = ">=0.2.43,<0.3.0 || >0.3.0,<0.3.1 || >0.3.1,<0.3.2 || >0.3.2,<0.3.3 || >0.3.3,<0.3.4 || >0.3.4,<0.3.5 || >0.3.5,<0.3.6 || >0.3.6,<0.3.7 || >0.3.7,<0.3.8 || >0.3.8,<0.3.9 || >0.3.9,<0.3.10 || >0.3.10,<0.3.11 || >0.3.11,<0.3.12 || >0.3.12,<0.3.13 || >0.3.13,<0.3.14 || >0.3.14,<0.3.15 || >0.3.15,<0.3.16 || >0.3.16,<0.3.17 || >0.3.17,<0.3.18 || >0.3.18,<0.3.19 || >0.3.19,<0.3.20 || >0.3.20,<0.3.21 || >0.3.21,<0.3.22 || >0.3.22,<0.4.0"
|
||||
langgraph-checkpoint = ">=2.0.4,<3.0.0"
|
||||
langgraph-sdk = ">=0.1.32,<0.2.0"
|
||||
langgraph-sdk = ">=0.1.42,<0.2.0"
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-api"
|
||||
version = "0.0.6"
|
||||
version = "0.0.8"
|
||||
description = ""
|
||||
optional = true
|
||||
python-versions = "<4.0,>=3.11.0"
|
||||
files = [
|
||||
{file = "langgraph_api-0.0.6-py3-none-any.whl", hash = "sha256:f64b13959d721143f6a023af5b9ffc9aa054064af98d21d5d8090cda7e7bffd2"},
|
||||
{file = "langgraph_api-0.0.6.tar.gz", hash = "sha256:badac44fa1ec979509e56fc0da57eeb5f278ee5871f27803f73ea6d8822c21b9"},
|
||||
{file = "langgraph_api-0.0.8-py3-none-any.whl", hash = "sha256:ab9b4a5ec8393d17ef921b94f3f019f49f3f17910ba9351beb1e1f962b02a33e"},
|
||||
{file = "langgraph_api-0.0.8.tar.gz", hash = "sha256:b1304beb9d72392ce5d5602cd524a4f57839f1dcd1da6f4e1391832fb4a6edc5"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -579,7 +568,7 @@ cryptography = ">=43.0.3,<44.0.0"
|
||||
httpx = ">=0.27.0"
|
||||
jsonschema-rs = ">=0.25.0,<0.26.0"
|
||||
langchain-core = ">=0.2.38,<0.4.0"
|
||||
langgraph = ">=0.2.52,<0.3.0"
|
||||
langgraph = ">=0.2.56,<0.3.0"
|
||||
langgraph-checkpoint = ">=2.0.7,<3.0"
|
||||
langsmith = ">=0.1.63,<0.2.0"
|
||||
orjson = ">=3.10.1"
|
||||
@@ -593,13 +582,13 @@ watchfiles = ">=0.13"
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-checkpoint"
|
||||
version = "2.0.7"
|
||||
version = "2.0.9"
|
||||
description = "Library with base interfaces for LangGraph checkpoint savers."
|
||||
optional = true
|
||||
python-versions = "<4.0.0,>=3.9.0"
|
||||
files = [
|
||||
{file = "langgraph_checkpoint-2.0.7-py3-none-any.whl", hash = "sha256:9709f672e1c5a47e13352067c2ffa114dd91d443967b7ce8a1d36d6fc170370e"},
|
||||
{file = "langgraph_checkpoint-2.0.7.tar.gz", hash = "sha256:88d648a331d20aa8ce65280de34a34a9190380b004f6afcc5f9894fe3abeed08"},
|
||||
{file = "langgraph_checkpoint-2.0.9-py3-none-any.whl", hash = "sha256:b546ed6129929b8941ac08af6ce5cd26c8ebe1d25883d3c48638d34ade91ce42"},
|
||||
{file = "langgraph_checkpoint-2.0.9.tar.gz", hash = "sha256:43847d7e385a2d9d2b684155920998e44ed42d2d1780719e4f6111fe3d6db84c"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -608,18 +597,17 @@ msgpack = ">=1.1.0,<2.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-sdk"
|
||||
version = "0.1.40"
|
||||
version = "0.1.44"
|
||||
description = "SDK for interacting with LangGraph API"
|
||||
optional = true
|
||||
python-versions = "<4.0.0,>=3.9.0"
|
||||
files = [
|
||||
{file = "langgraph_sdk-0.1.40-py3-none-any.whl", hash = "sha256:8810cca5e4144cf3a5441fc76b4ee6e658ec95f932d3a0bf9ad63de117e925b9"},
|
||||
{file = "langgraph_sdk-0.1.40.tar.gz", hash = "sha256:ab2719ac7274612a791a7a0ad9395d250357106cba8ba81bca9968fc91009af2"},
|
||||
{file = "langgraph_sdk-0.1.44-py3-none-any.whl", hash = "sha256:a5a623429b44616c4369c0caf2ea30ce8d3a103123f7bb3ca50dc726eaa313a4"},
|
||||
{file = "langgraph_sdk-0.1.44.tar.gz", hash = "sha256:5b17fe7c0a0fe4b83170c9b8987e0f98dc0636fcc0166dbdba725cc8ffd9b1cf"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
httpx = ">=0.25.2"
|
||||
httpx-sse = ">=0.4.0"
|
||||
orjson = ">=3.10.1"
|
||||
|
||||
[[package]]
|
||||
@@ -906,13 +894,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.10.2"
|
||||
version = "2.10.3"
|
||||
description = "Data validation using Python type hints"
|
||||
optional = true
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pydantic-2.10.2-py3-none-any.whl", hash = "sha256:cfb96e45951117c3024e6b67b25cdc33a3cb7b2fa62e239f7af1378358a1d99e"},
|
||||
{file = "pydantic-2.10.2.tar.gz", hash = "sha256:2bc2d7f17232e0841cbba4641e65ba1eb6fafb3a08de3a091ff3ce14a197c4fa"},
|
||||
{file = "pydantic-2.10.3-py3-none-any.whl", hash = "sha256:be04d85bbc7b65651c5f8e6b9976ed9c6f41782a55524cef079a34a0bb82144d"},
|
||||
{file = "pydantic-2.10.3.tar.gz", hash = "sha256:cb5ac360ce894ceacd69c403187900a02c4b20b693a9dd1d643e1effab9eadf9"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1474,82 +1462,82 @@ watchmedo = ["PyYAML (>=3.10)"]
|
||||
|
||||
[[package]]
|
||||
name = "watchfiles"
|
||||
version = "1.0.0"
|
||||
version = "1.0.3"
|
||||
description = "Simple, modern and high performance file watching and code reload in python."
|
||||
optional = true
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "watchfiles-1.0.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:1d19df28f99d6a81730658fbeb3ade8565ff687f95acb59665f11502b441be5f"},
|
||||
{file = "watchfiles-1.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:28babb38cf2da8e170b706c4b84aa7e4528a6fa4f3ee55d7a0866456a1662041"},
|
||||
{file = "watchfiles-1.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12ab123135b2f42517f04e720526d41448667ae8249e651385afb5cda31fedc0"},
|
||||
{file = "watchfiles-1.0.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:13a4f9ee0cd25682679eea5c14fc629e2eaa79aab74d963bc4e21f43b8ea1877"},
|
||||
{file = "watchfiles-1.0.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e1d9284cc84de7855fcf83472e51d32daf6f6cecd094160192628bc3fee1b78"},
|
||||
{file = "watchfiles-1.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ee5edc939f53466b329bbf2e58333a5461e6c7b50c980fa6117439e2c18b42d"},
|
||||
{file = "watchfiles-1.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5dccfc70480087567720e4e36ec381bba1ed68d7e5f368fe40c93b3b1eba0105"},
|
||||
{file = "watchfiles-1.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c83a6d33a9eda0af6a7470240d1af487807adc269704fe76a4972dd982d16236"},
|
||||
{file = "watchfiles-1.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:905f69aad276639eff3893759a07d44ea99560e67a1cf46ff389cd62f88872a2"},
|
||||
{file = "watchfiles-1.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:09551237645d6bff3972592f2aa5424df9290e7a2e15d63c5f47c48cde585935"},
|
||||
{file = "watchfiles-1.0.0-cp310-none-win32.whl", hash = "sha256:d2b39aa8edd9e5f56f99a2a2740a251dc58515398e9ed5a4b3e5ff2827060755"},
|
||||
{file = "watchfiles-1.0.0-cp310-none-win_amd64.whl", hash = "sha256:2de52b499e1ab037f1a87cb8ebcb04a819bf087b1015a4cf6dcf8af3c2a2613e"},
|
||||
{file = "watchfiles-1.0.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:fbd0ab7a9943bbddb87cbc2bf2f09317e74c77dc55b1f5657f81d04666c25269"},
|
||||
{file = "watchfiles-1.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:774ef36b16b7198669ce655d4f75b4c3d370e7f1cbdfb997fb10ee98717e2058"},
|
||||
{file = "watchfiles-1.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b4fb98100267e6a5ebaff6aaa5d20aea20240584647470be39fe4823012ac96"},
|
||||
{file = "watchfiles-1.0.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0fc3bf0effa2d8075b70badfdd7fb839d7aa9cea650d17886982840d71fdeabf"},
|
||||
{file = "watchfiles-1.0.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:648e2b6db53eca6ef31245805cd528a16f56fa4cc15aeec97795eaf713c11435"},
|
||||
{file = "watchfiles-1.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa13d604fcb9417ae5f2e3de676e66aa97427d888e83662ad205bed35a313176"},
|
||||
{file = "watchfiles-1.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:936f362e7ff28311b16f0b97ec51e8f2cc451763a3264640c6ed40fb252d1ee4"},
|
||||
{file = "watchfiles-1.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:245fab124b9faf58430da547512d91734858df13f2ddd48ecfa5e493455ffccb"},
|
||||
{file = "watchfiles-1.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4ff9c7e84e8b644a8f985c42bcc81457240316f900fc72769aaedec9d088055a"},
|
||||
{file = "watchfiles-1.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9c9a8d8fd97defe935ef8dd53d562e68942ad65067cd1c54d6ed8a088b1d931d"},
|
||||
{file = "watchfiles-1.0.0-cp311-none-win32.whl", hash = "sha256:a0abf173975eb9dd17bb14c191ee79999e650997cc644562f91df06060610e62"},
|
||||
{file = "watchfiles-1.0.0-cp311-none-win_amd64.whl", hash = "sha256:2a825ba4b32c214e3855b536eb1a1f7b006511d8e64b8215aac06eb680642d84"},
|
||||
{file = "watchfiles-1.0.0-cp311-none-win_arm64.whl", hash = "sha256:a5a7a06cfc65e34fd0a765a7623c5ba14707a0870703888e51d3d67107589817"},
|
||||
{file = "watchfiles-1.0.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:28fb64b5843d94e2c2483f7b024a1280662a44409bedee8f2f51439767e2d107"},
|
||||
{file = "watchfiles-1.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e3750434c83b61abb3163b49c64b04180b85b4dabb29a294513faec57f2ffdb7"},
|
||||
{file = "watchfiles-1.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bedf84835069f51c7b026b3ca04e2e747ea8ed0a77c72006172c72d28c9f69fc"},
|
||||
{file = "watchfiles-1.0.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:90004553be36427c3d06ec75b804233f8f816374165d5225b93abd94ba6e7234"},
|
||||
{file = "watchfiles-1.0.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b46e15c34d4e401e976d6949ad3a74d244600d5c4b88c827a3fdf18691a46359"},
|
||||
{file = "watchfiles-1.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:487d15927f1b0bd24e7df921913399bb1ab94424c386bea8b267754d698f8f0e"},
|
||||
{file = "watchfiles-1.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ff236d7a3f4b0a42f699a22fc374ba526bc55048a70cbb299661158e1bb5e1f"},
|
||||
{file = "watchfiles-1.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c01446626574561756067f00b37e6b09c8622b0fc1e9fdbc7cbcea328d4e514"},
|
||||
{file = "watchfiles-1.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b551c465a59596f3d08170bd7e1c532c7260dd90ed8135778038e13c5d48aa81"},
|
||||
{file = "watchfiles-1.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e1ed613ee107269f66c2df631ec0fc8efddacface85314d392a4131abe299f00"},
|
||||
{file = "watchfiles-1.0.0-cp312-none-win32.whl", hash = "sha256:5f75cd42e7e2254117cf37ff0e68c5b3f36c14543756b2da621408349bd9ca7c"},
|
||||
{file = "watchfiles-1.0.0-cp312-none-win_amd64.whl", hash = "sha256:cf517701a4a872417f4e02a136e929537743461f9ec6cdb8184d9a04f4843545"},
|
||||
{file = "watchfiles-1.0.0-cp312-none-win_arm64.whl", hash = "sha256:8a2127cd68950787ee36753e6d401c8ea368f73beaeb8e54df5516a06d1ecd82"},
|
||||
{file = "watchfiles-1.0.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:95de85c254f7fe8cbdf104731f7f87f7f73ae229493bebca3722583160e6b152"},
|
||||
{file = "watchfiles-1.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:533a7cbfe700e09780bb31c06189e39c65f06c7f447326fee707fd02f9a6e945"},
|
||||
{file = "watchfiles-1.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2218e78e2c6c07b1634a550095ac2a429026b2d5cbcd49a594f893f2bb8c936"},
|
||||
{file = "watchfiles-1.0.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9122b8fdadc5b341315d255ab51d04893f417df4e6c1743b0aac8bf34e96e025"},
|
||||
{file = "watchfiles-1.0.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9272fdbc0e9870dac3b505bce1466d386b4d8d6d2bacf405e603108d50446940"},
|
||||
{file = "watchfiles-1.0.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a3b33c3aefe9067ebd87846806cd5fc0b017ab70d628aaff077ab9abf4d06b3"},
|
||||
{file = "watchfiles-1.0.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bc338ce9f8846543d428260fa0f9a716626963148edc937d71055d01d81e1525"},
|
||||
{file = "watchfiles-1.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ac778a460ea22d63c7e6fb0bc0f5b16780ff0b128f7f06e57aaec63bd339285"},
|
||||
{file = "watchfiles-1.0.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:53ae447f06f8f29f5ab40140f19abdab822387a7c426a369eb42184b021e97eb"},
|
||||
{file = "watchfiles-1.0.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1f73c2147a453315d672c1ad907abe6d40324e34a185b51e15624bc793f93cc6"},
|
||||
{file = "watchfiles-1.0.0-cp313-none-win32.whl", hash = "sha256:eba98901a2eab909dbd79681190b9049acc650f6111fde1845484a4450761e98"},
|
||||
{file = "watchfiles-1.0.0-cp313-none-win_amd64.whl", hash = "sha256:d562a6114ddafb09c33246c6ace7effa71ca4b6a2324a47f4b09b6445ea78941"},
|
||||
{file = "watchfiles-1.0.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3d94fd83ed54266d789f287472269c0def9120a2022674990bd24ad989ebd7a0"},
|
||||
{file = "watchfiles-1.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48051d1c504448b2fcda71c5e6e3610ae45de6a0b8f5a43b961f250be4bdf5a8"},
|
||||
{file = "watchfiles-1.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29cf884ad4285d23453c702ed03d689f9c0e865e3c85d20846d800d4787de00f"},
|
||||
{file = "watchfiles-1.0.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d3572d4c34c4e9c33d25b3da47d9570d5122f8433b9ac6519dca49c2740d23cd"},
|
||||
{file = "watchfiles-1.0.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2c2696611182c85eb0e755b62b456f48debff484b7306b56f05478b843ca8ece"},
|
||||
{file = "watchfiles-1.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:550109001920a993a4383b57229c717fa73627d2a4e8fcb7ed33c7f1cddb0c85"},
|
||||
{file = "watchfiles-1.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b555a93c15bd2c71081922be746291d776d47521a00703163e5fbe6d2a402399"},
|
||||
{file = "watchfiles-1.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:947ccba18a38b85c366dafeac8df2f6176342d5992ca240a9d62588b214d731f"},
|
||||
{file = "watchfiles-1.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ffd98a299b0a74d1b704ef0ed959efb753e656a4e0425c14e46ae4c3cbdd2919"},
|
||||
{file = "watchfiles-1.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f8c4f3a1210ed099a99e6a710df4ff2f8069411059ffe30fa5f9467ebed1256b"},
|
||||
{file = "watchfiles-1.0.0-cp39-none-win32.whl", hash = "sha256:1e176b6b4119b3f369b2b4e003d53a226295ee862c0962e3afd5a1c15680b4e3"},
|
||||
{file = "watchfiles-1.0.0-cp39-none-win_amd64.whl", hash = "sha256:2d9c0518fabf4a3f373b0a94bb9e4ea7a1df18dec45e26a4d182aa8918dee855"},
|
||||
{file = "watchfiles-1.0.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f159ac795785cde4899e0afa539f4c723fb5dd336ce5605bc909d34edd00b79b"},
|
||||
{file = "watchfiles-1.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:c3d258d78341d5d54c0c804a5b7faa66cd30ba50b2756a7161db07ce15363b8d"},
|
||||
{file = "watchfiles-1.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bbd0311588c2de7f9ea5cf3922ccacfd0ec0c1922870a2be503cc7df1ca8be7"},
|
||||
{file = "watchfiles-1.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9a13ac46b545a7d0d50f7641eefe47d1597e7d1783a5d89e09d080e6dff44b0"},
|
||||
{file = "watchfiles-1.0.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2bca898c1dc073912d3db7fa6926cc08be9575add9e84872de2c99c688bac4e"},
|
||||
{file = "watchfiles-1.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:06d828fe2adc4ac8a64b875ca908b892a3603d596d43e18f7948f3fef5fc671c"},
|
||||
{file = "watchfiles-1.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:074c7618cd6c807dc4eaa0982b4a9d3f8051cd0b72793511848fd64630174b17"},
|
||||
{file = "watchfiles-1.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95dc785bc284552d044e561b8f4fe26d01ab5ca40d35852a6572d542adfeb4bc"},
|
||||
{file = "watchfiles-1.0.0.tar.gz", hash = "sha256:37566c844c9ce3b5deb964fe1a23378e575e74b114618d211fbda8f59d7b5dab"},
|
||||
{file = "watchfiles-1.0.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:1da46bb1eefb5a37a8fb6fd52ad5d14822d67c498d99bda8754222396164ae42"},
|
||||
{file = "watchfiles-1.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2b961b86cd3973f5822826017cad7f5a75795168cb645c3a6b30c349094e02e3"},
|
||||
{file = "watchfiles-1.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34e87c7b3464d02af87f1059fedda5484e43b153ef519e4085fe1a03dd94801e"},
|
||||
{file = "watchfiles-1.0.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d9dd2b89a16cf7ab9c1170b5863e68de6bf83db51544875b25a5f05a7269e678"},
|
||||
{file = "watchfiles-1.0.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b4691234d31686dca133c920f94e478b548a8e7c750f28dbbc2e4333e0d3da9"},
|
||||
{file = "watchfiles-1.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:90b0fe1fcea9bd6e3084b44875e179b4adcc4057a3b81402658d0eb58c98edf8"},
|
||||
{file = "watchfiles-1.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0b90651b4cf9e158d01faa0833b073e2e37719264bcee3eac49fc3c74e7d304b"},
|
||||
{file = "watchfiles-1.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2e9fe695ff151b42ab06501820f40d01310fbd58ba24da8923ace79cf6d702d"},
|
||||
{file = "watchfiles-1.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62691f1c0894b001c7cde1195c03b7801aaa794a837bd6eef24da87d1542838d"},
|
||||
{file = "watchfiles-1.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:275c1b0e942d335fccb6014d79267d1b9fa45b5ac0639c297f1e856f2f532552"},
|
||||
{file = "watchfiles-1.0.3-cp310-cp310-win32.whl", hash = "sha256:06ce08549e49ba69ccc36fc5659a3d0ff4e3a07d542b895b8a9013fcab46c2dc"},
|
||||
{file = "watchfiles-1.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:f280b02827adc9d87f764972fbeb701cf5611f80b619c20568e1982a277d6146"},
|
||||
{file = "watchfiles-1.0.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ffe709b1d0bc2e9921257569675674cafb3a5f8af689ab9f3f2b3f88775b960f"},
|
||||
{file = "watchfiles-1.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:418c5ce332f74939ff60691e5293e27c206c8164ce2b8ce0d9abf013003fb7fe"},
|
||||
{file = "watchfiles-1.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f492d2907263d6d0d52f897a68647195bc093dafed14508a8d6817973586b6b"},
|
||||
{file = "watchfiles-1.0.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:48c9f3bc90c556a854f4cab6a79c16974099ccfa3e3e150673d82d47a4bc92c9"},
|
||||
{file = "watchfiles-1.0.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75d3bcfa90454dba8df12adc86b13b6d85fda97d90e708efc036c2760cc6ba44"},
|
||||
{file = "watchfiles-1.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5691340f259b8f76b45fb31b98e594d46c36d1dc8285efa7975f7f50230c9093"},
|
||||
{file = "watchfiles-1.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e263cc718545b7f897baeac1f00299ab6fabe3e18caaacacb0edf6d5f35513c"},
|
||||
{file = "watchfiles-1.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c6cf7709ed3e55704cc06f6e835bf43c03bc8e3cb8ff946bf69a2e0a78d9d77"},
|
||||
{file = "watchfiles-1.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:703aa5e50e465be901e0e0f9d5739add15e696d8c26c53bc6fc00eb65d7b9469"},
|
||||
{file = "watchfiles-1.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bfcae6aecd9e0cb425f5145afee871465b98b75862e038d42fe91fd753ddd780"},
|
||||
{file = "watchfiles-1.0.3-cp311-cp311-win32.whl", hash = "sha256:6a76494d2c5311584f22416c5a87c1e2cb954ff9b5f0988027bc4ef2a8a67181"},
|
||||
{file = "watchfiles-1.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:cf745cbfad6389c0e331786e5fe9ae3f06e9d9c2ce2432378e1267954793975c"},
|
||||
{file = "watchfiles-1.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:2dcc3f60c445f8ce14156854a072ceb36b83807ed803d37fdea2a50e898635d6"},
|
||||
{file = "watchfiles-1.0.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:93436ed550e429da007fbafb723e0769f25bae178fbb287a94cb4ccdf42d3af3"},
|
||||
{file = "watchfiles-1.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c18f3502ad0737813c7dad70e3e1cc966cc147fbaeef47a09463bbffe70b0a00"},
|
||||
{file = "watchfiles-1.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a5bc3ca468bb58a2ef50441f953e1f77b9a61bd1b8c347c8223403dc9b4ac9a"},
|
||||
{file = "watchfiles-1.0.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0d1ec043f02ca04bf21b1b32cab155ce90c651aaf5540db8eb8ad7f7e645cba8"},
|
||||
{file = "watchfiles-1.0.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f58d3bfafecf3d81c15d99fc0ecf4319e80ac712c77cf0ce2661c8cf8bf84066"},
|
||||
{file = "watchfiles-1.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1df924ba82ae9e77340101c28d56cbaff2c991bd6fe8444a545d24075abb0a87"},
|
||||
{file = "watchfiles-1.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:632a52dcaee44792d0965c17bdfe5dc0edad5b86d6a29e53d6ad4bf92dc0ff49"},
|
||||
{file = "watchfiles-1.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bf4b459d94a0387617a1b499f314aa04d8a64b7a0747d15d425b8c8b151da0"},
|
||||
{file = "watchfiles-1.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ca94c85911601b097d53caeeec30201736ad69a93f30d15672b967558df02885"},
|
||||
{file = "watchfiles-1.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:65ab1fb635476f6170b07e8e21db0424de94877e4b76b7feabfe11f9a5fc12b5"},
|
||||
{file = "watchfiles-1.0.3-cp312-cp312-win32.whl", hash = "sha256:49bc1bc26abf4f32e132652f4b3bfeec77d8f8f62f57652703ef127e85a3e38d"},
|
||||
{file = "watchfiles-1.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:48681c86f2cb08348631fed788a116c89c787fdf1e6381c5febafd782f6c3b44"},
|
||||
{file = "watchfiles-1.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:9e080cf917b35b20c889225a13f290f2716748362f6071b859b60b8847a6aa43"},
|
||||
{file = "watchfiles-1.0.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e153a690b7255c5ced17895394b4f109d5dcc2a4f35cb809374da50f0e5c456a"},
|
||||
{file = "watchfiles-1.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ac1be85fe43b4bf9a251978ce5c3bb30e1ada9784290441f5423a28633a958a7"},
|
||||
{file = "watchfiles-1.0.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2ec98e31e1844eac860e70d9247db9d75440fc8f5f679c37d01914568d18721"},
|
||||
{file = "watchfiles-1.0.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0179252846be03fa97d4d5f8233d1c620ef004855f0717712ae1c558f1974a16"},
|
||||
{file = "watchfiles-1.0.3-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:995c374e86fa82126c03c5b4630c4e312327ecfe27761accb25b5e1d7ab50ec8"},
|
||||
{file = "watchfiles-1.0.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29b9cb35b7f290db1c31fb2fdf8fc6d3730cfa4bca4b49761083307f441cac5a"},
|
||||
{file = "watchfiles-1.0.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f8dc09ae69af50bead60783180f656ad96bd33ffbf6e7a6fce900f6d53b08f1"},
|
||||
{file = "watchfiles-1.0.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:489b80812f52a8d8c7b0d10f0d956db0efed25df2821c7a934f6143f76938bd6"},
|
||||
{file = "watchfiles-1.0.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:228e2247de583475d4cebf6b9af5dc9918abb99d1ef5ee737155bb39fb33f3c0"},
|
||||
{file = "watchfiles-1.0.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1550be1a5cb3be08a3fb84636eaafa9b7119b70c71b0bed48726fd1d5aa9b868"},
|
||||
{file = "watchfiles-1.0.3-cp313-cp313-win32.whl", hash = "sha256:16db2d7e12f94818cbf16d4c8938e4d8aaecee23826344addfaaa671a1527b07"},
|
||||
{file = "watchfiles-1.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:160eff7d1267d7b025e983ca8460e8cc67b328284967cbe29c05f3c3163711a3"},
|
||||
{file = "watchfiles-1.0.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c05b021f7b5aa333124f2a64d56e4cb9963b6efdf44e8d819152237bbd93ba15"},
|
||||
{file = "watchfiles-1.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:310505ad305e30cb6c5f55945858cdbe0eb297fc57378f29bacceb534ac34199"},
|
||||
{file = "watchfiles-1.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ddff3f8b9fa24a60527c137c852d0d9a7da2a02cf2151650029fdc97c852c974"},
|
||||
{file = "watchfiles-1.0.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46e86ed457c3486080a72bc837300dd200e18d08183f12b6ca63475ab64ed651"},
|
||||
{file = "watchfiles-1.0.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f79fe7993e230a12172ce7d7c7db061f046f672f2b946431c81aff8f60b2758b"},
|
||||
{file = "watchfiles-1.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea2b51c5f38bad812da2ec0cd7eec09d25f521a8b6b6843cbccedd9a1d8a5c15"},
|
||||
{file = "watchfiles-1.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fe4e740ea94978b2b2ab308cbf9270a246bcbb44401f77cc8740348cbaeac3d"},
|
||||
{file = "watchfiles-1.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9af037d3df7188ae21dc1c7624501f2f90d81be6550904e07869d8d0e6766655"},
|
||||
{file = "watchfiles-1.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:52bb50a4c4ca2a689fdba84ba8ecc6a4e6210f03b6af93181bb61c4ec3abaf86"},
|
||||
{file = "watchfiles-1.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c14a07bdb475eb696f85c715dbd0f037918ccbb5248290448488a0b4ef201aad"},
|
||||
{file = "watchfiles-1.0.3-cp39-cp39-win32.whl", hash = "sha256:be37f9b1f8934cd9e7eccfcb5612af9fb728fecbe16248b082b709a9d1b348bf"},
|
||||
{file = "watchfiles-1.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:ef9ec8068cf23458dbf36a08e0c16f0a2df04b42a8827619646637be1769300a"},
|
||||
{file = "watchfiles-1.0.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:84fac88278f42d61c519a6c75fb5296fd56710b05bbdcc74bdf85db409a03780"},
|
||||
{file = "watchfiles-1.0.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:c68be72b1666d93b266714f2d4092d78dc53bd11cf91ed5a3c16527587a52e29"},
|
||||
{file = "watchfiles-1.0.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:889a37e2acf43c377b5124166bece139b4c731b61492ab22e64d371cce0e6e80"},
|
||||
{file = "watchfiles-1.0.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca05cacf2e5c4a97d02a2878a24020daca21dbb8823b023b978210a75c79098"},
|
||||
{file = "watchfiles-1.0.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:8af4b582d5fc1b8465d1d2483e5e7b880cc1a4e99f6ff65c23d64d070867ac58"},
|
||||
{file = "watchfiles-1.0.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:127de3883bdb29dbd3b21f63126bb8fa6e773b74eaef46521025a9ce390e1073"},
|
||||
{file = "watchfiles-1.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:713f67132346bdcb4c12df185c30cf04bdf4bf6ea3acbc3ace0912cab6b7cb8c"},
|
||||
{file = "watchfiles-1.0.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abd85de513eb83f5ec153a802348e7a5baa4588b818043848247e3e8986094e8"},
|
||||
{file = "watchfiles-1.0.3.tar.gz", hash = "sha256:f3ff7da165c99a5412fe5dd2304dd2dbaaaa5da718aad942dcb3a178eaa70c56"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1561,4 +1549,4 @@ inmem = ["langgraph-api", "python-dotenv"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.9.0,<4.0"
|
||||
content-hash = "8eaaa66d9e6e447699e3bcee336dfe779b58c956f8c2ad6678008a07be935838"
|
||||
content-hash = "c36514f708e8b2b32c20ad9b7a3ba6fca41f043ced0149f0aae4eef43fc6c10b"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "langgraph-cli"
|
||||
version = "0.1.61"
|
||||
version = "0.1.64"
|
||||
description = "CLI for interacting with LangGraph API"
|
||||
authors = []
|
||||
license = "MIT"
|
||||
@@ -14,7 +14,7 @@ langgraph = "langgraph_cli.cli:cli"
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.9.0,<4.0"
|
||||
click = "^8.1.7"
|
||||
langgraph-api = { version = ">=0.0.6,<0.1.0", optional = true, python = ">=3.11,<4.0" }
|
||||
langgraph-api = { version = ">=0.0.8,<0.1.0", optional = true, python = ">=3.11,<4.0" }
|
||||
python-dotenv = { version = ">=0.8.0", optional = true }
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
|
||||
@@ -31,6 +31,7 @@ def test_validate_config():
|
||||
"dockerfile_lines": [],
|
||||
"env": {},
|
||||
"store": None,
|
||||
"auth": None,
|
||||
**expected_config,
|
||||
}
|
||||
actual_config = validate_config(expected_config)
|
||||
@@ -48,6 +49,7 @@ def test_validate_config():
|
||||
},
|
||||
"env": env,
|
||||
"store": None,
|
||||
"auth": None,
|
||||
}
|
||||
actual_config = validate_config(expected_config)
|
||||
assert actual_config == expected_config
|
||||
|
||||
@@ -548,33 +548,25 @@ class ToolNode(RunnableCallable):
|
||||
|
||||
# convert to message objects if updates are in a dict format
|
||||
messages_update = convert_to_messages(messages_update)
|
||||
have_seen_tool_messages = False
|
||||
has_matching_tool_message = False
|
||||
for message in messages_update:
|
||||
if not isinstance(message, ToolMessage):
|
||||
continue
|
||||
|
||||
if have_seen_tool_messages:
|
||||
raise ValueError(
|
||||
f"Expected at most one ToolMessage in Command.update for tool '{call['name']}', got multiple: {messages_update}."
|
||||
)
|
||||
if message.tool_call_id == call["id"]:
|
||||
message.name = call["name"]
|
||||
has_matching_tool_message = True
|
||||
|
||||
if message.tool_call_id != call["id"]:
|
||||
raise ValueError(
|
||||
f"ToolMessage.tool_call_id must match the tool call id. Expected: {call['id']}, got: {message.tool_call_id} for tool '{call['name']}'."
|
||||
)
|
||||
|
||||
message.name = call["name"]
|
||||
have_seen_tool_messages = True
|
||||
|
||||
# validate that we always have exactly one ToolMessage in Command.update if command is sent to the CURRENT graph
|
||||
if updated_command.graph is None and not have_seen_tool_messages:
|
||||
# validate that we always have a ToolMessage matching the tool call in
|
||||
# Command.update if command is sent to the CURRENT graph
|
||||
if updated_command.graph is None and not has_matching_tool_message:
|
||||
example_update = (
|
||||
'`Command(update={"messages": [ToolMessage("Success", tool_call_id=tool_call_id), ...]}, ...)`'
|
||||
if input_type == "dict"
|
||||
else '`Command(update=[ToolMessage("Success", tool_call_id=tool_call_id), ...], ...)`'
|
||||
)
|
||||
raise ValueError(
|
||||
f"Expected exactly one message (ToolMessage) in Command.update for tool '{call['name']}', got: {messages_update}. "
|
||||
f"Expected to have a matching ToolMessage in Command.update for tool '{call['name']}', got: {messages_update}. "
|
||||
"Every tool call (LLM requesting to call a tool) in the message history MUST have a corresponding ToolMessage. "
|
||||
f"You can fix it by modifying the tool to return {example_update}."
|
||||
)
|
||||
|
||||
@@ -271,7 +271,7 @@ class Command(Generic[N], ToolOutputMixin):
|
||||
"""
|
||||
|
||||
graph: Optional[str] = None
|
||||
update: Union[dict[str, Any], Sequence[tuple[str, Any]]] = ()
|
||||
update: Any = ()
|
||||
resume: Optional[Union[Any, dict[str, Any]]] = None
|
||||
goto: Union[Send, Sequence[Union[Send, str]], str] = ()
|
||||
|
||||
|
||||
@@ -1,230 +1,4 @@
|
||||
# serializer version: 1
|
||||
# name: test_branch_then.1
|
||||
'''
|
||||
%%{init: {'flowchart': {'curve': 'linear'}}}%%
|
||||
graph TD;
|
||||
__start__([__start__]):::first
|
||||
prepare(prepare)
|
||||
tool_two_slow(tool_two_slow)
|
||||
tool_two_fast(tool_two_fast)
|
||||
finish(finish)
|
||||
__end__([__end__]):::last
|
||||
__start__ --> prepare;
|
||||
finish --> __end__;
|
||||
prepare -.-> tool_two_slow;
|
||||
tool_two_slow --> finish;
|
||||
prepare -.-> tool_two_fast;
|
||||
tool_two_fast --> finish;
|
||||
classDef default fill:#f2f0ff,line-height:1.2
|
||||
classDef first fill-opacity:0
|
||||
classDef last fill:#bfb6fc
|
||||
|
||||
'''
|
||||
# ---
|
||||
# name: test_branch_then[duckdb]
|
||||
'''
|
||||
graph TD;
|
||||
__start__ --> prepare;
|
||||
finish --> __end__;
|
||||
prepare -.-> tool_two_slow;
|
||||
tool_two_slow --> finish;
|
||||
prepare -.-> tool_two_fast;
|
||||
tool_two_fast --> finish;
|
||||
|
||||
'''
|
||||
# ---
|
||||
# name: test_branch_then[duckdb].1
|
||||
'''
|
||||
%%{init: {'flowchart': {'curve': 'linear'}}}%%
|
||||
graph TD;
|
||||
__start__([<p>__start__</p>]):::first
|
||||
prepare(prepare)
|
||||
tool_two_slow(tool_two_slow)
|
||||
tool_two_fast(tool_two_fast)
|
||||
finish(finish)
|
||||
__end__([<p>__end__</p>]):::last
|
||||
__start__ --> prepare;
|
||||
finish --> __end__;
|
||||
prepare -.-> tool_two_slow;
|
||||
tool_two_slow --> finish;
|
||||
prepare -.-> tool_two_fast;
|
||||
tool_two_fast --> finish;
|
||||
classDef default fill:#f2f0ff,line-height:1.2
|
||||
classDef first fill-opacity:0
|
||||
classDef last fill:#bfb6fc
|
||||
|
||||
'''
|
||||
# ---
|
||||
# name: test_branch_then[memory]
|
||||
'''
|
||||
graph TD;
|
||||
__start__ --> prepare;
|
||||
finish --> __end__;
|
||||
prepare -.-> tool_two_slow;
|
||||
tool_two_slow --> finish;
|
||||
prepare -.-> tool_two_fast;
|
||||
tool_two_fast --> finish;
|
||||
|
||||
'''
|
||||
# ---
|
||||
# name: test_branch_then[memory].1
|
||||
'''
|
||||
%%{init: {'flowchart': {'curve': 'linear'}}}%%
|
||||
graph TD;
|
||||
__start__([<p>__start__</p>]):::first
|
||||
prepare(prepare)
|
||||
tool_two_slow(tool_two_slow)
|
||||
tool_two_fast(tool_two_fast)
|
||||
finish(finish)
|
||||
__end__([<p>__end__</p>]):::last
|
||||
__start__ --> prepare;
|
||||
finish --> __end__;
|
||||
prepare -.-> tool_two_slow;
|
||||
tool_two_slow --> finish;
|
||||
prepare -.-> tool_two_fast;
|
||||
tool_two_fast --> finish;
|
||||
classDef default fill:#f2f0ff,line-height:1.2
|
||||
classDef first fill-opacity:0
|
||||
classDef last fill:#bfb6fc
|
||||
|
||||
'''
|
||||
# ---
|
||||
# name: test_branch_then[postgres]
|
||||
'''
|
||||
graph TD;
|
||||
__start__ --> prepare;
|
||||
finish --> __end__;
|
||||
prepare -.-> tool_two_slow;
|
||||
tool_two_slow --> finish;
|
||||
prepare -.-> tool_two_fast;
|
||||
tool_two_fast --> finish;
|
||||
|
||||
'''
|
||||
# ---
|
||||
# name: test_branch_then[postgres].1
|
||||
'''
|
||||
%%{init: {'flowchart': {'curve': 'linear'}}}%%
|
||||
graph TD;
|
||||
__start__([<p>__start__</p>]):::first
|
||||
prepare(prepare)
|
||||
tool_two_slow(tool_two_slow)
|
||||
tool_two_fast(tool_two_fast)
|
||||
finish(finish)
|
||||
__end__([<p>__end__</p>]):::last
|
||||
__start__ --> prepare;
|
||||
finish --> __end__;
|
||||
prepare -.-> tool_two_slow;
|
||||
tool_two_slow --> finish;
|
||||
prepare -.-> tool_two_fast;
|
||||
tool_two_fast --> finish;
|
||||
classDef default fill:#f2f0ff,line-height:1.2
|
||||
classDef first fill-opacity:0
|
||||
classDef last fill:#bfb6fc
|
||||
|
||||
'''
|
||||
# ---
|
||||
# name: test_branch_then[postgres_pipe]
|
||||
'''
|
||||
graph TD;
|
||||
__start__ --> prepare;
|
||||
finish --> __end__;
|
||||
prepare -.-> tool_two_slow;
|
||||
tool_two_slow --> finish;
|
||||
prepare -.-> tool_two_fast;
|
||||
tool_two_fast --> finish;
|
||||
|
||||
'''
|
||||
# ---
|
||||
# name: test_branch_then[postgres_pipe].1
|
||||
'''
|
||||
%%{init: {'flowchart': {'curve': 'linear'}}}%%
|
||||
graph TD;
|
||||
__start__([<p>__start__</p>]):::first
|
||||
prepare(prepare)
|
||||
tool_two_slow(tool_two_slow)
|
||||
tool_two_fast(tool_two_fast)
|
||||
finish(finish)
|
||||
__end__([<p>__end__</p>]):::last
|
||||
__start__ --> prepare;
|
||||
finish --> __end__;
|
||||
prepare -.-> tool_two_slow;
|
||||
tool_two_slow --> finish;
|
||||
prepare -.-> tool_two_fast;
|
||||
tool_two_fast --> finish;
|
||||
classDef default fill:#f2f0ff,line-height:1.2
|
||||
classDef first fill-opacity:0
|
||||
classDef last fill:#bfb6fc
|
||||
|
||||
'''
|
||||
# ---
|
||||
# name: test_branch_then[postgres_pool]
|
||||
'''
|
||||
graph TD;
|
||||
__start__ --> prepare;
|
||||
finish --> __end__;
|
||||
prepare -.-> tool_two_slow;
|
||||
tool_two_slow --> finish;
|
||||
prepare -.-> tool_two_fast;
|
||||
tool_two_fast --> finish;
|
||||
|
||||
'''
|
||||
# ---
|
||||
# name: test_branch_then[postgres_pool].1
|
||||
'''
|
||||
%%{init: {'flowchart': {'curve': 'linear'}}}%%
|
||||
graph TD;
|
||||
__start__([<p>__start__</p>]):::first
|
||||
prepare(prepare)
|
||||
tool_two_slow(tool_two_slow)
|
||||
tool_two_fast(tool_two_fast)
|
||||
finish(finish)
|
||||
__end__([<p>__end__</p>]):::last
|
||||
__start__ --> prepare;
|
||||
finish --> __end__;
|
||||
prepare -.-> tool_two_slow;
|
||||
tool_two_slow --> finish;
|
||||
prepare -.-> tool_two_fast;
|
||||
tool_two_fast --> finish;
|
||||
classDef default fill:#f2f0ff,line-height:1.2
|
||||
classDef first fill-opacity:0
|
||||
classDef last fill:#bfb6fc
|
||||
|
||||
'''
|
||||
# ---
|
||||
# name: test_branch_then[sqlite]
|
||||
'''
|
||||
graph TD;
|
||||
__start__ --> prepare;
|
||||
finish --> __end__;
|
||||
prepare -.-> tool_two_slow;
|
||||
tool_two_slow --> finish;
|
||||
prepare -.-> tool_two_fast;
|
||||
tool_two_fast --> finish;
|
||||
|
||||
'''
|
||||
# ---
|
||||
# name: test_branch_then[sqlite].1
|
||||
'''
|
||||
%%{init: {'flowchart': {'curve': 'linear'}}}%%
|
||||
graph TD;
|
||||
__start__([<p>__start__</p>]):::first
|
||||
prepare(prepare)
|
||||
tool_two_slow(tool_two_slow)
|
||||
tool_two_fast(tool_two_fast)
|
||||
finish(finish)
|
||||
__end__([<p>__end__</p>]):::last
|
||||
__start__ --> prepare;
|
||||
finish --> __end__;
|
||||
prepare -.-> tool_two_slow;
|
||||
tool_two_slow --> finish;
|
||||
prepare -.-> tool_two_fast;
|
||||
tool_two_fast --> finish;
|
||||
classDef default fill:#f2f0ff,line-height:1.2
|
||||
classDef first fill-opacity:0
|
||||
classDef last fill:#bfb6fc
|
||||
|
||||
'''
|
||||
# ---
|
||||
# name: test_conditional_entrypoint_graph
|
||||
'{"title": "LangGraphInput"}'
|
||||
# ---
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
from typing import Literal, Union
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
# define these objects to avoid importing langchain_core.agents
|
||||
# and therefore avoid relying on core Pydantic version
|
||||
class AgentAction(BaseModel):
|
||||
tool: str
|
||||
tool_input: Union[str, dict]
|
||||
log: str
|
||||
type: Literal["AgentAction"] = "AgentAction"
|
||||
|
||||
model_config = {
|
||||
"json_schema_extra": {
|
||||
"description": (
|
||||
"""Represents a request to execute an action by an agent.
|
||||
|
||||
The action consists of the name of the tool to execute and the input to pass
|
||||
to the tool. The log is used to pass along extra information about the action."""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class AgentFinish(BaseModel):
|
||||
"""Final return value of an ActionAgent.
|
||||
|
||||
Agents return an AgentFinish when they have reached a stopping condition.
|
||||
"""
|
||||
|
||||
return_values: dict
|
||||
log: str
|
||||
type: Literal["AgentFinish"] = "AgentFinish"
|
||||
model_config = {
|
||||
"json_schema_extra": {
|
||||
"description": (
|
||||
"""Final return value of an ActionAgent.
|
||||
|
||||
Agents return an AgentFinish when they have reached a stopping condition."""
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1249,6 +1249,33 @@ async def test_tool_node_command():
|
||||
}
|
||||
)
|
||||
|
||||
# test validation (tool message with a wrong tool call ID)
|
||||
with pytest.raises(ValueError):
|
||||
|
||||
@dec_tool
|
||||
def mismatching_tool_call_id_tool():
|
||||
"""My tool"""
|
||||
return Command(
|
||||
update={"messages": [ToolMessage(content="foo", tool_call_id="2")]}
|
||||
)
|
||||
|
||||
ToolNode([mismatching_tool_call_id_tool]).invoke(
|
||||
{
|
||||
"messages": [
|
||||
AIMessage(
|
||||
"",
|
||||
tool_calls=[
|
||||
{
|
||||
"args": {},
|
||||
"id": "1",
|
||||
"name": "mismatching_tool_call_id_tool",
|
||||
}
|
||||
],
|
||||
)
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
# test validation (missing tool message in the update for parent graph is OK)
|
||||
@dec_tool
|
||||
def node_update_parent_tool():
|
||||
@@ -1268,40 +1295,6 @@ async def test_tool_node_command():
|
||||
}
|
||||
) == [Command(update={"messages": []}, graph=Command.PARENT)]
|
||||
|
||||
# test validation (multiple tool messages)
|
||||
with pytest.raises(ValueError):
|
||||
for graph in (None, Command.PARENT):
|
||||
|
||||
@dec_tool
|
||||
def multiple_tool_messages_tool():
|
||||
"""My tool"""
|
||||
return Command(
|
||||
update={
|
||||
"messages": [
|
||||
ToolMessage(content="foo", tool_call_id=""),
|
||||
ToolMessage(content="bar", tool_call_id=""),
|
||||
]
|
||||
},
|
||||
graph=graph,
|
||||
)
|
||||
|
||||
ToolNode([multiple_tool_messages_tool]).invoke(
|
||||
{
|
||||
"messages": [
|
||||
AIMessage(
|
||||
"",
|
||||
tool_calls=[
|
||||
{
|
||||
"args": {},
|
||||
"id": "1",
|
||||
"name": "multiple_tool_messages_tool",
|
||||
}
|
||||
],
|
||||
)
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not IS_LANGCHAIN_CORE_030_OR_GREATER,
|
||||
@@ -1524,6 +1517,25 @@ async def test_tool_node_command_list_input():
|
||||
]
|
||||
)
|
||||
|
||||
# test validation (tool message with a wrong tool call ID)
|
||||
with pytest.raises(ValueError):
|
||||
|
||||
@dec_tool
|
||||
def mismatching_tool_call_id_tool():
|
||||
"""My tool"""
|
||||
return Command(update=[ToolMessage(content="foo", tool_call_id="2")])
|
||||
|
||||
ToolNode([mismatching_tool_call_id_tool]).invoke(
|
||||
[
|
||||
AIMessage(
|
||||
"",
|
||||
tool_calls=[
|
||||
{"args": {}, "id": "1", "name": "mismatching_tool_call_id_tool"}
|
||||
],
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
# test validation (missing tool message in the update for parent graph is OK)
|
||||
@dec_tool
|
||||
def node_update_parent_tool():
|
||||
@@ -1539,36 +1551,6 @@ async def test_tool_node_command_list_input():
|
||||
]
|
||||
) == [Command(update=[], graph=Command.PARENT)]
|
||||
|
||||
# test validation (multiple tool messages)
|
||||
with pytest.raises(ValueError):
|
||||
for graph in (None, Command.PARENT):
|
||||
|
||||
@dec_tool
|
||||
def multiple_tool_messages_tool():
|
||||
"""My tool"""
|
||||
return Command(
|
||||
update=[
|
||||
ToolMessage(content="foo", tool_call_id=""),
|
||||
ToolMessage(content="bar", tool_call_id=""),
|
||||
],
|
||||
graph=graph,
|
||||
)
|
||||
|
||||
ToolNode([multiple_tool_messages_tool]).invoke(
|
||||
[
|
||||
AIMessage(
|
||||
"",
|
||||
tool_calls=[
|
||||
{
|
||||
"args": {},
|
||||
"id": "1",
|
||||
"name": "multiple_tool_messages_tool",
|
||||
}
|
||||
],
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not IS_LANGCHAIN_CORE_030_OR_GREATER,
|
||||
|
||||
@@ -20,5 +20,5 @@ lint lint_diff:
|
||||
[ "$(PYTHON_FILES)" = "" ] || mkdir -p $(MYPY_CACHE) || poetry run mypy $(PYTHON_FILES) --cache-dir $(MYPY_CACHE)
|
||||
|
||||
format format_diff:
|
||||
poetry run ruff format $(PYTHON_FILES)
|
||||
poetry run ruff check --select I --fix $(PYTHON_FILES)
|
||||
poetry run ruff format $(PYTHON_FILES)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from langgraph_sdk.auth import Auth
|
||||
from langgraph_sdk.client import get_client, get_sync_client
|
||||
|
||||
try:
|
||||
@@ -7,4 +8,4 @@ try:
|
||||
except metadata.PackageNotFoundError:
|
||||
__version__ = "unknown"
|
||||
|
||||
__all__ = ["get_client", "get_sync_client"]
|
||||
__all__ = ["Auth", "get_client", "get_sync_client"]
|
||||
|
||||
@@ -0,0 +1,645 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import typing
|
||||
from collections.abc import Callable, Sequence
|
||||
|
||||
from langgraph_sdk.auth import exceptions, types
|
||||
|
||||
TH = typing.TypeVar("TH", bound=types.Handler)
|
||||
AH = typing.TypeVar("AH", bound=types.Authenticator)
|
||||
|
||||
|
||||
class Auth:
|
||||
"""Add custom authentication and authorization management to your LangGraph application.
|
||||
|
||||
The Auth class provides a unified system for handling authentication and
|
||||
authorization in LangGraph applications. It supports custom user authentication
|
||||
protocols and fine-grained authorization rules for different resources and
|
||||
actions.
|
||||
|
||||
To use, create a separate python file and add the path to the file to your
|
||||
LangGraph API configuration file (`langgraph.json`). Within that file, create
|
||||
an instance of the Auth class and register authentication and authorization
|
||||
handlers as needed.
|
||||
|
||||
Example `langgraph.json` file:
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": ["."],
|
||||
"graphs": {
|
||||
"agent": "./my_agent/agent.py:graph"
|
||||
},
|
||||
"env": ".env",
|
||||
"auth": {
|
||||
"path": "./auth.py:my_auth"
|
||||
}
|
||||
```
|
||||
|
||||
Then the LangGraph server will load your auth file and run it server-side whenever a request comes in.
|
||||
|
||||
???+ example "Basic Usage"
|
||||
```python
|
||||
from langgraph_sdk import Auth
|
||||
|
||||
my_auth = Auth()
|
||||
|
||||
async def verify_token(token: str) -> str:
|
||||
# Verify token and return user_id
|
||||
# This would typically be a call to your auth server
|
||||
return "user_id"
|
||||
|
||||
@auth.authenticate
|
||||
async def authenticate(authorization: str) -> str:
|
||||
# Verify token and return user_id
|
||||
result = await verify_token(authorization)
|
||||
if result != "user_id":
|
||||
raise Auth.exceptions.HTTPException(
|
||||
status_code=401, detail="Unauthorized"
|
||||
)
|
||||
return result
|
||||
|
||||
# Global fallback handler
|
||||
@auth.on
|
||||
async def authorize_default(params: Auth.on.value):
|
||||
return False # Reject all requests (default behavior)
|
||||
|
||||
@auth.on.threads.create
|
||||
async def authorize_thread_create(params: Auth.on.threads.create.value):
|
||||
# Allow the allowed user to create a thread
|
||||
assert params.get("metadata", {}).get("owner") == "allowed_user"
|
||||
```
|
||||
|
||||
???+ note "Request Processing Flow"
|
||||
1. Authentication (your `@auth.authenticate` handler) is performed first on **every request**
|
||||
2. For authorization, the most specific matching handler is called:
|
||||
* If a handler exists for the exact resource and action, it is used (e.g., `@auth.on.threads.create`)
|
||||
* Otherwise, if a handler exists for the resource with any action, it is used (e.g., `@auth.on.threads`)
|
||||
* Finally, if no specific handlers match, the global handler is used (e.g., `@auth.on`)
|
||||
* If no global handler is set, the request is accepted
|
||||
|
||||
This allows you to set default behavior with a global handler while
|
||||
overriding specific routes as needed.
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
"on",
|
||||
"_handlers",
|
||||
"_global_handlers",
|
||||
"_authenticate_handler",
|
||||
"_handler_cache",
|
||||
)
|
||||
types = types
|
||||
"""Reference to auth type definitions.
|
||||
|
||||
Provides access to all type definitions used in the auth system,
|
||||
like ThreadsCreate, AssistantsRead, etc."""
|
||||
|
||||
exceptions = exceptions
|
||||
"""Reference to auth exception definitions.
|
||||
|
||||
Provides access to all exception definitions used in the auth system,
|
||||
like HTTPException, etc.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.on = _On(self)
|
||||
"""Entry point for authorization handlers that control access to specific resources.
|
||||
|
||||
The on class provides a flexible way to define authorization rules for different
|
||||
resources and actions in your application. It supports three main usage patterns:
|
||||
|
||||
1. Global handlers that run for all resources and actions
|
||||
2. Resource-specific handlers that run for all actions on a resource
|
||||
3. Resource and action specific handlers for fine-grained control
|
||||
|
||||
Each handler must be an async function that accepts two parameters:
|
||||
- ctx (AuthContext): Contains request context and authenticated user info
|
||||
- value: The data being authorized (type varies by endpoint)
|
||||
|
||||
The handler should return one of:
|
||||
|
||||
- None or True: Accept the request
|
||||
- False: Reject with 403 error
|
||||
- FilterType: Apply filtering rules to the response
|
||||
|
||||
???+ example "Examples"
|
||||
Global handler for all requests:
|
||||
```python
|
||||
@auth.on
|
||||
async def reject_unhandled_requests(ctx: AuthContext, value: Any) -> None:
|
||||
print(f"Request to {ctx.path} by {ctx.user.identity}")
|
||||
return False
|
||||
```
|
||||
|
||||
Resource-specific handler. This would take precedence over the global handler
|
||||
for all actions on the `threads` resource:
|
||||
```python
|
||||
@auth.on.threads
|
||||
async def check_thread_access(ctx: AuthContext, value: Any) -> bool:
|
||||
# Allow access only to threads created by the user
|
||||
return value.get("created_by") == ctx.user.identity
|
||||
```
|
||||
|
||||
Resource and action specific handler:
|
||||
```python
|
||||
@auth.on.threads.delete
|
||||
async def prevent_thread_deletion(ctx: AuthContext, value: Any) -> bool:
|
||||
# Only admins can delete threads
|
||||
return "admin" in ctx.user.permissions
|
||||
```
|
||||
|
||||
Multiple resources or actions:
|
||||
```python
|
||||
@auth.on(resources=["threads", "runs"], actions=["create", "update"])
|
||||
async def rate_limit_writes(ctx: AuthContext, value: Any) -> bool:
|
||||
# Implement rate limiting for write operations
|
||||
return await check_rate_limit(ctx.user.identity)
|
||||
```
|
||||
"""
|
||||
# These are accessed by the API. Changes to their names or types is
|
||||
# will be considered a breaking change.
|
||||
self._handlers: dict[tuple[str, str], list[types.Handler]] = {}
|
||||
self._global_handlers: list[types.Handler] = []
|
||||
self._authenticate_handler: typing.Optional[types.Authenticator] = None
|
||||
self._handler_cache: dict[tuple[str, str], types.Handler] = {}
|
||||
|
||||
def authenticate(self, fn: AH) -> AH:
|
||||
"""Register an authentication handler function.
|
||||
|
||||
The authentication handler is responsible for verifying credentials
|
||||
and returning user scopes. It can accept any of the following parameters
|
||||
by name:
|
||||
|
||||
- request (Request): The raw ASGI request object
|
||||
- body (dict): The parsed request body
|
||||
- path (str): The request path, e.g., "/threads/abcd-1234-abcd-1234/runs/abcd-1234-abcd-1234/stream"
|
||||
- method (str): The HTTP method, e.g., "GET"
|
||||
- path_params (dict[str, str]): URL path parameters, e.g., {"thread_id": "abcd-1234-abcd-1234", "run_id": "abcd-1234-abcd-1234"}
|
||||
- query_params (dict[str, str]): URL query parameters, e.g., {"stream": "true"}
|
||||
- headers (dict[bytes, bytes]): Request headers
|
||||
- authorization (str | None): The Authorization header value (e.g., "Bearer <token>")
|
||||
|
||||
Args:
|
||||
fn (Callable): The authentication handler function to register.
|
||||
Must return a representation of the user. This could be a:
|
||||
- string (the user id)
|
||||
- dict containing {"identity": str, "permissions": list[str]}
|
||||
- or an object with identity and permissions properties
|
||||
Permissions can be optionally used by your handlers downstream.
|
||||
|
||||
Returns:
|
||||
The registered handler function.
|
||||
|
||||
Raises:
|
||||
ValueError: If an authentication handler is already registered.
|
||||
|
||||
???+ example "Examples"
|
||||
Basic token authentication:
|
||||
```python
|
||||
@auth.authenticate
|
||||
async def authenticate(authorization: str) -> str:
|
||||
user_id = verify_token(authorization)
|
||||
return user_id
|
||||
```
|
||||
|
||||
Accept the full request context:
|
||||
```python
|
||||
@auth.authenticate
|
||||
async def authenticate(
|
||||
method: str,
|
||||
path: str,
|
||||
headers: dict[str, bytes]
|
||||
) -> str:
|
||||
user = await verify_request(method, path, headers)
|
||||
return user
|
||||
```
|
||||
|
||||
Return user name and permissions:
|
||||
```python
|
||||
@auth.authenticate
|
||||
async def authenticate(
|
||||
method: str,
|
||||
path: str,
|
||||
headers: dict[str, bytes]
|
||||
) -> Auth.types.MinimalUserDict:
|
||||
permissions, user = await verify_request(method, path, headers)
|
||||
# Permissions could be things like ["runs:read", "runs:write", "threads:read", "threads:write"]
|
||||
return {
|
||||
"identity": user["id"],
|
||||
"permissions": permissions,
|
||||
"display_name": user["name"],
|
||||
}
|
||||
```
|
||||
"""
|
||||
if self._authenticate_handler is not None:
|
||||
raise ValueError(
|
||||
"Authentication handler already set as {self._authenticate_handler}."
|
||||
)
|
||||
self._authenticate_handler = fn
|
||||
return fn
|
||||
|
||||
|
||||
## Helper types & utilities
|
||||
|
||||
V = typing.TypeVar("V", contravariant=True)
|
||||
|
||||
|
||||
class _ActionHandler(typing.Protocol[V]):
|
||||
async def __call__(
|
||||
self, *, ctx: types.AuthContext, value: V
|
||||
) -> types.HandlerResult: ...
|
||||
|
||||
|
||||
T = typing.TypeVar("T", covariant=True)
|
||||
|
||||
|
||||
class _ResourceActionOn(typing.Generic[T]):
|
||||
def __init__(
|
||||
self,
|
||||
auth: Auth,
|
||||
resource: typing.Literal["threads", "crons", "assistants"],
|
||||
action: typing.Literal[
|
||||
"create", "read", "update", "delete", "search", "create_run"
|
||||
],
|
||||
value: type[T],
|
||||
) -> None:
|
||||
self.auth = auth
|
||||
self.resource = resource
|
||||
self.action = action
|
||||
self.value = value
|
||||
|
||||
def __call__(self, fn: _ActionHandler[T]) -> _ActionHandler[T]:
|
||||
_validate_handler(fn)
|
||||
_register_handler(self.auth, self.resource, self.action, fn)
|
||||
return fn
|
||||
|
||||
|
||||
VCreate = typing.TypeVar("VCreate", covariant=True)
|
||||
VUpdate = typing.TypeVar("VUpdate", covariant=True)
|
||||
VRead = typing.TypeVar("VRead", covariant=True)
|
||||
VDelete = typing.TypeVar("VDelete", covariant=True)
|
||||
VSearch = typing.TypeVar("VSearch", covariant=True)
|
||||
|
||||
|
||||
class _ResourceOn(typing.Generic[VCreate, VRead, VUpdate, VDelete, VSearch]):
|
||||
"""
|
||||
Generic base class for resource-specific handlers.
|
||||
"""
|
||||
|
||||
value: type[typing.Union[VCreate, VUpdate, VRead, VDelete, VSearch]]
|
||||
|
||||
Create: type[VCreate]
|
||||
Read: type[VRead]
|
||||
Update: type[VUpdate]
|
||||
Delete: type[VDelete]
|
||||
Search: type[VSearch]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
auth: Auth,
|
||||
resource: typing.Literal["threads", "crons", "assistants"],
|
||||
) -> None:
|
||||
self.auth = auth
|
||||
self.resource = resource
|
||||
self.create: _ResourceActionOn[VCreate] = _ResourceActionOn(
|
||||
auth, resource, "create", self.Create
|
||||
)
|
||||
self.read: _ResourceActionOn[VRead] = _ResourceActionOn(
|
||||
auth, resource, "read", self.Read
|
||||
)
|
||||
self.update: _ResourceActionOn[VUpdate] = _ResourceActionOn(
|
||||
auth, resource, "update", self.Update
|
||||
)
|
||||
self.delete: _ResourceActionOn[VDelete] = _ResourceActionOn(
|
||||
auth, resource, "delete", self.Delete
|
||||
)
|
||||
self.search: _ResourceActionOn[VSearch] = _ResourceActionOn(
|
||||
auth, resource, "search", self.Search
|
||||
)
|
||||
|
||||
@typing.overload
|
||||
def __call__(
|
||||
self,
|
||||
fn: typing.Union[
|
||||
_ActionHandler[typing.Union[VCreate, VUpdate, VRead, VDelete, VSearch]],
|
||||
_ActionHandler[dict[str, typing.Any]],
|
||||
],
|
||||
) -> _ActionHandler[typing.Union[VCreate, VUpdate, VRead, VDelete, VSearch]]: ...
|
||||
|
||||
@typing.overload
|
||||
def __call__(
|
||||
self,
|
||||
*,
|
||||
resources: typing.Union[str, Sequence[str]],
|
||||
actions: typing.Optional[typing.Union[str, Sequence[str]]] = None,
|
||||
) -> Callable[
|
||||
[_ActionHandler[typing.Union[VCreate, VUpdate, VRead, VDelete, VSearch]]],
|
||||
_ActionHandler[typing.Union[VCreate, VUpdate, VRead, VDelete, VSearch]],
|
||||
]: ...
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
fn: typing.Union[
|
||||
_ActionHandler[typing.Union[VCreate, VUpdate, VRead, VDelete, VSearch]],
|
||||
_ActionHandler[dict[str, typing.Any]],
|
||||
None,
|
||||
] = None,
|
||||
*,
|
||||
resources: typing.Union[str, Sequence[str], None] = None,
|
||||
actions: typing.Optional[typing.Union[str, Sequence[str]]] = None,
|
||||
) -> typing.Union[
|
||||
_ActionHandler[typing.Union[VCreate, VUpdate, VRead, VDelete, VSearch]],
|
||||
Callable[
|
||||
[_ActionHandler[typing.Union[VCreate, VUpdate, VRead, VDelete, VSearch]]],
|
||||
_ActionHandler[typing.Union[VCreate, VUpdate, VRead, VDelete, VSearch]],
|
||||
],
|
||||
]:
|
||||
if fn is not None:
|
||||
_validate_handler(fn)
|
||||
return typing.cast(
|
||||
_ActionHandler[typing.Union[VCreate, VUpdate, VRead, VDelete, VSearch]],
|
||||
_register_handler(self.auth, self.resource, "*", fn),
|
||||
)
|
||||
|
||||
def decorator(
|
||||
handler: _ActionHandler[
|
||||
typing.Union[VCreate, VUpdate, VRead, VDelete, VSearch]
|
||||
],
|
||||
) -> _ActionHandler[typing.Union[VCreate, VUpdate, VRead, VDelete, VSearch]]:
|
||||
_validate_handler(handler)
|
||||
return typing.cast(
|
||||
_ActionHandler[typing.Union[VCreate, VUpdate, VRead, VDelete, VSearch]],
|
||||
_register_handler(self.auth, self.resource, "*", handler),
|
||||
)
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
class _AssistantsOn(
|
||||
_ResourceOn[
|
||||
types.AssistantsCreate,
|
||||
types.AssistantsRead,
|
||||
types.AssistantsUpdate,
|
||||
types.AssistantsDelete,
|
||||
types.AssistantsSearch,
|
||||
]
|
||||
):
|
||||
value = typing.Union[
|
||||
types.AssistantsCreate,
|
||||
types.AssistantsRead,
|
||||
types.AssistantsUpdate,
|
||||
types.AssistantsDelete,
|
||||
types.AssistantsSearch,
|
||||
]
|
||||
Create = types.AssistantsCreate
|
||||
Read = types.AssistantsRead
|
||||
Update = types.AssistantsUpdate
|
||||
Delete = types.AssistantsDelete
|
||||
Search = types.AssistantsSearch
|
||||
|
||||
|
||||
class _ThreadsOn(
|
||||
_ResourceOn[
|
||||
types.ThreadsCreate,
|
||||
types.ThreadsRead,
|
||||
types.ThreadsUpdate,
|
||||
types.ThreadsDelete,
|
||||
types.ThreadsSearch,
|
||||
]
|
||||
):
|
||||
value = typing.Union[
|
||||
type[types.ThreadsCreate],
|
||||
type[types.ThreadsRead],
|
||||
type[types.ThreadsUpdate],
|
||||
type[types.ThreadsDelete],
|
||||
type[types.ThreadsSearch],
|
||||
type[types.RunsCreate],
|
||||
]
|
||||
Create = types.ThreadsCreate
|
||||
Read = types.ThreadsRead
|
||||
Update = types.ThreadsUpdate
|
||||
Delete = types.ThreadsDelete
|
||||
Search = types.ThreadsSearch
|
||||
CreateRun = types.RunsCreate
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
auth: Auth,
|
||||
resource: typing.Literal["threads", "crons", "assistants"],
|
||||
) -> None:
|
||||
super().__init__(auth, resource)
|
||||
self.create_run: _ResourceActionOn[types.RunsCreate] = _ResourceActionOn(
|
||||
auth, resource, "create_run", self.CreateRun
|
||||
)
|
||||
|
||||
|
||||
class _CronsOn(
|
||||
_ResourceOn[
|
||||
types.CronsCreate,
|
||||
types.CronsRead,
|
||||
types.CronsUpdate,
|
||||
types.CronsDelete,
|
||||
types.CronsSearch,
|
||||
]
|
||||
):
|
||||
value = type[
|
||||
typing.Union[
|
||||
types.CronsCreate,
|
||||
types.CronsRead,
|
||||
types.CronsUpdate,
|
||||
types.CronsDelete,
|
||||
types.CronsSearch,
|
||||
]
|
||||
]
|
||||
|
||||
Create = types.CronsCreate
|
||||
Read = types.CronsRead
|
||||
Update = types.CronsUpdate
|
||||
Delete = types.CronsDelete
|
||||
Search = types.CronsSearch
|
||||
|
||||
|
||||
AHO = typing.TypeVar("AHO", bound=_ActionHandler[dict[str, typing.Any]])
|
||||
|
||||
|
||||
class _On:
|
||||
"""Entry point for authorization handlers that control access to specific resources.
|
||||
|
||||
The _On class provides a flexible way to define authorization rules for different resources
|
||||
and actions in your application. It supports three main usage patterns:
|
||||
|
||||
1. Global handlers that run for all resources and actions
|
||||
2. Resource-specific handlers that run for all actions on a resource
|
||||
3. Resource and action specific handlers for fine-grained control
|
||||
|
||||
Each handler must be an async function that accepts two parameters:
|
||||
- ctx (AuthContext): Contains request context and authenticated user info
|
||||
- value: The data being authorized (type varies by endpoint)
|
||||
|
||||
The handler should return one of:
|
||||
- None or True: Accept the request
|
||||
- False: Reject with 403 error
|
||||
- FilterType: Apply filtering rules to the response
|
||||
|
||||
???+ example "Examples"
|
||||
|
||||
Global handler for all requests:
|
||||
```python
|
||||
@auth.on
|
||||
async def log_all_requests(ctx: AuthContext, value: Any) -> None:
|
||||
print(f"Request to {ctx.path} by {ctx.user.identity}")
|
||||
return True
|
||||
```
|
||||
|
||||
Resource-specific handler:
|
||||
```python
|
||||
@auth.on.threads
|
||||
async def check_thread_access(ctx: AuthContext, value: Any) -> bool:
|
||||
# Allow access only to threads created by the user
|
||||
return value.get("created_by") == ctx.user.identity
|
||||
```
|
||||
|
||||
Resource and action specific handler:
|
||||
```python
|
||||
@auth.on.threads.delete
|
||||
async def prevent_thread_deletion(ctx: AuthContext, value: Any) -> bool:
|
||||
# Only admins can delete threads
|
||||
return "admin" in ctx.user.permissions
|
||||
```
|
||||
|
||||
Multiple resources or actions:
|
||||
```python
|
||||
@auth.on(resources=["threads", "runs"], actions=["create", "update"])
|
||||
async def rate_limit_writes(ctx: AuthContext, value: Any) -> bool:
|
||||
# Implement rate limiting for write operations
|
||||
return await check_rate_limit(ctx.user.identity)
|
||||
```
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
"_auth",
|
||||
"assistants",
|
||||
"threads",
|
||||
"runs",
|
||||
"crons",
|
||||
"value",
|
||||
)
|
||||
|
||||
def __init__(self, auth: Auth) -> None:
|
||||
self._auth = auth
|
||||
self.assistants = _AssistantsOn(auth, "assistants")
|
||||
self.threads = _ThreadsOn(auth, "threads")
|
||||
self.crons = _CronsOn(auth, "crons")
|
||||
self.value = dict[str, typing.Any]
|
||||
|
||||
@typing.overload
|
||||
def __call__(
|
||||
self,
|
||||
*,
|
||||
resources: typing.Union[str, Sequence[str]],
|
||||
actions: typing.Optional[typing.Union[str, Sequence[str]]] = None,
|
||||
) -> Callable[[AHO], AHO]: ...
|
||||
|
||||
@typing.overload
|
||||
def __call__(self, fn: AHO) -> AHO: ...
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
fn: typing.Optional[AHO] = None,
|
||||
*,
|
||||
resources: typing.Union[str, Sequence[str], None] = None,
|
||||
actions: typing.Optional[typing.Union[str, Sequence[str]]] = None,
|
||||
) -> typing.Union[AHO, Callable[[AHO], AHO]]:
|
||||
"""Register a handler for specific resources and actions.
|
||||
|
||||
Can be used as a decorator or with explicit resource/action parameters:
|
||||
|
||||
@auth.on
|
||||
async def handler(): ... # Global handler
|
||||
|
||||
@auth.on(resources="threads")
|
||||
async def handler(): ... # types.Handler for all thread actions
|
||||
|
||||
@auth.on(resources="threads", actions="create")
|
||||
async def handler(): ... # types.Handler for thread creation
|
||||
"""
|
||||
if fn is not None:
|
||||
# Used as a plain decorator
|
||||
_register_handler(self._auth, None, None, fn)
|
||||
return fn
|
||||
|
||||
# Used with parameters, return a decorator
|
||||
def decorator(
|
||||
handler: AHO,
|
||||
) -> AHO:
|
||||
if isinstance(resources, str):
|
||||
resource_list = [resources]
|
||||
else:
|
||||
resource_list = list(resources) if resources is not None else ["*"]
|
||||
|
||||
if isinstance(actions, str):
|
||||
action_list = [actions]
|
||||
else:
|
||||
action_list = list(actions) if actions is not None else ["*"]
|
||||
for resource in resource_list:
|
||||
for action in action_list:
|
||||
_register_handler(self._auth, resource, action, handler)
|
||||
return handler
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def _register_handler(
|
||||
auth: Auth,
|
||||
resource: typing.Optional[str],
|
||||
action: typing.Optional[str],
|
||||
fn: types.Handler,
|
||||
) -> types.Handler:
|
||||
_validate_handler(fn)
|
||||
resource = resource or "*"
|
||||
action = action or "*"
|
||||
if resource == "*" and action == "*":
|
||||
if auth._global_handlers:
|
||||
raise ValueError("Global handler already set.")
|
||||
auth._global_handlers.append(fn)
|
||||
else:
|
||||
r = resource if resource is not None else "*"
|
||||
a = action if action is not None else "*"
|
||||
if (r, a) in auth._handlers:
|
||||
raise ValueError(f"types.Handler already set for {r}, {a}.")
|
||||
auth._handlers[(r, a)] = [fn]
|
||||
return fn
|
||||
|
||||
|
||||
def _validate_handler(fn: Callable[..., typing.Any]) -> None:
|
||||
"""Validates that an auth handler function meets the required signature.
|
||||
|
||||
Auth handlers must:
|
||||
1. Be async functions
|
||||
2. Accept a ctx parameter of type AuthContext
|
||||
3. Accept a value parameter for the data being authorized
|
||||
"""
|
||||
if not inspect.iscoroutinefunction(fn):
|
||||
raise ValueError(
|
||||
f"Auth handler '{fn.__name__}' must be an async function. "
|
||||
"Add 'async' before 'def' to make it asynchronous and ensure"
|
||||
" any IO operations are non-blocking."
|
||||
)
|
||||
|
||||
sig = inspect.signature(fn)
|
||||
if "ctx" not in sig.parameters:
|
||||
raise ValueError(
|
||||
f"Auth handler '{fn.__name__}' must have a 'ctx: AuthContext' parameter. "
|
||||
"Update the function signature to include this required parameter."
|
||||
)
|
||||
if "value" not in sig.parameters:
|
||||
raise ValueError(
|
||||
f"Auth handler '{fn.__name__}' must have a 'value' parameter. "
|
||||
" The value contains the mutable data being sent to the endpoint."
|
||||
"Update the function signature to include this required parameter."
|
||||
)
|
||||
|
||||
|
||||
__all__ = ["Auth", "types", "exceptions"]
|
||||
@@ -0,0 +1,73 @@
|
||||
"""Exceptions used in the auth system."""
|
||||
|
||||
import http
|
||||
import typing
|
||||
|
||||
|
||||
class HTTPException(Exception):
|
||||
"""HTTP exception that you can raise to return a specific HTTP error response.
|
||||
|
||||
Since this is defined in the auth module, we default to a 401 status code.
|
||||
|
||||
Args:
|
||||
status_code (int, optional): HTTP status code for the error. Defaults to 401 "Unauthorized".
|
||||
detail (str | None, optional): Detailed error message. If None, uses a default
|
||||
message based on the status code.
|
||||
headers (typing.Mapping[str, str] | None, optional): Additional HTTP headers to
|
||||
include in the error response.
|
||||
|
||||
Attributes:
|
||||
status_code (int): The HTTP status code of the error
|
||||
detail (str): The error message or description
|
||||
headers (typing.Mapping[str, str] | None): Additional HTTP headers
|
||||
|
||||
Example:
|
||||
Default:
|
||||
```python
|
||||
raise HTTPException()
|
||||
# HTTPException(status_code=401, detail='Unauthorized')
|
||||
```
|
||||
|
||||
Add headers:
|
||||
```python
|
||||
raise HTTPException(headers={"X-Custom-Header": "Custom Value"})
|
||||
# HTTPException(status_code=401, detail='Unauthorized', headers={"WWW-Authenticate": "Bearer"})
|
||||
```
|
||||
|
||||
Custom error:
|
||||
```python
|
||||
raise HTTPException(status_code=404, detail="Not found")
|
||||
```
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
status_code: int = 401,
|
||||
detail: typing.Optional[str] = None,
|
||||
headers: typing.Optional[typing.Mapping[str, str]] = None,
|
||||
) -> None:
|
||||
if detail is None:
|
||||
detail = http.HTTPStatus(status_code).phrase
|
||||
self.status_code = status_code
|
||||
self.detail = detail
|
||||
self.headers = headers
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Return a string representation of the HTTP exception.
|
||||
|
||||
Returns:
|
||||
str: A string in the format 'status_code: detail'
|
||||
"""
|
||||
return f"{self.status_code}: {self.detail}"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""Return a detailed string representation of the HTTP exception.
|
||||
|
||||
Returns:
|
||||
str: A string representation showing the class name and all attributes
|
||||
"""
|
||||
class_name = self.__class__.__name__
|
||||
return f"{class_name}(status_code={self.status_code!r}, detail={self.detail!r})"
|
||||
|
||||
|
||||
__all__ = ["HTTPException"]
|
||||
@@ -0,0 +1,844 @@
|
||||
"""Authentication and authorization types for LangGraph.
|
||||
|
||||
This module defines the core types used for authentication, authorization, and
|
||||
request handling in LangGraph. It includes user protocols, authentication contexts,
|
||||
and typed dictionaries for various API operations.
|
||||
|
||||
Note:
|
||||
All typing.TypedDict classes use total=False to make all fields optional by default.
|
||||
"""
|
||||
|
||||
import functools
|
||||
import sys
|
||||
import typing
|
||||
from collections.abc import Awaitable, Callable, Sequence
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from uuid import UUID
|
||||
|
||||
import typing_extensions
|
||||
|
||||
RunStatus = typing.Literal["pending", "error", "success", "timeout", "interrupted"]
|
||||
"""Status of a run execution.
|
||||
|
||||
Values:
|
||||
- pending: Run is queued or in progress
|
||||
- error: Run failed with an error
|
||||
- success: Run completed successfully
|
||||
- timeout: Run exceeded time limit
|
||||
- interrupted: Run was manually interrupted
|
||||
"""
|
||||
|
||||
MultitaskStrategy = typing.Literal["reject", "rollback", "interrupt", "enqueue"]
|
||||
"""Strategy for handling multiple concurrent tasks.
|
||||
|
||||
Values:
|
||||
- reject: Reject new tasks while one is in progress
|
||||
- rollback: Cancel current task and start new one
|
||||
- interrupt: Interrupt current task and start new one
|
||||
- enqueue: Queue new tasks to run after current one
|
||||
"""
|
||||
|
||||
OnConflictBehavior = typing.Literal["raise", "do_nothing"]
|
||||
"""Behavior when encountering conflicts.
|
||||
|
||||
Values:
|
||||
- raise: Raise an exception on conflict
|
||||
- do_nothing: Silently ignore conflicts
|
||||
"""
|
||||
|
||||
IfNotExists = typing.Literal["create", "reject"]
|
||||
"""Behavior when an entity doesn't exist.
|
||||
|
||||
Values:
|
||||
- create: Create the entity
|
||||
- reject: Reject the operation
|
||||
"""
|
||||
|
||||
FilterType = typing.Union[
|
||||
typing.Dict[
|
||||
str, typing.Union[str, typing.Dict[typing.Literal["$eq", "$contains"], str]]
|
||||
],
|
||||
typing.Dict[str, str],
|
||||
]
|
||||
"""Type for filtering queries.
|
||||
|
||||
Supports exact matches and operators:
|
||||
- Simple match: {"field": "value"}
|
||||
- Equals: {"field": {"$eq": "value"}}
|
||||
- Contains: {"field": {"$contains": "value"}}
|
||||
|
||||
???+ example "Examples"
|
||||
```python
|
||||
# Simple match
|
||||
filter = {"status": "pending"}
|
||||
|
||||
# Equals operator
|
||||
filter = {"status": {"$eq": "success"}}
|
||||
|
||||
# Contains operator
|
||||
filter = {"metadata.tags": {"$contains": "important"}}
|
||||
```
|
||||
"""
|
||||
|
||||
ThreadStatus = typing.Literal["idle", "busy", "interrupted", "error"]
|
||||
"""Status of a thread.
|
||||
|
||||
Values:
|
||||
- idle: Thread is available for work
|
||||
- busy: Thread is currently processing
|
||||
- interrupted: Thread was interrupted
|
||||
- error: Thread encountered an error
|
||||
"""
|
||||
|
||||
MetadataInput = typing.Dict[str, typing.Any]
|
||||
"""Type for arbitrary metadata attached to entities.
|
||||
|
||||
Allows storing custom key-value pairs with any entity.
|
||||
Keys must be strings, values can be any JSON-serializable type.
|
||||
|
||||
???+ example "Examples"
|
||||
```python
|
||||
metadata = {
|
||||
"created_by": "user123",
|
||||
"priority": 1,
|
||||
"tags": ["important", "urgent"]
|
||||
}
|
||||
```
|
||||
"""
|
||||
|
||||
HandlerResult = typing.Union[None, bool, FilterType]
|
||||
"""The result of a handler can be:
|
||||
- None | True: accept the request.
|
||||
- False: reject the request with a 403 error
|
||||
- FilterType: filter to apply
|
||||
"""
|
||||
|
||||
Handler = Callable[..., Awaitable[HandlerResult]]
|
||||
|
||||
T = typing.TypeVar("T")
|
||||
|
||||
|
||||
def _slotify(fn: T) -> T:
|
||||
if sys.version_info >= (3, 10): # noqa: UP036
|
||||
return functools.partial(fn, slots=True) # type: ignore
|
||||
return fn
|
||||
|
||||
|
||||
dataclass = _slotify(dataclass)
|
||||
|
||||
|
||||
@typing.runtime_checkable
|
||||
class MinimalUser(typing.Protocol):
|
||||
"""User objects must at least expose the identity property."""
|
||||
|
||||
@property
|
||||
def identity(self) -> str:
|
||||
"""The unique identifier for the user.
|
||||
|
||||
This could be a username, email, or any other unique identifier used
|
||||
to distinguish between different users in the system.
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
class MinimalUserDict(typing.TypedDict, total=False):
|
||||
"""The minimal user dictionary."""
|
||||
|
||||
identity: typing_extensions.Required[str]
|
||||
display_name: str
|
||||
is_authenticated: bool
|
||||
permissions: Sequence[str]
|
||||
|
||||
|
||||
@typing.runtime_checkable
|
||||
class BaseUser(typing.Protocol):
|
||||
"""The base ASGI user protocol"""
|
||||
|
||||
@property
|
||||
def is_authenticated(self) -> bool:
|
||||
"""Whether the user is authenticated."""
|
||||
...
|
||||
|
||||
@property
|
||||
def display_name(self) -> str:
|
||||
"""The display name of the user."""
|
||||
...
|
||||
|
||||
@property
|
||||
def identity(self) -> str:
|
||||
"""The unique identifier for the user."""
|
||||
...
|
||||
|
||||
@property
|
||||
def permissions(self) -> Sequence[str]:
|
||||
"""The permissions associated with the user."""
|
||||
...
|
||||
|
||||
|
||||
Authenticator = Callable[
|
||||
...,
|
||||
Awaitable[
|
||||
typing.Union[
|
||||
MinimalUser, str, BaseUser, MinimalUserDict, typing.Mapping[str, typing.Any]
|
||||
],
|
||||
],
|
||||
]
|
||||
"""Type for authentication functions.
|
||||
|
||||
An authenticator can return either:
|
||||
1. A string (user_id)
|
||||
2. A dict containing {"identity": str, "permissions": list[str]}
|
||||
3. An object with identity and permissions properties
|
||||
|
||||
Permissions can be used downstream by your authorization logic to determine
|
||||
access permissions to different resources.
|
||||
|
||||
The authenticate decorator will automatically inject any of the following parameters
|
||||
by name if they are included in your function signature:
|
||||
|
||||
Parameters:
|
||||
request (Request): The raw ASGI request object
|
||||
body (dict): The parsed request body
|
||||
path (str): The request path
|
||||
method (str): The HTTP method (GET, POST, etc.)
|
||||
path_params (dict[str, str] | None): URL path parameters
|
||||
query_params (dict[str, str] | None): URL query parameters
|
||||
headers (dict[str, bytes] | None): Request headers
|
||||
authorization (str | None): The Authorization header value (e.g. "Bearer <token>")
|
||||
|
||||
???+ example "Examples"
|
||||
Basic authentication with token:
|
||||
```python
|
||||
from langgraph_sdk import Auth
|
||||
|
||||
auth = Auth()
|
||||
|
||||
@auth.authenticate
|
||||
async def authenticate1(authorization: str) -> Auth.types.MinimalUserDict:
|
||||
return await get_user(authorization)
|
||||
```
|
||||
|
||||
Authentication with multiple parameters:
|
||||
```
|
||||
@auth.authenticate
|
||||
async def authenticate2(
|
||||
method: str,
|
||||
path: str,
|
||||
headers: dict[str, bytes]
|
||||
) -> Auth.types.MinimalUserDict:
|
||||
# Custom auth logic using method, path and headers
|
||||
user = verify_request(method, path, headers)
|
||||
return user
|
||||
```
|
||||
|
||||
Accepting the raw ASGI request:
|
||||
```python
|
||||
MY_SECRET = "my-secret-key"
|
||||
@auth.authenticate
|
||||
async def get_current_user(request: Request) -> Auth.types.MinimalUserDict:
|
||||
try:
|
||||
token = (request.headers.get("authorization") or "").split(" ", 1)[1]
|
||||
payload = jwt.decode(token, MY_SECRET, algorithms=["HS256"])
|
||||
except (IndexError, InvalidTokenError):
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail="Invalid token",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(
|
||||
f"https://api.myauth-provider.com/auth/v1/user",
|
||||
headers={"Authorization": f"Bearer {MY_SECRET}"}
|
||||
)
|
||||
if response.status_code != 200:
|
||||
raise HTTPException(status_code=401, detail="User not found")
|
||||
|
||||
user_data = response.json()
|
||||
return {
|
||||
"identity": user_data["id"],
|
||||
"display_name": user_data.get("name"),
|
||||
"permissions": user_data.get("permissions", []),
|
||||
"is_authenticated": True,
|
||||
}
|
||||
```
|
||||
"""
|
||||
|
||||
|
||||
@dataclass
|
||||
class BaseAuthContext:
|
||||
"""Base class for authentication context.
|
||||
|
||||
Provides the fundamental authentication information needed for
|
||||
authorization decisions.
|
||||
"""
|
||||
|
||||
permissions: Sequence[str]
|
||||
"""The permissions granted to the authenticated user."""
|
||||
|
||||
user: BaseUser
|
||||
"""The authenticated user."""
|
||||
|
||||
|
||||
@typing.final
|
||||
@dataclass
|
||||
class AuthContext(BaseAuthContext):
|
||||
"""Complete authentication context with resource and action information.
|
||||
|
||||
Extends BaseAuthContext with specific resource and action being accessed,
|
||||
allowing for fine-grained access control decisions.
|
||||
"""
|
||||
|
||||
resource: typing.Literal["runs", "threads", "crons", "assistants"]
|
||||
"""The resource being accessed."""
|
||||
|
||||
action: typing.Literal["create", "read", "update", "delete", "search", "create_run"]
|
||||
"""The action being performed on the resource."""
|
||||
|
||||
|
||||
class ThreadsCreate(typing.TypedDict, total=False):
|
||||
"""Parameters for creating a new thread.
|
||||
|
||||
???+ example "Examples"
|
||||
```python
|
||||
create_params = {
|
||||
"thread_id": UUID("123e4567-e89b-12d3-a456-426614174000"),
|
||||
"metadata": {"owner": "user123"},
|
||||
"if_exists": "do_nothing"
|
||||
}
|
||||
```
|
||||
"""
|
||||
|
||||
thread_id: UUID
|
||||
"""Unique identifier for the thread."""
|
||||
|
||||
metadata: MetadataInput
|
||||
"""typing.Optional metadata to attach to the thread."""
|
||||
|
||||
if_exists: OnConflictBehavior
|
||||
"""Behavior when a thread with the same ID already exists."""
|
||||
|
||||
|
||||
class ThreadsRead(typing.TypedDict, total=False):
|
||||
"""Parameters for reading thread state or run information.
|
||||
|
||||
This type is used in three contexts:
|
||||
1. Reading thread, thread version, or thread state information: Only thread_id is provided
|
||||
2. Reading run information: Both thread_id and run_id are provided
|
||||
"""
|
||||
|
||||
thread_id: UUID
|
||||
"""Unique identifier for the thread."""
|
||||
|
||||
run_id: typing.Optional[UUID]
|
||||
"""Run ID to filter by. Only used when reading run information within a thread."""
|
||||
|
||||
|
||||
class ThreadsUpdate(typing.TypedDict, total=False):
|
||||
"""Parameters for updating a thread or run.
|
||||
|
||||
Called for updates to a thread, thread version, or run
|
||||
cancellation.
|
||||
"""
|
||||
|
||||
thread_id: UUID
|
||||
"""Unique identifier for the thread."""
|
||||
|
||||
metadata: MetadataInput
|
||||
"""typing.Optional metadata to update."""
|
||||
|
||||
action: typing.Optional[typing.Literal["interrupt", "rollback"]]
|
||||
"""typing.Optional action to perform on the thread."""
|
||||
|
||||
|
||||
class ThreadsDelete(typing.TypedDict, total=False):
|
||||
"""Parameters for deleting a thread.
|
||||
|
||||
Called for deletes to a thread, thread version, or run
|
||||
"""
|
||||
|
||||
thread_id: UUID
|
||||
"""Unique identifier for the thread."""
|
||||
|
||||
run_id: typing.Optional[UUID]
|
||||
"""typing.Optional run ID to filter by."""
|
||||
|
||||
|
||||
class ThreadsSearch(typing.TypedDict, total=False):
|
||||
"""Parameters for searching threads.
|
||||
|
||||
Called for searches to threads or runs.
|
||||
"""
|
||||
|
||||
metadata: MetadataInput
|
||||
"""typing.Optional metadata to filter by."""
|
||||
|
||||
values: MetadataInput
|
||||
"""typing.Optional values to filter by."""
|
||||
|
||||
status: typing.Optional[ThreadStatus]
|
||||
"""typing.Optional status to filter by."""
|
||||
|
||||
limit: int
|
||||
"""Maximum number of results to return."""
|
||||
|
||||
offset: int
|
||||
"""Offset for pagination."""
|
||||
|
||||
thread_id: typing.Optional[UUID]
|
||||
"""typing.Optional thread ID to filter by."""
|
||||
|
||||
|
||||
class RunsCreate(typing.TypedDict, total=False):
|
||||
"""Payload for creating a run.
|
||||
|
||||
???+ example "Examples"
|
||||
```python
|
||||
create_params = {
|
||||
"assistant_id": UUID("123e4567-e89b-12d3-a456-426614174000"),
|
||||
"thread_id": UUID("123e4567-e89b-12d3-a456-426614174001"),
|
||||
"run_id": UUID("123e4567-e89b-12d3-a456-426614174002"),
|
||||
"status": "pending",
|
||||
"metadata": {"owner": "user123"},
|
||||
"prevent_insert_if_inflight": True,
|
||||
"multitask_strategy": "reject",
|
||||
"if_not_exists": "create",
|
||||
"after_seconds": 10,
|
||||
"kwargs": {"key": "value"},
|
||||
"action": "interrupt"
|
||||
}
|
||||
```
|
||||
"""
|
||||
|
||||
assistant_id: typing.Optional[UUID]
|
||||
"""typing.Optional assistant ID to use for this run."""
|
||||
|
||||
thread_id: typing.Optional[UUID]
|
||||
"""typing.Optional thread ID to use for this run."""
|
||||
|
||||
run_id: typing.Optional[UUID]
|
||||
"""typing.Optional run ID to use for this run."""
|
||||
|
||||
status: typing.Optional[RunStatus]
|
||||
"""typing.Optional status for this run."""
|
||||
|
||||
metadata: MetadataInput
|
||||
"""typing.Optional metadata for the run."""
|
||||
|
||||
prevent_insert_if_inflight: bool
|
||||
"""Prevent inserting a new run if one is already in flight."""
|
||||
|
||||
multitask_strategy: MultitaskStrategy
|
||||
"""Multitask strategy for this run."""
|
||||
|
||||
if_not_exists: IfNotExists
|
||||
"""IfNotExists for this run."""
|
||||
|
||||
after_seconds: int
|
||||
"""Number of seconds to wait before creating the run."""
|
||||
|
||||
kwargs: typing.Dict[str, typing.Any]
|
||||
"""Keyword arguments to pass to the run."""
|
||||
|
||||
action: typing.Optional[typing.Literal["interrupt", "rollback"]]
|
||||
"""Action to take if updating an existing run."""
|
||||
|
||||
|
||||
class AssistantsCreate(typing.TypedDict, total=False):
|
||||
"""Payload for creating an assistant.
|
||||
|
||||
???+ example "Examples"
|
||||
```python
|
||||
create_params = {
|
||||
"assistant_id": UUID("123e4567-e89b-12d3-a456-426614174000"),
|
||||
"graph_id": "graph123",
|
||||
"config": {"key": "value"},
|
||||
"metadata": {"owner": "user123"},
|
||||
"if_exists": "do_nothing",
|
||||
"name": "Assistant 1"
|
||||
}
|
||||
```
|
||||
"""
|
||||
|
||||
assistant_id: UUID
|
||||
"""Unique identifier for the assistant."""
|
||||
|
||||
graph_id: str
|
||||
"""Graph ID to use for this assistant."""
|
||||
|
||||
config: typing.Optional[typing.Union[typing.Dict[str, typing.Any], typing.Any]]
|
||||
"""typing.Optional configuration for the assistant."""
|
||||
|
||||
metadata: MetadataInput
|
||||
"""typing.Optional metadata to attach to the assistant."""
|
||||
|
||||
if_exists: OnConflictBehavior
|
||||
"""Behavior when an assistant with the same ID already exists."""
|
||||
|
||||
name: str
|
||||
"""Name of the assistant."""
|
||||
|
||||
|
||||
class AssistantsRead(typing.TypedDict, total=False):
|
||||
"""Payload for reading an assistant.
|
||||
|
||||
???+ example "Examples"
|
||||
```python
|
||||
read_params = {
|
||||
"assistant_id": UUID("123e4567-e89b-12d3-a456-426614174000"),
|
||||
"metadata": {"owner": "user123"}
|
||||
}
|
||||
```
|
||||
"""
|
||||
|
||||
assistant_id: UUID
|
||||
"""Unique identifier for the assistant."""
|
||||
|
||||
metadata: MetadataInput
|
||||
"""typing.Optional metadata to filter by."""
|
||||
|
||||
|
||||
class AssistantsUpdate(typing.TypedDict, total=False):
|
||||
"""Payload for updating an assistant.
|
||||
|
||||
???+ example "Examples"
|
||||
```python
|
||||
update_params = {
|
||||
"assistant_id": UUID("123e4567-e89b-12d3-a456-426614174000"),
|
||||
"graph_id": "graph123",
|
||||
"config": {"key": "value"},
|
||||
"metadata": {"owner": "user123"},
|
||||
"name": "Assistant 1",
|
||||
"version": 1
|
||||
}
|
||||
```
|
||||
"""
|
||||
|
||||
assistant_id: UUID
|
||||
"""Unique identifier for the assistant."""
|
||||
|
||||
graph_id: typing.Optional[str]
|
||||
"""typing.Optional graph ID to update."""
|
||||
|
||||
config: typing.Optional[typing.Union[typing.Dict[str, typing.Any], typing.Any]]
|
||||
"""typing.Optional configuration to update."""
|
||||
|
||||
metadata: MetadataInput
|
||||
"""typing.Optional metadata to update."""
|
||||
|
||||
name: typing.Optional[str]
|
||||
"""typing.Optional name to update."""
|
||||
|
||||
version: typing.Optional[int]
|
||||
"""typing.Optional version to update."""
|
||||
|
||||
|
||||
class AssistantsDelete(typing.TypedDict):
|
||||
"""Payload for deleting an assistant.
|
||||
|
||||
???+ example "Examples"
|
||||
```python
|
||||
delete_params = {
|
||||
"assistant_id": UUID("123e4567-e89b-12d3-a456-426614174000")
|
||||
}
|
||||
```
|
||||
"""
|
||||
|
||||
assistant_id: UUID
|
||||
"""Unique identifier for the assistant."""
|
||||
|
||||
|
||||
class AssistantsSearch(typing.TypedDict):
|
||||
"""Payload for searching assistants.
|
||||
|
||||
???+ example "Examples"
|
||||
```python
|
||||
search_params = {
|
||||
"graph_id": "graph123",
|
||||
"metadata": {"owner": "user123"},
|
||||
"limit": 10,
|
||||
"offset": 0
|
||||
}
|
||||
```
|
||||
"""
|
||||
|
||||
graph_id: typing.Optional[str]
|
||||
"""typing.Optional graph ID to filter by."""
|
||||
|
||||
metadata: MetadataInput
|
||||
"""typing.Optional metadata to filter by."""
|
||||
|
||||
limit: int
|
||||
"""Maximum number of results to return."""
|
||||
|
||||
offset: int
|
||||
"""Offset for pagination."""
|
||||
|
||||
|
||||
class CronsCreate(typing.TypedDict, total=False):
|
||||
"""Payload for creating a cron job.
|
||||
|
||||
???+ example "Examples"
|
||||
```python
|
||||
create_params = {
|
||||
"payload": {"key": "value"},
|
||||
"schedule": "0 0 * * *",
|
||||
"cron_id": UUID("123e4567-e89b-12d3-a456-426614174000"),
|
||||
"thread_id": UUID("123e4567-e89b-12d3-a456-426614174001"),
|
||||
"user_id": "user123",
|
||||
"end_time": datetime(2024, 3, 16, 10, 0, 0)
|
||||
}
|
||||
```
|
||||
"""
|
||||
|
||||
payload: typing.Dict[str, typing.Any]
|
||||
"""Payload for the cron job."""
|
||||
|
||||
schedule: str
|
||||
"""Schedule for the cron job."""
|
||||
|
||||
cron_id: typing.Optional[UUID]
|
||||
"""typing.Optional unique identifier for the cron job."""
|
||||
|
||||
thread_id: typing.Optional[UUID]
|
||||
"""typing.Optional thread ID to use for this cron job."""
|
||||
|
||||
user_id: typing.Optional[str]
|
||||
"""typing.Optional user ID to use for this cron job."""
|
||||
|
||||
end_time: typing.Optional[datetime]
|
||||
"""typing.Optional end time for the cron job."""
|
||||
|
||||
|
||||
class CronsDelete(typing.TypedDict):
|
||||
"""Payload for deleting a cron job.
|
||||
|
||||
???+ example "Examples"
|
||||
```python
|
||||
delete_params = {
|
||||
"cron_id": UUID("123e4567-e89b-12d3-a456-426614174000")
|
||||
}
|
||||
```
|
||||
"""
|
||||
|
||||
cron_id: UUID
|
||||
"""Unique identifier for the cron job."""
|
||||
|
||||
|
||||
class CronsRead(typing.TypedDict):
|
||||
"""Payload for reading a cron job.
|
||||
|
||||
???+ example "Examples"
|
||||
```python
|
||||
read_params = {
|
||||
"cron_id": UUID("123e4567-e89b-12d3-a456-426614174000")
|
||||
}
|
||||
```
|
||||
"""
|
||||
|
||||
cron_id: UUID
|
||||
"""Unique identifier for the cron job."""
|
||||
|
||||
|
||||
class CronsUpdate(typing.TypedDict, total=False):
|
||||
"""Payload for updating a cron job.
|
||||
|
||||
???+ example "Examples"
|
||||
```python
|
||||
update_params = {
|
||||
"cron_id": UUID("123e4567-e89b-12d3-a456-426614174000"),
|
||||
"payload": {"key": "value"},
|
||||
"schedule": "0 0 * * *"
|
||||
}
|
||||
```
|
||||
"""
|
||||
|
||||
cron_id: UUID
|
||||
"""Unique identifier for the cron job."""
|
||||
|
||||
payload: typing.Optional[typing.Dict[str, typing.Any]]
|
||||
"""typing.Optional payload to update."""
|
||||
|
||||
schedule: typing.Optional[str]
|
||||
"""typing.Optional schedule to update."""
|
||||
|
||||
|
||||
class CronsSearch(typing.TypedDict, total=False):
|
||||
"""Payload for searching cron jobs.
|
||||
|
||||
???+ example "Examples"
|
||||
```python
|
||||
search_params = {
|
||||
"assistant_id": UUID("123e4567-e89b-12d3-a456-426614174000"),
|
||||
"thread_id": UUID("123e4567-e89b-12d3-a456-426614174001"),
|
||||
"limit": 10,
|
||||
"offset": 0
|
||||
}
|
||||
```
|
||||
"""
|
||||
|
||||
assistant_id: typing.Optional[UUID]
|
||||
"""typing.Optional assistant ID to filter by."""
|
||||
|
||||
thread_id: typing.Optional[UUID]
|
||||
"""typing.Optional thread ID to filter by."""
|
||||
|
||||
limit: int
|
||||
"""Maximum number of results to return."""
|
||||
|
||||
offset: int
|
||||
"""Offset for pagination."""
|
||||
|
||||
|
||||
class on:
|
||||
"""Namespace for type definitions of different API operations.
|
||||
|
||||
This class organizes type definitions for create, read, update, delete,
|
||||
and search operations across different resources (threads, assistants, crons).
|
||||
|
||||
???+ note "Usage"
|
||||
```python
|
||||
from langgraph_sdk import Auth
|
||||
|
||||
auth = Auth()
|
||||
|
||||
@auth.on
|
||||
def handle_all(params: Auth.on.value):
|
||||
raise Exception("Not authorized")
|
||||
|
||||
@auth.on.threads.create
|
||||
def handle_thread_create(params: Auth.on.threads.create.value):
|
||||
# Handle thread creation
|
||||
pass
|
||||
|
||||
@auth.on.assistants.search
|
||||
def handle_assistant_search(params: Auth.on.assistants.search.value):
|
||||
# Handle assistant search
|
||||
pass
|
||||
```
|
||||
"""
|
||||
|
||||
value = typing.Dict[str, typing.Any]
|
||||
|
||||
class threads:
|
||||
"""Types for thread-related operations."""
|
||||
|
||||
value = typing.Union[
|
||||
ThreadsCreate, ThreadsRead, ThreadsUpdate, ThreadsDelete, ThreadsSearch
|
||||
]
|
||||
|
||||
class create:
|
||||
"""Type for thread creation parameters."""
|
||||
|
||||
value = ThreadsCreate
|
||||
|
||||
class create_run:
|
||||
"""Type for creating or streaming a run."""
|
||||
|
||||
value = RunsCreate
|
||||
|
||||
class read:
|
||||
"""Type for thread read parameters."""
|
||||
|
||||
value = ThreadsRead
|
||||
|
||||
class update:
|
||||
"""Type for thread update parameters."""
|
||||
|
||||
value = ThreadsUpdate
|
||||
|
||||
class delete:
|
||||
"""Type for thread deletion parameters."""
|
||||
|
||||
value = ThreadsDelete
|
||||
|
||||
class search:
|
||||
"""Type for thread search parameters."""
|
||||
|
||||
value = ThreadsSearch
|
||||
|
||||
class assistants:
|
||||
"""Types for assistant-related operations."""
|
||||
|
||||
value = typing.Union[
|
||||
AssistantsCreate,
|
||||
AssistantsRead,
|
||||
AssistantsUpdate,
|
||||
AssistantsDelete,
|
||||
AssistantsSearch,
|
||||
]
|
||||
|
||||
class create:
|
||||
"""Type for assistant creation parameters."""
|
||||
|
||||
value = AssistantsCreate
|
||||
|
||||
class read:
|
||||
"""Type for assistant read parameters."""
|
||||
|
||||
value = AssistantsRead
|
||||
|
||||
class update:
|
||||
"""Type for assistant update parameters."""
|
||||
|
||||
value = AssistantsUpdate
|
||||
|
||||
class delete:
|
||||
"""Type for assistant deletion parameters."""
|
||||
|
||||
value = AssistantsDelete
|
||||
|
||||
class search:
|
||||
"""Type for assistant search parameters."""
|
||||
|
||||
value = AssistantsSearch
|
||||
|
||||
class crons:
|
||||
"""Types for cron-related operations."""
|
||||
|
||||
value = typing.Union[
|
||||
CronsCreate, CronsRead, CronsUpdate, CronsDelete, CronsSearch
|
||||
]
|
||||
|
||||
class create:
|
||||
"""Type for cron creation parameters."""
|
||||
|
||||
value = CronsCreate
|
||||
|
||||
class read:
|
||||
"""Type for cron read parameters."""
|
||||
|
||||
value = CronsRead
|
||||
|
||||
class update:
|
||||
"""Type for cron update parameters."""
|
||||
|
||||
value = CronsUpdate
|
||||
|
||||
class delete:
|
||||
"""Type for cron deletion parameters."""
|
||||
|
||||
value = CronsDelete
|
||||
|
||||
class search:
|
||||
"""Type for cron search parameters."""
|
||||
|
||||
value = CronsSearch
|
||||
|
||||
|
||||
__all__ = [
|
||||
"on",
|
||||
"MetadataInput",
|
||||
"RunsCreate",
|
||||
"ThreadsCreate",
|
||||
"ThreadsRead",
|
||||
"ThreadsUpdate",
|
||||
"ThreadsDelete",
|
||||
"ThreadsSearch",
|
||||
"AssistantsCreate",
|
||||
"AssistantsRead",
|
||||
"AssistantsUpdate",
|
||||
"AssistantsDelete",
|
||||
"AssistantsSearch",
|
||||
]
|
||||
@@ -1318,7 +1318,9 @@ class RunsClient:
|
||||
""" # noqa: E501
|
||||
payload = {
|
||||
"input": input,
|
||||
"command": command,
|
||||
"command": {k: v for k, v in command.items() if v is not None}
|
||||
if command
|
||||
else None,
|
||||
"config": config,
|
||||
"metadata": metadata,
|
||||
"stream_mode": stream_mode,
|
||||
@@ -1503,7 +1505,9 @@ class RunsClient:
|
||||
""" # noqa: E501
|
||||
payload = {
|
||||
"input": input,
|
||||
"command": command,
|
||||
"command": {k: v for k, v in command.items() if v is not None}
|
||||
if command
|
||||
else None,
|
||||
"stream_mode": stream_mode,
|
||||
"stream_subgraphs": stream_subgraphs,
|
||||
"config": config,
|
||||
@@ -1672,7 +1676,9 @@ class RunsClient:
|
||||
""" # noqa: E501
|
||||
payload = {
|
||||
"input": input,
|
||||
"command": command,
|
||||
"command": {k: v for k, v in command.items() if v is not None}
|
||||
if command
|
||||
else None,
|
||||
"config": config,
|
||||
"metadata": metadata,
|
||||
"assistant_id": assistant_id,
|
||||
@@ -3477,7 +3483,9 @@ class SyncRunsClient:
|
||||
""" # noqa: E501
|
||||
payload = {
|
||||
"input": input,
|
||||
"command": command,
|
||||
"command": {k: v for k, v in command.items() if v is not None}
|
||||
if command
|
||||
else None,
|
||||
"config": config,
|
||||
"metadata": metadata,
|
||||
"stream_mode": stream_mode,
|
||||
@@ -3662,7 +3670,9 @@ class SyncRunsClient:
|
||||
""" # noqa: E501
|
||||
payload = {
|
||||
"input": input,
|
||||
"command": command,
|
||||
"command": {k: v for k, v in command.items() if v is not None}
|
||||
if command
|
||||
else None,
|
||||
"stream_mode": stream_mode,
|
||||
"stream_subgraphs": stream_subgraphs,
|
||||
"config": config,
|
||||
@@ -3828,7 +3838,9 @@ class SyncRunsClient:
|
||||
""" # noqa: E501
|
||||
payload = {
|
||||
"input": input,
|
||||
"command": command,
|
||||
"command": {k: v for k, v in command.items() if v is not None}
|
||||
if command
|
||||
else None,
|
||||
"config": config,
|
||||
"metadata": metadata,
|
||||
"assistant_id": assistant_id,
|
||||
|
||||
@@ -1,47 +1,47 @@
|
||||
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.3.0"
|
||||
version = "4.7.0"
|
||||
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"},
|
||||
{file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"},
|
||||
{file = "anyio-4.7.0-py3-none-any.whl", hash = "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352"},
|
||||
{file = "anyio-4.7.0.tar.gz", hash = "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""}
|
||||
idna = ">=2.8"
|
||||
sniffio = ">=1.1"
|
||||
typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""}
|
||||
typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""}
|
||||
|
||||
[package.extras]
|
||||
doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
|
||||
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
|
||||
trio = ["trio (>=0.23)"]
|
||||
doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"]
|
||||
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"]
|
||||
trio = ["trio (>=0.26.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2024.7.4"
|
||||
version = "2024.8.30"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"},
|
||||
{file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"},
|
||||
{file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"},
|
||||
{file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codespell"
|
||||
version = "2.2.6"
|
||||
version = "2.3.0"
|
||||
description = "Codespell"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "codespell-2.2.6-py3-none-any.whl", hash = "sha256:9ee9a3e5df0990604013ac2a9f22fa8e57669c827124a2e961fe8a1da4cacc07"},
|
||||
{file = "codespell-2.2.6.tar.gz", hash = "sha256:a8c65d8eb3faa03deabab6b3bbe798bea72e1799c7e9e955d57eca4096abcff9"},
|
||||
{file = "codespell-2.3.0-py3-none-any.whl", hash = "sha256:a9c7cef2501c9cfede2110fd6d4e5e62296920efe9abfb84648df866e47f58d1"},
|
||||
{file = "codespell-2.3.0.tar.gz", hash = "sha256:360c7d10f75e65f67bad720af7007e1060a5d395670ec11a7ed1fed9dd17471f"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -73,13 +73,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.2.1"
|
||||
version = "1.2.2"
|
||||
description = "Backport of PEP 654 (exception groups)"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"},
|
||||
{file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"},
|
||||
{file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"},
|
||||
{file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -98,13 +98,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "1.0.5"
|
||||
version = "1.0.7"
|
||||
description = "A minimal low-level HTTP client."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"},
|
||||
{file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"},
|
||||
{file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"},
|
||||
{file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -115,17 +115,17 @@ h11 = ">=0.13,<0.15"
|
||||
asyncio = ["anyio (>=4.0,<5.0)"]
|
||||
http2 = ["h2 (>=3,<5)"]
|
||||
socks = ["socksio (==1.*)"]
|
||||
trio = ["trio (>=0.22.0,<0.26.0)"]
|
||||
trio = ["trio (>=0.22.0,<1.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.27.0"
|
||||
version = "0.28.1"
|
||||
description = "The next generation HTTP client."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"},
|
||||
{file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"},
|
||||
{file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"},
|
||||
{file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -133,25 +133,28 @@ anyio = "*"
|
||||
certifi = "*"
|
||||
httpcore = "==1.*"
|
||||
idna = "*"
|
||||
sniffio = "*"
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotli", "brotlicffi"]
|
||||
cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
|
||||
http2 = ["h2 (>=3,<5)"]
|
||||
socks = ["socksio (==1.*)"]
|
||||
zstd = ["zstandard (>=0.18.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.7"
|
||||
version = "3.10"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
|
||||
{file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
|
||||
{file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
|
||||
{file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.0.0"
|
||||
@@ -165,47 +168,53 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "1.10.0"
|
||||
version = "1.13.0"
|
||||
description = "Optional static typing for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "mypy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da1cbf08fb3b851ab3b9523a884c232774008267b1f83371ace57f412fe308c2"},
|
||||
{file = "mypy-1.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:12b6bfc1b1a66095ab413160a6e520e1dc076a28f3e22f7fb25ba3b000b4ef99"},
|
||||
{file = "mypy-1.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e36fb078cce9904c7989b9693e41cb9711e0600139ce3970c6ef814b6ebc2b2"},
|
||||
{file = "mypy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2b0695d605ddcd3eb2f736cd8b4e388288c21e7de85001e9f85df9187f2b50f9"},
|
||||
{file = "mypy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:cd777b780312ddb135bceb9bc8722a73ec95e042f911cc279e2ec3c667076051"},
|
||||
{file = "mypy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3be66771aa5c97602f382230165b856c231d1277c511c9a8dd058be4784472e1"},
|
||||
{file = "mypy-1.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8b2cbaca148d0754a54d44121b5825ae71868c7592a53b7292eeb0f3fdae95ee"},
|
||||
{file = "mypy-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ec404a7cbe9fc0e92cb0e67f55ce0c025014e26d33e54d9e506a0f2d07fe5de"},
|
||||
{file = "mypy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e22e1527dc3d4aa94311d246b59e47f6455b8729f4968765ac1eacf9a4760bc7"},
|
||||
{file = "mypy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:a87dbfa85971e8d59c9cc1fcf534efe664d8949e4c0b6b44e8ca548e746a8d53"},
|
||||
{file = "mypy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a781f6ad4bab20eef8b65174a57e5203f4be627b46291f4589879bf4e257b97b"},
|
||||
{file = "mypy-1.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b808e12113505b97d9023b0b5e0c0705a90571c6feefc6f215c1df9381256e30"},
|
||||
{file = "mypy-1.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f55583b12156c399dce2df7d16f8a5095291354f1e839c252ec6c0611e86e2e"},
|
||||
{file = "mypy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cf18f9d0efa1b16478c4c129eabec36148032575391095f73cae2e722fcf9d5"},
|
||||
{file = "mypy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:bc6ac273b23c6b82da3bb25f4136c4fd42665f17f2cd850771cb600bdd2ebeda"},
|
||||
{file = "mypy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9fd50226364cd2737351c79807775136b0abe084433b55b2e29181a4c3c878c0"},
|
||||
{file = "mypy-1.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f90cff89eea89273727d8783fef5d4a934be2fdca11b47def50cf5d311aff727"},
|
||||
{file = "mypy-1.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fcfc70599efde5c67862a07a1aaf50e55bce629ace26bb19dc17cece5dd31ca4"},
|
||||
{file = "mypy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:075cbf81f3e134eadaf247de187bd604748171d6b79736fa9b6c9685b4083061"},
|
||||
{file = "mypy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:3f298531bca95ff615b6e9f2fc0333aae27fa48052903a0ac90215021cdcfa4f"},
|
||||
{file = "mypy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa7ef5244615a2523b56c034becde4e9e3f9b034854c93639adb667ec9ec2976"},
|
||||
{file = "mypy-1.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3236a4c8f535a0631f85f5fcdffba71c7feeef76a6002fcba7c1a8e57c8be1ec"},
|
||||
{file = "mypy-1.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a2b5cdbb5dd35aa08ea9114436e0d79aceb2f38e32c21684dcf8e24e1e92821"},
|
||||
{file = "mypy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92f93b21c0fe73dc00abf91022234c79d793318b8a96faac147cd579c1671746"},
|
||||
{file = "mypy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:28d0e038361b45f099cc086d9dd99c15ff14d0188f44ac883010e172ce86c38a"},
|
||||
{file = "mypy-1.10.0-py3-none-any.whl", hash = "sha256:f8c083976eb530019175aabadb60921e73b4f45736760826aa1689dda8208aee"},
|
||||
{file = "mypy-1.10.0.tar.gz", hash = "sha256:3d087fcbec056c4ee34974da493a826ce316947485cef3901f511848e687c131"},
|
||||
{file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"},
|
||||
{file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"},
|
||||
{file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"},
|
||||
{file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"},
|
||||
{file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"},
|
||||
{file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"},
|
||||
{file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"},
|
||||
{file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"},
|
||||
{file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"},
|
||||
{file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"},
|
||||
{file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"},
|
||||
{file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"},
|
||||
{file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"},
|
||||
{file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"},
|
||||
{file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"},
|
||||
{file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"},
|
||||
{file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"},
|
||||
{file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"},
|
||||
{file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"},
|
||||
{file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"},
|
||||
{file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"},
|
||||
{file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"},
|
||||
{file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"},
|
||||
{file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"},
|
||||
{file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"},
|
||||
{file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"},
|
||||
{file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"},
|
||||
{file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"},
|
||||
{file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"},
|
||||
{file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"},
|
||||
{file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"},
|
||||
{file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
mypy-extensions = ">=1.0.0"
|
||||
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
||||
typing-extensions = ">=4.1.0"
|
||||
typing-extensions = ">=4.6.0"
|
||||
|
||||
[package.extras]
|
||||
dmypy = ["psutil (>=4.0)"]
|
||||
faster-cache = ["orjson"]
|
||||
install-types = ["pip"]
|
||||
mypyc = ["setuptools (>=50)"]
|
||||
reports = ["lxml"]
|
||||
@@ -223,68 +232,97 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "orjson"
|
||||
version = "3.10.3"
|
||||
version = "3.10.12"
|
||||
description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "orjson-3.10.3-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9fb6c3f9f5490a3eb4ddd46fc1b6eadb0d6fc16fb3f07320149c3286a1409dd8"},
|
||||
{file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:252124b198662eee80428f1af8c63f7ff077c88723fe206a25df8dc57a57b1fa"},
|
||||
{file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9f3e87733823089a338ef9bbf363ef4de45e5c599a9bf50a7a9b82e86d0228da"},
|
||||
{file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8334c0d87103bb9fbbe59b78129f1f40d1d1e8355bbed2ca71853af15fa4ed3"},
|
||||
{file = "orjson-3.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1952c03439e4dce23482ac846e7961f9d4ec62086eb98ae76d97bd41d72644d7"},
|
||||
{file = "orjson-3.10.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c0403ed9c706dcd2809f1600ed18f4aae50be263bd7112e54b50e2c2bc3ebd6d"},
|
||||
{file = "orjson-3.10.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:382e52aa4270a037d41f325e7d1dfa395b7de0c367800b6f337d8157367bf3a7"},
|
||||
{file = "orjson-3.10.3-cp310-none-win32.whl", hash = "sha256:be2aab54313752c04f2cbaab4515291ef5af8c2256ce22abc007f89f42f49109"},
|
||||
{file = "orjson-3.10.3-cp310-none-win_amd64.whl", hash = "sha256:416b195f78ae461601893f482287cee1e3059ec49b4f99479aedf22a20b1098b"},
|
||||
{file = "orjson-3.10.3-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:73100d9abbbe730331f2242c1fc0bcb46a3ea3b4ae3348847e5a141265479700"},
|
||||
{file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:544a12eee96e3ab828dbfcb4d5a0023aa971b27143a1d35dc214c176fdfb29b3"},
|
||||
{file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:520de5e2ef0b4ae546bea25129d6c7c74edb43fc6cf5213f511a927f2b28148b"},
|
||||
{file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ccaa0a401fc02e8828a5bedfd80f8cd389d24f65e5ca3954d72c6582495b4bcf"},
|
||||
{file = "orjson-3.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7bc9e8bc11bac40f905640acd41cbeaa87209e7e1f57ade386da658092dc16"},
|
||||
{file = "orjson-3.10.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3582b34b70543a1ed6944aca75e219e1192661a63da4d039d088a09c67543b08"},
|
||||
{file = "orjson-3.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1c23dfa91481de880890d17aa7b91d586a4746a4c2aa9a145bebdbaf233768d5"},
|
||||
{file = "orjson-3.10.3-cp311-none-win32.whl", hash = "sha256:1770e2a0eae728b050705206d84eda8b074b65ee835e7f85c919f5705b006c9b"},
|
||||
{file = "orjson-3.10.3-cp311-none-win_amd64.whl", hash = "sha256:93433b3c1f852660eb5abdc1f4dd0ced2be031ba30900433223b28ee0140cde5"},
|
||||
{file = "orjson-3.10.3-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a39aa73e53bec8d410875683bfa3a8edf61e5a1c7bb4014f65f81d36467ea098"},
|
||||
{file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0943a96b3fa09bee1afdfccc2cb236c9c64715afa375b2af296c73d91c23eab2"},
|
||||
{file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e852baafceff8da3c9defae29414cc8513a1586ad93e45f27b89a639c68e8176"},
|
||||
{file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18566beb5acd76f3769c1d1a7ec06cdb81edc4d55d2765fb677e3eaa10fa99e0"},
|
||||
{file = "orjson-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bd2218d5a3aa43060efe649ec564ebedec8ce6ae0a43654b81376216d5ebd42"},
|
||||
{file = "orjson-3.10.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cf20465e74c6e17a104ecf01bf8cd3b7b252565b4ccee4548f18b012ff2f8069"},
|
||||
{file = "orjson-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba7f67aa7f983c4345eeda16054a4677289011a478ca947cd69c0a86ea45e534"},
|
||||
{file = "orjson-3.10.3-cp312-none-win32.whl", hash = "sha256:17e0713fc159abc261eea0f4feda611d32eabc35708b74bef6ad44f6c78d5ea0"},
|
||||
{file = "orjson-3.10.3-cp312-none-win_amd64.whl", hash = "sha256:4c895383b1ec42b017dd2c75ae8a5b862fc489006afde06f14afbdd0309b2af0"},
|
||||
{file = "orjson-3.10.3-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:be2719e5041e9fb76c8c2c06b9600fe8e8584e6980061ff88dcbc2691a16d20d"},
|
||||
{file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0175a5798bdc878956099f5c54b9837cb62cfbf5d0b86ba6d77e43861bcec2"},
|
||||
{file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:978be58a68ade24f1af7758626806e13cff7748a677faf95fbb298359aa1e20d"},
|
||||
{file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16bda83b5c61586f6f788333d3cf3ed19015e3b9019188c56983b5a299210eb5"},
|
||||
{file = "orjson-3.10.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ad1f26bea425041e0a1adad34630c4825a9e3adec49079b1fb6ac8d36f8b754"},
|
||||
{file = "orjson-3.10.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9e253498bee561fe85d6325ba55ff2ff08fb5e7184cd6a4d7754133bd19c9195"},
|
||||
{file = "orjson-3.10.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0a62f9968bab8a676a164263e485f30a0b748255ee2f4ae49a0224be95f4532b"},
|
||||
{file = "orjson-3.10.3-cp38-none-win32.whl", hash = "sha256:8d0b84403d287d4bfa9bf7d1dc298d5c1c5d9f444f3737929a66f2fe4fb8f134"},
|
||||
{file = "orjson-3.10.3-cp38-none-win_amd64.whl", hash = "sha256:8bc7a4df90da5d535e18157220d7915780d07198b54f4de0110eca6b6c11e290"},
|
||||
{file = "orjson-3.10.3-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9059d15c30e675a58fdcd6f95465c1522b8426e092de9fff20edebfdc15e1cb0"},
|
||||
{file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d40c7f7938c9c2b934b297412c067936d0b54e4b8ab916fd1a9eb8f54c02294"},
|
||||
{file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4a654ec1de8fdaae1d80d55cee65893cb06494e124681ab335218be6a0691e7"},
|
||||
{file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:831c6ef73f9aa53c5f40ae8f949ff7681b38eaddb6904aab89dca4d85099cb78"},
|
||||
{file = "orjson-3.10.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99b880d7e34542db89f48d14ddecbd26f06838b12427d5a25d71baceb5ba119d"},
|
||||
{file = "orjson-3.10.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2e5e176c994ce4bd434d7aafb9ecc893c15f347d3d2bbd8e7ce0b63071c52e25"},
|
||||
{file = "orjson-3.10.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b69a58a37dab856491bf2d3bbf259775fdce262b727f96aafbda359cb1d114d8"},
|
||||
{file = "orjson-3.10.3-cp39-none-win32.whl", hash = "sha256:b8d4d1a6868cde356f1402c8faeb50d62cee765a1f7ffcfd6de732ab0581e063"},
|
||||
{file = "orjson-3.10.3-cp39-none-win_amd64.whl", hash = "sha256:5102f50c5fc46d94f2033fe00d392588564378260d64377aec702f21a7a22912"},
|
||||
{file = "orjson-3.10.3.tar.gz", hash = "sha256:2b166507acae7ba2f7c315dcf185a9111ad5e992ac81f2d507aac39193c2c818"},
|
||||
{file = "orjson-3.10.12-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ece01a7ec71d9940cc654c482907a6b65df27251255097629d0dea781f255c6d"},
|
||||
{file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c34ec9aebc04f11f4b978dd6caf697a2df2dd9b47d35aa4cc606cabcb9df69d7"},
|
||||
{file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd6ec8658da3480939c79b9e9e27e0db31dffcd4ba69c334e98c9976ac29140e"},
|
||||
{file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f17e6baf4cf01534c9de8a16c0c611f3d94925d1701bf5f4aff17003677d8ced"},
|
||||
{file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6402ebb74a14ef96f94a868569f5dccf70d791de49feb73180eb3c6fda2ade56"},
|
||||
{file = "orjson-3.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0000758ae7c7853e0a4a6063f534c61656ebff644391e1f81698c1b2d2fc8cd2"},
|
||||
{file = "orjson-3.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:888442dcee99fd1e5bd37a4abb94930915ca6af4db50e23e746cdf4d1e63db13"},
|
||||
{file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c1f7a3ce79246aa0e92f5458d86c54f257fb5dfdc14a192651ba7ec2c00f8a05"},
|
||||
{file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:802a3935f45605c66fb4a586488a38af63cb37aaad1c1d94c982c40dcc452e85"},
|
||||
{file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1da1ef0113a2be19bb6c557fb0ec2d79c92ebd2fed4cfb1b26bab93f021fb885"},
|
||||
{file = "orjson-3.10.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a3273e99f367f137d5b3fecb5e9f45bcdbfac2a8b2f32fbc72129bbd48789c2"},
|
||||
{file = "orjson-3.10.12-cp310-none-win32.whl", hash = "sha256:475661bf249fd7907d9b0a2a2421b4e684355a77ceef85b8352439a9163418c3"},
|
||||
{file = "orjson-3.10.12-cp310-none-win_amd64.whl", hash = "sha256:87251dc1fb2b9e5ab91ce65d8f4caf21910d99ba8fb24b49fd0c118b2362d509"},
|
||||
{file = "orjson-3.10.12-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a734c62efa42e7df94926d70fe7d37621c783dea9f707a98cdea796964d4cf74"},
|
||||
{file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:750f8b27259d3409eda8350c2919a58b0cfcd2054ddc1bd317a643afc646ef23"},
|
||||
{file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb52c22bfffe2857e7aa13b4622afd0dd9d16ea7cc65fd2bf318d3223b1b6252"},
|
||||
{file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:440d9a337ac8c199ff8251e100c62e9488924c92852362cd27af0e67308c16ef"},
|
||||
{file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9e15c06491c69997dfa067369baab3bf094ecb74be9912bdc4339972323f252"},
|
||||
{file = "orjson-3.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:362d204ad4b0b8724cf370d0cd917bb2dc913c394030da748a3bb632445ce7c4"},
|
||||
{file = "orjson-3.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b57cbb4031153db37b41622eac67329c7810e5f480fda4cfd30542186f006ae"},
|
||||
{file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:165c89b53ef03ce0d7c59ca5c82fa65fe13ddf52eeb22e859e58c237d4e33b9b"},
|
||||
{file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5dee91b8dfd54557c1a1596eb90bcd47dbcd26b0baaed919e6861f076583e9da"},
|
||||
{file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77a4e1cfb72de6f905bdff061172adfb3caf7a4578ebf481d8f0530879476c07"},
|
||||
{file = "orjson-3.10.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:038d42c7bc0606443459b8fe2d1f121db474c49067d8d14c6a075bbea8bf14dd"},
|
||||
{file = "orjson-3.10.12-cp311-none-win32.whl", hash = "sha256:03b553c02ab39bed249bedd4abe37b2118324d1674e639b33fab3d1dafdf4d79"},
|
||||
{file = "orjson-3.10.12-cp311-none-win_amd64.whl", hash = "sha256:8b8713b9e46a45b2af6b96f559bfb13b1e02006f4242c156cbadef27800a55a8"},
|
||||
{file = "orjson-3.10.12-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:53206d72eb656ca5ac7d3a7141e83c5bbd3ac30d5eccfe019409177a57634b0d"},
|
||||
{file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac8010afc2150d417ebda810e8df08dd3f544e0dd2acab5370cfa6bcc0662f8f"},
|
||||
{file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed459b46012ae950dd2e17150e838ab08215421487371fa79d0eced8d1461d70"},
|
||||
{file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8dcb9673f108a93c1b52bfc51b0af422c2d08d4fc710ce9c839faad25020bb69"},
|
||||
{file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22a51ae77680c5c4652ebc63a83d5255ac7d65582891d9424b566fb3b5375ee9"},
|
||||
{file = "orjson-3.10.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:910fdf2ac0637b9a77d1aad65f803bac414f0b06f720073438a7bd8906298192"},
|
||||
{file = "orjson-3.10.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:24ce85f7100160936bc2116c09d1a8492639418633119a2224114f67f63a4559"},
|
||||
{file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a76ba5fc8dd9c913640292df27bff80a685bed3a3c990d59aa6ce24c352f8fc"},
|
||||
{file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ff70ef093895fd53f4055ca75f93f047e088d1430888ca1229393a7c0521100f"},
|
||||
{file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f4244b7018b5753ecd10a6d324ec1f347da130c953a9c88432c7fbc8875d13be"},
|
||||
{file = "orjson-3.10.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:16135ccca03445f37921fa4b585cff9a58aa8d81ebcb27622e69bfadd220b32c"},
|
||||
{file = "orjson-3.10.12-cp312-none-win32.whl", hash = "sha256:2d879c81172d583e34153d524fcba5d4adafbab8349a7b9f16ae511c2cee8708"},
|
||||
{file = "orjson-3.10.12-cp312-none-win_amd64.whl", hash = "sha256:fc23f691fa0f5c140576b8c365bc942d577d861a9ee1142e4db468e4e17094fb"},
|
||||
{file = "orjson-3.10.12-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:47962841b2a8aa9a258b377f5188db31ba49af47d4003a32f55d6f8b19006543"},
|
||||
{file = "orjson-3.10.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6334730e2532e77b6054e87ca84f3072bee308a45a452ea0bffbbbc40a67e296"},
|
||||
{file = "orjson-3.10.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:accfe93f42713c899fdac2747e8d0d5c659592df2792888c6c5f829472e4f85e"},
|
||||
{file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a7974c490c014c48810d1dede6c754c3cc46598da758c25ca3b4001ac45b703f"},
|
||||
{file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3f250ce7727b0b2682f834a3facff88e310f52f07a5dcfd852d99637d386e79e"},
|
||||
{file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f31422ff9486ae484f10ffc51b5ab2a60359e92d0716fcce1b3593d7bb8a9af6"},
|
||||
{file = "orjson-3.10.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5f29c5d282bb2d577c2a6bbde88d8fdcc4919c593f806aac50133f01b733846e"},
|
||||
{file = "orjson-3.10.12-cp313-none-win32.whl", hash = "sha256:f45653775f38f63dc0e6cd4f14323984c3149c05d6007b58cb154dd080ddc0dc"},
|
||||
{file = "orjson-3.10.12-cp313-none-win_amd64.whl", hash = "sha256:229994d0c376d5bdc91d92b3c9e6be2f1fbabd4cc1b59daae1443a46ee5e9825"},
|
||||
{file = "orjson-3.10.12-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7d69af5b54617a5fac5c8e5ed0859eb798e2ce8913262eb522590239db6c6763"},
|
||||
{file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ed119ea7d2953365724a7059231a44830eb6bbb0cfead33fcbc562f5fd8f935"},
|
||||
{file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c5fc1238ef197e7cad5c91415f524aaa51e004be5a9b35a1b8a84ade196f73f"},
|
||||
{file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43509843990439b05f848539d6f6198d4ac86ff01dd024b2f9a795c0daeeab60"},
|
||||
{file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f72e27a62041cfb37a3de512247ece9f240a561e6c8662276beaf4d53d406db4"},
|
||||
{file = "orjson-3.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a904f9572092bb6742ab7c16c623f0cdccbad9eeb2d14d4aa06284867bddd31"},
|
||||
{file = "orjson-3.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:855c0833999ed5dc62f64552db26f9be767434917d8348d77bacaab84f787d7b"},
|
||||
{file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:897830244e2320f6184699f598df7fb9db9f5087d6f3f03666ae89d607e4f8ed"},
|
||||
{file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:0b32652eaa4a7539f6f04abc6243619c56f8530c53bf9b023e1269df5f7816dd"},
|
||||
{file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:36b4aa31e0f6a1aeeb6f8377769ca5d125db000f05c20e54163aef1d3fe8e833"},
|
||||
{file = "orjson-3.10.12-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5535163054d6cbf2796f93e4f0dbc800f61914c0e3c4ed8499cf6ece22b4a3da"},
|
||||
{file = "orjson-3.10.12-cp38-none-win32.whl", hash = "sha256:90a5551f6f5a5fa07010bf3d0b4ca2de21adafbbc0af6cb700b63cd767266cb9"},
|
||||
{file = "orjson-3.10.12-cp38-none-win_amd64.whl", hash = "sha256:703a2fb35a06cdd45adf5d733cf613cbc0cb3ae57643472b16bc22d325b5fb6c"},
|
||||
{file = "orjson-3.10.12-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f29de3ef71a42a5822765def1febfb36e0859d33abf5c2ad240acad5c6a1b78d"},
|
||||
{file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de365a42acc65d74953f05e4772c974dad6c51cfc13c3240899f534d611be967"},
|
||||
{file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91a5a0158648a67ff0004cb0df5df7dcc55bfc9ca154d9c01597a23ad54c8d0c"},
|
||||
{file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c47ce6b8d90fe9646a25b6fb52284a14ff215c9595914af63a5933a49972ce36"},
|
||||
{file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0eee4c2c5bfb5c1b47a5db80d2ac7aaa7e938956ae88089f098aff2c0f35d5d8"},
|
||||
{file = "orjson-3.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35d3081bbe8b86587eb5c98a73b97f13d8f9fea685cf91a579beddacc0d10566"},
|
||||
{file = "orjson-3.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c23a6e90383884068bc2dba83d5222c9fcc3b99a0ed2411d38150734236755"},
|
||||
{file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5472be7dc3269b4b52acba1433dac239215366f89dc1d8d0e64029abac4e714e"},
|
||||
{file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:7319cda750fca96ae5973efb31b17d97a5c5225ae0bc79bf5bf84df9e1ec2ab6"},
|
||||
{file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:74d5ca5a255bf20b8def6a2b96b1e18ad37b4a122d59b154c458ee9494377f80"},
|
||||
{file = "orjson-3.10.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ff31d22ecc5fb85ef62c7d4afe8301d10c558d00dd24274d4bbe464380d3cd69"},
|
||||
{file = "orjson-3.10.12-cp39-none-win32.whl", hash = "sha256:c22c3ea6fba91d84fcb4cda30e64aff548fcf0c44c876e681f47d61d24b12e6b"},
|
||||
{file = "orjson-3.10.12-cp39-none-win_amd64.whl", hash = "sha256:be604f60d45ace6b0b33dd990a66b4526f1a7a186ac411c942674625456ca548"},
|
||||
{file = "orjson-3.10.12.tar.gz", hash = "sha256:0a78bbda3aea0f9f079057ee1ee8a1ecf790d4f1af88dd67493c6b8ee52506ff"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "24.0"
|
||||
version = "24.2"
|
||||
description = "Core utilities for Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"},
|
||||
{file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"},
|
||||
{file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
|
||||
{file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -377,29 +415,29 @@ watchdog = ">=0.6.0"
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.6.2"
|
||||
version = "0.6.9"
|
||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "ruff-0.6.2-py3-none-linux_armv6l.whl", hash = "sha256:5c8cbc6252deb3ea840ad6a20b0f8583caab0c5ef4f9cca21adc5a92b8f79f3c"},
|
||||
{file = "ruff-0.6.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:17002fe241e76544448a8e1e6118abecbe8cd10cf68fde635dad480dba594570"},
|
||||
{file = "ruff-0.6.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3dbeac76ed13456f8158b8f4fe087bf87882e645c8e8b606dd17b0b66c2c1158"},
|
||||
{file = "ruff-0.6.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:094600ee88cda325988d3f54e3588c46de5c18dae09d683ace278b11f9d4d534"},
|
||||
{file = "ruff-0.6.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:316d418fe258c036ba05fbf7dfc1f7d3d4096db63431546163b472285668132b"},
|
||||
{file = "ruff-0.6.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d72b8b3abf8a2d51b7b9944a41307d2f442558ccb3859bbd87e6ae9be1694a5d"},
|
||||
{file = "ruff-0.6.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:2aed7e243be68487aa8982e91c6e260982d00da3f38955873aecd5a9204b1d66"},
|
||||
{file = "ruff-0.6.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d371f7fc9cec83497fe7cf5eaf5b76e22a8efce463de5f775a1826197feb9df8"},
|
||||
{file = "ruff-0.6.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8f310d63af08f583363dfb844ba8f9417b558199c58a5999215082036d795a1"},
|
||||
{file = "ruff-0.6.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7db6880c53c56addb8638fe444818183385ec85eeada1d48fc5abe045301b2f1"},
|
||||
{file = "ruff-0.6.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1175d39faadd9a50718f478d23bfc1d4da5743f1ab56af81a2b6caf0a2394f23"},
|
||||
{file = "ruff-0.6.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5b939f9c86d51635fe486585389f54582f0d65b8238e08c327c1534844b3bb9a"},
|
||||
{file = "ruff-0.6.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d0d62ca91219f906caf9b187dea50d17353f15ec9bb15aae4a606cd697b49b4c"},
|
||||
{file = "ruff-0.6.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7438a7288f9d67ed3c8ce4d059e67f7ed65e9fe3aa2ab6f5b4b3610e57e3cb56"},
|
||||
{file = "ruff-0.6.2-py3-none-win32.whl", hash = "sha256:279d5f7d86696df5f9549b56b9b6a7f6c72961b619022b5b7999b15db392a4da"},
|
||||
{file = "ruff-0.6.2-py3-none-win_amd64.whl", hash = "sha256:d9f3469c7dd43cd22eb1c3fc16926fb8258d50cb1b216658a07be95dd117b0f2"},
|
||||
{file = "ruff-0.6.2-py3-none-win_arm64.whl", hash = "sha256:f28fcd2cd0e02bdf739297516d5643a945cc7caf09bd9bcb4d932540a5ea4fa9"},
|
||||
{file = "ruff-0.6.2.tar.gz", hash = "sha256:239ee6beb9e91feb8e0ec384204a763f36cb53fb895a1a364618c6abb076b3be"},
|
||||
{file = "ruff-0.6.9-py3-none-linux_armv6l.whl", hash = "sha256:064df58d84ccc0ac0fcd63bc3090b251d90e2a372558c0f057c3f75ed73e1ccd"},
|
||||
{file = "ruff-0.6.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:140d4b5c9f5fc7a7b074908a78ab8d384dd7f6510402267bc76c37195c02a7ec"},
|
||||
{file = "ruff-0.6.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53fd8ca5e82bdee8da7f506d7b03a261f24cd43d090ea9db9a1dc59d9313914c"},
|
||||
{file = "ruff-0.6.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645d7d8761f915e48a00d4ecc3686969761df69fb561dd914a773c1a8266e14e"},
|
||||
{file = "ruff-0.6.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eae02b700763e3847595b9d2891488989cac00214da7f845f4bcf2989007d577"},
|
||||
{file = "ruff-0.6.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d5ccc9e58112441de8ad4b29dcb7a86dc25c5f770e3c06a9d57e0e5eba48829"},
|
||||
{file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:417b81aa1c9b60b2f8edc463c58363075412866ae4e2b9ab0f690dc1e87ac1b5"},
|
||||
{file = "ruff-0.6.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c866b631f5fbce896a74a6e4383407ba7507b815ccc52bcedabb6810fdb3ef7"},
|
||||
{file = "ruff-0.6.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b118afbb3202f5911486ad52da86d1d52305b59e7ef2031cea3425142b97d6f"},
|
||||
{file = "ruff-0.6.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67267654edc23c97335586774790cde402fb6bbdb3c2314f1fc087dee320bfa"},
|
||||
{file = "ruff-0.6.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3ef0cc774b00fec123f635ce5c547dac263f6ee9fb9cc83437c5904183b55ceb"},
|
||||
{file = "ruff-0.6.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:12edd2af0c60fa61ff31cefb90aef4288ac4d372b4962c2864aeea3a1a2460c0"},
|
||||
{file = "ruff-0.6.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:55bb01caeaf3a60b2b2bba07308a02fca6ab56233302406ed5245180a05c5625"},
|
||||
{file = "ruff-0.6.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:925d26471fa24b0ce5a6cdfab1bb526fb4159952385f386bdcc643813d472039"},
|
||||
{file = "ruff-0.6.9-py3-none-win32.whl", hash = "sha256:eb61ec9bdb2506cffd492e05ac40e5bc6284873aceb605503d8494180d6fc84d"},
|
||||
{file = "ruff-0.6.9-py3-none-win_amd64.whl", hash = "sha256:785d31851c1ae91f45b3d8fe23b8ae4b5170089021fbb42402d811135f0b7117"},
|
||||
{file = "ruff-0.6.9-py3-none-win_arm64.whl", hash = "sha256:a9641e31476d601f83cd602608739a0840e348bda93fec9f1ee816f8b6798b93"},
|
||||
{file = "ruff-0.6.9.tar.gz", hash = "sha256:b076ef717a8e5bc819514ee1d602bbdca5b4420ae13a9cf61a0c0a4f53a2baa2"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -415,62 +453,93 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "tomli"
|
||||
version = "2.0.1"
|
||||
version = "2.2.1"
|
||||
description = "A lil' TOML parser"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
|
||||
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"},
|
||||
{file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"},
|
||||
{file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"},
|
||||
{file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"},
|
||||
{file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"},
|
||||
{file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.11.0"
|
||||
version = "4.12.2"
|
||||
description = "Backported and Experimental Type Hints for Python 3.8+"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"},
|
||||
{file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"},
|
||||
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
|
||||
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "watchdog"
|
||||
version = "4.0.0"
|
||||
version = "6.0.0"
|
||||
description = "Filesystem events monitoring"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "watchdog-4.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:39cb34b1f1afbf23e9562501673e7146777efe95da24fab5707b88f7fb11649b"},
|
||||
{file = "watchdog-4.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c522392acc5e962bcac3b22b9592493ffd06d1fc5d755954e6be9f4990de932b"},
|
||||
{file = "watchdog-4.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6c47bdd680009b11c9ac382163e05ca43baf4127954c5f6d0250e7d772d2b80c"},
|
||||
{file = "watchdog-4.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8350d4055505412a426b6ad8c521bc7d367d1637a762c70fdd93a3a0d595990b"},
|
||||
{file = "watchdog-4.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c17d98799f32e3f55f181f19dd2021d762eb38fdd381b4a748b9f5a36738e935"},
|
||||
{file = "watchdog-4.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4986db5e8880b0e6b7cd52ba36255d4793bf5cdc95bd6264806c233173b1ec0b"},
|
||||
{file = "watchdog-4.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:11e12fafb13372e18ca1bbf12d50f593e7280646687463dd47730fd4f4d5d257"},
|
||||
{file = "watchdog-4.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5369136a6474678e02426bd984466343924d1df8e2fd94a9b443cb7e3aa20d19"},
|
||||
{file = "watchdog-4.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76ad8484379695f3fe46228962017a7e1337e9acadafed67eb20aabb175df98b"},
|
||||
{file = "watchdog-4.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:45cc09cc4c3b43fb10b59ef4d07318d9a3ecdbff03abd2e36e77b6dd9f9a5c85"},
|
||||
{file = "watchdog-4.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eed82cdf79cd7f0232e2fdc1ad05b06a5e102a43e331f7d041e5f0e0a34a51c4"},
|
||||
{file = "watchdog-4.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba30a896166f0fee83183cec913298151b73164160d965af2e93a20bbd2ab605"},
|
||||
{file = "watchdog-4.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d18d7f18a47de6863cd480734613502904611730f8def45fc52a5d97503e5101"},
|
||||
{file = "watchdog-4.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2895bf0518361a9728773083908801a376743bcc37dfa252b801af8fd281b1ca"},
|
||||
{file = "watchdog-4.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87e9df830022488e235dd601478c15ad73a0389628588ba0b028cb74eb72fed8"},
|
||||
{file = "watchdog-4.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6e949a8a94186bced05b6508faa61b7adacc911115664ccb1923b9ad1f1ccf7b"},
|
||||
{file = "watchdog-4.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6a4db54edea37d1058b08947c789a2354ee02972ed5d1e0dca9b0b820f4c7f92"},
|
||||
{file = "watchdog-4.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d31481ccf4694a8416b681544c23bd271f5a123162ab603c7d7d2dd7dd901a07"},
|
||||
{file = "watchdog-4.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8fec441f5adcf81dd240a5fe78e3d83767999771630b5ddfc5867827a34fa3d3"},
|
||||
{file = "watchdog-4.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:6a9c71a0b02985b4b0b6d14b875a6c86ddea2fdbebd0c9a720a806a8bbffc69f"},
|
||||
{file = "watchdog-4.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:557ba04c816d23ce98a06e70af6abaa0485f6d94994ec78a42b05d1c03dcbd50"},
|
||||
{file = "watchdog-4.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:d0f9bd1fd919134d459d8abf954f63886745f4660ef66480b9d753a7c9d40927"},
|
||||
{file = "watchdog-4.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f9b2fdca47dc855516b2d66eef3c39f2672cbf7e7a42e7e67ad2cbfcd6ba107d"},
|
||||
{file = "watchdog-4.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:73c7a935e62033bd5e8f0da33a4dcb763da2361921a69a5a95aaf6c93aa03a87"},
|
||||
{file = "watchdog-4.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6a80d5cae8c265842c7419c560b9961561556c4361b297b4c431903f8c33b269"},
|
||||
{file = "watchdog-4.0.0-py3-none-win32.whl", hash = "sha256:8f9a542c979df62098ae9c58b19e03ad3df1c9d8c6895d96c0d51da17b243b1c"},
|
||||
{file = "watchdog-4.0.0-py3-none-win_amd64.whl", hash = "sha256:f970663fa4f7e80401a7b0cbeec00fa801bf0287d93d48368fc3e6fa32716245"},
|
||||
{file = "watchdog-4.0.0-py3-none-win_ia64.whl", hash = "sha256:9a03e16e55465177d416699331b0f3564138f1807ecc5f2de9d55d8f188d08c7"},
|
||||
{file = "watchdog-4.0.0.tar.gz", hash = "sha256:e3e7065cbdabe6183ab82199d7a4f6b3ba0a438c5a512a68559846ccb76a78ec"},
|
||||
{file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"},
|
||||
{file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"},
|
||||
{file = "watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3"},
|
||||
{file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c"},
|
||||
{file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2"},
|
||||
{file = "watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c"},
|
||||
{file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948"},
|
||||
{file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860"},
|
||||
{file = "watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0"},
|
||||
{file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c"},
|
||||
{file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134"},
|
||||
{file = "watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b"},
|
||||
{file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8"},
|
||||
{file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a"},
|
||||
{file = "watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c"},
|
||||
{file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881"},
|
||||
{file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11"},
|
||||
{file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa"},
|
||||
{file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e"},
|
||||
{file = "watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13"},
|
||||
{file = "watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379"},
|
||||
{file = "watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e"},
|
||||
{file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f"},
|
||||
{file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26"},
|
||||
{file = "watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c"},
|
||||
{file = "watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2"},
|
||||
{file = "watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a"},
|
||||
{file = "watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680"},
|
||||
{file = "watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f"},
|
||||
{file = "watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "langgraph-sdk"
|
||||
version = "0.1.43"
|
||||
version = "0.1.47"
|
||||
description = "SDK for interacting with LangGraph API"
|
||||
authors = []
|
||||
license = "MIT"
|
||||
|
||||
@@ -3005,18 +3005,18 @@ pillow = ">=10.3.0,<11.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "langchain-openai"
|
||||
version = "0.2.1"
|
||||
version = "0.2.12"
|
||||
description = "An integration package connecting OpenAI and LangChain"
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.9"
|
||||
files = [
|
||||
{file = "langchain_openai-0.2.1-py3-none-any.whl", hash = "sha256:215efa4526c88f8105f002b43b7cbf98cebd9baeb4f62c3b58faebdb578715bc"},
|
||||
{file = "langchain_openai-0.2.1.tar.gz", hash = "sha256:a131ea18736f1a8792925391b91a8c8bd834431ffc2055c92ba49f59c3dcaaf0"},
|
||||
{file = "langchain_openai-0.2.12-py3-none-any.whl", hash = "sha256:916965c45584d9ea565825ad3bb7629b1ff57f12f36d4b937e5b7d65903839d6"},
|
||||
{file = "langchain_openai-0.2.12.tar.gz", hash = "sha256:8b92096623065a2820e89aa5fb0a262fb109d56c346e3b09ba319af424c45cd1"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
langchain-core = ">=0.3,<0.4"
|
||||
openai = ">=1.40.0,<2.0.0"
|
||||
langchain-core = ">=0.3.21,<0.4.0"
|
||||
openai = ">=1.55.3,<2.0.0"
|
||||
tiktoken = ">=0.7,<1"
|
||||
|
||||
[[package]]
|
||||
@@ -3035,7 +3035,7 @@ langchain-core = ">=0.3.0,<0.4.0"
|
||||
|
||||
[[package]]
|
||||
name = "langgraph"
|
||||
version = "0.2.57"
|
||||
version = "0.2.59"
|
||||
description = "Building stateful, multi-actor applications with LLMs"
|
||||
optional = false
|
||||
python-versions = ">=3.9.0,<4.0"
|
||||
@@ -3068,6 +3068,23 @@ msgpack = "^1.1.0"
|
||||
type = "directory"
|
||||
url = "libs/checkpoint"
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-checkpoint-mongodb"
|
||||
version = "0.1.0"
|
||||
description = "Library with a MongoDB implementation of LangGraph checkpoint saver."
|
||||
optional = false
|
||||
python-versions = "<4.0.0,>=3.9.0"
|
||||
files = [
|
||||
{file = "langgraph_checkpoint_mongodb-0.1.0-py3-none-any.whl", hash = "sha256:52f20956b36e0275ff805a1eea1db4c1a7e5e0ffe0a1ade65969004fa1654703"},
|
||||
{file = "langgraph_checkpoint_mongodb-0.1.0.tar.gz", hash = "sha256:3165c134ad5c82a3fe02fef04c81dcd48a3f5d031e07a9d1cb84457241f76793"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
langgraph = ">=0.2.38,<0.3.0"
|
||||
langgraph-checkpoint = ">=2.0.0,<3.0.0"
|
||||
motor = ">3.5.0"
|
||||
pymongo = ">=4.9.0,<4.10.0"
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-checkpoint-postgres"
|
||||
version = "2.0.8"
|
||||
@@ -4339,13 +4356,13 @@ sympy = "*"
|
||||
|
||||
[[package]]
|
||||
name = "openai"
|
||||
version = "1.50.1"
|
||||
version = "1.57.2"
|
||||
description = "The official Python library for the openai API"
|
||||
optional = false
|
||||
python-versions = ">=3.7.1"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "openai-1.50.1-py3-none-any.whl", hash = "sha256:7967fc8372d5e005ad61514586fb286d593facafccedbee00416bc38ee07c2e6"},
|
||||
{file = "openai-1.50.1.tar.gz", hash = "sha256:80cbdf275488894c70bfbad711dbba6f31ea71d579b97e364bfd99cdf030158e"},
|
||||
{file = "openai-1.57.2-py3-none-any.whl", hash = "sha256:f7326283c156fdee875746e7e54d36959fb198eadc683952ee05e3302fbd638d"},
|
||||
{file = "openai-1.57.2.tar.gz", hash = "sha256:5f49fd0f38e9f2131cda7deb45dafdd1aee4f52a637e190ce0ecf40147ce8cee"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -7468,4 +7485,4 @@ type = ["pytest-mypy"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "cf18eed5e183fc4f7786d095540b6c9261e130750f2d1fcc427e08b78d522c61"
|
||||
content-hash = "367f5fb480a8fa5d8ab1c0964a1e9450dbb28e6998097e7536966e7a5fe30c90"
|
||||
|
||||
@@ -41,6 +41,7 @@ langchain-nomic = "^0.1.3"
|
||||
langchain-fireworks = "^0.2.0"
|
||||
langchain-community = "^0.3.0"
|
||||
langchain-experimental = "^0.3.2"
|
||||
langgraph-checkpoint-mongodb = "^0.1.0"
|
||||
langsmith = "^0.1.129"
|
||||
chromadb = "^0.5.5"
|
||||
gpt4all = "^2.8.2"
|
||||
|
||||