Compare commits

..
Author SHA1 Message Date
Nuno Campos 746142fb07 One more 2025-06-02 16:16:11 -07:00
Nuno Campos b9c9c32c31 Allow releases from v0 2025-06-02 16:12:54 -07:00
Nuno Campos c89fe4c45d 0.4.8 2025-06-02 16:11:09 -07:00
Nuno CamposandGitHub b0e28851a6 v0: Fix Command(graph=PARENT) when used together w checkpointer=True (#4920) 2025-06-02 16:10:00 -07:00
Nuno Campos 48fb91deda Fix Command(graph=PARENT) when used together w checkpointer=True 2025-06-02 16:02:35 -07:00
Nuno CamposandGitHub efb282a197 Remove SchemaCoercionMapper (#4855) 2025-05-28 10:51:32 -07:00
Nuno Campos 89ce6ea2a8 Remove SchemaCoercionMapper 2025-05-28 10:42:43 -07:00
DHParkandGitHub 1e87312d1f Update graph-api.ipynb (#4824) 2025-05-28 13:39:22 +00:00
vksxandGitHub bcc6485f6c (docs) fix broken links (#4823) 2025-05-28 13:36:48 +00:00
Sydney RunkleandGitHub ce5f248e3b docs: fix typo (#4852)
minor fix
2025-05-28 13:35:17 +00:00
Sydney RunkleandGitHub 5602c29668 prebuilt: release 0.2.2 (#4851)
bump prebuilt
2025-05-28 13:33:39 +00:00
Sydney RunkleandGitHub 19ca6b416b fix: post_model_hook should inject state + store into tool calls if necessary (#4842) 2025-05-27 17:42:20 +00:00
Lauren Hirata SinghandGitHub 2553ae0b87 docs: Fix links (#4807) 2025-05-23 22:34:08 -04:00
Lauren Hirata Singh 3e4b69af3f Fix links 2025-05-23 22:30:43 -04:00
Lauren Hirata SinghandGitHub 0c6367c186 docs: fix link to examples page in readme.md (#4710) 2025-05-23 22:26:30 -04:00
Lauren Hirata SinghandGitHub 99860b5713 Update README.md 2025-05-23 22:12:18 -04:00
Lauren Hirata SinghandGitHub a69860baa6 Merge branch 'main' into fix-examples-link-readme 2025-05-23 22:08:32 -04:00
Lauren Hirata SinghandGitHub 6fc21046cd Update libs/langgraph/README.md 2025-05-23 22:06:43 -04:00
Lauren Hirata SinghandGitHub 754420e9a2 Update libs/langgraph/README.md 2025-05-23 22:03:39 -04:00
Lauren Hirata SinghandGitHub 15126ad827 Create overview.md 2025-05-23 22:02:14 -04:00
芥子观须弥andGitHub 6633173918 Fix: Ensure route function returns "b" to enable a → b → a loop (#4738)
fix: correct route function to return 'b' instead of 'a' when not terminating

This fixes a bug in the graph API how-to where the route function incorrectly returned 'a'. Now it correctly returns 'b' as intended.
2025-05-23 23:56:51 +00:00
Nuno Campos 0139e11ae5 0.4.7 2025-05-23 16:51:48 -07:00
Nuno CamposandGitHub bfbe55ab64 Fix stream mode not respected in subgraphs (#4806) 2025-05-23 16:47:01 -07:00
Nuno Campos 36478eb745 Fix stream mode not respected in subgraphs
- The default applied for subgraphs should be applied only when stream mode arg not passed in
2025-05-23 16:40:51 -07:00
Nuno CamposandGitHub 8fb91569b9 Add tests for stream_events when using imperative api (#4805) 2025-05-23 16:40:38 -07:00
Nuno Campos 9bf6728354 Try to make test less flaky 2025-05-23 16:32:51 -07:00
Nuno Campos 126a8f5bc6 Lock 2025-05-23 16:22:12 -07:00
Nuno Campos 9170f636d0 Lock 2025-05-23 16:14:15 -07:00
Nuno Campos 8207d3fefb Add tests for stream_events when using imperative api 2025-05-23 16:11:12 -07:00
Nuno Campos ade3f372a5 0.4.6 2025-05-23 15:24:07 -07:00
Nuno CamposandGitHub 3a55d1137b Fix exception handling for imperative tasks (#4802) 2025-05-23 15:23:15 -07:00
Nuno Campos bece43dc67 Fix 2025-05-23 15:06:18 -07:00
Nuno Campos 913b8d5e95 Lint 2025-05-23 14:32:45 -07:00
Andrew NguonlyandGitHub 29f6ea7f61 docs: Add warning about immutable deployment types (#4804)
* Add more details about database for deployment types. Clarify that deployment type cannot be changed.

* Add note about Development type disk capacity.
2025-05-23 14:04:46 -07:00
Nuno Campos 3c0d9346c2 Add sync test 2025-05-23 13:51:13 -07:00
Nuno Campos 0ebb78d9b2 Fix 2025-05-23 13:50:11 -07:00
Nuno Campos 70153ceba2 Fix exception handling for imperative tasks
- These exceptions should not be re-raised at end of tick, given they're handled explicitly by the developer in their entrypoint
2025-05-23 12:26:27 -07:00
Sydney RunkleandGitHub a9c87ed8b6 prebuilt: release 0.2.1 (#4801)
lockfile and version updates
2025-05-23 18:07:24 +00:00
Sydney RunkleandGitHub 837fe59e24 prebuilt: support provider builtin tools in create_react_agent (#4800) 2025-05-23 13:58:53 -04:00
Andrew NguonlyandGitHub 596a26461c docs: Add note about Enterprise plan in banner for all self-hosted deployment options. (#4797)
Add note about Enterprise plan in banner for all self-hosted deployment options.
2025-05-22 18:38:06 -07:00
Sydney RunkleandGitHub aff3be03dc prebuilt: release 0.2.0 (#4793) 2025-05-22 17:54:58 +00:00
David DuongandGitHub 1000b81eca feat(langgraph): push_messages should directly write to the state (#4791) 2025-05-22 19:41:51 +02:00
Tat Dat Duong 10c6ed7320 Code review 2025-05-22 19:35:44 +02:00
Tat Dat Duong 28e0e7f3ae feat(langgraph): push_messages should directly write to the state 2025-05-22 19:35:15 +02:00
Sydney RunkleandGitHub 87fa661ac0 temp: remove ToolInterruptNode code (#4792)
remove tool node specific code for post_model_hook release
2025-05-22 13:28:59 -04:00
David DuongandGitHub 3189b8fcda feat(sdk-js): Add rejoining of streams (#4744) 2025-05-22 19:19:07 +02:00
Tat Dat Duong 74b18acfc2 Bump to 0.0.78 2025-05-22 19:08:39 +02:00
Tat Dat Duong 1ff5e2966b Add joinStream method 2025-05-22 19:07:01 +02:00
Diwakar GuptaandGitHub 1ba2b3fba9 Merge branch 'main' into fix-examples-link-readme 2025-05-22 22:34:22 +05:30
Tat Dat Duong 102b1f63f3 Fix assumption on sessionStorage 2025-05-22 19:01:14 +02:00
Tat Dat Duong 74dbccd408 Rename to reconnectOnMount 2025-05-22 18:42:38 +02:00
Tat Dat Duong f5a2410ddc Make useStream more hackable 2025-05-22 18:38:09 +02:00
Sydney RunkleandGitHub 4cade428d8 docs: adding interactive graph widget (#4785) 2025-05-22 12:23:43 -04:00
Tat Dat Duong 5823a659fc use sessionStorage instead 2025-05-22 18:11:38 +02:00
Tat Dat Duong f2c32727e8 Fix invalid states 2025-05-22 18:09:16 +02:00
Tat Dat Duong 9d71d941fd resumable 2025-05-22 17:45:52 +02:00
Tat Dat Duong 0db618ae75 Further cleanup 2025-05-22 17:45:52 +02:00
Tat Dat Duong 49746ab3a3 Code cleanup 2025-05-22 17:45:51 +02:00
Tat Dat Duong 5c7ef9a4fc Fix race condition 2025-05-22 17:45:51 +02:00
Tat Dat Duong 62e688bb37 Add rejoining of streams 2025-05-22 17:45:51 +02:00
Sydney RunkleandGitHub 42eff39cd0 docs: fix top nav (#4783)
fix top nav
2025-05-21 19:30:13 -04:00
David DuongandGitHub 39172f2ff9 feat(graph): add push_message method to push manually to messages / message-tuple stream (#4722) 2025-05-22 01:01:48 +02:00
Nuno CamposandGitHub 0202c83e73 Apply same condition for stream_mode=values in suppress interrupt (#4782) 2025-05-21 15:55:55 -07:00
Tat Dat Duong ab924c72fa Add message state test 2025-05-22 00:49:23 +02:00
Nuno Campos 1833889316 Apply same condition for stream_mode=values in suppress interrupt 2025-05-21 15:23:27 -07:00
Tat Dat Duong 7a6bdb3441 Remove redundant cast 2025-05-22 00:23:26 +02:00
David DuongandGitHub ea7f45dba7 feat(sdk-py): resumable streams (#4765) 2025-05-22 00:19:43 +02:00
Tat Dat Duong d2875cc576 feat(graph): add push_message method to push manually to messages / message-tuple stream 2025-05-22 00:18:12 +02:00
Tat Dat Duong e77197a00f Here as well 2025-05-22 00:13:22 +02:00
Tat Dat Duong 30e6ea7aed Last uv.lock? 2025-05-22 00:13:10 +02:00
Tat Dat Duong 461afe0aa6 Again? 2025-05-22 00:12:40 +02:00
Tat Dat Duong 92f8dde61e uv.lock? 2025-05-22 00:07:56 +02:00
Tat Dat Duong f4f7a79907 Bump to 0.1.70 2025-05-22 00:03:48 +02:00
Tat Dat Duong 9c1267af1a Import re 2025-05-22 00:03:41 +02:00
Tat Dat Duong e0a4420b8e feat(sdk-py): resumable streams 2025-05-22 00:03:41 +02:00
Andrew NguonlyandGitHub 94a8067c12 docs: Remove outdated docs regarding graph construction (#4779)
Remove outdated docs regarding graph construction.
2025-05-21 12:21:32 -07:00
Lauren Hirata SinghandGitHub 957db60d89 docs: fix (#4778) 2025-05-21 14:48:36 -04:00
Lauren Hirata Singh a17efb1120 fix 2025-05-21 14:47:55 -04:00
Eugene YurtsevandGitHub d4b428de62 docs: improved llms.txt (#4777)
Improved llms.txt
2025-05-21 14:40:01 -04:00
Eugene YurtsevandGitHub fd0b70eb05 docs: improve llms.txt to generate title and description based on content (#4775)
Update llms.txt based on content
2025-05-21 14:39:13 -04:00
Lauren Hirata SinghandGitHub b8daf323ac docs: fix redirect (#4776) 2025-05-21 14:35:04 -04:00
Lauren Hirata Singh 2d3a3de1ce docs: fix redirect 2025-05-21 14:34:02 -04:00
Nuno CamposandGitHub 96e2f68b2d Only emit stream values chunks when the output channels have changed (#4774) 2025-05-21 13:09:42 -04:00
Eugene YurtsevandGitHub 364508ff20 docs: Add script to generate llms-txt links from yaml (#4771)
A first pass at the script to fix the immediate issue. Will follow up with a few additional improvements.
2025-05-21 11:54:23 -04:00
Eugene YurtsevandGitHub fa1af5c364 docs: replace llms.txt with updated links (#4772)
This is a stop gap solution to fix broken links.

This PR will be followed up with a better llms.txt file
2025-05-21 11:53:26 -04:00
le-codeur-rapideandGitHub 8ec29b4df7 docs: fix example pregel reducer (#4740) 2025-05-21 10:38:29 -04:00
Sydney RunkleandGitHub 06607e08ea prebuilts hitl: fix branching logic + add structural snapshot tests (#4767) 2025-05-21 09:29:59 -04:00
Lauren Hirata SinghandGitHub 0a8f54ca4a docs: add redirect (#4762) 2025-05-20 15:06:34 -04:00
Lauren Hirata Singh 9d41d439cc docs: add redirect 2025-05-20 14:52:02 -04:00
Diwakar GuptaandGitHub 364fdf5dfe Merge branch 'main' into fix-examples-link-readme 2025-05-21 00:00:09 +05:30
Leeroy BrunandGitHub ae5c01fbab docs: typo in npx command to install LangGraph CLI (#4761) 2025-05-20 17:08:02 +00:00
Sydney RunkleandGitHub bd52b1faa1 docs: deferred nodes (#4759) 2025-05-20 13:07:11 -04:00
Stefano LottiniandGitHub 4a04ca7268 [docs] Revise pip-install packages for PostgresSaver in "how-to/Persistence" notebook (#4752)
revise pip-install packages for PostgresSaver usage in how-to page
2025-05-20 15:46:39 +00:00
Yazan JianandGitHub 23b868da00 docs: fix async usage example from await .invoke() to `await .ainvo… (#4758)
docs: fix async usage example from `await .invoke()` to `await .ainvoke()`
2025-05-20 15:45:47 +00:00
David DuongandGitHub 51bfd16460 feat(sdk-js): add stream_resumable flag, that marks the stream as resumable (#4757) 2025-05-20 14:16:39 +02:00
Tat Dat Duong 325d9e3134 Bump to 0.0.77 2025-05-20 14:14:48 +02:00
Tat Dat Duong b052ebf984 feat(sdk-js): add stream_resumable flag, that marks the stream as resumable 2025-05-20 14:13:47 +02:00
Sydney RunkleandGitHub 1e938a692f docs: node caching (#4749) 2025-05-19 15:49:31 -04:00
Nuno CamposandGitHub 46a9d3159d Remove local_write utility (#4751)
- The validation isn't worth the cost of having to pass list of nodes to task config
2025-05-19 15:49:17 -04:00
Nuno CamposandGitHub d825e39df9 Print output for cached @task functions (#4750) 2025-05-19 11:42:16 -07:00
Nuno Campos 53a1e7c9de Print output for cached @task functions 2025-05-19 11:35:51 -07:00
Sydney RunkleandGitHub 7bd8616b1e feature: Implement post_model_hook and HumanInterruptNode (#4583) 2025-05-19 14:07:42 -04:00
William FHandGitHub 95f92069a7 sqlite: Add test for search with list filters (#4747) 2025-05-18 23:50:27 -07:00
Diwakar GuptaandGitHub fe5d303ccd Merge branch 'main' into fix-examples-link-readme 2025-05-17 13:56:41 +05:30
Diwakar GuptaandGitHub c9fa11ae7d Merge branch 'langchain-ai:main' into fix-examples-link-readme 2025-05-16 16:32:53 +05:30
Diw 988805d60b fix link to examples page in readme.md 2025-05-15 23:11:17 +05:30
89 changed files with 4354 additions and 2655 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ env:
jobs:
build:
if: github.ref == 'refs/heads/main'
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/v0'
runs-on: ubuntu-latest
outputs:
+1 -1
View File
@@ -13,7 +13,7 @@ env:
jobs:
build:
if: github.ref == 'refs/heads/main'
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/v0'
runs-on: ubuntu-latest
outputs:
+1 -1
View File
@@ -74,7 +74,7 @@ While LangGraph can be used standalone, it also integrates seamlessly with any L
- [Guides](https://langchain-ai.github.io/langgraph/how-tos/): Quick, actionable code snippets for topics such as streaming, adding memory & persistence, and design patterns (e.g. branching, subgraphs, etc.).
- [Reference](https://langchain-ai.github.io/langgraph/reference/graphs/): Detailed reference on core classes, methods, how to use the graph and checkpointing APIs, and higher-level prebuilt components.
- [Examples](https://langchain-ai.github.io/langgraph/tutorials/): Guided examples on getting started with LangGraph.
- [Examples](https://langchain-ai.github.io/langgraph/tutorials/overview/): Guided examples on getting started with LangGraph.
- [LangChain Academy](https://academy.langchain.com/courses/intro-to-langgraph): Learn the basics of LangGraph in our free, structured course.
- [Templates](https://langchain-ai.github.io/langgraph/concepts/template_applications/): Pre-built reference apps for common agentic workflows (e.g. ReAct agent, memory, retrieval etc.) that can be cloned and adapted.
- [Case studies](https://www.langchain.com/built-with-langgraph): Hear how industry leaders use LangGraph to ship AI applications at scale.
+238 -26
View File
@@ -1,10 +1,19 @@
"""Experimental script to generate consolidated llms text from the docs."""
import asyncio
import glob
import os
from typing import TypedDict, List, Optional
import pydantic
import re
from pydantic import BaseModel, Field
from langchain_core.rate_limiters import InMemoryRateLimiter
import yaml
from langchain.chat_models import init_chat_model
from mkdocs.structure.files import File
from mkdocs.structure.pages import Page
from yaml import SafeLoader
from _scripts.notebook_hooks import _on_page_markdown_with_config
@@ -13,7 +22,49 @@ HERE = os.path.dirname(os.path.abspath(__file__))
SOURCE_DIR = os.path.abspath(os.path.join(os.path.dirname(HERE), "docs"))
def _make_llms_text(output_file: str) -> str:
async def convert_ipynb_to_md(file_path: str) -> Optional[str]:
"""Process a file (markdown or notebook) to markdown format.
Args:
file_path: Path to the file to process
Returns:
Processed markdown content if successful, None otherwise
"""
rel_path = os.path.relpath(file_path, SOURCE_DIR)
# Create File and Page objects to match mkdocs structure
file_obj = File(
path=rel_path, src_dir=SOURCE_DIR, dest_dir="", use_directory_urls=True
)
page = Page(
title="",
file=file_obj,
config={},
)
try:
# Read raw content
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()
# Convert to markdown without logic to resolve API references
processed_content = _on_page_markdown_with_config(
content, page, add_api_references=False, remove_base64_images=True
)
# Remove self-closing img tags <img ... />
processed_content = re.sub(r"<img[^>]*/>", "", processed_content)
# Remove img tags with content <img ...>...</img>
processed_content = re.sub(
r"<img[^>]*>.*?</img>", "", processed_content, flags=re.DOTALL
)
return processed_content
except Exception as e:
print(f"Error processing file {file_path}: {e}")
return None
async def generate_full_llms_text(output_file: str) -> None:
"""Generate a consolidated text file from markdown/notebook files for LLM training.
Args:
@@ -21,11 +72,9 @@ def _make_llms_text(output_file: str) -> str:
"""
# Collect all markdown and notebook files
all_files = glob.glob(os.path.join(SOURCE_DIR, "how-tos/*.md"), recursive=True)
all_files.extend(
glob.glob(os.path.join(SOURCE_DIR, "how-tos/*.ipynb"), recursive=True)
)
# Add all concepts
all_files.extend(
glob.glob(os.path.join(SOURCE_DIR, "concepts/*.md"), recursive=True)
)
@@ -35,30 +84,14 @@ def _make_llms_text(output_file: str) -> str:
all_content = []
# Process each file
for file_path in all_files:
print(f"Processing {file_path}")
rel_path = os.path.relpath(file_path, SOURCE_DIR)
# Process files concurrently
tasks = [convert_ipynb_to_md(file_path) for file_path in all_files]
results = await asyncio.gather(*tasks)
# Create File and Page objects to match mkdocs structure
file_obj = File(
path=rel_path, src_dir=SOURCE_DIR, dest_dir="", use_directory_urls=True
)
page = Page(
title="",
file=file_obj,
config={},
)
# Read raw content
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()
# Convert to markdown without logic to resolve API references
processed_content = _on_page_markdown_with_config(
content, page, add_api_references=False, remove_base64_images=True
)
# Combine results with file paths
for file_path, processed_content in zip(all_files, results):
if processed_content:
rel_path = os.path.relpath(file_path, SOURCE_DIR)
# Add file name
all_content.append(f"---\n{rel_path}\n---")
# Add content
@@ -69,6 +102,168 @@ def _make_llms_text(output_file: str) -> str:
f.write("\n\n".join(all_content))
def no_op_constructor(*args):
"""No-op"""
SafeLoader.add_multi_constructor(
"tag:yaml.org,2002:python/name",
no_op_constructor,
)
class NavItem(TypedDict):
title: str
url: str
hierarchy: tuple[str, ...]
description: str
def _flatten_nav(
nav: list[dict[str, str | list] | str], path: tuple[str, ...] = ()
) -> list[NavItem]:
flat: List[NavItem] = []
for item in nav:
if isinstance(item, dict):
for title, node in item.items():
new_path = path + (title,)
if isinstance(node, str):
# Leaf page
flat.append(
{
"title": title,
"url": node,
"hierarchy": new_path,
"description": "",
}
)
elif isinstance(node, list):
# Dive in, carrying along the updated path
flat.extend(_flatten_nav(node, new_path))
else:
raise TypeError(
f"Unexpected node type {type(node)} under {title!r}"
)
elif isinstance(item, str):
# Bare string entry → use itself as title, and as URL
new_path = path + (item,)
flat.append(
{"title": item, "url": item, "hierarchy": new_path, "description": ""}
)
else:
raise TypeError(f"Unexpected item type {type(item)} in nav")
return flat
class PageInfo(BaseModel):
title: str = Field(description="The title of the page")
description: str = Field(
description="A short description of the page no longer than 3 sentences "
"explaining the kind of content that can be found in the page."
)
async def process_nav_items(nav_items: list[NavItem]) -> list[NavItem]:
"""Open the contents of each nav item and come up with a better title and description."""
rate_limiter = InMemoryRateLimiter(requests_per_second=10)
model = init_chat_model("gpt-4o-mini", temperature=0.0, rate_limiter=rate_limiter)
model = model.with_structured_output(PageInfo)
async def process_single_item(item: NavItem) -> NavItem:
path = item["url"]
file_path = os.path.join(SOURCE_DIR, path)
# Process the file content (handles both markdown and notebooks)
if path.endswith(".ipynb"):
content = await convert_ipynb_to_md(file_path)
else:
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()
if not content:
return item
# Generate a better title and description
response = await model.ainvoke(
[
{
"role": "system",
"content": "You are a technical documentation writer. "
"You are given a markdown page of documentation. "
"Please come up with an appropriate title and "
"description for the page. The description should "
"be a short summary of the page content that is "
"no longer than 3 sentences.",
},
{
"role": "user",
"content": "The markdown page is as follows:\n\n" + content,
},
]
)
return {
"title": response.title,
"url": item["url"],
"hierarchy": item["hierarchy"],
"description": response.description,
}
# Remove any items that start with http:// or https:// looking only for
# local file at this stages.
nav_items = [
item for item in nav_items if not item["url"].startswith(("http://", "https://"))
]
# Process items in parallel
tasks = [process_single_item(item) for item in nav_items]
new_nav_items = await asyncio.gather(*tasks)
return new_nav_items
async def generate_nav_links_text(
output_file: str, *, replace_links: bool = False
) -> None:
"""Generate llms.txt from mkdocs.yaml."""
# Get path to mkdocs.yaml relative to this script
script_dir = os.path.dirname(os.path.abspath(__file__))
mkdocs_path = os.path.join(os.path.dirname(script_dir), "mkdocs.yml")
# Load and parse yaml
with open(mkdocs_path, "r") as f:
config = yaml.safe_load(f)
# Extract nav section
nav = config.get("nav", [])
flattened = _flatten_nav(nav)
processed_nav = await process_nav_items(flattened)
with open(output_file, "w") as f:
current_section = None
for item in processed_nav:
# Get the top-level section (first item in hierarchy)
section = item["hierarchy"][0]
if section not in {"Guides", "Examples", "Resources"}:
continue
# If we're starting a new section, add a heading
if section != current_section:
f.write(f"\n# {section}\n\n")
current_section = section
title = item["title"]
# Process URL based on replace_links flag
url = item["url"]
if replace_links:
# Remove .md extension and ensure single trailing slash
url = url.removesuffix(".md")
url = url.removesuffix(".ipynb")
url = url.rstrip("/") + "/"
url = f"https://langchain-ai.github.io/langgraph/{url}"
f.write(f"- [{title}]({url}): {item['description']}\n")
if __name__ == "__main__":
import argparse
@@ -78,6 +273,23 @@ if __name__ == "__main__":
)
)
parser.add_argument("output_file", help="Path to output the consolidated text file")
parser.add_argument(
"--link-only",
action="store_true",
help="Only include link references in the output",
)
parser.add_argument(
"--replace-links",
action="store_true",
help="Replace markdown links with full URLs in the output",
)
args = parser.parse_args()
_make_llms_text(args.output_file)
if args.link_only:
coro = generate_nav_links_text(
args.output_file, replace_links=args.replace_links
)
else:
coro = generate_full_llms_text(args.output_file)
asyncio.run(coro)
+2 -1
View File
@@ -53,6 +53,7 @@ REDIRECT_MAP = {
"how-tos/persistence_redis.ipynb": "how-tos/persistence.ipynb#use-in-production",
"how-tos/subgraph-persistence.ipynb": "how-tos/persistence.ipynb#use-with-subgraphs",
"how-tos/cross-thread-persistence.ipynb": "how-tos/persistence.ipynb#add-long-term-memory",
"cloud/how-tos/copy_threads": "cloud/how-tos/use_threads",
# tool calling how-tos
"how-tos/tool-calling-errors.ipynb": "how-tos/tool-calling.ipynb#handle-errors",
"how-tos/pass-config-to-tools.ipynb": "how-tos/tool-calling.ipynb#access-config",
@@ -70,7 +71,7 @@ REDIRECT_MAP = {
"cloud/faq/studio.md": "concepts/langgraph_studio.md#studio-faqs",
"cloud/how-tos/human_in_the_loop_edit_state.md": "cloud/how-tos/add-human-in-the-loop.md",
"cloud/how-tos/human_in_the_loop_user_input.md": "cloud/how-tos/add-human-in-the-loop.md",
"concepts/platform_architecture.md": "langgraph/concepts/langgraph_cloud#architecture",
"concepts/platform_architecture.md": "concepts/langgraph_cloud#architecture",
# cloud streaming redirects
"cloud/how-tos/stream_values.md": "cloud/how-tos/streaming.md#stream-graph-state",
"cloud/how-tos/stream_updates.md": "cloud/how-tos/streaming.md#stream-graph-state",
+1 -1
View File
@@ -15,7 +15,7 @@ This guide shows you how to set up and use LangGraph's **prebuilt**, **reusable*
Before you start this tutorial, ensure you have the following:
- An [Anthropic](https://console.anthropic.com/settings/admin-keys) API key
- An [Anthropic](https://console.anthropic.com/settings/keys) API key
## 1. Install dependencies
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

+1 -1
View File
@@ -82,7 +82,7 @@ ny_response = agent.invoke(
```
1. The `InMemorySaver` is a checkpointer that stores the agent's state in memory. In a production setting, you would typically use a database or other persistent storage. Please review the [checkpointer documentation](../reference/checkpoints.md) for more options. If you're deploying with **LangGraph Platform**, the platform will provide a production-ready checkpointer for you.
2. The `checkpointer` is passed to the agent. This enables the agent to persist its state across invocations. Please note that
2. The `checkpointer` is passed to the agent. This enables the agent to persist its state across invocations.
3. A unique `thread_id` is provided in the config. This ID is used to identify the conversation session. The value is controlled by the user and can be any string.
4. The agent will continue the conversation using the same `thread_id`. This will allow the agent to infer that the user is asking specifically about the **weather** in New York.
+136
View File
@@ -53,3 +53,139 @@ The high-level components are organized into several packages, each with a speci
| `langmem` | Agent memory management: [**short-term and long-term**](./memory.md) | `pip install -U langmem` |
| `agentevals` | Utilities to [**evaluate agent performance**](./evals.md) | `pip install -U agentevals` |
## Visualize an agent graph
Use the following tool to visualize the graph generated by
[`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent]
and to view an outline of the corresponding code.
It allows you to explore the infrastructure of the agent as defined by the presence of:
* [`tools`](../agents/tools.md): A list of tools (functions, APIs, or other callable objects) that the agent can use to perform tasks.
* [`pre_model_hook`](../how-tos/create-react-agent-manage-message-history.ipynb): A function that is called before the model is invoked. It can be used to condense messages or perform other preprocessing tasks.
* `post_model_hook`: A function that is called after the model is invoked. It can be used to implement guardrails, human-in-the-loop flows, or other postprocessing tasks.
* [`response_format`](../agents/agents.md#6-configure-structured-output): A data structure used to constrain the type of the final output, e.g., a `pydantic` `BaseModel`.
<div class="agent-layout">
<div class="agent-graph-features-container">
<div class="agent-graph-features">
<h3 class="agent-section-title">Features</h3>
<label><input type="checkbox" id="tools" checked> <code>tools</code></label>
<label><input type="checkbox" id="pre_model_hook"> <code>pre_model_hook</code></label>
<label><input type="checkbox" id="post_model_hook"> <code>post_model_hook</code></label>
<label><input type="checkbox" id="response_format"> <code>response_format</code></label>
</div>
</div>
<div class="agent-graph-container">
<h3 class="agent-section-title">Graph</h3>
<img id="agent-graph-img" src="../assets/react_agent_graphs/0001.svg" alt="graph image" style="max-width: 100%;"/>
</div>
</div>
The following code snippet shows how to create the above agent (and underlying graph) with
[`create_react_agent`][langgraph.prebuilt.chat_agent_executor.create_react_agent]:
<div class="language-python">
<pre><code id="agent-code" class="language-python"></code></pre>
</div>
<script>
function getCheckedValue(id) {
return document.getElementById(id).checked ? "1" : "0";
}
function getKey() {
return [
getCheckedValue("response_format"),
getCheckedValue("post_model_hook"),
getCheckedValue("pre_model_hook"),
getCheckedValue("tools")
].join("");
}
function generateCodeSnippet({ tools, pre, post, response }) {
const lines = [
"from langgraph.prebuilt import create_react_agent",
"from langchain_openai import ChatOpenAI"
];
if (response) lines.push("from pydantic import BaseModel");
lines.push("", 'model = ChatOpenAI("o4-mini")', "");
if (tools) {
lines.push(
"def tool() -> None:",
' """Testing tool."""',
" ...",
""
);
}
if (pre) {
lines.push(
"def pre_model_hook() -> None:",
' """Pre-model hook."""',
" ...",
""
);
}
if (post) {
lines.push(
"def post_model_hook() -> None:",
' """Post-model hook."""',
" ...",
""
);
}
if (response) {
lines.push(
"class ResponseFormat(BaseModel):",
' """Response format for the agent."""',
" result: str",
""
);
}
lines.push("agent = create_react_agent(");
lines.push(" model,");
if (tools) lines.push(" tools=[tool],");
if (pre) lines.push(" pre_model_hook=pre_model_hook,");
if (post) lines.push(" post_model_hook=post_model_hook,");
if (response) lines.push(" response_format=ResponseFormat,");
lines.push(")", "", "agent.get_graph().draw_mermaid_png()");
return lines.join("\n");
}
async function render() {
const key = getKey();
document.getElementById("agent-graph-img").src = `../assets/react_agent_graphs/${key}.svg`;
const state = {
tools: document.getElementById("tools").checked,
pre: document.getElementById("pre_model_hook").checked,
post: document.getElementById("post_model_hook").checked,
response: document.getElementById("response_format").checked
};
document.getElementById("agent-code").textContent = generateCodeSnippet(state);
}
function initializeWidget() {
render(); // no need for `await` here
document.querySelectorAll(".agent-graph-features input").forEach((input) => {
input.addEventListener("change", render);
});
}
// Init for both full reload and SPA nav (used by MkDocs Material)
window.addEventListener("DOMContentLoaded", initializeWidget);
document$.subscribe(initializeWidget);
</script>
+2 -2
View File
@@ -10,7 +10,7 @@ hide:
# Running agents
Agents support both synchronous and asynchronous execution using either `.invoke()` / `await .invoke()` for full responses, or `.stream()` / `.astream()` for **incremental** [streaming](streaming.md) output. This section explains how to provide input, interpret output, enable streaming, and control execution limits.
Agents support both synchronous and asynchronous execution using either `.invoke()` / `await .ainvoke()` for full responses, or `.stream()` / `.astream()` for **incremental** [streaming](streaming.md) output. This section explains how to provide input, interpret output, enable streaming, and control execution limits.
## Basic usage
@@ -18,7 +18,7 @@ Agents support both synchronous and asynchronous execution using either `.invoke
Agents can be executed in two primary modes:
- **Synchronous** using `.invoke()` or `.stream()`
- **Asynchronous** using `await .invoke()` or `async for` with `.astream()`
- **Asynchronous** using `await .ainvoke()` or `async for` with `.astream()`
=== "Sync invocation"
```python
+15 -1
View File
@@ -280,7 +280,21 @@ LangGraph allows access to short-term and long-term memory from tools. See [Memo
## Prebuilt tools
LangChain supports a wide range of prebuilt tool integrations for interacting with APIs, databases, file systems, web data, and more. These tools extend the functionality of agents and enable rapid development.
You can use prebuilt tools from model providers by passing a dictionary with tool specs to the `tools` parameter of `create_react_agent`. For example, to use the `web_search_preview` tool from OpenAI:
```python
from langgraph.prebuilt import create_react_agent
agent = create_react_agent(
model="openai:gpt-4o-mini",
tools=[{"type": "web_search_preview"}]
)
response = agent.invoke(
{"messages": ["What was a positive news story from today?"]}
)
```
Additionally, LangChain supports a wide range of prebuilt tool integrations for interacting with APIs, databases, file systems, web data, and more. These tools extend the functionality of agents and enable rapid development.
You can browse the full list of available integrations in the [LangChain integrations directory](https://python.langchain.com/docs/integrations/tools/).
+19
View File
@@ -0,0 +1,19 @@
{
"0000": "graph TD;\n\t__start__ --> agent;\n\tagent --> __end__;",
"0001": "graph TD;\n\t__start__ --> agent;\n\tagent -.-> __end__;\n\tagent -.-> tools;\n\ttools --> agent;",
"0010": "graph TD;\n\t__start__ --> pre_model_hook;\n\tpre_model_hook --> agent;\n\tagent --> __end__;",
"0011": "graph TD;\n\t__start__ --> pre_model_hook;\n\tagent -.-> __end__;\n\tagent -.-> tools;\n\tpre_model_hook --> agent;\n\ttools --> pre_model_hook;",
"0100": "graph TD;\n\t__start__ --> agent;\n\tagent --> post_model_hook;\n\tpost_model_hook --> __end__;",
"0101": "graph TD;\n\t__start__ --> agent;\n\tagent --> post_model_hook;\n\tpost_model_hook -.-> __end__;\n\tpost_model_hook -.-> agent;\n\tpost_model_hook -.-> tools;\n\ttools --> agent;",
"0110": "graph TD;\n\t__start__ --> pre_model_hook;\n\tagent --> post_model_hook;\n\tpre_model_hook --> agent;\n\tpost_model_hook --> __end__;",
"0111": "graph TD;\n\t__start__ --> pre_model_hook;\n\tagent --> post_model_hook;\n\tpost_model_hook -.-> __end__;\n\tpost_model_hook -.-> pre_model_hook;\n\tpost_model_hook -.-> tools;\n\tpre_model_hook --> agent;\n\ttools --> pre_model_hook;",
"1000": "graph TD;\n\t__start__ --> agent;\n\tagent --> generate_structured_response;\n\tgenerate_structured_response --> __end__;",
"1001": "graph TD;\n\t__start__ --> agent;\n\tagent -.-> generate_structured_response;\n\tagent -.-> tools;\n\ttools --> agent;\n\tgenerate_structured_response --> __end__;",
"1010": "graph TD;\n\t__start__ --> pre_model_hook;\n\tagent --> generate_structured_response;\n\tpre_model_hook --> agent;\n\tgenerate_structured_response --> __end__;",
"1011": "graph TD;\n\t__start__ --> pre_model_hook;\n\tagent -.-> generate_structured_response;\n\tagent -.-> tools;\n\tpre_model_hook --> agent;\n\ttools --> pre_model_hook;\n\tgenerate_structured_response --> __end__;",
"1100": "graph TD;\n\t__start__ --> agent;\n\tagent --> post_model_hook;\n\tpost_model_hook --> generate_structured_response;\n\tgenerate_structured_response --> __end__;",
"1101": "graph TD;\n\t__start__ --> agent;\n\tagent --> post_model_hook;\n\tpost_model_hook -.-> agent;\n\tpost_model_hook -.-> generate_structured_response;\n\tpost_model_hook -.-> tools;\n\ttools --> agent;\n\tgenerate_structured_response --> __end__;",
"1110": "graph TD;\n\t__start__ --> pre_model_hook;\n\tagent --> post_model_hook;\n\tpost_model_hook --> generate_structured_response;\n\tpre_model_hook --> agent;\n\tgenerate_structured_response --> __end__;",
"1111": "graph TD;\n\t__start__ --> pre_model_hook;\n\tagent --> post_model_hook;\n\tpost_model_hook -.-> generate_structured_response;\n\tpost_model_hook -.-> pre_model_hook;\n\tpost_model_hook -.-> tools;\n\tpre_model_hook --> agent;\n\ttools --> pre_model_hook;\n\tgenerate_structured_response --> __end__;"
}
@@ -2,8 +2,8 @@
Before deploying, review the [conceptual guide for the Self-Hosted Control Plane](../../concepts/langgraph_self_hosted_control_plane.md) deployment option.
!!! important "Beta"
The Self-Hosted Control Plane deployment option is currently in beta stage.
!!! info "Important"
The Self-Hosted Control Plane deployment option is currently in beta stage and requires an [Enterprise](../../concepts/plans.md) plan.
## Prerequisites
@@ -2,8 +2,8 @@
Before deploying, review the [conceptual guide for the Self-Hosted Data Plane](../../concepts/langgraph_self_hosted_data_plane.md) deployment option.
!!! important "Beta"
The Self-Hosted Data Plane deployment option is currently in beta stage.
!!! info "Important"
The Self-Hosted Data Plane deployment option is currently in beta stage and requires an [Enterprise](../../concepts/plans.md) plan.
## Prerequisites
-3
View File
@@ -129,9 +129,6 @@ workflow.add_edge("action", "agent")
graph = workflow.compile()
```
!!! warning "Assign `CompiledGraph` to Variable"
The build process for LangGraph Platform requires that the `CompiledGraph` object be assigned to a variable at the top-level of a Python module (alternatively, you can provide [a function that creates a graph](./graph_rebuild.md)).
Example file directory:
```bash
@@ -155,10 +155,6 @@ const workflow = new StateGraph(MessagesAnnotation)
export const graph = workflow.compile();
```
!!! info "Assign `CompiledGraph` to Variable"
The build process for LangGraph Platform requires that the `CompiledGraph` object be assigned to a variable at the top-level of a JavaScript module (alternatively, you can provide [a function that creates a graph](./graph_rebuild.md)).
Example file directory:
```bash
@@ -142,9 +142,6 @@ workflow.add_edge("action", "agent")
graph = workflow.compile()
```
!!! warning "Assign `CompiledGraph` to Variable"
The build process for LangGraph Platform requires that the `CompiledGraph` object be assigned to a variable at the top-level of a Python module.
Example file directory:
```bash
+2 -2
View File
@@ -40,7 +40,7 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
| Key | Description |
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <span style="white-space: nowrap;">`dependencies`</span> | **Required**. Array of dependencies for LangGraph Platform API server. Dependencies can be one of the following: <ul><li>A single period (`"."`), which will look for local Python packages.</li><li>The directory path where `pyproject.toml`, `setup.py` or `requirements.txt` is located.</br></br>For example, if `requirements.txt` is located in the root of the project directory, specify `"./"`. If it's located in a subdirectory called `local_package`, specify `"./local_package"`. Do not specify the string `"requirements.txt"` itself.</li><li>A Python package name.</li></ul> |
| <span style="white-space: nowrap;">`graphs`</span> | **Required**. Mapping from graph ID to path where the compiled graph or a function that makes a graph is defined. Example: <ul><li>`./your_package/your_file.py:variable`, where `variable` is an instance of `langgraph.graph.state.CompiledStateGraph`</li><li>`./your_package/your_file.py:make_graph`, where `make_graph` is a function that takes a config dictionary (`langchain_core.runnables.RunnableConfig`) and creates an instance of `langgraph.graph.state.StateGraph` / `langgraph.graph.state.CompiledStateGraph`.</li></ul> |
| <span style="white-space: nowrap;">`graphs`</span> | **Required**. Mapping from graph ID to path where the compiled graph or a function that makes a graph is defined. Example: <ul><li>`./your_package/your_file.py:variable`, where `variable` is an instance of `langgraph.graph.state.CompiledStateGraph`</li><li>`./your_package/your_file.py:make_graph`, where `make_graph` is a function that takes a config dictionary (`langchain_core.runnables.RunnableConfig`) and returns an instance of `langgraph.graph.state.StateGraph` or `langgraph.graph.state.CompiledStateGraph`. See [how to rebuild a graph at runtime](../../cloud/deployment/graph_rebuild.md) for more details.</li></ul> |
| <span style="white-space: nowrap;">`auth`</span> | _(Added in v0.0.11)_ Auth configuration containing the path to your authentication handler. Example: `./your_package/auth.py:auth`, where `auth` is an instance of `langgraph_sdk.Auth`. See [authentication guide](../../concepts/auth.md) for details. |
| <span style="white-space: nowrap;">`base_image`</span> | Optional. Base image to use for the LangGraph API server. Defaults to `langchain/langgraph-api` or `langchain/langgraphjs-api`. Use this to pin your builds to a particular version of the langgraph API, such as `"langchain/langgraph-server:0.2"`. See https://hub.docker.com/r/langchain/langgraph-server/tags for more details. (added in `langgraph-cli==0.2.8`) |
| <span style="white-space: nowrap;">`env`</span> | Path to `.env` file or a mapping from environment variable to its value. |
@@ -57,7 +57,7 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
| Key | Description |
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <span style="white-space: nowrap;">`graphs`</span> | **Required**. Mapping from graph ID to path where the compiled graph or a function that makes a graph is defined. Example: <ul><li>`./src/graph.ts:variable`, where `variable` is an instance of `CompiledStateGraph`</li><li>`./src/graph.ts:makeGraph`, where `makeGraph` is a function that takes a config dictionary (`LangGraphRunnableConfig`) and creates an instance of `StateGraph` / `CompiledStateGraph`.</li></ul> |
| <span style="white-space: nowrap;">`graphs`</span> | **Required**. Mapping from graph ID to path where the compiled graph or a function that makes a graph is defined. Example: <ul><li>`./src/graph.ts:variable`, where `variable` is an instance of `CompiledStateGraph`</li><li>`./src/graph.ts:makeGraph`, where `makeGraph` is a function that takes a config dictionary (`LangGraphRunnableConfig`) and returns an instance of `StateGraph` or `CompiledStateGraph`. See [how to rebuild a graph at runtime](../../cloud/deployment/graph_rebuild.md) for more details.</li></ul> |
| <span style="white-space: nowrap;">`env`</span> | Path to `.env` file or a mapping from environment variable to its value. |
| <span style="white-space: nowrap;">`store`</span> | Configuration for adding semantic search and/or time-to-live (TTL) to the BaseStore. Contains the following fields: <ul><li>`index` (optional): Configuration for semantic search indexing with fields `embed`, `dims`, and optional `fields`.</li><li>`ttl` (optional): Configuration for item expiration. An object with optional fields: `refresh_on_read` (boolean, defaults to `true`), `default_ttl` (float, lifespan in **minutes**, defaults to no expiration), and `sweep_interval_minutes` (integer, how often to check for expired items, defaults to no sweeping).</li></ul> |
| <span style="white-space: nowrap;">`node_version`</span> | Specify `node_version: 20` to use LangGraph.js. |
+4 -4
View File
@@ -40,8 +40,8 @@ For more information, please see:
## Self-Hosted Data Plane
!!! important "Beta"
The Self-Hosted Data Plane deployment option is currently in beta stage.
!!! info "Important"
The Self-Hosted Data Plane deployment option is currently in beta stage and requires an [Enterprise](../concepts/plans.md) plan.
The [Self-Hosted Data Plane](./langgraph_self_hosted_data_plane.md) deployment option is a "hybrid" model for deployment where we manage the [control plane](./langgraph_control_plane.md) in our cloud and you manage the [data plane](./langgraph_data_plane.md) in your cloud. This option provides a way to securely manage your data plane infrastructure, while offloading control plane management to us.
@@ -56,8 +56,8 @@ For more information, please see:
## Self-Hosted Control Plane
!!! important "Beta"
The Self-Hosted Control Plane deployment option is currently in beta stage.
!!! info "Important"
The Self-Hosted Control Plane deployment option is currently in beta stage and requires an [Enterprise](../concepts/plans.md) plan.
The [Self-Hosted Control Plane](./langgraph_self_hosted_control_plane.md) deployment option is a fully self-hosted model for deployment where you manage the [control plane](./langgraph_control_plane.md) and [data plane](./langgraph_data_plane.md) in your cloud. This option give you full control and responsibility of the control plane and data plane infrastructure.
+11 -6
View File
@@ -47,17 +47,22 @@ This section describes various features of the control plane.
For simplicity, the control plane offers two deployment types with different resource allocations: `Development` and `Production`.
| **Deployment Type** | **CPU** | **Memory** | **Scaling** |
|---------------------|---------|------------|---------------------|
| Development | 1 CPU | 1 GB | Up to 1 container |
| Production | 2 CPU | 2 GB | Up to 10 containers |
| **Deployment Type** | **CPU/Memory** | **Scaling** | **Database** |
|---------------------|-----------------|---------------------|----------------------------------------------------------------------------------|
| Development | 1 CPU, 1 GB RAM | Up to 1 container | 10 GB disk, no backups |
| Production | 2 CPU, 2 GB RAM | Up to 10 containers | Autoscaling disk, automatic backups, highly available (multi-zone configuration) |
CPU and memory resources are per container.
!!! info "For [Cloud SaaS](../concepts/langgraph_cloud.md)"
!!! warning "Immutable Deployment Type"
Once a deployment is created, the deployment type cannot be changed.
!!! info "Resource Customization"
For `Production` type deployments, resources can be manually increased on a case-by-case basis depending on use case and capacity constraints. Contact support@langchain.dev to request an increase in resources.
!!! info
For `Development` types deployments, database disk size can be manually increased on a case-by-case basis depending on use case and capacity constraints. For most use cases, [TTLs](../how-tos/ttl/configure_ttl.md) should be configured to manage disk usage. Contact support@langchain.dev to request an increase in resources.
Resources for [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md) deployments can be fully customized.
### Database Provisioning
@@ -2,8 +2,8 @@
There are two versions of the self-hosted deployment: [Self-Hosted Data Plane](./deployment_options.md#self-hosted-data-plane) and [Self-Hosted Control Plane](./deployment_options.md#self-hosted-control-plane).
!!! important "Beta"
The Self-Hosted Control Plane deployment option is currently in beta stage.
!!! info "Important"
The Self-Hosted Control Plane deployment option is currently in beta stage and requires an [Enterprise](../../concepts/plans.md) plan.
## Requirements
@@ -7,8 +7,8 @@ search:
There are two versions of the self-hosted deployment: [Self-Hosted Data Plane](./deployment_options.md#self-hosted-data-plane) and [Self-Hosted Control Plane](./deployment_options.md#self-hosted-control-plane).
!!! important "Beta"
The Self-Hosted Data Plane deployment option is currently in beta stage.
!!! info "Important"
The Self-Hosted Data Plane deployment option is currently in beta stage and requires an [Enterprise](../../concepts/plans.md) plan.
## Requirements
+48
View File
@@ -247,6 +247,54 @@ from langgraph.graph import END
graph.add_edge("node_a", END)
```
### Node Caching
LangGraph supports caching of tasks/nodes based on the input to the node. To use caching:
* Specify a cache when compiling a graph (or specifying an entrypoint)
* Specify a cache policy for nodes. Each cache policy supports:
* `key_func` used to generate a cache key based on the input to a node, which defaults to a `hash` of the input with pickle.
* `ttl`, the time to live for the cache in seconds. If not specified, the cache will never expire.
For example:
```py
import time
from typing_extensions import TypedDict
from langgraph.graph import StateGraph
from langgraph.cache.memory import InMemoryCache
from langgraph.types import CachePolicy
class State(TypedDict):
x: int
result: int
builder = StateGraph(State)
def expensive_node(state: State) -> dict[str, int]:
# expensive computation
time.sleep(2)
return {"result": state["x"] * 2}
builder.add_node("expensive_node", expensive_node, cache_policy=CachePolicy(ttl=3))
builder.set_entry_point("expensive_node")
builder.set_finish_point("expensive_node")
graph = builder.compile(cache=InMemoryCache())
print(graph.invoke({"x": 5}, stream_mode='updates')) # (1)!
[{'expensive_node': {'result': 10}}]
print(graph.invoke({"x": 5}, stream_mode='updates')) # (2)!
[{'expensive_node': {'result': 10}, '__metadata__': {'cached': True}}]
```
1. First run takes the full second to run (due to mocked expensive computation).
2. Second run utilizes cache and returns quickly.
## Edges
Edges define how the logic is routed and how the graph decides to stop. This is a big part of how your agents work and how different nodes communicate with each other. There are a few key types of edges:
+1 -1
View File
@@ -180,7 +180,7 @@ Below are a few different examples to give you a sense of the Pregel API.
def reducer(current, update):
if current:
return current + " | " + "update"
return current + " | " + update
else:
return update
+45 -35
View File
@@ -1288,12 +1288,43 @@
},
{
"cell_type": "markdown",
"id": "4eeb895c-adca-40ab-b289-93ee56e18661",
"id": "068f806a",
"metadata": {},
"source": [
"</details>"
]
},
{
"cell_type": "markdown",
"id": "6d99d63c",
"metadata": {},
"source": [
"## Add node caching\n",
"\n",
"Node caching is useful in cases where you want to avoid repeating operations, like when doing something expensive (either in terms of time or cost). LangGraph lets you add individualized caching policies to nodes in a graph.\n",
"\n",
"To configure a cache policy, pass the `cache_policy` parameter to the [add_node](https://langchain-ai.github.io/langgraph/reference/graphs/#langgraph.graph.state.StateGraph.add_node) function. In the following example, a [`CachePolicy`](https://langchain-ai.github.io/langgraph/reference/types/?h=cachepolicy#langgraph.types.CachePolicy) object is instantiated with a time to live of 120 seconds and the default `key_func` generator. Then it is associated with a node:\n",
"\n",
"```python\n",
"from langgraph.types import CachePolicy\n",
"\n",
"builder.add_node(\n",
" \"node_name\",\n",
" node_function,\n",
" cache_policy=CachePolicy(ttl=120),\n",
")\n",
"```\n",
"\n",
"Then, to enable node-level caching for a graph, set the `cache` argument when compiling the graph. The example below uses `InMemoryCache` to set up a graph with in-memory cache, but `SqliteCache` is also available.\n",
"\n",
"```python\n",
"from langgraph.cache.memory import InMemoryCache\n",
"\n",
"\n",
"graph = builder.compile(cache=InMemoryCache())\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "e1a0213e-282f-4fad-b048-5f7465edfccb",
@@ -1754,18 +1785,19 @@
},
{
"cell_type": "markdown",
"id": "205ff836-0f97-4ee8-9830-6bd8368e48c9",
"id": "48731230",
"metadata": {},
"source": [
"<details class=\"example\"><summary>Extended example: unequal length branches</summary>\n",
"### Defer node execution\n",
"\n",
"The above example showed how to fan-out and fan-in when each path was only one step. But what if one path had more than one step? Let's add a node <code>b_2</code> in the \"b\" branch:\n",
"<br>"
"Deferring node execution is useful when you want to delay the execution of a node until all other pending tasks are completed. This is particularly relevant when branches have different lengths, which is common in workflows like map-reduce flows.\n",
"\n",
"The above example showed how to fan-out and fan-in when each path was only one step. But what if one branch had more than one step? Let's add a node `\"b_2\"` in the `\"b\"` branch:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"execution_count": 26,
"id": "3890af2f-fb14-4569-b48d-a91db2d3f026",
"metadata": {},
"outputs": [],
@@ -1813,13 +1845,14 @@
"builder.add_node(b)\n",
"builder.add_node(b_2)\n",
"builder.add_node(c)\n",
"builder.add_node(d)\n",
"# highlight-next-line\n",
"builder.add_node(d, defer=True)\n",
"builder.add_edge(START, \"a\")\n",
"builder.add_edge(\"a\", \"b\")\n",
"builder.add_edge(\"a\", \"c\")\n",
"builder.add_edge(\"b\", \"b_2\")\n",
"# highlight-next-line\n",
"builder.add_edge([\"b_2\", \"c\"], \"d\")\n",
"builder.add_edge(\"b_2\", \"d\")\n",
"builder.add_edge(\"c\", \"d\")\n",
"builder.add_edge(\"d\", END)\n",
"graph = builder.compile()"
]
@@ -1881,23 +1914,10 @@
},
{
"cell_type": "markdown",
"id": "903f0da5-8c2c-4a7e-96fb-0b16b4756eff",
"id": "70e67ced",
"metadata": {},
"source": [
"<div class=\"admonition note\">\n",
" <p class=\"admonition-title\">Note</p>\n",
"<p>In the above example, nodes <code>\"b\"</code> and <code>\"c\"</code> are executed concurrently in the same [superstep](../../concepts/low_level/#graphs). What happens in the next step?</p>\n",
" <p>We use <code>add_edge([\"b_2\", \"c\"], \"d\")</code> here to force node <code>\"d\"</code> to only run when both nodes <code>\"b_2\"</code> and <code>\"c\"</code> have finished execution. If we added two separate edges,\n",
" node <code>\"d\"</code> would run twice: after node <code>b2</code> finishes and once again after node <code>c</code> (in whichever order those nodes finish).</p>\n",
"</div>"
]
},
{
"cell_type": "markdown",
"id": "c1653341-3215-4ca0-b0e7-9be22f0adaa1",
"metadata": {},
"source": [
"</details>"
"In the above example, nodes `\"b\"` and `\"c\"` are executed concurrently in the same superstep. We set `defer=True` on node `d` so it will not execute until all pending tasks are finished. In this case, this means that `\"d\"` waits to execute until the entire `\"b\"` branch is finished."
]
},
{
@@ -2215,7 +2235,7 @@
" if termination_condition(state):\n",
" return END\n",
" else:\n",
" return \"a\"\n",
" return \"b\"\n",
"\n",
"builder.add_edge(START, \"a\")\n",
"builder.add_conditional_edges(\"a\", route)\n",
@@ -2930,16 +2950,6 @@
" When you send updates from a subgraph node to a parent graph node for a key that's shared by both parent and subgraph [state schemas](../../concepts/low_level#schema), you **must** define a [reducer](../../concepts/low_level#reducers) for the key you're updating in the parent graph state. See the example below."
]
},
{
"cell_type": "markdown",
"id": "6be0aeb9-e138-4adc-a1df-5d743a8eb348",
"metadata": {},
"source": [
"!!! important \"State updates with `Command.PARENT`\"\n",
"\n",
" When you send updates from a subgraph node to a parent graph node for a key that's shared by both parent and subgraph [state schemas](../../concepts/low_level#schema), you **must** define a [reducer](../../concepts/low_level#reducers) for the key you're updating in the parent graph state."
]
},
{
"cell_type": "code",
"execution_count": 6,
+2 -2
View File
@@ -184,7 +184,7 @@
"??? example \"Example: using [Postgres](https://pypi.org/project/langgraph-checkpoint-postgres/) checkpointer\"\n",
"\n",
" ```\n",
" pip install -U psycopg psycopg-pool langgraph langgraph-checkpoint-postgres\n",
" pip install -U \"psycopg[binary,pool]\" langgraph langgraph-checkpoint-postgres\n",
" ```\n",
"\n",
" !!! Setup\n",
@@ -1129,7 +1129,7 @@
"??? example \"Example: using [Postgres](https://pypi.org/project/langgraph-checkpoint-postgres/) store\"\n",
"\n",
" ```\n",
" pip install -U psycopg psycopg-pool langgraph langgraph-checkpoint-postgres\n",
" pip install -U \"psycopg[binary,pool]\" langgraph langgraph-checkpoint-postgres\n",
" ```\n",
"\n",
" !!! Setup\n",
+32
View File
@@ -349,6 +349,38 @@ main.invoke({'any_input': 'foobar'}, config=config)
'OK'
```
## Caching Tasks
```python
import time
from langgraph.cache.memory import InMemoryCache
from langgraph.func import entrypoint, task
from langgraph.types import CachePolicy
@task(cache_policy=CachePolicy(ttl=120)) # (1)!
def slow_add(x: int) -> int:
time.sleep(1)
return x * 2
@entrypoint(cache=InMemoryCache())
def main(inputs: dict) -> dict[str, int]:
result1 = slow_add(inputs["x"]).result()
result2 = slow_add(inputs["x"]).result()
return {"result1": result1, "result2": result2}
for chunk in main.stream({"x": 5}, stream_mode="updates"):
print(chunk)
#> {'slow_add': 10}
#> {'slow_add': 10, '__metadata__': {'cached': True}}
#> {'main': {'result1': 10, 'result2': 10}}
```
1. `ttl` is specified in seconds. The cache will be invalidated after this time.
## Resuming after an error
```python
+148 -201
View File
@@ -1,202 +1,149 @@
# LangGraph
## Tutorials
[Learn the basics](https://langchain-ai.github.io/langgraph/tutorials/introduction/): LLM should read this page when needing to build a LangGraph chatbot or when learning about chat agents with memory, human-in-the-loop functionality, and state management. This page provides a comprehensive LangGraph quickstart tutorial covering building a support chatbot with web search capability, conversation memory, human review routing, custom state management, and time travel functionality to explore alternative conversation paths.
[Local Deploy](https://langchain-ai.github.io/langgraph/tutorials/langgraph-platform/local-server/): LLM should read this page when setting up a LangGraph app locally using `langgraph dev` and troubleshooting LangGraph server deployment. This page contains a quickstart guide for launching a LangGraph server locally, including installation steps, app creation from templates, environment setup, API testing with Python/JS SDKs, and links to deployment options and further documentation.
[Workflows and Agents](https://langchain-ai.github.io/langgraph/tutorials/workflows/): LLM should read this page when implementing agent systems, designing workflow architectures, or troubleshooting LLM orchestration strategies. The page covers patterns for LLM system design, comparing workflows (predefined paths) vs agents (dynamic control), with implementations of prompt chaining, parallelization, routing, orchestrator-worker, evaluator-optimizer, and agent patterns using both graph and functional APIs in LangGraph.
## Concepts
[Concepts](https://langchain-ai.github.io/langgraph/concepts/): LLM should read this page when needing to understand LangGraph's key concepts or when planning to deploy LangGraph applications. Comprehensive guide covering LangGraph fundamentals (graph primitives, agents, multi-agent systems, breakpoints, persistence), features (time travel, memory, streaming), and LangGraph Platform deployment options (self-hosted, cloud, enterprise).
[Agent architectures](https://langchain-ai.github.io/langgraph/concepts/agentic_concepts/): LLM should read this page when designing agent architectures, implementing control flows for LLM applications, or customizing agent behavior patterns. This page covers different LLM agent architectures including routers, tool calling agents (ReAct), structured outputs, memory systems, planning capabilities, and advanced customization options like human-in-the-loop, parallelization, subgraphs, and reflection mechanisms.
[Application Structure](https://langchain-ai.github.io/langgraph/concepts/application_structure/): LLM should read this page when needing to understand LangGraph application structure, preparing to deploy a LangGraph application, or troubleshooting configuration issues. This page details the structure of LangGraph applications, including required components (graphs, langgraph.json config file, dependency files, optional .env), file organization patterns for Python/JavaScript projects, configuration file format with all supported fields, and how to specify dependencies, graphs, and environment variables.
[Assistants](https://langchain-ai.github.io/langgraph/concepts/assistants/): LLM should read this page when looking for information about LangGraph assistants, understanding assistant configuration in LangGraph Platform, or learning about versioning agent configurations. This page explains LangGraph assistants, which allow developers to modify agent configurations (prompts, models, etc.) without changing graph logic, supports versioning for tracking changes, and is available only in LangGraph Platform (not open source).
[Authentication & Access Control](https://langchain-ai.github.io/langgraph/concepts/auth/): LLM should read this page when implementing authentication in LangGraph Platform, designing access control for LangGraph applications, or troubleshooting security issues in LangGraph deployments. This page explains LangGraph's authentication and authorization system, covering the difference between authentication and authorization, system architecture, implementing custom auth handlers, common access patterns, and supported resources/actions for access control.
[Deployment Options](https://langchain-ai.github.io/langgraph/concepts/deployment_options/): LLM should read this page when needing information about LangGraph deployment options, comparing different deployment methods, or understanding LangGraph Platform plans. This page outlines four deployment options for LangGraph Platform: Self-Hosted Lite (available for all plans), Self-Hosted Enterprise (Enterprise plan only), Cloud SaaS (Plus and Enterprise plans), and Bring Your Own Cloud (Enterprise plan only, AWS-only).
[Double Texting](https://langchain-ai.github.io/langgraph/concepts/double_texting/): LLM should read this page when handling concurrent user interactions in LangGraph Platform, implementing double-texting safeguards, or designing stateful conversation systems. This page explains four approaches to handling "double texting" in LangGraph (when users send a second message before the first completes): Reject, Enqueue, Interrupt, and Rollback, noting these features are currently only available in LangGraph Platform.
[Durable Execution](https://langchain-ai.github.io/langgraph/concepts/durable_execution/): LLM should read this page when needing to understand durable execution in LangGraph, implementing workflow persistence, or troubleshooting workflow resumption. This page explains durable execution in LangGraph: how workflows save progress to resume later, requirements (checkpointers and thread IDs), determinism guidelines for consistent replay, using tasks to encapsulate non-deterministic operations, and approaches for pausing/resuming workflows.
[FAQ](https://langchain-ai.github.io/langgraph/concepts/faq/): LLM should read this page when needing to understand differences between LangGraph and LangChain, exploring deployment options for LangGraph Platform, or determining compatibility with various LLMs. FAQ covering LangGraph basics, comparisons with other frameworks, deployment options (free self-hosted, Cloud SaaS, Enterprise), compatibility with different LLMs including OSS models, and feature differences between open-source LangGraph and proprietary LangGraph Platform.
[Functional API](https://langchain-ai.github.io/langgraph/concepts/functional_api/): LLM should read this page when implementing workflows with persistent state, adding human-in-the-loop features, or converting existing code to use LangGraph. The page documents LangGraph's Functional API, which allows adding persistence, memory, and human-in-the-loop capabilities with minimal code changes using @entrypoint and @task decorators, handling serialization requirements, state management, and common patterns for parallel execution and error handling.
[Why LangGraph?](https://langchain-ai.github.io/langgraph/concepts/high_level/): LLM should read this page when understanding LangGraph's core capabilities, exploring LLM application infrastructure, or evaluating agent/workflow persistence options. LangGraph provides infrastructure for LLM applications with three key benefits: persistence for memory and human-in-the-loop capabilities, streaming of workflow events and LLM outputs, and tools for debugging and deployment via LangGraph Platform.
[Human-in-the-loop](https://langchain-ai.github.io/langgraph/concepts/human_in_the_loop/): LLM should read this page when implementing human-in-the-loop workflows in LangGraph, designing approval systems with LLMs, or creating interactive multi-turn conversation agents. This page explains human-in-the-loop patterns in LangGraph using the interrupt function, showing how to pause graph execution for human review/input and resume with Command. Includes design patterns for approval workflows, state editing, tool call reviews, and multi-turn conversations, with code examples and warnings about execution flow and common pitfalls.
[LangGraph CLI](https://langchain-ai.github.io/langgraph/concepts/langgraph_cli/): LLM should read this page when looking for information about LangGraph CLI installation or when needing to deploy a LangGraph API server locally. The page covers LangGraph CLI installation methods (Homebrew, pip), key commands (build, dev, up, dockerfile), and features like hot reloading, debugger support, and database management for running LangGraph servers.
[Cloud SaaS](https://langchain-ai.github.io/langgraph/concepts/langgraph_cloud/): LLM should read this page when learning about LangGraph's Cloud SaaS offering, understanding deployment options for LangGraph Servers, or planning autoscaling infrastructure for LangGraph applications. This page describes LangGraph Cloud SaaS, a managed deployment service for LangGraph Servers with details on deployment types (Development/Production), revisions, persistence, autoscaling capabilities (up to 10 containers), LangSmith integration, IP whitelisting, and automatic deletion policies after 28 days of non-use.
[LangGraph Platform](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/): LLM should read this page when seeking information about LangGraph Platform's components or evaluating production deployment options for agentic applications. The page details the LangGraph Platform, a commercial solution for deploying agentic applications, including its components (Server, Studio, CLI, SDK, Remote Graph) and key benefits like streaming support, background runs, long run handling, burstiness management, and human-in-the-loop capabilities.
[LangGraph Server](https://langchain-ai.github.io/langgraph/concepts/langgraph_server/): LLM should read this page when developing applications with LangGraph Server, deploying agent-based applications, or integrating persistent state management in agent workflows. LangGraph Server provides an API for creating and managing agent applications with key features like streaming endpoints, background runs, task queues, persistence, webhooks, cron jobs, and monitoring capabilities through a structured system of assistants, threads, runs, and stores.
[LangGraph Studio](https://langchain-ai.github.io/langgraph/concepts/langgraph_studio/): LLM should read this page when looking for information about LangGraph Studio features, needing to troubleshoot LangGraph Studio issues, or learning how to connect a LangGraph application to the Studio. LangGraph Studio is a specialized agent IDE for visualizing, interacting with, and debugging LLM applications, offering features such as graph visualization, state editing, assistant management, and integration with LangSmith, with instructions for connecting via deployed applications or local development servers, plus troubleshooting FAQs.
[LangGraph Glossary](https://langchain-ai.github.io/langgraph/concepts/low_level/): LLM should read this page when needing to understand LangGraph terminology, implementing agent workflows as graphs, or developing modular multi-step AI systems. The page covers core LangGraph concepts including StateGraph, nodes, edges, state management, messaging, persistence, configuration, human-in-the-loop features, subgraphs, and visualization capabilities.
[Memory](https://langchain-ai.github.io/langgraph/concepts/memory/): LLM should read this page when implementing memory systems for AI agents, managing conversation context across sessions, or designing systems that require both short-term and long-term information retention. This page explains memory systems in LangGraph, covering short-term (thread-scoped) memory for managing conversation history and long-term memory across threads, with techniques for handling long conversations, summarizing past interactions, and organizing persistent memories in namespaces.
[Multi-agent Systems](https://langchain-ai.github.io/langgraph/concepts/multi_agent/): LLM should read this page when implementing multi-agent systems, troubleshooting complex agent architectures, or designing agent communication patterns. Multi-agent systems organize LLMs into modular architectures (network, supervisor, hierarchical, custom) with different communication patterns, using Command objects for handoffs between agents, and supporting various state management approaches.
[Persistence](https://langchain-ai.github.io/langgraph/concepts/persistence/): LLM should read this page when needing to understand LangGraph persistence mechanisms, implementing stateful workflows, or managing conversation history across interactions. This page covers LangGraph's persistence features including checkpointers, threads, state snapshots, replay functionality, forking state, cross-thread memory via InMemoryStore, and semantic search capabilities for stored memories.
[LangGraph Platform Plans](https://langchain-ai.github.io/langgraph/concepts/plans/): LLM should read this page when determining LangGraph Platform pricing tiers, comparing deployment options, or researching features available across different plans. This page outlines LangGraph Platform plans (Developer, Plus, Enterprise), detailing deployment options, usage limitations, feature availability, and pricing structure for agentic application deployment.
[LangGraph Platform Architecture](https://langchain-ai.github.io/langgraph/concepts/platform_architecture/): LLM should read this page when needing to understand LangGraph Platform's technical architecture or troubleshooting deployment issues. The page details how LangGraph Platform uses Postgres for persistent storage of user/run data and Redis for worker communication (run cancellation, output streaming) and ephemeral metadata storage (retry attempts).
[LangGraph's Runtime (Pregel)](https://langchain-ai.github.io/langgraph/concepts/pregel/): LLM should read this page when learning about LangGraph's runtime, implementing applications with Pregel directly, or understanding how LangGraph executes graph applications. Explains LangGraph's Pregel runtime which manages graph application execution through a three-phase process (Plan, Execution, Update), describes different channel types (LastValue, Topic, Context, BinaryOperatorAggregate), provides direct implementation examples, and contrasts the StateGraph API with the Functional API.
[LangGraph Platform: Scalability & Resilience](https://langchain-ai.github.io/langgraph/concepts/scalability_and_resilience/): LLM should read this page when needing to understand LangGraph Platform's scaling capabilities, designing high-availability LangGraph deployments, or troubleshooting resilience issues. This page details LangGraph Platform's horizontal scaling features including stateless server instances, queue worker scaling, resilience mechanisms for handling crashes, and database failover strategies in Postgres and Redis.
[LangGraph SDK](https://langchain-ai.github.io/langgraph/concepts/sdk/): LLM should read this page when looking for installation instructions for LangGraph SDK, needing to choose between sync and async Python clients, or requiring SDK API references. The page covers LangGraph SDK installation for Python and JS, provides API reference links, explains the difference between synchronous and asynchronous Python clients, and includes code examples for both client types.
[Self-Hosted](https://langchain-ai.github.io/langgraph/concepts/self_hosted/): LLM should read this page when looking for LangGraph deployment options, understanding self-hosted versions, or seeking requirements for self-hosting LangGraph. This page details two self-hosted deployment options for LangGraph Platform: Self-Hosted Lite (limited to 1M nodes/year) and Self-Hosted Enterprise (full version requiring license). Includes requirements, deployment process using Redis/Postgres, Docker, and optional Kubernetes deployment via Helm chart.
[Streaming](https://langchain-ai.github.io/langgraph/concepts/streaming/): LLM should read this page when implementing streaming features in LangGraph applications, understanding different streaming modes, or building responsive LLM applications. This page explains streaming in LangGraph, covering the main types (workflow progress, LLM tokens, custom updates) and streaming modes (values, updates, custom, messages, debug, events), with details on how to use multiple modes simultaneously and differences between LangGraph library and Platform implementations.
[Template Applications](https://langchain-ai.github.io/langgraph/concepts/template_applications/): LLM should read this page when looking for LangGraph template applications, setting up a new LangGraph project, or finding reference implementations for agentic workflows. This page presents LangGraph template applications with installation requirements, available templates (including ReAct Agent, Memory Agent, Retrieval Agent, etc.), instructions for creating new apps using the CLI, deployment options, and links to further learning resources.
[Time Travel ⏱️](https://langchain-ai.github.io/langgraph/concepts/time-travel/): LLM should read this page when debugging LLM-based agent behavior, analyzing decision-making paths, or exploring alternative execution branches in LangGraph. This page explains LangGraph's Time Travel debugging features: Replaying (reproducing past actions up to specific checkpoints) and Forking (creating alternative execution paths from specific points), with code examples for retrieving checkpoints, configuring replay, and creating forked states.
## How Tos
[How-to Guides](https://langchain-ai.github.io/langgraph/how-tos/): LLM should read this page when looking for specific implementation techniques in LangGraph or when trying to deploy LangGraph applications to production environments. This page contains an extensive collection of how-to guides for LangGraph, covering graph fundamentals, persistence, memory management, human-in-the-loop features, tool calling, multi-agent systems, streaming, and deployment options through LangGraph Platform.
[How to implement handoffs between agents](https://langchain-ai.github.io/langgraph/how-tos/agent-handoffs/): LLM should read this page when implementing multi-agent systems that require agent coordination, when building systems with specialized agents that need to work together, or when needing to implement handoffs between agents. This page explains how to implement handoffs between agents in LangGraph using Command objects, both directly from agent nodes and through specialized handoff tools, with code examples for creating multi-agent systems.
[How to run a graph asynchronously](https://langchain-ai.github.io/langgraph/how-tos/async/): LLM should read this page when needing to implement asynchronous graph execution in LangGraph or when optimizing IO-bound LLM applications. This page explains how to convert synchronous graphs to asynchronous in LangGraph, including updating node definitions with async/await, using StateGraph with TypedDict, implementing conditional edges, and streaming results.
[How to integrate LangGraph with AutoGen, CrewAI, and other frameworks](https://langchain-ai.github.io/langgraph/how-tos/autogen-integration/): LLM should read this page when integrating LangGraph with other agent frameworks, building multi-agent systems, or adding persistence features to agents. The page demonstrates how to combine LangGraph with AutoGen by calling AutoGen agents inside LangGraph nodes, showing code examples for setting up the integration with memory and conversation persistence.
[How to integrate LangGraph (functional API) with AutoGen, CrewAI, and other frameworks](https://langchain-ai.github.io/langgraph/how-tos/autogen-integration-functional/): LLM should read this page when integrating LangGraph with other agent frameworks, building multi-agent systems with different frameworks, or adding LangGraph features to existing agent systems. This page demonstrates how to integrate LangGraph's functional API with AutoGen, including code examples for creating a workflow that calls AutoGen agents, leveraging LangGraph's memory and persistence features.
[How to create branches for parallel node execution](https://langchain-ai.github.io/langgraph/how-tos/branching/): LLM should read this page when needing to implement parallel node execution in LangGraph, optimizing graph performance, or handling conditional branching in workflows. This page explains how to create branches for parallel execution in LangGraph using fan-out/fan-in mechanisms, reducer functions for state accumulation, handling exceptions during parallel execution, and implementing conditional branching logic between nodes.
[How to combine control flow and state updates with Command](https://langchain-ai.github.io/langgraph/how-tos/command): LLM should read this page when learning how to combine control flow with state updates in LangGraph, understanding Command objects, or navigating between parent graphs and subgraphs. This page explains how to use Command objects to simultaneously update state and control flow between nodes, demonstrates using Command.PARENT to navigate from subgraphs to parent graphs, and includes examples of implementing reducers for state updates across graph hierarchies.
[How to add runtime configuration to your graph](https://langchain-ai.github.io/langgraph/how-tos/configuration/): LLM should read this page when implementing runtime configuration for LangGraph, adding model selection options to agents, or enabling dynamic system messages. This page demonstrates how to configure LangGraph at runtime, including selecting different LLMs dynamically and adding custom configuration options like system messages through the configurable dictionary.
[How to use the pre-built ReAct agent](https://langchain-ai.github.io/langgraph/how-tos/create-react-agent/): LLM should read this page when implementing a ReAct agent, needing pre-built agent solutions, or learning how to integrate tools with LLM agents. This page covers how to use the pre-built ReAct agent in LangGraph, including setup instructions, creating a weather checking tool, implementing the agent architecture, and examples of running the agent with and without tool calls.
[How to add human-in-the-loop processes to the prebuilt ReAct agent](https://langchain-ai.github.io/langgraph/how-tos/create-react-agent-hitl/): LLM should read this page when implementing human-in-the-loop processes for ReAct agents, debugging tool calls, or learning about interrupts in LangGraph. This guide demonstrates how to add human-in-the-loop functionality to prebuilt ReAct agents using interrupt_before=["tools"], working with MemorySaver checkpoints, and showing how to approve or edit tool calls before they execute.
[How to add thread-level memory to a ReAct Agent](https://langchain-ai.github.io/langgraph/how-tos/create-react-agent-memory/): LLM should read this page when adding memory to ReAct agents, implementing thread-level persistence in LangGraph, or building stateful conversational agents. This guide demonstrates how to add memory to a ReAct agent using LangGraph's checkpointer interface, with code examples showing MemorySaver implementation, thread_id configuration, and persistent chat context across multiple interactions.
[How to return structured output from the prebuilt ReAct agent](https://langchain-ai.github.io/langgraph/how-tos/create-react-agent-structured-output/): LLM should read this page when implementing structured output with ReAct agents, customizing agent response formats, or working with LangGraph agents. This page explains how to return structured output from prebuilt ReAct agents by providing a response_format parameter with a Pydantic schema, including examples with weather data and options for customizing the prompt.
[How to add a custom system prompt to the prebuilt ReAct agent](https://langchain-ai.github.io/langgraph/how-tos/create-react-agent-system-prompt/): LLM should read this page when learning to customize ReAct agents, needing to add system prompts to agents, or working with LangGraph's prebuilt agents. This tutorial demonstrates how to add a custom system prompt to a prebuilt ReAct agent, with code examples showing model setup, tool creation, and using the prompt parameter in the create_react_agent function.
[How to add cross-thread persistence to your graph](https://langchain-ai.github.io/langgraph/how-tos/cross-thread-persistence): LLM should read this page when needing to implement persistence across multiple threads in LangGraph, when storing user data between conversations, or when implementing shared memory in graph-based LLM applications. This page demonstrates how to use LangGraph's Store API to persist data across threads, including creating an InMemoryStore with embedding search capabilities, passing stores to graph nodes, and accessing user-specific memories in different conversation threads.
[How to add cross-thread persistence (functional API)](https://langchain-ai.github.io/langgraph/how-tos/cross-thread-persistence-functional): LLM should read this page when needing to implement cross-thread persistence in LangGraph functional API, storing user data across different conversation threads, or creating shared memory between workflows. This page explains how to add cross-thread persistence to LangGraph using the Store interface, including defining a store, configuring the entrypoint decorator, and implementing a workflow that can store and retrieve user information across different conversation threads.
[How to do a Self-hosted deployment of LangGraph](https://langchain-ai.github.io/langgraph/how-tos/deploy-self-hosted/): LLM should read this page when implementing a self-hosted deployment of LangGraph, configuring required environment variables, or building Docker images for LangGraph applications. This page explains how to deploy LangGraph applications using Docker, covering environment requirements (Redis, Postgres), how to build Docker images with the LangGraph CLI, configuration using environment variables, and deployment options using Docker or Docker Compose.
[How to disable streaming for models that don't support it](https://langchain-ai.github.io/langgraph/how-tos/disable-streaming/): LLM should read this page when handling models that don't support streaming, implementing LangGraph with non-streaming models, or troubleshooting streaming errors with OpenAI's O1 models. This page explains how to use the disable_streaming=True parameter with ChatOpenAI to make non-streaming models work with LangGraph's astream_events API, with code examples showing the error case and proper implementation.
[How to edit graph state](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/edit-graph-state/): LLM should read this page when needing to implement human intervention in LangGraph workflows, wanting to edit graph state during execution, or implementing breakpoints in agent systems. This page explains how to edit graph state in LangGraph using breakpoints, including implementing human-in-the-loop interactions, setting up interruptions before specific nodes, and updating state during agent execution.
[How to Review Tool Calls](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/review-tool-calls/): LLM should read this page when implementing human review of tool calls, creating interactive agent workflows, or building approval systems for AI actions. This page explains how to implement human-in-the-loop review for tool calls in LangGraph, including approving tool calls, modifying tool calls manually, and providing natural language feedback to agents with complete code examples and explanations.
[How to view and update past graph state](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/time-travel/): LLM should read this page when needing to access or modify past states in LangGraph, when debugging agent execution, or when implementing user interventions in agent workflows. This page demonstrates how to view and update past graph states in LangGraph using get_state and update_state methods, with examples of replaying execution from checkpoints and branching workflows.
[How to wait for user input using interrupt](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/wait-user-input/): LLM should read this page when implementing wait-for-user functions in LangGraph, implementing human-in-the-loop interactions, or learning how to use the interrupt() function. This page explains how to pause graph execution to collect user input using LangGraph's interrupt() function, with examples of simple feedback collection and more complex agent interactions that ask clarifying questions.
[How to define input/output schema for your graph](https://langchain-ai.github.io/langgraph/how-tos/input_output_schema/): LLM should read this page when needing to define separate input/output schemas for LangGraph, implementing schema-based data filtering, or understanding schema definitions in StateGraph. This page explains how to define distinct input and output schemas for a StateGraph, showing how input schema validates the provided data structure while output schema filters internal data to return only relevant information, with code examples demonstrating implementation.
[How to handle large numbers of tools](https://langchain-ai.github.io/langgraph/how-tos/many-tools/): LLM should read this page when handling large tool collections, implementing dynamic tool selection, or creating retrieval-based tool management in LangGraph. This page demonstrates how to manage large numbers of tools by using vector search to dynamically select relevant tools based on user queries, implementing tool selection nodes in LangGraph, and handling tool selection errors with retry mechanisms.
[How to create map-reduce branches for parallel execution](https://langchain-ai.github.io/langgraph/how-tos/map-reduce/): LLM should read this page when learning to implement parallel execution in LangGraph, creating map-reduce operations, or handling dynamic task decomposition. This guide explains how to use LangGraph's Send API to create map-reduce workflows, breaking tasks into parallel sub-tasks and recombining results, with examples showing joke generation across multiple subjects.
[How to add summary of the conversation history](https://langchain-ai.github.io/langgraph/how-tos/memory/add-summary-conversation-history/): LLM should read this page when implementing conversation summarization, managing context windows, or building chatbots with memory management. This page demonstrates how to add summary functionality to conversation history using LangGraph, including checking conversation length, creating summaries, and removing old messages while maintaining context.
[How to delete messages](https://langchain-ai.github.io/langgraph/how-tos/memory/delete-messages): LLM should read this page when attempting to manage message history in LangGraph, needing to delete specific messages from conversational state, or implementing memory management in LLM applications. This page explains how to delete messages from a LangGraph application using RemoveMessage modifiers, covering both manual deletion with message IDs and programmatic deletion within graph logic to maintain conversation history limits.
[How to manage conversation history](https://langchain-ai.github.io/langgraph/how-tos/memory/manage-conversation-history/): LLM should read this page when managing conversation history in LangGraph, preventing context window issues, or implementing custom message filtering. This page explains how to manage conversation history in LangGraph to prevent context window overflow by implementing message filtering functions that control which messages are sent to the LLM.
[How to add semantic search to your agent's memory](https://langchain-ai.github.io/langgraph/how-tos/memory/semantic-search/): LLM should read this page when implementing semantic search in agent memory, enabling memory-aware AI assistants, or configuring advanced memory retrieval systems. This page demonstrates how to add semantic search to LangGraph agent memory stores, covering basic setup with embeddings, storing memories, searching by semantic similarity, integrating memory in agents and ReAct agents, and advanced usage like multi-vector indexing and selective memory indexing.
[How to add multi-turn conversation in a multi-agent application](https://langchain-ai.github.io/langgraph/how-tos/multi-agent-multi-turn-convo/): LLM should read this page when implementing multi-turn conversations between agents, creating interactive agent systems with human input, or learning about langgraph interrupts and agent handoffs. This page demonstrates how to build a multi-agent system with multi-turn conversations, including human-in-the-loop interactions, agent handoffs, and state management using LangGraph, Command objects, and interrupts.
[How to add multi-turn conversation in a multi-agent application (functional API)](https://langchain-ai.github.io/langgraph/how-tos/multi-agent-multi-turn-convo-functional/): LLM should read this page when building multi-turn conversational agents, implementing agent-to-agent handoffs, or using interrupts to collect user input in LangGraph. This guide demonstrates how to create a multi-agent system with multi-turn conversations using LangGraph's functional API, featuring agent handoffs, interrupt mechanics for user input, and a complete example of travel and hotel advisor agents that can transfer control between each other.
[How to build a multi-agent network](https://langchain-ai.github.io/langgraph/how-tos/multi-agent-network/): LLM should read this page when implementing multi-agent networks, setting up agent communication via handoffs, or building travel assistance agents. This page explains how to create a fully-connected multi-agent network with LangGraph where agents can communicate with each other via handoffs, including custom agent implementation and using prebuilt ReAct agents with tools.
[How to build a multi-agent network (functional API)](https://langchain-ai.github.io/langgraph/how-tos/multi-agent-network-functional/): LLM should read this page when building multi-agent systems, implementing agent handoffs between specialists, or creating fully-connected agent networks. This guide demonstrates how to create a multi-agent network using LangGraph's functional API, with tasks for individual agents and entrypoint functions to manage agent handoffs based on tool calls.
[How to add node retry policies](https://langchain-ai.github.io/langgraph/how-tos/node-retries/): LLM should read this page when implementing error handling in LangGraph nodes, configuring API retry mechanisms, or troubleshooting node failures in graph workflows. Shows how to add custom retry policies to LangGraph nodes, including specifying which exceptions to retry on, setting max attempts, intervals, backoff factors, and implementing different retry behaviors for different node types.
[How to pass config to tools](https://langchain-ai.github.io/langgraph/how-tos/pass-config-to-tools/): LLM should read this page when implementing secure tool configuration in LangChain, passing user-specific parameters to tools, or configuring tools with runtime values. This page explains how to pass configuration to LangChain tools using RunnableConfig, allowing application-controlled values (like user IDs) to be securely passed to tools without LLM control, with examples of implementing tools that access user-specific data.
[How to pass private state between nodes](https://langchain-ai.github.io/langgraph/how-tos/pass_private_state/): LLM should read this page when implementing data sharing between specific nodes in LangGraph, handling private state in graph workflows, or designing multi-node sequential processes with selective data visibility. This page demonstrates how to pass private data between specific nodes in a LangGraph without making it part of the main schema, using typed dictionaries to define both public and private states, and showing a three-node example where private data flows only between the first two nodes.
[How to add thread-level persistence to your graph](https://langchain-ai.github.io/langgraph/how-tos/persistence/): LLM should read this page when implementing persistence in LangGraph, needing to preserve context across user interactions, or learning about thread-level state management. This page explains how to add thread-level persistence to LangGraph applications using MemorySaver, including code examples for creating stateful conversations where context is maintained across multiple interactions.
[How to add thread-level persistence (functional API)](https://langchain-ai.github.io/langgraph/how-tos/persistence-functional/): LLM should read this page when implementing thread-level persistence in LangGraph, creating conversational agents with memory, or using functional API with state management. This page explains how to add thread-level persistence to LangGraph functional API workflows using checkpointers, including code examples for creating a simple chatbot with memory across conversation turns.
[How to use MongoDB checkpointer for persistence](https://langchain-ai.github.io/langgraph/how-tos/persistence_mongodb/): LLM should read this page when implementing persistence in LangGraph agents, setting up MongoDB for state checkpointing, or working with MongoDB connections in LangGraph applications. This page explains how to use the MongoDB checkpointer for LangGraph persistence, covering connection methods (direct, client-based, async), basic setup requirements, and practical examples of saving and retrieving agent state between interactions.
[How to use Postgres checkpointer for persistence](https://langchain-ai.github.io/langgraph/how-tos/persistence_postgres/): LLM should read this page when setting up persistence for LangGraph agents, implementing PostgreSQL as a checkpoint storage backend, or working with either synchronous or asynchronous database connections. This page details how to use PostgreSQL for persisting LangGraph agent state, covering setup and configuration of PostgresSaver and AsyncPostgresSaver with different connection methods (pool, direct connection, connection string).
[How to create a custom checkpointer using Redis](https://langchain-ai.github.io/langgraph/how-tos/persistence_redis/): LLM should read this page when implementing persistence in LangGraph applications, creating custom checkpoint mechanisms for agents, or working with Redis as a storage backend. This page demonstrates how to create custom checkpointers for LangGraph agents using Redis, including implementations for both synchronous and asynchronous interfaces that save and retrieve agent state.
[How to create a ReAct agent from scratch](https://langchain-ai.github.io/langgraph/how-tos/react-agent-from-scratch/): LLM should read this page when needing to create a custom ReAct agent, wanting more control than prebuilt agents, or implementing ReAct from scratch with LangGraph. This guide shows how to build a custom ReAct agent using LangGraph, covering state definition, model/tool setup, node/edge configuration, graph creation, and testing the implementation with a weather query example.
[How to create a ReAct agent from scratch (Functional API)](https://langchain-ai.github.io/langgraph/how-tos/react-agent-from-scratch-functional): LLM should read this page when creating a ReAct agent using LangGraph's Functional API, implementing tool-calling workflows, or building conversational agents with thread persistence. This page explains how to build a ReAct agent from scratch using LangGraph's Functional API, including model and tool setup, defining tasks for model/tool calling, creating an entrypoint for orchestration, and adding thread-level persistence for conversational experiences.
[How to force tool-calling agent to structure output](https://langchain-ai.github.io/langgraph/how-tos/react-agent-structured-output): LLM should read this page when needing to force tool-calling agents to produce structured output, implementing consistent output formats for downstream software, or choosing between single-LLM vs two-LLM structured output approaches. The page explains two methods for implementing structured output with tool-calling agents: binding output as a tool (single LLM approach) and using two LLMs with structured output conversion, with code examples for both approaches using LangGraph.
[How to create and control loops](https://langchain-ai.github.io/langgraph/how-tos/recursion-limit/): LLM should read this page when building loops in computational graphs, needing to implement termination conditions, or handling recursion limits in LangGraph. The page explains how to create graphs with loops using conditional edges for termination, set recursion limits, handle GraphRecursionError, and implement complex loops with branches.
[How to review tool calls (Functional API)](https://langchain-ai.github.io/langgraph/how-tos/review-tool-calls-functional/): LLM should read this page when implementing human review of tool calls, creating ReAct agents with Functional API, or adding human-in-the-loop workflows. This page demonstrates how to review tool calls before execution in a ReAct agent using LangGraph's Functional API, including accepting, revising, or generating custom tool messages with the interrupt function.
[How to pass custom run ID or set tags and metadata for graph runs in LangSmith](https://langchain-ai.github.io/langgraph/how-tos/run-id-langsmith/): LLM should read this page when needing to customize trace information in LangSmith for LangGraph runs or when debugging graph runs with custom identifiers. The page explains how to pass custom run_id, set tags, add metadata, and customize run names for LangGraph traces in LangSmith using RunnableConfig, with examples showing implementation with a ReAct agent.
[How to create a sequence of steps](https://langchain-ai.github.io/langgraph/how-tos/sequence/): LLM should read this page when implementing sequential workflows in LangGraph, creating multi-step processes in applications, or learning about state management in graph-based systems. This page explains how to create sequences in LangGraph, covering methods for building sequential graphs using .add_node/.add_edge or the shorthand .add_sequence, defining state with TypedDict, creating nodes as functions that update state, and compiling/invoking graphs with examples.
[How to use Pydantic model as graph state](https://langchain-ai.github.io/langgraph/how-tos/state-model): LLM should read this page when implementing Pydantic models for state validation in LangGraph, handling complex state schema definitions, or troubleshooting validation errors in graph nodes. This guide explains how to use Pydantic BaseModel as a state schema in LangGraph for runtime validation, covering basic implementation, limitations, validation behavior across multiple nodes, serialization patterns, type coercion, and working with message models.
[How to update graph state from nodes](https://langchain-ai.github.io/langgraph/how-tos/state-reducers/): LLM should read this page when needing to update state in LangGraph, designing graphs with nodes that modify state, or implementing reducers for state management. This page explains how to define state schemas in LangGraph using TypedDict, how nodes can update state, and how to use reducers to control state updates, with specific examples using message handling.
[How to stream](https://langchain-ai.github.io/langgraph/how-tos/streaming/): LLM should read this page when needing to implement streaming in LangGraph applications, understanding different streaming modes, or troubleshooting LLM response delivery. This page explains how to stream LLM outputs using LangGraph, covering different streaming modes (values, updates, custom, messages, debug), with code examples for each mode and how to combine multiple streaming modes.
[How to stream data from within a tool](https://langchain-ai.github.io/langgraph/how-tos/streaming-events-from-within-tools/): LLM should read this page when implementing streaming functionality in tools, integrating LLM outputs with custom data streams, or developing LangGraph applications with real-time feedback. This page explains how to stream data from within tools using LangGraph, covering custom data streaming with stream_mode="custom", LLM token streaming with stream_mode="messages", and implementation approaches both with and without LangChain.
[How to stream LLM tokens from specific nodes](https://langchain-ai.github.io/langgraph/how-tos/streaming-specific-nodes/): LLM should read this page when needing to filter token streaming from specific nodes in LangGraph, implementing selective streaming in multi-node workflows, or controlling which node outputs are displayed. Guide explains how to stream LLM tokens from specific nodes using stream_mode="messages" and filtering by the langgraph_node metadata field, with complete code examples for implementing this in StateGraph applications.
[How to stream from subgraphs](https://langchain-ai.github.io/langgraph/how-tos/streaming-subgraphs/): LLM should read this page when needing to stream outputs from subgraphs in LangGraph, implementing nested graph streaming, or debugging hierarchical graph execution. This page explains how to stream outputs from subgraphs in LangGraph by using the subgraphs=True parameter in the parent graph's stream() method, with a complete code example showing the difference between regular streaming and subgraph streaming.
[How to stream LLM tokens from your graph](https://langchain-ai.github.io/langgraph/how-tos/streaming-tokens): LLM should read this page when needing to stream LLM tokens from a LangGraph application, implementing custom token streaming, or filtering streamed outputs. This page explains how to stream individual LLM tokens from LangGraph nodes using graph.stream() with different stream_mode options, including examples with and without LangChain, async implementations, and how to filter streamed tokens using metadata.
[How to use subgraphs](https://langchain-ai.github.io/langgraph/how-tos/subgraph/): LLM should read this page when building complex systems with subgraphs, implementing multi-agent systems, or needing to share state between parent graphs and subgraphs. The page explains two methods for using subgraphs: adding compiled subgraphs when schemas share keys, and invoking subgraphs via node functions when schemas differ, with code examples for both approaches.
[How to add thread-level persistence to a subgraph](https://langchain-ai.github.io/langgraph/how-tos/subgraph-persistence/): LLM should read this page when implementing persistence in nested LangGraph architectures, adding thread-level storage to subgraphs, or debugging state propagation in LangGraph applications. This guide demonstrates how to add thread-level persistence to subgraphs by passing a checkpointer only to the parent graph during compilation, accessing persisted states from both parent and child graphs, and retrieving subgraph state using the proper configuration parameters.
[How to transform inputs and outputs of a subgraph](https://langchain-ai.github.io/langgraph/how-tos/subgraph-transform-state/): LLM should read this page when needing to work with nested subgraphs, transforming state between parent and child graphs, or integrating independent state components in LangGraph. This page demonstrates how to transform inputs and outputs between parent graphs and subgraphs with different state structures, showing implementation of three nested graphs (parent, child, grandchild) with separate state dictionaries and transformation functions.
[How to view and update state in subgraphs](https://langchain-ai.github.io/langgraph/how-tos/subgraphs-manage-state/): LLM should read this page when working with state management in nested subgraphs, implementing human-in-the-loop patterns, or debugging complex graph flows. This guide covers viewing and updating state in LangGraph subgraphs, including how to resume execution from breakpoints, modify subgraph state, act as specific nodes, and work with multi-level nested subgraphs.
[How to call tools using ToolNode](https://langchain-ai.github.io/langgraph/how-tos/tool-calling/): LLM should read this page when learning how to implement tool calling with LangGraph, when working with the ToolNode component, or when building ReAct agents. This page covers using LangGraph's ToolNode for tool calling, including setup, manual invocation, working with chat models, building a ReAct agent, handling single and parallel tool calls, and error handling.
[How to handle tool calling errors](https://langchain-ai.github.io/langgraph/how-tos/tool-calling-errors/): LLM should read this page when handling tool call errors, implementing error handling for LLM-tool interactions, or creating fallback strategies for failed tool calls. This page covers strategies for handling tool calling errors in LangGraph, including using the prebuilt ToolNode with built-in error handling, implementing custom error handling patterns, and fallback mechanisms with model upgrades when tools fail.
[How to update graph state from tools](https://langchain-ai.github.io/langgraph/how-tos/update-state-from-tools/): LLM should read this page when needing to update graph state from tools in LangGraph, implementing personalized responses based on tool updates, or using Command objects to modify state. This page details how to update graph state from tools using Command objects, creating personalized agents with state tracking, and implementing dynamic prompt construction based on updated state values.
[How to interact with the deployment using RemoteGraph](https://langchain-ai.github.io/langgraph/how-tos/use-remote-graph/): LLM should read this page when needing to interact with LangGraph Platform deployments remotely, when implementing RemoteGraph interfaces, or when using deployed graphs as subgraphs. This page explains how to use RemoteGraph to interact with LangGraph Platform deployments, covering initialization methods (URL-based or client-based), synchronous/asynchronous invocation, thread-level persistence, and using RemoteGraph as a subgraph in larger applications.
[How to visualize your graph](https://langchain-ai.github.io/langgraph/how-tos/visualization): LLM should read this page when needing to visualize LangGraph graphs, looking for graph visualization methods, or working with graph visualization in Python. Comprehensive guide for visualizing graphs in LangGraph with multiple methods: Mermaid syntax, Mermaid.ink API for PNG rendering, Pyppeteer-based visualization, and Graphviz, with customization options for colors, styles, and layout.
[How to wait for user input (Functional API)](https://langchain-ai.github.io/langgraph/how-tos/wait-user-input-functional/): LLM should read this page when implementing human-in-the-loop workflows, integrating user input into agent systems, or adding interruption capabilities to LangGraph applications. The page explains how to use the `interrupt()` function in LangGraph's Functional API to pause execution for human input, with examples for both simple workflows and ReAct agents, including code implementations with checkpointing.
# Guides
- [LangGraph Documentation](https://langchain-ai.github.io/langgraph/index/): This page provides an overview of the LangGraph project, including its logo and essential scripts for functionality within MkDocs. It also includes a reference to the README.md file for detailed information about the project. The content is designed to be user-friendly and visually appealing.
- [LangGraph Quickstart Guide](https://langchain-ai.github.io/langgraph/agents/agents/): This quickstart guide provides step-by-step instructions for setting up and using LangGraph's prebuilt components to create agentic systems. It covers prerequisites, installation, agent creation, configuration of language models, and advanced features like memory and structured output. Ideal for developers looking to leverage LangGraph for building intelligent agents.
- [Getting Started with LangGraph: Building AI Agents](https://langchain-ai.github.io/langgraph/concepts/why-langgraph/): This page provides an overview of LangGraph, a platform designed for developers to create adaptable AI agents. It highlights key features such as reliability, extensibility, and streaming support, and offers a series of tutorials to help users build a support chatbot with various capabilities. By following the tutorials, developers will learn to implement essential functionalities like conversation state management and human-in-the-loop controls.
- [Building a Basic Chatbot with LangGraph](https://langchain-ai.github.io/langgraph/tutorials/get-started/1-build-basic-chatbot/): This tutorial guides you through the process of creating a basic chatbot using LangGraph. It covers prerequisites, installation of necessary packages, and step-by-step instructions to set up a state machine for the chatbot. By the end of the tutorial, you will have a functional chatbot that can engage in simple conversations.
- [Integrating Web Search Tools into Your Chatbot](https://langchain-ai.github.io/langgraph/tutorials/get-started/2-add-tools/): This tutorial guides you through the process of enhancing your chatbot's capabilities by integrating a web search tool, specifically the Tavily Search Engine. It covers prerequisites, installation, configuration, and the implementation of the search tool within a LangGraph-based chatbot. By the end, you'll have a functional chatbot that can retrieve real-time information to answer user queries beyond its training data.
- [Implementing Memory in Chatbots with LangGraph](https://langchain-ai.github.io/langgraph/tutorials/get-started/3-add-memory/): This page provides a comprehensive guide on how to add memory functionality to chatbots using LangGraph's persistent checkpointing feature. It details the steps to create a `MemorySaver` checkpointer, compile the graph, and interact with the chatbot to maintain context across multiple interactions. Additionally, it explains how to inspect the state of the chatbot and highlights the advantages of checkpointing over simple memory solutions.
- [Implementing Human-in-the-Loop Controls in LangGraph](https://langchain-ai.github.io/langgraph/tutorials/get-started/4-human-in-the-loop/): This page provides a comprehensive guide on adding human-in-the-loop controls to LangGraph workflows, enabling agents to pause execution for human input. It details the use of the `interrupt` function to facilitate user feedback and outlines the steps to integrate a `human_assistance` tool into a chatbot. Additionally, the tutorial covers graph compilation, visualization, and resuming execution with human input.
- [Customizing State in LangGraph for Enhanced Chatbot Functionality](https://langchain-ai.github.io/langgraph/tutorials/get-started/5-customize-state/): This tutorial guides you through the process of adding custom fields to the state in LangGraph, enabling complex behaviors in your chatbot without relying solely on message lists. You will learn how to implement human-in-the-loop controls to verify information before it is stored in the state. By the end of this tutorial, you will have a deeper understanding of state management and how to enhance your chatbot's capabilities.
- [Implementing Time Travel in LangGraph Chatbots](https://langchain-ai.github.io/langgraph/tutorials/get-started/6-time-travel/): This page provides a comprehensive guide on utilizing the time travel functionality in LangGraph to enhance chatbot interactions. It covers how to rewind, add steps, and replay the state history of a chatbot, allowing users to explore different outcomes and fix mistakes. Additionally, it includes code snippets and practical examples to help developers implement these features effectively.
- [LangGraph Deployment Options](https://langchain-ai.github.io/langgraph/tutorials/deployment/): This page outlines the various options available for deploying LangGraph applications, including local testing and different cloud-based solutions. It details free deployment methods such as Local and Standalone Container (Lite), as well as production options like Cloud SaaS and self-hosted solutions. Each deployment method is linked to further documentation for in-depth guidance.
- [Agent Development with LangGraph](https://langchain-ai.github.io/langgraph/agents/overview/): This page provides an overview of agent development using LangGraph, highlighting its prebuilt components and capabilities for building agent-based applications. It explains the structure of an agent, key features such as memory integration and human-in-the-loop control, and outlines the package ecosystem available for developers. With LangGraph, users can focus on application logic while leveraging robust infrastructure for state management and feedback.
- [Guide to Running Agents in LangGraph](https://langchain-ai.github.io/langgraph/agents/run_agents/): This page provides a comprehensive overview of how to execute agents in LangGraph, detailing both synchronous and asynchronous methods. It covers input and output formats, streaming capabilities, and how to manage execution limits to prevent infinite loops. Additionally, it includes code examples and links to further resources for deeper understanding.
- [Streaming Data in LangGraph](https://langchain-ai.github.io/langgraph/agents/streaming/): This page provides an overview of streaming data types in LangGraph, including agent progress, LLM tokens, and custom updates. It includes code examples for both synchronous and asynchronous streaming methods. Additionally, it covers how to stream multiple modes and disable streaming when necessary.
- [Configuring Chat Models for Agents](https://langchain-ai.github.io/langgraph/agents/models/): This page provides detailed instructions on how to configure various chat models for use with agents in LangChain. It covers model initialization, tool calling support, and how to specify models from different providers such as OpenAI, Anthropic, Azure, Google Gemini, and AWS Bedrock. Additionally, it includes information on disabling streaming, adding model fallbacks, and links to further resources.
- [Using Tools in LangChain](https://langchain-ai.github.io/langgraph/agents/tools/): This page provides an overview of how to define, customize, and manage tools within the LangChain framework. It covers creating simple tools, handling tool errors, and utilizing prebuilt integrations for enhanced functionality. Additionally, it discusses advanced features such as memory management and controlling tool behavior during agent execution.
- [Integrating MCP with LangGraph Agents](https://langchain-ai.github.io/langgraph/agents/mcp/): This page provides a comprehensive guide on how to integrate the Model Context Protocol (MCP) with LangGraph agents using the `langchain-mcp-adapters` library. It includes installation instructions, example code for using MCP tools, and guidance on creating custom MCP servers. Additional resources for further reading on MCP are also provided.
- [Understanding Context in LangGraph Agents](https://langchain-ai.github.io/langgraph/agents/context/): This page provides an overview of how to supply context to agents in LangGraph, detailing the three primary types: Config, State, and Long-Term Memory. It explains how to use these context types to enhance agent behavior, customize prompts, and access context in tools. Additionally, it includes code examples for implementing context in various scenarios.
- [Understanding Memory in LangGraph for Conversational Agents](https://langchain-ai.github.io/langgraph/agents/memory/): This documentation page provides an overview of the two types of memory supported by LangGraph: short-term and long-term memory. It explains how to implement these memory types in conversational agents, including code examples and best practices for managing message history. Additionally, it covers the use of persistent storage and tools for enhancing memory functionality.
- [Implementing Human-in-the-Loop in LangGraph](https://langchain-ai.github.io/langgraph/agents/human-in-the-loop/): This documentation page provides a comprehensive guide on how to implement Human-in-the-Loop (HIL) features in LangGraph, allowing for human review and approval of tool calls in agents. It covers the use of the `interrupt()` function to pause execution for human input, along with practical examples and code snippets. Additionally, it explains how to create a wrapper to add HIL capabilities to any tool seamlessly.
- [Building Multi-Agent Systems](https://langchain-ai.github.io/langgraph/agents/multi-agent/): This page provides an overview of multi-agent systems, detailing how to create and manage them using supervisor and swarm architectures. It includes practical examples of implementing a flight and hotel booking assistant using the LangGraph libraries. Additionally, the page explains the concept of handoffs between agents, allowing for seamless communication and task delegation.
- [Evaluating Agent Performance with LangSmith](https://langchain-ai.github.io/langgraph/agents/evals/): This page provides a comprehensive guide on how to evaluate the performance of agents using the LangSmith evaluations framework. It includes instructions on defining evaluator functions, utilizing prebuilt evaluators from the AgentEvals package, and running evaluations with specific datasets. Additionally, it covers different evaluation techniques, including trajectory matching and using LLMs as judges.
- [Deploying Your LangGraph Agent](https://langchain-ai.github.io/langgraph/agents/deployment/): This page provides a comprehensive guide on how to deploy a LangGraph agent, including setting up a LangGraph app for both local development and production. It covers essential features, installation steps, and configuration requirements, along with instructions for launching the local server and utilizing the LangGraph Studio Web UI for debugging. Additionally, it offers links to further resources for deployment options.
- [Agent Chat UI Documentation](https://langchain-ai.github.io/langgraph/agents/ui/): This page provides comprehensive guidance on using the Agent Chat UI for interacting with LangGraph agents. It covers setup instructions, features like human-in-the-loop workflows, and the integration of generative UI components. Users can find links to relevant resources and tips for customizing their chat experience.
- [Overview of Agent Architectures in LLM Applications](https://langchain-ai.github.io/langgraph/concepts/agentic_concepts/): This page provides a comprehensive overview of various agent architectures used in large language model (LLM) applications, highlighting their control flows and functionalities. It discusses key concepts such as routers, tool-calling agents, memory management, and planning, along with customization options for specific tasks. Additionally, it covers advanced features like human-in-the-loop, parallelization, subgraphs, and reflection mechanisms to enhance agent performance.
- [Understanding Workflows and Agents in LangGraph](https://langchain-ai.github.io/langgraph/tutorials/workflows/): This documentation page provides an in-depth overview of workflows and agents within LangGraph, highlighting their differences and use cases. It covers various patterns for building agentic systems, including setup instructions, building blocks, and advanced concepts like prompt chaining, parallelization, and routing. Additionally, it offers practical examples and code snippets to help users implement these workflows effectively.
- [Understanding LangGraph: Core Concepts and Components](https://langchain-ai.github.io/langgraph/concepts/low_level/): This documentation page provides an in-depth overview of the core concepts of LangGraph, focusing on how agent workflows are modeled as graphs. It covers essential components such as States, Nodes, and Edges, and explains how they interact to create complex workflows. Additionally, it discusses graph compilation, message handling, and configuration options to enhance the functionality of your graphs.
- [LangGraph Runtime Overview](https://langchain-ai.github.io/langgraph/concepts/pregel/): This page provides a comprehensive overview of the LangGraph runtime, specifically focusing on the Pregel execution model. It details the structure and functionality of actors and channels within the Pregel framework, along with examples of how to implement applications. Additionally, it introduces high-level APIs for creating Pregel applications using StateGraph and Functional API.
- [Using the LangGraph API: A Comprehensive Guide](https://langchain-ai.github.io/langgraph/how-tos/graph-api/): This documentation provides a detailed overview of how to utilize the LangGraph Graph API, covering essential concepts such as state management, node creation, and control flow. It includes practical examples for building sequences, branches, and loops, as well as advanced features like retry policies and async execution. Additionally, the guide offers insights into visualizing graphs and integrating with external tools.
- [LangGraph Streaming System](https://langchain-ai.github.io/langgraph/concepts/streaming/): This page provides an overview of the streaming capabilities of LangGraph, enabling real-time updates for enhanced user experiences. It details the types of data that can be streamed, including workflow progress, LLM tokens, and custom updates. Additionally, it outlines various functionalities and modes available for streaming within the LangGraph framework.
- [Streaming Outputs in LangGraph](https://langchain-ai.github.io/langgraph/how-tos/streaming/): This documentation page provides an overview of how to utilize the streaming capabilities of LangGraph, including synchronous and asynchronous streaming methods. It covers various stream modes, such as updates, values, and custom data, along with examples of how to implement them in your graphs. Additionally, it discusses the integration of Large Language Models (LLMs) and how to handle streaming outputs effectively.
- [LangGraph Persistence and Checkpointing](https://langchain-ai.github.io/langgraph/concepts/persistence/): This page provides an in-depth overview of the persistence layer in LangGraph, focusing on the use of checkpointers to save graph states at each super-step. It covers key concepts such as threads, checkpoints, state retrieval, and memory management, along with practical examples and code snippets. Additionally, it discusses advanced features like time travel, fault tolerance, and the integration of memory stores for cross-thread information retention.
- [Understanding Durable Execution in LangGraph](https://langchain-ai.github.io/langgraph/concepts/durable_execution/): This page provides an overview of durable execution, a technique that allows workflows to save their progress and resume from key points. It details the requirements for implementing durable execution in LangGraph, including the use of persistence and tasks to ensure deterministic and consistent replay. Additionally, it covers how to handle pausing, resuming, and recovering workflows effectively.
- [Implementing Memory in LangGraph for AI Applications](https://langchain-ai.github.io/langgraph/how-tos/persistence/): This documentation page provides a comprehensive guide on adding persistence to AI applications using LangGraph. It covers both short-term and long-term memory implementations, including code examples for managing conversation context and user-specific data. Additionally, it discusses the use of various storage backends and semantic search capabilities for enhanced memory management.
- [Understanding Memory in AI Agents](https://langchain-ai.github.io/langgraph/concepts/memory/): This documentation page provides an in-depth overview of memory types in AI agents, focusing on short-term and long-term memory. It explains how these memory types can be implemented and managed within applications using LangGraph, including techniques for handling conversation history and storing memories. Additionally, it discusses the importance of memory in enhancing user interactions and the various strategies for writing and updating memories.
- [Memory Management in LangGraph for AI Applications](https://langchain-ai.github.io/langgraph/how-tos/memory/): This page provides an overview of memory management in LangGraph, focusing on short-term and long-term memory functionalities essential for conversational agents. It includes detailed instructions on how to implement memory strategies such as trimming, summarizing, and deleting messages to optimize conversation tracking without exceeding context limits. Code examples are provided to illustrate the implementation of these memory management techniques.
- [Human-in-the-Loop Workflows in LangGraph](https://langchain-ai.github.io/langgraph/concepts/human_in_the_loop/): This page provides an overview of the human-in-the-loop (HIL) capabilities within LangGraph, highlighting how human intervention can enhance automated processes. It details key features such as persistent execution state and flexible integration points, along with typical use cases for validating outputs and providing context. Additionally, it outlines the implementation of HIL through specific functions and primitives.
- [Implementing Human-in-the-Loop Workflows with Interrupts](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/add-human-in-the-loop/): This documentation page provides a comprehensive guide on using the `interrupt` function in LangGraph to facilitate human-in-the-loop workflows. It covers the implementation details, design patterns, and best practices for pausing graph execution to gather human input, as well as how to resume execution with that input. Additionally, it highlights common pitfalls and offers extended examples to illustrate various use cases.
- [Understanding Breakpoints in LangGraph](https://langchain-ai.github.io/langgraph/concepts/breakpoints/): This page provides an overview of breakpoints in LangGraph, which allow users to pause graph execution at specific points for inspection. It explains how breakpoints utilize the persistence layer to save the graph state and how execution can be resumed after inspection. An illustrative example is included to demonstrate the concept visually.
- [Using Breakpoints in Graph Execution](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/breakpoints/): This page provides a comprehensive guide on how to implement breakpoints in graph execution for debugging purposes. It covers the requirements for setting breakpoints, the difference between static and dynamic breakpoints, and includes code examples for both compile-time and run-time configurations. Additionally, it explains how to manage breakpoints in subgraphs.
- [Time Travel Functionality in LangGraph](https://langchain-ai.github.io/langgraph/concepts/time-travel/): This page explains the time travel feature in LangGraph, which allows users to analyze and debug decision-making processes in non-deterministic systems. It outlines how to understand reasoning, debug mistakes, and explore alternative solutions by resuming execution from prior checkpoints. The functionality enables users to create new forks in the execution history for deeper insights.
- [Using Time-Travel in LangGraph](https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/time-travel/): This page provides a comprehensive guide on how to implement time-travel functionality in LangGraph. It outlines the steps to run a graph, identify checkpoints, modify graph states, and resume execution from specific checkpoints. Additionally, an example workflow is included to illustrate the process of generating and modifying jokes using LangGraph.
- [Integrating Tools with AI Models](https://langchain-ai.github.io/langgraph/concepts/tools/): This page provides an overview of how AI models can interact with external systems using tool calling. It explains the concept of tools, their integration with chat models, and how to create or use prebuilt tools for various applications. Additionally, it highlights the importance of relevance in tool invocation and offers links to further resources and guides.
- [Using Tools in LangChain](https://langchain-ai.github.io/langgraph/how-tos/tool-calling/): This documentation page provides a comprehensive guide on how to create and utilize tools within the LangChain framework. It covers defining simple and customized tools, managing tool arguments, accessing configuration and state, and integrating tools with chat models and agents. Additionally, it discusses error handling and strategies for managing a large number of tools.
- [Understanding Subgraphs in LangGraph](https://langchain-ai.github.io/langgraph/concepts/subgraphs/): This page provides an overview of subgraphs in LangGraph, explaining their role as encapsulated nodes within larger graphs. It discusses the benefits of using subgraphs, such as facilitating multi-agent systems and enabling independent team work. Additionally, it outlines the communication methods between parent graphs and subgraphs, detailing scenarios involving shared and different state schemas.
- [Using Subgraphs in LangGraph](https://langchain-ai.github.io/langgraph/how-tos/subgraph/): This guide provides an overview of how to effectively use subgraphs within LangGraph, including communication methods between parent graphs and subgraphs. It covers shared and different state schemas, setup instructions, and examples for implementing subgraphs in multi-agent systems. Additionally, it discusses persistence, state management, and streaming outputs from subgraphs.
- [Understanding Multi-Agent Systems](https://langchain-ai.github.io/langgraph/concepts/multi_agent/): This page provides an in-depth overview of multi-agent systems, focusing on the architecture and benefits of using multiple independent agents to manage complex applications. It discusses various multi-agent architectures, including network, supervisor, and hierarchical models, as well as communication strategies and state management techniques for effective agent interaction.
- [Building Multi-Agent Systems with LangGraph](https://langchain-ai.github.io/langgraph/how-tos/multi_agent/): This guide provides an overview of how to build multi-agent systems using LangGraph, focusing on the implementation of handoffs for agent communication. It covers the creation of independent agents, the use of handoffs to transfer control and data between agents, and examples of prebuilt multi-agent architectures. Additionally, it includes code snippets and best practices for managing agent interactions and state.
- [Understanding the Functional API in LangGraph](https://langchain-ai.github.io/langgraph/concepts/functional_api/): This documentation page provides an overview of the Functional API in LangGraph, detailing its key features such as persistence, memory, and human-in-the-loop capabilities. It explains how to define workflows using the `@entrypoint` and `@task` decorators, along with examples and best practices for implementing workflows with state management and streaming. Additionally, it compares the Functional API with the Graph API, highlighting their differences and use cases.
- [Functional API Documentation](https://langchain-ai.github.io/langgraph/how-tos/use-functional-api/): This page provides comprehensive guidance on using the Functional API, including creating workflows, handling parallel execution, and integrating with other APIs. It covers various features such as retry policies, caching, and human-in-the-loop workflows, along with practical examples. Additionally, it discusses memory management strategies for both short-term and long-term use cases.
- [Overview of LangGraph Platform](https://langchain-ai.github.io/langgraph/concepts/langgraph_platform/): The LangGraph Platform is designed for developing, deploying, and managing long-running agent workflows with ease. This page outlines the platform's features, including streaming support, background runs, and memory management, which enhance the performance and reliability of agent applications. Additionally, it provides links to resources for getting started and deploying agents effectively.
- [LangGraph Platform Quickstart Guide](https://langchain-ai.github.io/langgraph/tutorials/langgraph-platform/local-server/): This quickstart guide provides step-by-step instructions for running a LangGraph application locally. It covers prerequisites, installation of the LangGraph CLI, app creation, dependency installation, and launching the server. Additionally, it includes testing your application using the LangGraph Studio and API.
- [LangGraph Platform Deployment Quickstart](https://langchain-ai.github.io/langgraph/cloud/quick_start/): This quickstart guide provides step-by-step instructions for deploying an application on the LangGraph Platform using GitHub. It covers prerequisites, repository creation, deployment procedures, and testing your application and API. Follow these steps to successfully set up and run your application in the LangGraph environment.
- [Overview of LangGraph Platform Components](https://langchain-ai.github.io/langgraph/concepts/langgraph_components/): This page provides a comprehensive overview of the various components that make up the LangGraph Platform. It details the functionalities of each component, including the LangGraph Server, CLI, Studio, SDKs, and the control and data planes. Users can learn how these components work together to facilitate the development, deployment, and management of LangGraph applications.
- [LangGraph Server Documentation](https://langchain-ai.github.io/langgraph/concepts/langgraph_server/): This page provides an overview of the LangGraph Server, an API designed for creating and managing agent-based applications. It details the server versions, application structure, deployment components, and the use of assistants, persistence, and task queues. Additionally, it includes links to further resources and guides for effective deployment and usage.
- [LangGraph Application Structure Guide](https://langchain-ai.github.io/langgraph/concepts/application_structure/): This page provides an overview of the structure of a LangGraph application, detailing the essential components such as the configuration file, dependencies, graphs, and environment variables. It includes examples of directory structures for both Python and JavaScript applications, as well as guidance on how to specify the necessary information for deployment. Additionally, it covers key concepts related to the configuration file and the role of dependencies and environment variables in the application.
- [Setting Up a LangGraph Application with requirements.txt](https://langchain-ai.github.io/langgraph/cloud/deployment/setup/): This guide provides step-by-step instructions for configuring a LangGraph application for deployment using a requirements.txt file to manage dependencies. It covers essential topics such as specifying dependencies, defining environment variables, and creating the LangGraph configuration file. Additionally, it includes examples and tips for alternative setup methods.
- [Setting Up a LangGraph Application with pyproject.toml](https://langchain-ai.github.io/langgraph/cloud/deployment/setup_pyproject/): This guide provides step-by-step instructions for configuring a LangGraph application using the `pyproject.toml` file for dependency management. It covers the necessary components, including specifying dependencies, environment variables, and defining graphs, along with examples and best practices. Additionally, it offers tips for alternative setups and links to further resources for deployment.
- [Setting Up a LangGraph.js Application](https://langchain-ai.github.io/langgraph/cloud/deployment/setup_javascript/): This guide provides step-by-step instructions for configuring a LangGraph.js application for deployment on the LangGraph Platform or for self-hosting. It covers essential topics such as specifying dependencies, environment variables, defining graphs, and creating the necessary configuration file. By following this walkthrough, users will learn how to structure their application and prepare it for deployment.
- [Customizing Your Dockerfile in LangGraph](https://langchain-ai.github.io/langgraph/cloud/deployment/custom_docker/): This page provides a guide on how to customize your Dockerfile by adding additional commands through the `langgraph.json` configuration file. It explains how to specify the `dockerfile_lines` key to include necessary dependencies, such as installing system packages and Python libraries. An example is provided to illustrate the process of integrating the Pillow library for image processing.
- [LangGraph CLI Documentation](https://langchain-ai.github.io/langgraph/concepts/langgraph_cli/): This page provides an overview of the LangGraph CLI, a command-line tool for building and running the LangGraph API server locally. It includes installation instructions, a list of core commands, and their descriptions to help users effectively utilize the CLI for development and deployment. For further details, users can refer to the LangGraph CLI Reference.
- [LangGraph Studio Documentation](https://langchain-ai.github.io/langgraph/concepts/langgraph_studio/): This page provides an overview of LangGraph Studio, an IDE for visualizing, interacting with, and debugging agentic systems that utilize the LangGraph Server API. It outlines the prerequisites for using the studio, key features, and the two operational modes: Graph mode and Chat mode. Additionally, it includes links to further resources for getting started with LangGraph Studio.
- [Getting Started with LangGraph Studio](https://langchain-ai.github.io/langgraph/cloud/how-tos/studio/quick_start/): This page provides a comprehensive guide on how to connect and use LangGraph Studio with both deployed applications on the LangGraph Platform and local development servers. It includes instructions for installation, running the server, accessing the Studio UI, and debugging options. Additionally, troubleshooting tips and next steps for further exploration of LangGraph Studio features are also provided.
- [Running Applications: A Comprehensive Guide](https://langchain-ai.github.io/langgraph/cloud/how-tos/invoke_studio/): This page provides a detailed guide on how to submit a run to your application, covering both Graph and Chat modes. It includes instructions on specifying input, managing assistants, enabling streaming, and using breakpoints. Additionally, it offers tips for running applications from specific checkpoints in existing threads.
- [Managing Assistants in LangGraph Studio](https://langchain-ai.github.io/langgraph/cloud/how-tos/studio/manage_assistants/): This page provides guidance on how to manage assistants within LangGraph Studio, including viewing, editing, and updating assistant configurations. It covers both Graph mode and Chat mode, detailing how to activate assistants and make changes to their settings. Users will learn how to navigate the interface to effectively manage their assistant configurations for graph runs.
- [Managing Threads in Studio](https://langchain-ai.github.io/langgraph/cloud/how-tos/threads_studio/): This page provides a comprehensive guide on how to view and edit threads within the Studio application. It covers both Graph and Chat modes, detailing the steps to create new threads, view thread history, and edit thread states. Additionally, it includes links to related concepts for further learning.
- [Modifying Prompts in LangGraph Studio](https://langchain-ai.github.io/langgraph/cloud/how-tos/iterate_graph_studio/): This page provides guidance on how to modify prompts within LangGraph Studio using two methods: direct node editing and the LangSmith Playground interface. It details the configuration options available for nodes, including `langgraph_nodes` and `langgraph_type`, along with examples for both Pydantic models and dataclasses. Additionally, it outlines the steps for editing prompts in the UI and utilizing the LangSmith Playground for testing LLM calls.
- [Debugging LangSmith Traces in LangGraph Studio](https://langchain-ai.github.io/langgraph/cloud/how-tos/clone_traces_studio/): This guide provides step-by-step instructions for opening and debugging LangSmith traces in LangGraph Studio. It covers how to deploy threads and test local agents with remote traces, ensuring a seamless debugging experience. Additionally, it outlines the requirements for local agents and the process for cloning threads for local testing.
- [How to Add Nodes to LangSmith Datasets](https://langchain-ai.github.io/langgraph/cloud/how-tos/datasets_studio/): This guide provides step-by-step instructions on how to add examples from nodes in the thread log to LangSmith datasets. It covers selecting threads, choosing nodes, and editing inputs/outputs before adding them to the dataset. Additionally, it includes links to further resources on evaluating intermediate steps.
- [LangGraph SDK Documentation](https://langchain-ai.github.io/langgraph/concepts/sdk/): This page provides an overview of the LangGraph SDK, including installation instructions for both Python and JavaScript. It details the synchronous and asynchronous client options available for interacting with the LangGraph Server. Additionally, it offers links to further resources and references for the SDK.
- [Integrating Semantic Search in LangGraph](https://langchain-ai.github.io/langgraph/cloud/deployment/semantic_search/): This guide provides step-by-step instructions on how to implement semantic search in your LangGraph deployment. It covers prerequisites, configuration of the store, and usage examples for searching memories and documents by semantic similarity. Additionally, it includes information on using custom embeddings and querying via the LangGraph SDK.
- [Configuring Time-to-Live (TTL) in LangGraph Applications](https://langchain-ai.github.io/langgraph/how-tos/ttl/configure_ttl/): This guide provides detailed instructions on how to configure Time-to-Live (TTL) settings for checkpoints and store items in LangGraph applications. It covers the necessary configurations in the `langgraph.json` file, including strategies for managing data lifecycle and memory. Additionally, it explains how to combine TTL configurations and override them at runtime.
- [LangGraph Authentication & Access Control Overview](https://langchain-ai.github.io/langgraph/concepts/auth/): This page provides a comprehensive guide to the authentication and authorization mechanisms within the LangGraph Platform. It explains the core concepts of authentication versus authorization, outlines default security models, and details the system architecture involved in user identity management. Additionally, it covers implementation examples for authentication and authorization handlers, along with common access patterns and supported resources.
- [Custom Authentication Setup for LangGraph Platform](https://langchain-ai.github.io/langgraph/how-tos/auth/custom_auth/): This guide provides step-by-step instructions on how to implement custom authentication in your LangGraph Platform application. It covers the necessary prerequisites, implementation details, configuration updates, and client connection methods. The guide is applicable to both managed and Enterprise self-hosted deployments, but not to Lite self-hosted plans.
- [Documenting API Authentication in OpenAPI for LangGraph](https://langchain-ai.github.io/langgraph/how-tos/auth/openapi_security/): This guide provides instructions on how to customize the security schema for your LangGraph Platform API documentation using OpenAPI. It covers default security schemes for both LangGraph Platform and self-hosted deployments, as well as how to implement custom authentication. Additionally, it includes examples for OAuth2 and API key authentication, along with testing procedures.
- [Managing Assistants in LangGraph](https://langchain-ai.github.io/langgraph/concepts/assistants/): This page provides an overview of how to create and manage assistants within the LangGraph Platform, which allows for separate configuration of agents without altering the core graph logic. It covers the prerequisites, configuration options, and versioning of assistants, highlighting their role in optimizing agent performance for different tasks. Additionally, it includes links to relevant API references and how-to guides for further assistance.
- [Managing Assistants in LangGraph](https://langchain-ai.github.io/langgraph/cloud/how-tos/configuration_cloud/): This documentation page provides a comprehensive guide on how to create, configure, and manage assistants using the LangGraph SDK and Platform UI. It includes code examples in Python and JavaScript, as well as instructions for creating new versions and using previous versions of assistants. Additionally, it covers the process of utilizing assistants in various environments.
- [Understanding Threads in LangGraph](https://langchain-ai.github.io/langgraph/cloud/concepts/threads/): This page provides an overview of threads in the LangGraph framework, detailing how they accumulate the state of runs and the importance of checkpoints. It explains the process of creating threads and retrieving their current and historical states. Additionally, it offers links to further resources on threads, checkpoints, and the LangGraph API for managing thread states.
- [Managing Threads in LangGraph](https://langchain-ai.github.io/langgraph/cloud/how-tos/use_threads/): This documentation page provides a comprehensive guide on how to create, view, and inspect threads using the LangGraph SDK. It includes detailed instructions for creating empty threads, copying existing threads, and initializing threads with prepopulated states. Additionally, it covers how to list and inspect threads, including filtering and sorting options.
- [Understanding Runs in LangGraph Platform](https://langchain-ai.github.io/langgraph/cloud/concepts/runs/): This page provides an overview of what constitutes a run in the LangGraph Platform, including its input, configuration, and metadata. It also highlights the ability to execute runs on threads and offers links to the API reference for managing runs.
- [Starting Background Runs for Your Agent](https://langchain-ai.github.io/langgraph/cloud/how-tos/background_run/): This guide provides step-by-step instructions on how to initiate background runs for your agent using Python, JavaScript, and CURL. It covers the setup process, checking current runs, starting new runs, and retrieving the final results. By following this documentation, users can efficiently manage long-running jobs within their applications.
- [Running Multiple Agents on the Same Thread in LangGraph](https://langchain-ai.github.io/langgraph/cloud/how-tos/same-thread/): This documentation page explains how to run multiple agents on the same thread using the LangGraph Platform. It provides step-by-step examples in Python, JavaScript, and CURL to create agents, run them on a thread, and demonstrate how the second agent can utilize the context from the first agent's responses. By following the examples, users can learn to effectively manage multiple agents and their interactions.
- [Scheduling Cron Jobs with LangGraph](https://langchain-ai.github.io/langgraph/cloud/how-tos/cron_jobs/): This page provides a comprehensive guide on how to schedule cron jobs using the LangGraph Platform. It includes setup instructions for different programming languages, examples of creating and deleting cron jobs, and details on managing stateless cron jobs. Users will learn how to automate tasks such as sending weekly emails without writing custom scripts.
- [Guide to Stateless Runs in LangGraph](https://langchain-ai.github.io/langgraph/cloud/how-tos/stateless_runs/): This page provides a comprehensive guide on how to implement stateless runs using the LangGraph Platform. It includes setup instructions for various programming languages, examples of streaming results, and methods for waiting for stateless results. Users will learn how to execute runs without maintaining persistent state, making their applications more efficient.
- [Configurable Headers in LangGraph](https://langchain-ai.github.io/langgraph/cloud/how-tos/configurable_headers/): This page provides guidance on how to configure headers dynamically in the LangGraph platform to modify agent behavior and permissions. It details how to include or exclude specific headers in the runtime configuration using the `langgraph.json` file. Additionally, it explains how to access these headers within your graph and offers an option to opt-out of configurable headers.
- [Streaming in LangGraph Platform](https://langchain-ai.github.io/langgraph/cloud/concepts/streaming/): This page provides an overview of streaming capabilities within the LangGraph Platform, detailing the various streaming modes available for LLM applications. It includes instructions for creating streaming runs, handling stateless runs, and joining active background runs. Additionally, code examples in Python, JavaScript, and cURL are provided to illustrate the implementation of these features.
- [Streaming Outputs with LangGraph SDK](https://langchain-ai.github.io/langgraph/cloud/how-tos/streaming/): This documentation page provides detailed instructions on how to stream outputs from the LangGraph API server using the LangGraph SDK in Python, JavaScript, and cURL. It covers various streaming modes, including updates, values, and custom data, along with examples for each mode. Additionally, it explains how to handle subgraphs, debug information, and LLM tokens during streaming.
- [Human-in-the-Loop Workflows in LangGraph](https://langchain-ai.github.io/langgraph/cloud/how-tos/add-human-in-the-loop/): This page provides an overview of the human-in-the-loop (HIL) capabilities in LangGraph, allowing for human intervention in automated processes. It details the `interrupt` function, which pauses execution for human input, and includes examples in Python, JavaScript, and cURL for implementing HIL workflows. Additionally, it links to further resources for understanding and utilizing HIL features effectively.
- [Using Breakpoints in LangGraph](https://langchain-ai.github.io/langgraph/cloud/how-tos/human_in_the_loop_breakpoint/): This page provides an overview of how to set and use breakpoints in LangGraph to pause graph execution for inspection. It includes examples for setting breakpoints at compile time and run time in Python, JavaScript, and cURL. Additionally, it offers guidance on resuming execution after hitting a breakpoint.
- [Using Time Travel in LangGraph](https://langchain-ai.github.io/langgraph/cloud/how-tos/human_in_the_loop_time_travel/): This page provides a comprehensive guide on how to utilize the time travel functionality in LangGraph, allowing users to resume execution from previous checkpoints. It outlines the steps to run a graph, identify checkpoints, modify graph states, and resume execution. Additionally, the page includes code examples in Python, JavaScript, and cURL for practical implementation.
- [Model Context Protocol (MCP) Endpoint Documentation](https://langchain-ai.github.io/langgraph/concepts/server-mcp/): This page provides comprehensive documentation on the Model Context Protocol (MCP) endpoint available in LangGraph Server. It covers the requirements for using MCP, how to expose agents as MCP tools, and includes examples for connecting with MCP-compliant clients in various programming languages. Additionally, it outlines session behavior, authentication, and instructions for disabling the MCP endpoint.
- [Managing Double Texting in LangGraph](https://langchain-ai.github.io/langgraph/concepts/double_texting/): This page provides an overview of how to handle double texting scenarios in LangGraph, where users may send multiple messages before the first has completed. It outlines four strategies: Reject, Enqueue, Interrupt, and Rollback, each with links to detailed configuration guides. Prerequisites for implementing these strategies include having the LangGraph Server set up.
- [Using the Interrupt Option in Double Texting](https://langchain-ai.github.io/langgraph/cloud/how-tos/interrupt_concurrent/): This guide provides detailed instructions on how to utilize the `interrupt` option for double texting, allowing users to interrupt a prior run of a graph and start a new one. It includes setup instructions, code examples in Python, JavaScript, and CURL, as well as guidance on viewing run results and verifying the status of interrupted runs. Familiarity with double texting is assumed, and a link to a conceptual guide is provided for further understanding.
- [Using the Rollback Option in Double Texting](https://langchain-ai.github.io/langgraph/cloud/how-tos/rollback_concurrent/): This guide provides detailed instructions on how to utilize the `rollback` option in double texting, which allows users to interrupt a previous run and start a new one while permanently deleting the prior run from the database. It includes setup instructions, code examples in Python, JavaScript, and CURL, and demonstrates how to view run results and verify the deletion of the original run. Familiarity with double texting is assumed, and a link to a conceptual guide is provided for further reading.
- [Using the Reject Option in Double Texting](https://langchain-ai.github.io/langgraph/cloud/how-tos/reject_concurrent/): This guide provides an overview of the `reject` option in double texting, which prevents new runs of a graph from starting while an original run is still in progress. It includes setup instructions, code examples in Python, JavaScript, and CURL, and demonstrates how to handle errors when attempting to create concurrent runs. Additionally, it shows how to view the results of the original run after the rejection.
- [Using the Enqueue Option for Double Texting](https://langchain-ai.github.io/langgraph/cloud/how-tos/enqueue_concurrent/): This guide provides an overview of the `enqueue` option for double texting, which allows interruptions to be queued and executed in the order they are received. It includes setup instructions, code examples in Python, JavaScript, and CURL for creating runs, and methods for viewing run results. Familiarity with double texting concepts is assumed, and a helper function for output formatting is also provided.
- [Understanding Webhooks in LangGraph Platform](https://langchain-ai.github.io/langgraph/cloud/concepts/webhooks/): This page provides an overview of webhooks and their role in enabling event-driven communication between LangGraph Platform applications and external services. It explains how to use the `webhook` parameter in various endpoints to trigger requests upon the completion of API calls. For further details, a link to a comprehensive how-to guide is also included.
- [Using Webhooks with LangGraph Platform](https://langchain-ai.github.io/langgraph/cloud/how-tos/webhooks/): This documentation page provides a comprehensive guide on how to implement webhooks in the LangGraph Platform to receive updates after API calls. It includes details on supported endpoints, setup instructions for different programming languages, and examples of how to specify webhook parameters in API requests. Additionally, it covers security measures and testing tools for verifying webhook functionality.
- [Scheduling Tasks with Cron Jobs on LangGraph Platform](https://langchain-ai.github.io/langgraph/cloud/concepts/cron_jobs/): This page provides an overview of how to use cron jobs on the LangGraph Platform to run assistants on a defined schedule. It explains the process of setting up a cron job, including specifying the schedule, assistant, and input. Additionally, it includes links to a how-to guide and API reference for further details.
- [Scheduling Cron Jobs with LangGraph](https://langchain-ai.github.io/langgraph/cloud/how-tos/cron_jobs/): This page provides a comprehensive guide on how to use cron jobs with the LangGraph Platform to automate graph executions on a schedule. It includes setup instructions for various programming languages, examples of creating and deleting cron jobs, and tips for managing stateless cron jobs. Users will learn how to efficiently schedule tasks without manual intervention, ensuring timely execution of automated processes.
- [Adding Custom Lifespan Events in LangGraph](https://langchain-ai.github.io/langgraph/how-tos/http/custom_lifespan/): This page provides a guide on how to implement custom lifespan events in your LangGraph Platform applications, specifically for Python deployments. It covers the initialization and cleanup of resources during server startup and shutdown using FastAPI. Additionally, it includes code examples and configuration steps to help you integrate these events into your application.
- [Adding Custom Middleware to LangGraph Platform](https://langchain-ai.github.io/langgraph/how-tos/http/custom_middleware/): This page provides a step-by-step guide on how to add custom middleware to your server when deploying agents to the LangGraph Platform. It covers the necessary code implementation using FastAPI, configuration settings in `langgraph.json`, and instructions for testing and deploying your application. Additionally, it offers links to related topics such as custom routes and lifespan events for further customization.
- [Adding Custom Routes in LangGraph](https://langchain-ai.github.io/langgraph/how-tos/http/custom_routes/): This page provides a step-by-step guide on how to add custom routes to your LangGraph platform application using a Starlette or FastAPI app. It includes instructions for creating a new app, configuring the `langgraph.json` file, and testing the server locally. Additionally, it explains how custom routes can override default endpoints and offers suggestions for further customization.
- [LangGraph Deployment Options](https://langchain-ai.github.io/langgraph/concepts/deployment_options/): This page outlines the various deployment options available for the LangGraph Platform, including Cloud SaaS, Self-Hosted Data Plane, Self-Hosted Control Plane, and Standalone Container. Each option is described in detail, highlighting key features, management responsibilities, and compatibility. A comparison table is also provided to help users choose the best deployment strategy for their needs.
- [LangGraph Data Plane Overview](https://langchain-ai.github.io/langgraph/concepts/langgraph_data_plane/): This page provides a comprehensive overview of the LangGraph Data Plane, detailing its components including the server infrastructure, listener application, and data management systems like Postgres and Redis. It also covers key features such as autoscaling, static IP addresses, and custom configurations for Postgres and Redis. Additionally, the page outlines telemetry, licensing, and tracing functionalities relevant to different deployment options.
- [LangGraph Control Plane Overview](https://langchain-ai.github.io/langgraph/concepts/langgraph_control_plane/): This page provides a comprehensive overview of the LangGraph Control Plane, detailing its UI and API functionalities for managing LangGraph Servers. It covers deployment types, environment variables, database provisioning, and asynchronous deployment processes. Additionally, it highlights the integration with LangSmith for tracing projects.
- [Cloud SaaS Deployment Guide](https://langchain-ai.github.io/langgraph/concepts/langgraph_cloud/): This page provides a comprehensive guide on deploying the LangGraph Server using the Cloud SaaS model. It outlines the roles of the control plane and data plane, detailing their functionalities and management. Additionally, it includes an architectural diagram to illustrate the deployment structure.
- [Deployment Guide for LangGraph Platform](https://langchain-ai.github.io/langgraph/cloud/deployment/cloud/): This page provides a comprehensive guide on how to deploy applications to the LangGraph Platform using GitHub repositories. It covers prerequisites, steps for creating new deployments and revisions, managing deployment settings, and viewing logs. Additionally, it includes instructions for whitelisting IP addresses and modifying GitHub repository access.
- [Self-Hosted Data Plane Deployment Guide](https://langchain-ai.github.io/langgraph/concepts/langgraph_self_hosted_data_plane/): This page provides an overview of the Self-Hosted Data Plane deployment option, which allows users to manage their data plane infrastructure while offloading control plane management to LangChain. It outlines the requirements, architecture, and supported compute platforms for deployment. Additionally, it includes important information regarding the beta status of this deployment option.
- [Deploying a Self-Hosted Data Plane](https://langchain-ai.github.io/langgraph/cloud/deployment/self_hosted_data_plane/): This page provides a comprehensive guide on deploying a Self-Hosted Data Plane using Kubernetes and Amazon ECS. It outlines the prerequisites, setup steps, and configuration details necessary for a successful deployment. Additionally, it highlights the current beta status of this deployment option.
- [Self-Hosted Control Plane Deployment Guide](https://langchain-ai.github.io/langgraph/concepts/langgraph_self_hosted_control_plane/): This page provides an overview of the Self-Hosted Control Plane deployment option, currently in beta. It outlines the requirements, architecture, and compute platforms supported for deploying the control and data planes in your cloud environment. Additionally, it includes important links and resources for managing your self-hosted infrastructure.
- [Deploying a Self-Hosted Control Plane](https://langchain-ai.github.io/langgraph/cloud/deployment/self_hosted_control_plane/): This page provides a comprehensive guide on deploying a Self-Hosted Control Plane using Kubernetes. It outlines the prerequisites, setup steps, and configuration details necessary for a successful deployment. Additionally, it highlights the beta status of this deployment option and includes links to relevant resources for further assistance.
- [Deploying LangGraph Server with Standalone Container](https://langchain-ai.github.io/langgraph/concepts/langgraph_standalone_container/): This page provides a comprehensive guide on deploying a LangGraph Server using the Standalone Container option. It outlines the architecture, supported compute platforms, and differences between Lite and Enterprise server versions. Users will find essential information on managing the data plane infrastructure without a control plane.
- [Deploying a Standalone Container with LangGraph](https://langchain-ai.github.io/langgraph/cloud/deployment/standalone_container/): This documentation provides a comprehensive guide on deploying a standalone container for the LangGraph application. It covers prerequisites, environment variable configurations, and deployment methods using Docker and Docker Compose. Additionally, it includes instructions for deploying on Kubernetes using Helm.
- [Scalability and Resilience of LangGraph Platform](https://langchain-ai.github.io/langgraph/concepts/scalability_and_resilience/): This page provides an overview of the scalability and resilience features of the LangGraph Platform. It details how the platform handles server and queue scalability, as well as the mechanisms in place for ensuring resilience during both graceful and hard shutdowns. Additionally, it covers the resilience strategies employed for Postgres and Redis to maintain service availability.
- [LangGraph Platform Plans Overview](https://langchain-ai.github.io/langgraph/concepts/plans/): This page provides an overview of the different plans available for the LangGraph Platform, including Developer, Plus, and Enterprise options. Each plan offers varying deployment options, usage limits, and features tailored to different user needs. For detailed pricing and related resources, links to additional documentation are also included.
# Examples
- [Building an Agentic RAG System](https://langchain-ai.github.io/langgraph/tutorials/rag/langgraph_agentic_rag/): This tutorial guides you through the process of creating a retrieval agent (RAG) system using LangChain and LangGraph. You will learn how to fetch and preprocess documents, create a retriever tool, and build an agentic RAG that intelligently decides when to retrieve information or respond directly to user queries. By the end, you'll have a functional system capable of semantic search and context-aware responses.
- [Building a Multi-Agent Supervisor System](https://langchain-ai.github.io/langgraph/tutorials/multi_agent/agent_supervisor/): This tutorial guides you through the process of creating a multi-agent supervisor system using specialized agents for research and math tasks. You will learn how to set up the environment, create individual worker agents, and implement a supervisor that orchestrates their interactions. By the end, you'll have a fully functional multi-agent architecture capable of handling complex queries.
- [Building a SQL Agent with LangChain](https://langchain-ai.github.io/langgraph/tutorials/sql-agent/): This tutorial provides a step-by-step guide on how to create a SQL agent capable of answering questions about a SQL database. It covers the setup of necessary dependencies, configuration of a SQLite database, and the implementation of a prebuilt agent that interacts with the database to generate and execute queries. Additionally, it discusses customizing the agent for more control over its behavior.
- [Custom Run ID, Tags, and Metadata for LangSmith Graph Runs](https://langchain-ai.github.io/langgraph/how-tos/run-id-langsmith/): This guide provides instructions on how to pass a custom run ID and set tags and metadata for graph runs in LangSmith. It covers prerequisites, configuration options, and includes code examples for setting up and running a graph with LangGraph. Additionally, it explains how to view and filter traces in the LangSmith platform.
- [Custom Authentication Setup for Chatbots](https://langchain-ai.github.io/langgraph/tutorials/auth/getting_started/): This tutorial guides you through the process of setting up custom authentication for a chatbot using the LangGraph platform. You will learn how to implement token-based security to control user access, starting with a basic example and preparing for more advanced authentication methods in future tutorials. By the end, you'll have a functional chatbot that restricts access to authenticated users.
- [Implementing Private Conversations in Chatbots](https://langchain-ai.github.io/langgraph/tutorials/auth/resource_auth/): This tutorial guides you through extending a chatbot to enable private conversations for each user by implementing resource-level access control. You'll learn how to add authorization handlers to ensure users can only access their own threads and test the functionality to confirm proper access restrictions. Additionally, the tutorial covers scoped authorization handlers for more granular control over resource access.
- [Integrating OAuth2 Authentication with Supabase](https://langchain-ai.github.io/langgraph/tutorials/auth/add_auth_server/): This tutorial guides you through replacing hard-coded tokens with real user accounts using OAuth2 for secure authentication in your LangGraph application. You'll learn how to set up Supabase as your identity provider, implement token validation, and ensure proper user authorization. By the end, you'll have a production-ready authentication system that allows users to securely access their own data.
- [Rebuilding Graphs at Runtime in LangGraph](https://langchain-ai.github.io/langgraph/cloud/deployment/graph_rebuild/): This guide explains how to rebuild your graph at runtime with different configurations in LangGraph. It covers the necessary prerequisites, how to define graphs, and the steps to modify your graph-making function for dynamic behavior based on user input. Additionally, it provides examples of both static and dynamic graph configurations.
- [Interacting with RemoteGraph in LangGraph](https://langchain-ai.github.io/langgraph/how-tos/use-remote-graph/): This documentation page provides a comprehensive guide on how to interact with a LangGraph Platform deployment using the RemoteGraph interface. It covers the initialization of RemoteGraph, invoking the graph both asynchronously and synchronously, and utilizing it as a subgraph. Additionally, it includes code examples in Python and JavaScript to facilitate understanding and implementation.
- [Deploying Agents on LangGraph Platform](https://langchain-ai.github.io/langgraph/how-tos/autogen-langgraph-platform/): This page provides a comprehensive guide on how to deploy agents like AutoGen and CrewAI using the LangGraph Platform. It covers the necessary setup, agent definition, and wrapping the agent in a LangGraph node for deployment. Additionally, it highlights the benefits of using LangGraph for scalable infrastructure and memory support.
- [Integrating LangGraph with React: A Comprehensive Guide](https://langchain-ai.github.io/langgraph/cloud/how-tos/use_stream_react/): This documentation provides a detailed guide on how to integrate the LangGraph platform into your React applications using the `useStream()` hook. It covers installation, key features, example implementations, and customization options for building chat experiences. Additionally, it includes advanced topics such as event handling, TypeScript support, and managing conversation threads.
- [Implementing Generative User Interfaces with LangGraph](https://langchain-ai.github.io/langgraph/cloud/how-tos/generative_ui_react/): This documentation provides a comprehensive guide on how to implement Generative User Interfaces (Generative UI) using the LangGraph platform. It covers prerequisites, step-by-step tutorials for defining UI components, sending them in graphs, and handling them in React applications. Additionally, it includes how-to guides for customizing components and managing UI state effectively.
# Resources
- [LangGraph FAQ](https://langchain-ai.github.io/langgraph/concepts/faq/): This FAQ page provides answers to common questions about LangGraph, an orchestration framework for complex agentic systems. It covers topics such as the differences between LangGraph and LangChain, performance impacts, open-source status, and compatibility with various LLMs. Additionally, it outlines the distinctions between LangGraph and LangGraph Platform, including features and deployment options.
- [Getting Started with LangGraph Templates](https://langchain-ai.github.io/langgraph/concepts/template_applications/): This page provides an overview of open source reference applications known as templates, designed to help users quickly build applications with LangGraph. It includes installation instructions for the LangGraph CLI, a list of available templates with their descriptions, and guidance on creating and deploying a new LangGraph app. Users can find links to repositories for each template and next steps for customizing their applications.
- [Guide to Using llms.txt and llms-full.txt for LLMs](https://langchain-ai.github.io/langgraph/llms-txt-overview/): This page provides an overview of the `llms.txt` and `llms-full.txt` formats, which facilitate access to programming documentation for large language models (LLMs) and agents. It outlines the differences between the two formats, usage instructions via an MCP server, and best practices for integrating these files into integrated development environments (IDEs). Additionally, it highlights considerations for managing large documentation files effectively.
- [Community Agents for LangGraph](https://langchain-ai.github.io/langgraph/agents/prebuilt/): This page provides a list of community-built libraries that extend the functionality of LangGraph. Each entry includes the library name, GitHub URL, a brief description, and additional metrics like weekly downloads and stars. Additionally, it outlines how to contribute your own library to the LangGraph documentation.
- [LangGraph Error Reference Guide](https://langchain-ai.github.io/langgraph/troubleshooting/errors/index/): This page serves as a comprehensive reference for resolving common errors encountered while using the LangGraph platform. It includes a list of error codes and links to detailed guides for troubleshooting specific issues. Users can find solutions for errors related to graph recursion, concurrent updates, node return values, and more.
- [Handling Recursion Limits in LangGraph](https://langchain-ai.github.io/langgraph/troubleshooting/errors/GRAPH_RECURSION_LIMIT/): This page provides guidance on managing recursion limits in LangGraph's StateGraph. It explains how to identify potential infinite loops in your graph and offers solutions for increasing the recursion limit when working with complex graphs. Additionally, it includes code examples to illustrate the concepts discussed.
- [Handling INVALID_CONCURRENT_GRAPH_UPDATE in LangGraph](https://langchain-ai.github.io/langgraph/troubleshooting/errors/INVALID_CONCURRENT_GRAPH_UPDATE/): This page explains the INVALID_CONCURRENT_GRAPH_UPDATE error that occurs in LangGraph when multiple nodes attempt to update the same state property concurrently. It provides an example of how this error can arise and offers a solution by using a reducer to combine values from parallel node executions. Additionally, troubleshooting tips are included to help resolve this issue.
- [Handling Invalid Node Return Values in LangGraph](https://langchain-ai.github.io/langgraph/troubleshooting/errors/INVALID_GRAPH_NODE_RETURN_VALUE/): This page provides guidance on the error encountered when a LangGraph node returns a non-dict value. It includes an example of incorrect node implementation and the resulting error message. Additionally, troubleshooting tips are offered to ensure that all nodes return the expected dictionary format.
- [Handling Multiple Subgraphs in LangGraph](https://langchain-ai.github.io/langgraph/troubleshooting/errors/MULTIPLE_SUBGRAPHS/): This page discusses the limitations of calling multiple subgraphs within a single LangGraph node when checkpointing is enabled. It provides troubleshooting tips to resolve related errors, including suggestions for compiling subgraphs without checkpointing and using the Send API for graph calls.
- [Handling INVALID_CHAT_HISTORY Error in create_react_agent](https://langchain-ai.github.io/langgraph/troubleshooting/errors/INVALID_CHAT_HISTORY/): This page provides an overview of the INVALID_CHAT_HISTORY error encountered in the create_react_agent function when a malformed list of messages is passed. It outlines the potential causes of the error and offers troubleshooting steps to resolve it. Users can learn how to properly invoke the graph and manage tool calls to avoid this issue.
- [Handling INVALID_LICENSE Error in LangGraph Platform](https://langchain-ai.github.io/langgraph/troubleshooting/errors/INVALID_LICENSE/): This page provides guidance on troubleshooting the INVALID_LICENSE error encountered when starting a self-hosted LangGraph Platform server. It outlines the scenarios in which this error may occur and offers solutions based on different deployment types. Additionally, it includes steps to verify the necessary credentials for successful deployment.
- [LangGraph Studio Troubleshooting Guide](https://langchain-ai.github.io/langgraph/troubleshooting/studio/): This page provides troubleshooting solutions for common connection issues encountered in LangGraph Studio, particularly with Safari and Brave browsers. It also addresses potential graph edge issues and offers methods to define routing paths for conditional edges. Users can find step-by-step instructions for resolving these issues using Cloudflare Tunnel and browser settings.
- [LangGraph Case Studies](https://langchain-ai.github.io/langgraph/adopters/): This page provides a comprehensive list of companies that have successfully implemented LangGraph, showcasing their unique use cases and the benefits they have achieved. Each entry includes links to detailed case studies or blog posts for further reading. If your company uses LangGraph, you are encouraged to share your success story to contribute to this growing collection.
+5
View File
@@ -0,0 +1,5 @@
## Caching
::: langgraph.cache.base
::: langgraph.cache.memory
::: langgraph.cache.sqlite
+1
View File
@@ -29,6 +29,7 @@ The core APIs for the LangGraph opens source library.
- [Pregel](pregel.md): Pregel-inspired computation model.
- [Checkpointing](checkpoints.md): Saving and restoring graph state.
- [Storage](store.md): Storage backends and options.
- [Caching](cache.md): Caching mechanisms for performance.
- [Types](types.md): Type definitions for graph components.
- [Config](config.md): Configuration options.
- [Errors](errors.md): Error types and handling.
@@ -0,0 +1,57 @@
.agent-layout {
display: flex;
flex-wrap: nowrap;
gap: 1rem;
align-items: flex-start;
margin-top: 1rem;
}
.agent-layout h3 {
margin: 0;
}
.agent-graph-features {
display: flex;
flex-direction: column;
gap: 0.5rem;
padding: 1rem;
max-width: 300px;
flex-shrink: 0;
border: 1px solid var(--md-default-fg-color--lightest);
border-radius: 0.5rem;
background-color: var(--md-default-bg-color);
}
.agent-graph-features label {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.9rem;
color: var(--md-typeset-color);
cursor: pointer;
}
.agent-graph-features input[type="checkbox"] {
accent-color: var(--md-accent-fg-color);
transform: scale(1.2);
}
.agent-graph-container {
flex: 1 1 50%;
max-width: 70%;
padding: 1rem;
overflow: auto;
height: auto;
box-sizing: border-box;
border: 1px solid var(--md-default-fg-color--lightest);
border-radius: 0.5rem;
background-color: var(--md-default-bg-color);
}
.agent-graph-container img {
display: block;
margin: 0 auto;
}
@@ -6,7 +6,7 @@ In this tutorial, you will build a basic chatbot. This chatbot is the basis for
Before you start this tutorial, ensure you have access to a LLM that supports
tool-calling features, such as [OpenAI](https://platform.openai.com/api-keys),
[Anthropic](https://console.anthropic.com/settings/admin-keys), or
[Anthropic](https://console.anthropic.com/settings/keys), or
[Google Gemini](https://ai.google.dev/gemini-api/docs/api-key).
## 1. Install packages
@@ -146,7 +146,7 @@ graph_builder.add_node("tools", tool_node)
!!! note
If you do not want to build this yourself in the future, you can use LangGraph's prebuilt [ToolNode](https://langchain-ai.github.io/langgraph/reference/prebuilt/#toolnode).
If you do not want to build this yourself in the future, you can use LangGraph's prebuilt [ToolNode](https://langchain-ai.github.io/langgraph/reference/agents/#langgraph.prebuilt.tool_node.ToolNode).
## 6. Define the `conditional_edges`
@@ -21,7 +21,7 @@ Before you begin, ensure you have the following:
=== "Node server"
```shell
npx @langchain/langgraph-cl
npx @langchain/langgraph-cli
```
## 2. Create a LangGraph app 🌱
+21
View File
@@ -0,0 +1,21 @@
# Examples
The pages in this section provide end-to-end examples for the following topics:
## General
- [Agentic RAG](./rag/langgraph_adaptive_rag.ipynb)
- [Agent Supervisor](./multi_agent/agent_supervisor.ipynb)
- [SQL agent](./sql-agent.ipynb)
- [Graph runs in LangSmith](../how-tos/run-id-langsmith.ipynb)
## LangGraph Platform
- [Set up custom authentication](./auth/getting_started.md)
- [Make conversations private](./auth/resource_auth.md)
- [Connect an authentication provider](./auth/add_auth_server.md)
- [Rebuild graph at runtime](../cloud/deployment/graph_rebuild.md)
- [Use RemoteGraph](../how-tos/use-remote-graph.md)
- [Deploy CrewAI, AutoGen, and other frameworks](../how-tos/autogen-langgraph-platform.ipynb)
- [Integrate LangGraph into a React app](../cloud/how-tos/use_stream_react.md)
- [Implement Generative User Interfaces with LangGraph](../cloud/how-tos/generative_ui_react.md)
+3 -2
View File
@@ -19,7 +19,6 @@ theme:
- content.tabs.link
- content.action.edit
- content.tooltips
- header.autohide
- navigation.indexes
- navigation.footer
- navigation.instant
@@ -28,7 +27,6 @@ theme:
- navigation.instant.progress
- navigation.path
- navigation.tabs
- navigation.tabs.sticky
- navigation.top
- navigation.prune
- navigation.tracking
@@ -255,6 +253,7 @@ nav:
- Pregel: reference/pregel.md
- Checkpointing: reference/checkpoints.md
- Storage: reference/store.md
- Caching: reference/cache.md
- Types: reference/types.md
- Config: reference/config.md
- Errors: reference/errors.md
@@ -417,3 +416,5 @@ extra_css:
- stylesheets/version_admonitions.css
- stylesheets/logos.css
- stylesheets/sticky_navigation.css
- stylesheets/agent_graph_widget.css
Generated
+75 -59
View File
@@ -291,16 +291,16 @@ name = "autogen"
version = "0.3.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "diskcache" },
{ name = "docker" },
{ name = "flaml" },
{ name = "numpy" },
{ name = "openai" },
{ name = "packaging" },
{ name = "pydantic" },
{ name = "python-dotenv" },
{ name = "termcolor" },
{ name = "tiktoken" },
{ name = "diskcache", marker = "python_full_version < '3.13'" },
{ name = "docker", marker = "python_full_version < '3.13'" },
{ name = "flaml", marker = "python_full_version < '3.13'" },
{ name = "numpy", marker = "python_full_version < '3.13'" },
{ name = "openai", marker = "python_full_version < '3.13'" },
{ name = "packaging", marker = "python_full_version < '3.13'" },
{ name = "pydantic", marker = "python_full_version < '3.13'" },
{ name = "python-dotenv", marker = "python_full_version < '3.13'" },
{ name = "termcolor", marker = "python_full_version < '3.13'" },
{ name = "tiktoken", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/7b/e8/33b7fb072fbcf63b8a1b5bbba15570e4e8c86d6374da398889b92fc420c8/autogen-0.3.2.tar.gz", hash = "sha256:9f8a1170ac2e5a1fc9efc3cfa6e23261dd014db97b17c8c416f97ee14951bc7b", size = 306281 }
wheels = [
@@ -1008,9 +1008,9 @@ name = "docker"
version = "7.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pywin32", marker = "sys_platform == 'win32'" },
{ name = "requests" },
{ name = "urllib3" },
{ name = "pywin32", marker = "python_full_version < '3.13' and sys_platform == 'win32'" },
{ name = "requests", marker = "python_full_version < '3.13'" },
{ name = "urllib3", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/91/9b/4a2ea29aeba62471211598dac5d96825bb49348fa07e906ea930394a83ce/docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c", size = 117834 }
wheels = [
@@ -1052,7 +1052,7 @@ name = "exceptiongroup"
version = "1.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions", marker = "python_full_version < '3.12.4'" },
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749 }
wheels = [
@@ -1153,7 +1153,7 @@ name = "flaml"
version = "2.3.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
{ name = "numpy", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/20/a8/17322311b77f3012194f92c47c81455463f99c48d358c463fa45bd3c8541/flaml-2.3.4.tar.gz", hash = "sha256:308c3e769976d8a0272f2fd7d98258d7d4a4fd2e4525ba540d1ba149ae266c54", size = 284728 }
wheels = [
@@ -2590,7 +2590,7 @@ wheels = [
[[package]]
name = "langgraph"
version = "0.4.3"
version = "0.4.7"
source = { editable = "../libs/langgraph" }
dependencies = [
{ name = "langchain-core" },
@@ -2641,7 +2641,7 @@ dev = [
[[package]]
name = "langgraph-checkpoint"
version = "2.0.25"
version = "2.0.26"
source = { editable = "../libs/checkpoint" }
dependencies = [
{ name = "langchain-core" },
@@ -2715,17 +2715,19 @@ dev = [
[[package]]
name = "langgraph-checkpoint-sqlite"
version = "2.0.7"
version = "2.0.10"
source = { editable = "../libs/checkpoint-sqlite" }
dependencies = [
{ name = "aiosqlite" },
{ name = "langgraph-checkpoint" },
{ name = "sqlite-vec" },
]
[package.metadata]
requires-dist = [
{ name = "aiosqlite", specifier = ">=0.20" },
{ name = "langgraph-checkpoint", editable = "../libs/checkpoint" },
{ name = "sqlite-vec", specifier = ">=0.1.6" },
]
[package.metadata.requires-dev]
@@ -2736,6 +2738,7 @@ dev = [
{ name = "pytest" },
{ name = "pytest-asyncio" },
{ name = "pytest-mock" },
{ name = "pytest-retry", specifier = ">=1.7.0" },
{ name = "pytest-watcher" },
{ name = "ruff" },
]
@@ -2825,11 +2828,11 @@ requires-dist = [
[package.metadata.requires-dev]
docs = [
{ name = "click", specifier = ">=8.1.7,<9" },
{ name = "jupyter", specifier = ">=1.1.1,<2" },
{ name = "langchain-cohere", specifier = ">=0.4.2,<0.5" },
{ name = "click" },
{ name = "jupyter" },
{ name = "langchain-cohere" },
{ name = "langchain-mcp-adapters", git = "https://github.com/langchain-ai/langchain-mcp-adapters" },
{ name = "langchain-ollama", specifier = ">=0.2.3,<0.3" },
{ name = "langchain-ollama" },
{ name = "langgraph", editable = "../libs/langgraph" },
{ name = "langgraph-checkpoint", editable = "../libs/checkpoint" },
{ name = "langgraph-checkpoint-postgres", editable = "../libs/checkpoint-postgres" },
@@ -2849,46 +2852,46 @@ docs = [
{ name = "mkdocs-rss-plugin" },
{ name = "mkdocstrings" },
{ name = "mkdocstrings-python" },
{ name = "psycopg", extras = ["binary"], specifier = ">=3.2.0,<4" },
{ name = "psycopg-pool", specifier = ">=3.2.0,<4" },
{ name = "pygments-ansi-color", specifier = ">=0.3" },
{ name = "ruff", specifier = ">=0.6.8,<0.7" },
{ name = "vcrpy", specifier = ">=6.0.1,<7" },
{ name = "psycopg", extras = ["binary"] },
{ name = "psycopg-pool" },
{ name = "pygments-ansi-color" },
{ name = "ruff" },
{ name = "vcrpy" },
]
test = [
{ name = "autogen", marker = "python_full_version >= '3.8' and python_full_version < '3.13'", specifier = ">=0.3.0,<0.4" },
{ name = "chromadb", specifier = ">=0.5.5,<0.6" },
{ name = "gpt4all", specifier = ">=2.8.2,<3" },
{ name = "grandalf", specifier = ">=0.8,<0.9" },
{ name = "langchain", specifier = ">=0.3.8,<0.4" },
{ name = "langchain-anthropic", specifier = ">=0.3.8,<0.4" },
{ name = "langchain-community", specifier = ">=0.3.0,<0.4" },
{ name = "langchain-core", specifier = ">=0.3.54,<0.4" },
{ name = "langchain-experimental", specifier = ">=0.3.2,<0.4" },
{ name = "langchain-fireworks", specifier = ">=0.2.0,<0.3" },
{ name = "langchain-mistralai", specifier = ">=0.2.6,<0.3" },
{ name = "langchain-nomic", specifier = ">=0.1.3,<0.2" },
{ name = "langchain-openai", specifier = ">=0.3.7,<0.4" },
{ name = "langchain-tavily", specifier = ">=0.1.5,<0.2" },
{ name = "langgraph-checkpoint-mongodb", specifier = ">=0.1.0,<0.2" },
{ name = "langmem", specifier = ">=0.0.19,<0.0.20" },
{ name = "langsmith", specifier = ">=0.3.0,<0.4" },
{ name = "matplotlib", specifier = ">=3.9.2,<4" },
{ name = "motor", specifier = ">=3.5.1,<4" },
{ name = "networkx", specifier = "~=3.3" },
{ name = "numexpr", specifier = ">=2.10.1,<3" },
{ name = "numpy", specifier = ">=1.26.4,<2" },
{ name = "pymongo", specifier = ">=4.8.0,<5" },
{ name = "pyppeteer", specifier = ">=2.0.0,<3" },
{ name = "pytest", specifier = ">=8.3.5,<9" },
{ name = "pytest-check-links", specifier = ">=0.10.1,<0.11" },
{ name = "redis", specifier = ">=5.0.8,<6" },
{ name = "scikit-learn", specifier = ">=1.5.2,<2" },
{ name = "autogen", marker = "python_full_version >= '3.8' and python_full_version < '3.13'" },
{ name = "chromadb" },
{ name = "gpt4all" },
{ name = "grandalf" },
{ name = "langchain" },
{ name = "langchain-anthropic" },
{ name = "langchain-community" },
{ name = "langchain-core" },
{ name = "langchain-experimental" },
{ name = "langchain-fireworks" },
{ name = "langchain-mistralai" },
{ name = "langchain-nomic" },
{ name = "langchain-openai" },
{ name = "langchain-tavily" },
{ name = "langgraph-checkpoint-mongodb" },
{ name = "langmem" },
{ name = "langsmith" },
{ name = "matplotlib" },
{ name = "motor" },
{ name = "networkx" },
{ name = "numexpr" },
{ name = "numpy" },
{ name = "pymongo" },
{ name = "pyppeteer" },
{ name = "pytest" },
{ name = "pytest-check-links" },
{ name = "redis" },
{ name = "scikit-learn" },
]
[[package]]
name = "langgraph-prebuilt"
version = "0.1.8"
version = "0.2.2"
source = { editable = "../libs/prebuilt" }
dependencies = [
{ name = "langchain-core" },
@@ -2914,11 +2917,12 @@ dev = [
{ name = "pytest-mock" },
{ name = "pytest-watcher" },
{ name = "ruff" },
{ name = "syrupy" },
]
[[package]]
name = "langgraph-sdk"
version = "0.1.69"
version = "0.1.70"
source = { editable = "../libs/sdk-py" }
dependencies = [
{ name = "httpx" },
@@ -2944,8 +2948,8 @@ dev = [
[[package]]
name = "langgraph-supervisor"
version = "0.0.21"
source = { git = "https://github.com/langchain-ai/langgraph-supervisor-py#6367bebd5462ac899e7def931ac6ab9cc6a9b070" }
version = "0.0.25"
source = { git = "https://github.com/langchain-ai/langgraph-supervisor-py#79380b5c21d3170e2d20dc6c55149ee057a306b1" }
dependencies = [
{ name = "langchain-core" },
{ name = "langgraph" },
@@ -5769,6 +5773,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1c/fc/9ba22f01b5cdacc8f5ed0d22304718d2c758fce3fd49a5372b886a86f37c/sqlalchemy-2.0.41-py3-none-any.whl", hash = "sha256:57df5dc6fdb5ed1a88a1ed2195fd31927e705cad62dedd86b46972752a80f576", size = 1911224 },
]
[[package]]
name = "sqlite-vec"
version = "0.1.6"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/88/ed/aabc328f29ee6814033d008ec43e44f2c595447d9cccd5f2aabe60df2933/sqlite_vec-0.1.6-py3-none-macosx_10_6_x86_64.whl", hash = "sha256:77491bcaa6d496f2acb5cc0d0ff0b8964434f141523c121e313f9a7d8088dee3", size = 164075 },
{ url = "https://files.pythonhosted.org/packages/a7/57/05604e509a129b22e303758bfa062c19afb020557d5e19b008c64016704e/sqlite_vec-0.1.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fdca35f7ee3243668a055255d4dee4dea7eed5a06da8cad409f89facf4595361", size = 165242 },
{ url = "https://files.pythonhosted.org/packages/f2/48/dbb2cc4e5bad88c89c7bb296e2d0a8df58aab9edc75853728c361eefc24f/sqlite_vec-0.1.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b0519d9cd96164cd2e08e8eed225197f9cd2f0be82cb04567692a0a4be02da3", size = 103704 },
{ url = "https://files.pythonhosted.org/packages/80/76/97f33b1a2446f6ae55e59b33869bed4eafaf59b7f4c662c8d9491b6a714a/sqlite_vec-0.1.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux1_x86_64.whl", hash = "sha256:823b0493add80d7fe82ab0fe25df7c0703f4752941aee1c7b2b02cec9656cb24", size = 151556 },
{ url = "https://files.pythonhosted.org/packages/6a/98/e8bc58b178266eae2fcf4c9c7a8303a8d41164d781b32d71097924a6bebe/sqlite_vec-0.1.6-py3-none-win_amd64.whl", hash = "sha256:c65bcfd90fa2f41f9000052bcb8bb75d38240b2dae49225389eca6c3136d3f0c", size = 281540 },
]
[[package]]
name = "sse-starlette"
version = "2.3.5"
@@ -410,11 +410,12 @@ class BaseSqliteStore:
"json_extract(value, '$." + key + "') = " + str(value)
)
else:
# For complex objects, use param binding with JSON serialization
# Complex objects (list, dict, …) compare JSON text
filter_conditions.append(
"json_extract(value, '$." + key + "') = ?"
)
filter_params.append(orjson.dumps(value))
# orjson.dumps returns bytes → decode to str so SQLite sees TEXT
filter_params.append(orjson.dumps(value).decode())
# Vector search branch
if op.query and self.index_config:
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "langgraph-checkpoint-sqlite"
version = "2.0.9"
version = "2.0.10"
description = "Library with a SQLite implementation of LangGraph checkpoint saver."
authors = []
requires-python = ">=3.9"
@@ -657,3 +657,63 @@ async def test_list_namespaces(
# Clean up
for namespace in test_namespaces:
await store.adelete(namespace, "dummy")
async def test_search_items(
fake_embeddings: CharacterEmbeddings,
) -> None:
"""Test search_items functionality by calling store methods directly."""
base = "test_search_items"
test_namespaces = [
(base, "documents", "user1"),
(base, "documents", "user2"),
(base, "reports", "department1"),
(base, "reports", "department2"),
]
test_items = [
{"title": "Doc 1", "author": "John Doe", "tags": ["important"]},
{"title": "Doc 2", "author": "Jane Smith", "tags": ["draft"]},
{"title": "Report A", "author": "John Doe", "tags": ["final"]},
{"title": "Report B", "author": "Alice Johnson", "tags": ["draft"]},
]
async with create_vector_store(
fake_embeddings, text_fields=["key0", "key1", "key3"]
) as store:
# Insert test data
for ns, item in zip(test_namespaces, test_items):
key = f"item_{ns[-1]}"
await store.aput(ns, key, item)
# 1. Search documents
docs = await store.asearch((base, "documents"))
assert len(docs) == 2
assert all(item.namespace[1] == "documents" for item in docs)
# 2. Search reports
reports = await store.asearch((base, "reports"))
assert len(reports) == 2
assert all(item.namespace[1] == "reports" for item in reports)
# 3. Pagination
first_page = await store.asearch((base,), limit=2, offset=0)
second_page = await store.asearch((base,), limit=2, offset=2)
assert len(first_page) == 2
assert len(second_page) == 2
keys_page1 = {item.key for item in first_page}
keys_page2 = {item.key for item in second_page}
assert keys_page1.isdisjoint(keys_page2)
all_items = await store.asearch((base,))
assert len(all_items) == 4
john_items = await store.asearch((base,), filter={"author": "John Doe"})
assert len(john_items) == 2
assert all(item.value["author"] == "John Doe" for item in john_items)
draft_items = await store.asearch((base,), filter={"tags": ["draft"]})
assert len(draft_items) == 2
assert all("draft" in item.value["tags"] for item in draft_items)
for ns in test_namespaces:
key = f"item_{ns[-1]}"
await store.adelete(ns, key)
@@ -987,3 +987,63 @@ def test_list_namespaces_operations(
# Clean up
for namespace in test_namespaces:
store.delete(namespace, "dummy")
def test_search_items(
fake_embeddings: CharacterEmbeddings,
) -> None:
"""Test search_items functionality by calling store methods directly."""
base = "test_search_items"
test_namespaces = [
(base, "documents", "user1"),
(base, "documents", "user2"),
(base, "reports", "department1"),
(base, "reports", "department2"),
]
test_items = [
{"title": "Doc 1", "author": "John Doe", "tags": ["important"]},
{"title": "Doc 2", "author": "Jane Smith", "tags": ["draft"]},
{"title": "Report A", "author": "John Doe", "tags": ["final"]},
{"title": "Report B", "author": "Alice Johnson", "tags": ["draft"]},
]
with create_vector_store(
fake_embeddings, text_fields=["key0", "key1", "key3"]
) as store:
# Insert test data
for ns, item in zip(test_namespaces, test_items):
key = f"item_{ns[-1]}"
store.put(ns, key, item)
# 1. Search documents
docs = store.search((base, "documents"))
assert len(docs) == 2
assert all(item.namespace[1] == "documents" for item in docs)
# 2. Search reports
reports = store.search((base, "reports"))
assert len(reports) == 2
assert all(item.namespace[1] == "reports" for item in reports)
# 3. Pagination
first_page = store.search((base,), limit=2, offset=0)
second_page = store.search((base,), limit=2, offset=2)
assert len(first_page) == 2
assert len(second_page) == 2
keys_page1 = {item.key for item in first_page}
keys_page2 = {item.key for item in second_page}
assert keys_page1.isdisjoint(keys_page2)
all_items = store.search((base,))
assert len(all_items) == 4
john_items = store.search((base,), filter={"author": "John Doe"})
assert len(john_items) == 2
assert all(item.value["author"] == "John Doe" for item in john_items)
draft_items = store.search((base,), filter={"tags": ["draft"]})
assert len(draft_items) == 2
assert all("draft" in item.value["tags"] for item in draft_items)
for ns in test_namespaces:
key = f"item_{ns[-1]}"
store.delete(ns, key)
+1 -1
View File
@@ -346,7 +346,7 @@ dev = [
[[package]]
name = "langgraph-checkpoint-sqlite"
version = "2.0.9"
version = "2.0.10"
source = { editable = "." }
dependencies = [
{ name = "aiosqlite" },
+1 -1
View File
@@ -74,7 +74,7 @@ While LangGraph can be used standalone, it also integrates seamlessly with any L
- [Guides](https://langchain-ai.github.io/langgraph/how-tos/): Quick, actionable code snippets for topics such as streaming, adding memory & persistence, and design patterns (e.g. branching, subgraphs, etc.).
- [Reference](https://langchain-ai.github.io/langgraph/reference/graphs/): Detailed reference on core classes, methods, how to use the graph and checkpointing APIs, and higher-level prebuilt components.
- [Examples](https://langchain-ai.github.io/langgraph/tutorials/): Guided examples on getting started with LangGraph.
- [Examples](https://langchain-ai.github.io/langgraph/tutorials/overview/): Guided examples on getting started with LangGraph.
- [LangChain Academy](https://academy.langchain.com/courses/intro-to-langgraph): Learn the basics of LangGraph in our free, structured course.
- [Templates](https://langchain-ai.github.io/langgraph/concepts/template_applications/): Pre-built reference apps for common agentic workflows (e.g. ReAct agent, memory, retrieval etc.) that can be cloned and adapted.
- [Case studies](https://www.langchain.com/built-with-langgraph): Hear how industry leaders use LangGraph to ship AI applications at scale.
+50
View File
@@ -23,6 +23,7 @@ from langchain_core.messages import (
)
from typing_extensions import TypedDict
from langgraph.constants import CONF, CONFIG_KEY_SEND
from langgraph.graph.state import StateGraph
Messages = Union[list[MessageLikeRepresentation], MessageLikeRepresentation]
@@ -294,3 +295,52 @@ def _format_messages(messages: Sequence[BaseMessage]) -> list[BaseMessage]:
return list(messages)
else:
return convert_to_messages(convert_to_openai_messages(messages))
def push_message(
message: Union[MessageLikeRepresentation, BaseMessageChunk],
*,
state_key: Optional[str] = "messages",
) -> AnyMessage:
"""Write a message manually to the `messages` / `messages-tuple` stream mode.
Will automatically write to the channel specified in the `state_key` unless `state_key` is `None`.
"""
from langchain_core.callbacks.base import (
BaseCallbackHandler,
BaseCallbackManager,
)
from langgraph.config import get_config
from langgraph.constants import NS_SEP
from langgraph.pregel.messages import StreamMessagesHandler
config = get_config()
message = next(x for x in convert_to_messages([message]))
if message.id is None:
raise ValueError("Message ID is required")
if isinstance(config["callbacks"], BaseCallbackManager):
manager = config["callbacks"]
handlers = manager.handlers
elif isinstance(config["callbacks"], list) and all(
isinstance(x, BaseCallbackHandler) for x in config["callbacks"]
):
handlers = config["callbacks"]
if stream_handler := next(
(x for x in handlers if isinstance(x, StreamMessagesHandler)), None
):
metadata = config["metadata"]
message_meta = (
tuple(cast(str, metadata["langgraph_checkpoint_ns"]).split(NS_SEP)),
metadata,
)
stream_handler._emit(message_meta, message, dedupe=False)
if state_key:
config[CONF][CONFIG_KEY_SEND]([(state_key, message)])
return message
@@ -1,268 +0,0 @@
import functools
import logging
import weakref
from dataclasses import is_dataclass
from inspect import isclass
from typing import (
Annotated,
Any,
Callable,
Optional,
Union,
get_args,
get_origin,
get_type_hints,
)
from pydantic import BaseModel, ConfigDict, TypeAdapter
from typing_extensions import is_typeddict
__all__ = ["SchemaCoercionMapper"]
logger = logging.getLogger(__name__)
_cache: weakref.WeakKeyDictionary[type[Any], dict[int, "SchemaCoercionMapper"]] = (
weakref.WeakKeyDictionary()
)
class SchemaCoercionMapper:
"""Lightweight coercion of *dict* → *BaseModel* instances."""
def __new__(
cls,
schema: type[Any],
type_hints: Optional[dict[str, Any]] = None,
*,
max_depth: int = 12,
) -> "SchemaCoercionMapper":
by_depth = _cache.setdefault(schema, {})
if max_depth in by_depth:
return by_depth[max_depth]
inst = super().__new__(cls)
by_depth[max_depth] = inst
return inst
def __init__(
self,
schema: type[BaseModel],
type_hints: Optional[dict[str, Any]] = None,
*,
max_depth: int = 12,
) -> None:
if hasattr(self, "_initialised"):
return
self._initialised = True
self.schema = schema
self.max_depth = max_depth
self.type_hints = (
type_hints
if type_hints is not None
else get_type_hints(schema, localns={schema.__name__: schema})
)
if issubclass(schema, BaseModel):
self._fields = {
n: self.type_hints.get(n, f.annotation)
for n, f in schema.model_fields.items()
}
self._construct: Callable[..., Any] = schema.model_construct
unhandled_attrs = ("validators", "field_validators", "root_validators")
if (decorators := getattr(schema, "__pydantic_decorators__", None)) and any(
getattr(decorators, attr, None) for attr in unhandled_attrs
):
self.coerce = lambda v, _: schema.model_validate(v)
else:
self.coerce = self._coerce
else:
raise TypeError("Schema must be a Pydantic V2 model.")
self._field_coercers: Optional[dict[str, Callable[[Any, int], Any]]] = None
def __call__(self, input_data: Any, depth: Optional[int] = None) -> Any:
return self.coerce(input_data, depth)
def _coerce(self, input_data: Any, depth: Optional[int] = None) -> Any:
if depth is None:
depth = self.max_depth
if not isinstance(input_data, dict) or depth <= 0:
return input_data
if self._field_coercers is None:
self._field_coercers = {
n: self._build_coercer(t, depth - 1) for n, t in self._fields.items()
}
processed: dict[str, Any] = {}
for k, v in input_data.items():
fn = self._field_coercers.get(k)
processed[k] = fn(v, depth - 1) if fn else v
return self._construct(**processed)
def _build_coercer(
self, field_type: Any, depth: int, *, throw: bool = False
) -> Callable[[Any, Any], Any]:
if depth == 0:
return self._passthrough
origin = get_origin(field_type)
if (field_type in _IDENTITY_TYPES) or (origin in _IDENTITY_TYPES):
return self._passthrough
if origin is Annotated:
real_type, *_ = get_args(field_type)
sub = self._build_coercer(real_type, depth - 1)
return lambda v, d: sub(v, d)
if isclass(field_type):
# This is needed bcs. of issubclass issues on older versions of python
try:
is_bm_subclass = issubclass(field_type, BaseModel)
except TypeError:
# python < 3.11 issue.
is_bm_subclass = False
if is_bm_subclass:
mapper = SchemaCoercionMapper(field_type, max_depth=depth - 1)
return lambda v, d: mapper.coerce(v, d) if isinstance(v, dict) else v
if origin is list:
args = get_args(field_type)
if len(args) != 1:
return self._passthrough
sub = self._build_coercer(args[0], depth - 1)
def list_coercer(v: Any, d: Any) -> Any:
if not isinstance(v, (list, tuple)):
return v
return [sub(x, d - 1) for x in v]
return list_coercer
if origin is set or field_type is set:
args = get_args(field_type)
if len(args) > 1:
return self._passthrough
elif len(args) == 1:
sub = self._build_coercer(args[0], depth - 1)
else:
sub = None # type: ignore
def set_coercer(v: Any, d: Any) -> Any:
if not isinstance(v, (list, tuple, set)):
return v
if sub is None:
return set(v)
return {sub(x, d - 1) for x in v}
return set_coercer
if origin is dict or field_type is dict:
args = get_args(field_type)
if len(args) != 2:
def dict_coercer(v: Any, d: Any) -> Any:
if not isinstance(v, dict):
if throw:
raise TypeError(f"Expected dict, got {type(v)}")
return v
return dict_coercer
k_sub = self._build_coercer(args[0], depth - 1)
v_sub = self._build_coercer(args[1], depth - 1)
def dict_coercer(v: Any, d: Any) -> Any:
if not isinstance(v, dict):
if throw:
raise TypeError(f"Expected dict, got {type(v)}")
return v
return {k_sub(k, d - 1): v_sub(val, d - 1) for k, val in v.items()}
return dict_coercer
if origin is tuple:
elem_types = get_args(field_type)
if not elem_types:
return self._passthrough
subs = [self._build_coercer(t, depth - 1) for t in elem_types]
return lambda v, d: (
tuple(
subs[i](v[i] if i < len(v) else None, d - 1)
for i in range(len(subs))
)
if isinstance(v, (list, tuple))
else v
)
if origin is Union:
uargs = get_args(field_type)
subs, none_in_union = [], False
for ix, arg in enumerate(uargs):
if arg is type(None):
none_in_union = True
else:
subs.append(
self._build_coercer(arg, depth - 1, throw=ix < len(uargs) - 1)
)
def union_coercer(v: Any, d: Any) -> Any:
if v is None and none_in_union:
return None
err = None
for sp in subs:
try:
return sp(v, d - 1)
except TypeError as e:
err = e
if err:
raise err
return v
return union_coercer
adapter_fn = _get_adapter(field_type)
return lambda v, _d: adapter_fn(v)
@staticmethod
def _passthrough(v: Any, _d: Any) -> Any: # noqa: D401
return v
_adapter_cache: dict[Any, Callable[[Any], Any]] = {}
_IDENTITY_TYPES: tuple[type[Any], ...] = (
int,
float,
str,
bool,
bytes,
bytearray,
complex,
memoryview,
type(None),
)
@functools.lru_cache(maxsize=2048)
def _adapter_for(tp: Any) -> Callable[[Any], Any]: # noqa: D401
try:
config = (
None
if (issubclass(tp, BaseModel) or is_dataclass(tp) or is_typeddict(tp))
else ConfigDict(arbitrary_types_allowed=True)
)
except TypeError:
config = None
return TypeAdapter(tp, config=config).validate_python
def _get_adapter(tp: Any) -> Callable[[Any], Any]:
try:
return _adapter_cache[tp]
except KeyError:
fn = _adapter_for(tp)
_adapter_cache[tp] = fn
return fn
+3 -6
View File
@@ -64,7 +64,6 @@ from langgraph.graph.graph import (
Graph,
Send,
)
from langgraph.graph.schema_utils import SchemaCoercionMapper
from langgraph.managed.base import (
ChannelKeyPlaceholder,
ChannelTypePlaceholder,
@@ -304,6 +303,7 @@ class StateGraph(Graph):
If a string is provided, it will be used as the node name, and action will be used as the function or runnable.
action: The action associated with the node. (default: None)
Will be used as the node function or runnable if `node` is a string (node name).
defer: Whether to defer the execution of the node until the run is about to end.
metadata: The metadata associated with the node. (default: None)
input: The input schema for the node. (default: the graph's input schema)
retry: The policy for retrying the node. (default: None)
@@ -1041,11 +1041,8 @@ def _pick_mapper(
) -> Optional[Callable[[Any], Any]]:
if state_keys == ["__root__"]:
return None
if isclass(schema):
if issubclass(schema, dict):
return None
if issubclass(schema, BaseModel):
return SchemaCoercionMapper(schema, type_hints=type_hints)
if isclass(schema) and issubclass(schema, dict):
return None
return partial(_coerce_state, schema)
+3 -2
View File
@@ -54,7 +54,7 @@ def push_ui_message(
id: Optional[str] = None,
metadata: Optional[dict[str, Any]] = None,
message: Optional[AnyMessage] = None,
state_key: str = "ui",
state_key: Optional[str] = "ui",
merge: bool = False,
) -> UIMessage:
"""Push a new UI message to update the UI state.
@@ -111,7 +111,8 @@ def push_ui_message(
}
writer(evt)
config[CONF][CONFIG_KEY_SEND]([(state_key, evt)])
if state_key:
config[CONF][CONFIG_KEY_SEND]([(state_key, evt)])
return evt
+9 -16
View File
@@ -85,7 +85,6 @@ from langgraph.pregel.algo import (
PregelTaskWrites,
apply_writes,
local_read,
local_write,
prepare_next_tasks,
)
from langgraph.pregel.call import identifier
@@ -412,7 +411,7 @@ class Pregel(PregelProtocol):
def reducer(current, update):
if current:
return current + " | " + "update"
return current + " | " + update
else:
return update
@@ -1684,11 +1683,7 @@ class Pregel(PregelProtocol):
run_name=self.name + "UpdateState",
configurable={
# deque.extend is thread-safe
CONFIG_KEY_SEND: partial(
local_write,
writes.extend,
self.nodes.keys(),
),
CONFIG_KEY_SEND: writes.extend,
CONFIG_KEY_READ: partial(
local_read,
channels,
@@ -2111,11 +2106,7 @@ class Pregel(PregelProtocol):
run_name=self.name + "UpdateState",
configurable={
# deque.extend is thread-safe
CONFIG_KEY_SEND: partial(
local_write,
writes.extend,
self.nodes.keys(),
),
CONFIG_KEY_SEND: writes.extend,
CONFIG_KEY_READ: partial(
local_read,
channels,
@@ -2223,12 +2214,14 @@ class Pregel(PregelProtocol):
validate_keys(output_keys, self.channels)
interrupt_before = interrupt_before or self.interrupt_before_nodes
interrupt_after = interrupt_after or self.interrupt_after_nodes
stream_mode = stream_mode if stream_mode is not None else self.stream_mode
if stream_mode is None and CONFIG_KEY_TASK_ID in config.get(CONF, {}):
# if being called as a node in another graph, default to values mode
# but don't overwrite stream_mode arg if provided
stream_mode = ["values"]
elif stream_mode is None:
stream_mode = self.stream_mode
if not isinstance(stream_mode, list):
stream_mode = [stream_mode]
if CONFIG_KEY_TASK_ID in config.get(CONF, {}):
# if being called as a node in another graph, always use values mode
stream_mode = ["values"]
if self.checkpointer is False:
checkpointer: BaseCheckpointSaver | None = None
elif CONFIG_KEY_CHECKPOINTER in config.get(CONF, {}):
+3 -32
View File
@@ -64,7 +64,6 @@ from langgraph.constants import (
TASKS,
Send,
)
from langgraph.errors import InvalidUpdateError
from langgraph.managed.base import ManagedValueMapping
from langgraph.pregel.call import get_runnable_for_task, identifier
from langgraph.pregel.io import read_channels
@@ -212,22 +211,6 @@ def local_read(
return values
def local_write(
commit: Callable[[Sequence[tuple[str, Any]]], None],
process_keys: Iterable[str],
writes: Sequence[tuple[str, Any]],
) -> None:
"""Function injected under CONFIG_KEY_SEND in task config, to write to channels.
Validates writes and forwards them to `commit` function."""
for chan, value in writes:
if chan in (PUSH, TASKS) and value is not None:
if not isinstance(value, Send):
raise InvalidUpdateError(f"Expected Send, got {value}")
if value.node not in process_keys:
raise InvalidUpdateError(f"Invalid node name {value.node} in packet")
commit(writes)
def increment(current: Optional[int], channel: BaseChannel) -> int:
"""Default channel versioning function, increments the current int version."""
return current + 1 if current is not None else 1
@@ -626,11 +609,7 @@ def prepare_single_task(
configurable={
CONFIG_KEY_TASK_ID: task_id,
# deque.extend is thread-safe
CONFIG_KEY_SEND: partial(
local_write,
writes.extend,
processes.keys(),
),
CONFIG_KEY_SEND: writes.extend,
CONFIG_KEY_READ: partial(
local_read,
channels,
@@ -750,11 +729,7 @@ def prepare_single_task(
configurable={
CONFIG_KEY_TASK_ID: task_id,
# deque.extend is thread-safe
CONFIG_KEY_SEND: partial(
local_write,
writes.extend,
processes.keys(),
),
CONFIG_KEY_SEND: writes.extend,
CONFIG_KEY_READ: partial(
local_read,
channels,
@@ -888,11 +863,7 @@ def prepare_single_task(
configurable={
CONFIG_KEY_TASK_ID: task_id,
# deque.extend is thread-safe
CONFIG_KEY_SEND: partial(
local_write,
writes.extend,
tuple(processes.keys()),
),
CONFIG_KEY_SEND: writes.extend,
CONFIG_KEY_READ: partial(
local_read,
channels,
+25 -14
View File
@@ -135,7 +135,6 @@ P = ParamSpec("P")
INPUT_DONE = object()
INPUT_RESUMING = object()
INPUT_SHOULD_VALIDATE = object()
SPECIAL_CHANNELS = (ERROR, INTERRUPT, SCHEDULED)
WritesT = Sequence[tuple[str, Any]]
@@ -509,9 +508,14 @@ class PregelLoop(LoopProtocol):
**read_channels(self.channels, self.stream_keys)
)
# produce values output
self._emit(
"values", map_output_values, self.output_keys, writes, self.channels
)
if not updated_channels.isdisjoint(
(self.output_keys,)
if isinstance(self.output_keys, str)
else self.output_keys
):
self._emit(
"values", map_output_values, self.output_keys, writes, self.channels
)
# clear pending writes
self.checkpoint_pending_writes.clear()
# "not skip_done_tasks" only applies to first tick after resuming
@@ -887,7 +891,7 @@ class PregelLoop(LoopProtocol):
and self.checkpoint_pending_writes
and any(task.writes for task in self.tasks.values())
):
mv_writes, _ = apply_writes(
mv_writes, updated_channels = apply_writes(
self.checkpoint,
self.channels,
self.tasks.values(),
@@ -896,13 +900,18 @@ class PregelLoop(LoopProtocol):
)
for key, values in mv_writes.items():
self._update_mv(key, values)
self._emit(
"values",
map_output_values,
self.output_keys,
[w for t in self.tasks.values() for w in t.writes],
self.channels,
)
if not updated_channels.isdisjoint(
(self.output_keys,)
if isinstance(self.output_keys, str)
else self.output_keys
):
self._emit(
"values",
map_output_values,
self.output_keys,
[w for t in self.tasks.values() for w in t.writes],
self.channels,
)
# emit INTERRUPT if exception is empty (otherwise emitted by put_writes)
if exc_value is not None and (not exc_value.args or not exc_value.args[0]):
self._emit(
@@ -1083,7 +1092,8 @@ class SyncPregelLoop(PregelLoop, AbstractContextManager):
self, task: PregelExecutableTask, write_idx: int, call: Optional[Call] = None
) -> Optional[PregelExecutableTask]:
if pushed := super().accept_push(task, write_idx, call):
self.match_cached_writes()
for task in self.match_cached_writes():
self.output_writes(task.id, task.writes, cached=True)
return pushed
def put_writes(self, task_id: str, writes: WritesT) -> None:
@@ -1279,7 +1289,8 @@ class AsyncPregelLoop(PregelLoop, AbstractAsyncContextManager):
self, task: PregelExecutableTask, write_idx: int, call: Optional[Call] = None
) -> Optional[PregelExecutableTask]:
if pushed := super().accept_push(task, write_idx, call):
await self.amatch_cached_writes()
for task in await self.amatch_cached_writes():
self.output_writes(task.id, task.writes, cached=True)
return pushed
def put_writes(self, task_id: str, writes: WritesT) -> None:
+2 -2
View File
@@ -41,7 +41,7 @@ def run_with_retry(
except ParentCommand as exc:
ns: str = config[CONF][CONFIG_KEY_CHECKPOINT_NS]
cmd = exc.args[0]
if cmd.graph == ns:
if cmd.graph in (ns, task.name):
# this command is for the current graph, handle it
for w in task.writers:
w.invoke(cmd, config)
@@ -137,7 +137,7 @@ async def arun_with_retry(
except ParentCommand as exc:
ns: str = config[CONF][CONFIG_KEY_CHECKPOINT_NS]
cmd = exc.args[0]
if cmd.graph == ns:
if cmd.graph in (ns, task.name):
# this command is for the current graph, handle it
for w in task.writers:
w.invoke(cmd, config)
+19 -17
View File
@@ -56,6 +56,10 @@ EXCLUDED_FRAME_FNAMES = (
"concurrent/futures/_base.py",
)
SKIP_RERAISE_SET: weakref.WeakSet[Union[concurrent.futures.Future, asyncio.Future]] = (
weakref.WeakSet()
)
class FuturesDict(Generic[F, E], dict[F, Optional[PregelExecutableTask]]):
event: E
@@ -165,7 +169,6 @@ class PregelRunner:
futures=weakref.ref(futures),
schedule_task=schedule_task,
submit=self.submit,
reraise=reraise,
),
},
)
@@ -207,7 +210,6 @@ class PregelRunner:
futures=weakref.ref(futures),
schedule_task=schedule_task,
submit=self.submit,
reraise=reraise,
),
},
__reraise_on_exit__=reraise,
@@ -302,7 +304,6 @@ class PregelRunner:
futures=weakref.ref(futures),
schedule_task=schedule_task,
submit=self.submit,
reraise=reraise,
loop=loop,
),
},
@@ -349,7 +350,6 @@ class PregelRunner:
futures=weakref.ref(futures),
schedule_task=schedule_task,
submit=self.submit,
reraise=reraise,
loop=loop,
),
},
@@ -434,7 +434,8 @@ class PregelRunner:
raise exception
else:
# save error to checkpointer
self.put_writes()(task.id, [(ERROR, exception)]) # type: ignore[misc]
task.writes.append((ERROR, exception))
self.put_writes()(task.id, task.writes) # type: ignore[misc]
else:
if self.node_finished and (
task.config is None or TAG_HIDDEN not in task.config.get("tags", [])
@@ -456,7 +457,7 @@ def _should_stop_others(
if fut.cancelled():
continue
elif exc := fut.exception():
if not isinstance(exc, GraphBubbleUp):
if not isinstance(exc, GraphBubbleUp) and fut not in SKIP_RERAISE_SET:
return True
return False
@@ -494,7 +495,8 @@ def _panic_or_proceed(
interrupts: list[GraphInterrupt] = []
while done:
# if any task failed
if exc := _exception(done.pop()):
fut = done.pop()
if exc := _exception(fut):
# cancel all pending tasks
while inflight:
inflight.pop().cancel()
@@ -503,7 +505,7 @@ def _panic_or_proceed(
if isinstance(exc, GraphInterrupt):
# collect interrupts
interrupts.append(exc)
else:
elif fut not in SKIP_RERAISE_SET:
raise exc
# raise combined interrupts
if interrupts:
@@ -530,7 +532,6 @@ def _call(
[PregelExecutableTask, int, Optional[Call]], Optional[PregelExecutableTask]
],
submit: weakref.ref[Submit],
reraise: bool,
) -> concurrent.futures.Future[Any]:
if asyncio.iscoroutinefunction(func):
raise RuntimeError("In an sync context async tasks cannot be called")
@@ -582,14 +583,16 @@ def _call(
callbacks=callbacks,
schedule_task=schedule_task,
submit=submit,
reraise=reraise,
),
},
__reraise_on_exit__=reraise,
__reraise_on_exit__=False,
# starting a new task in the next tick ensures
# updates from this tick are committed/streamed first
__next_tick__=True,
)
# exceptions for call() tasks are raised into the parent task
# so we should not re-raise at the end of the tick
SKIP_RERAISE_SET.add(fut)
futures()[fut] = next_task # type: ignore[index]
fut = cast(Union[asyncio.Future, concurrent.futures.Future], fut)
# return a chained future to ensure commit() callback is called
@@ -613,7 +616,6 @@ def _acall(
],
submit: weakref.ref[Submit],
loop: asyncio.AbstractEventLoop,
reraise: bool = False,
stream: bool = False,
) -> Union[asyncio.Future[Any], concurrent.futures.Future[Any]]:
# return a chained future to ensure commit() callback is called
@@ -643,7 +645,6 @@ def _acall(
schedule_task=schedule_task,
submit=submit,
loop=loop,
reraise=reraise,
stream=stream,
),
loop,
@@ -669,7 +670,6 @@ async def _acall_impl(
],
submit: weakref.ref[Submit],
loop: asyncio.AbstractEventLoop,
reraise: bool = False,
stream: bool = False,
) -> None:
try:
@@ -726,17 +726,19 @@ async def _acall_impl(
schedule_task=schedule_task,
submit=submit,
loop=loop,
reraise=reraise,
),
},
__name__=task().name, # type: ignore[union-attr]
__name__=next_task.name,
__cancel_on_exit__=True,
__reraise_on_exit__=reraise,
__reraise_on_exit__=False,
# starting a new task in the next tick ensures
# updates from this tick are committed/streamed first
__next_tick__=True,
),
)
# exceptions for call() tasks are raised into the parent task
# so we should not re-raise at the end of the tick
SKIP_RERAISE_SET.add(fut)
futures()[fut] = next_task # type: ignore[index]
if fut is not None:
chain_future(fut, destination)
+2 -2
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "langgraph"
version = "0.4.5"
version = "0.4.8"
description = "Building stateful, multi-actor applications with LLMs"
authors = []
requires-python = ">=3.9"
@@ -15,7 +15,7 @@ dependencies = [
"langchain-core>=0.1",
"langgraph-checkpoint>=2.0.26",
"langgraph-sdk>=0.1.42",
"langgraph-prebuilt>=0.1.8",
"langgraph-prebuilt>=0.2.0",
"xxhash>=3.5.0",
"pydantic>=2.7.4",
]
+32 -1
View File
@@ -15,7 +15,7 @@ from pydantic import BaseModel
from typing_extensions import TypedDict
from langgraph.graph import add_messages
from langgraph.graph.message import REMOVE_ALL_MESSAGES, MessagesState
from langgraph.graph.message import REMOVE_ALL_MESSAGES, MessagesState, push_message
from langgraph.graph.state import END, START, StateGraph
from tests.messages import _AnyIdHumanMessage
@@ -332,3 +332,34 @@ def test_remove_all_messages():
assert result == [
_AnyIdHumanMessage(content="Updated hi there"),
]
def test_push_messages_in_graph():
class MessagesState(TypedDict):
messages: Annotated[list[AnyMessage], add_messages]
def chat(_: MessagesState) -> MessagesState:
with pytest.raises(ValueError, match="Message ID is required"):
push_message(AIMessage(content="No ID"))
push_message(AIMessage(content="First", id="1"))
push_message(HumanMessage(content="Second", id="2"))
push_message(AIMessage(content="Third", id="3"))
builder = StateGraph(MessagesState)
builder.add_node(chat)
builder.add_edge(START, "chat")
graph = builder.compile()
messages, values = [], None
for event, chunk in graph.stream(
{"messages": []}, stream_mode=["messages", "values"]
):
if event == "values":
values = chunk
elif event == "messages":
message, _ = chunk
messages.append(message)
assert values["messages"] == messages
+79 -3
View File
@@ -5514,8 +5514,11 @@ def test_runnable_passthrough_node_graph() -> None:
assert graph.get_graph(xray=True).to_json() == graph.get_graph(xray=False).to_json()
@pytest.mark.parametrize("subgraph_persist", [True, False])
@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_SYNC)
def test_parent_command(request: pytest.FixtureRequest, checkpointer_name: str) -> None:
def test_parent_command(
request: pytest.FixtureRequest, checkpointer_name: str, subgraph_persist: bool
) -> None:
from langchain_core.messages import BaseMessage
from langchain_core.tools import tool
@@ -5527,7 +5530,7 @@ def test_parent_command(request: pytest.FixtureRequest, checkpointer_name: str)
subgraph_builder = StateGraph(MessagesState)
subgraph_builder.add_node("tool", get_user_name)
subgraph_builder.add_edge(START, "tool")
subgraph = subgraph_builder.compile()
subgraph = subgraph_builder.compile(checkpointer=subgraph_persist)
class CustomParentState(TypedDict):
messages: Annotated[list[BaseMessage], add_messages]
@@ -6873,7 +6876,7 @@ def test_sync_streaming_with_functional_api() -> None:
should be greater than the time delay between the two tasks.
"""
time_delay = 0.01
time_delay = 0.05
@task()
def slow() -> dict:
@@ -8769,3 +8772,76 @@ def test_get_graph_root_channel(snapshot: SnapshotAssertion) -> None:
assert json.dumps(graph.get_graph().to_json(), indent=2) == snapshot
assert graph.get_graph().draw_mermaid(with_styles=False) == snapshot
def test_imp_exception(
sync_checkpointer: BaseCheckpointSaver,
) -> None:
@task()
def my_task(number: int):
time.sleep(0.1)
return number * 2
@task()
def task_with_exception(number: int):
time.sleep(0.1)
raise Exception("This is a test exception")
@entrypoint(checkpointer=sync_checkpointer)
def my_workflow(number: int):
my_task(number).result()
try:
task_with_exception(number).result()
except Exception as e:
print(f"Exception caught: {e}")
my_task(number).result()
return "done"
thread1 = {"configurable": {"thread_id": "1"}}
assert my_workflow.invoke(1, thread1) == "done"
assert [c for c in my_workflow.stream(1, thread1)] == [
{"my_task": 2},
{"my_task": 2},
{"my_workflow": "done"},
]
@pytest.mark.parametrize("subgraph_persist", [True, False])
def test_parent_command_goto(
sync_checkpointer: BaseCheckpointSaver, subgraph_persist: bool
) -> None:
class State(TypedDict):
dialog_state: Annotated[list[str], operator.add]
def node_a_child(state):
return {"dialog_state": ["a_child_state"]}
def node_b_child(state):
return Command(
graph=Command.PARENT,
goto="node_b_parent",
update={"dialog_state": ["b_child_state"]},
)
sub_builder = StateGraph(State)
sub_builder.add_node(node_a_child)
sub_builder.add_node(node_b_child)
sub_builder.add_edge(START, "node_a_child")
sub_builder.add_edge("node_a_child", "node_b_child")
sub_graph = sub_builder.compile(checkpointer=subgraph_persist)
def node_b_parent(state):
return {"dialog_state": ["node_b_parent"]}
main_builder = StateGraph(State)
main_builder.add_node(node_b_parent)
main_builder.add_edge(START, "subgraph_node")
main_builder.add_node("subgraph_node", sub_graph, destinations=("node_b_parent",))
main_graph = main_builder.compile(sync_checkpointer, name="parent")
config = {"configurable": {"thread_id": 1}}
assert main_graph.invoke(input={"dialog_state": ["init_state"]}, config=config) == {
"dialog_state": ["init_state", "b_child_state", "node_b_parent"]
}
+341 -2
View File
@@ -6772,8 +6772,9 @@ async def test_debug_nested_subgraphs(async_checkpointer: BaseCheckpointSaver):
assert stream_task.get("state") == history_task.state
@pytest.mark.parametrize("subgraph_persist", [True, False])
@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_ASYNC)
async def test_parent_command(checkpointer_name: str) -> None:
async def test_parent_command(checkpointer_name: str, subgraph_persist: bool) -> None:
from langchain_core.messages import BaseMessage
from langchain_core.tools import tool
@@ -6785,7 +6786,7 @@ async def test_parent_command(checkpointer_name: str) -> None:
subgraph_builder = StateGraph(MessagesState)
subgraph_builder.add_node("tool", get_user_name)
subgraph_builder.add_edge(START, "tool")
subgraph = subgraph_builder.compile()
subgraph = subgraph_builder.compile(checkpointer=subgraph_persist)
class CustomParentState(TypedDict):
messages: Annotated[list[BaseMessage], add_messages]
@@ -9148,3 +9149,341 @@ async def test_draw_invalid():
{"source": "nothing", "target": "__end__"},
],
}
@NEEDS_CONTEXTVARS
async def test_imp_exception(
async_checkpointer: BaseCheckpointSaver,
) -> None:
@task()
async def my_task(number: int):
await asyncio.sleep(0.1)
return number * 2
@task()
async def task_with_exception(number: int):
await asyncio.sleep(0.1)
raise Exception("This is a test exception")
@entrypoint(checkpointer=async_checkpointer)
async def my_workflow(number: int):
await my_task(number)
try:
await task_with_exception(number)
except Exception as e:
print(f"Exception caught: {e}")
await my_task(number)
return "done"
thread1 = {"configurable": {"thread_id": "1"}}
assert await my_workflow.ainvoke(1, thread1) == "done"
assert [c async for c in my_workflow.astream(1, thread1)] == [
{"my_task": 2},
{"my_task": 2},
{"my_workflow": "done"},
]
assert [c async for c in my_workflow.astream_events(1, thread1)] == [
{
"event": "on_chain_start",
"data": {"input": 1},
"name": "LangGraph",
"tags": [],
"run_id": AnyStr(),
"metadata": {"thread_id": "1"},
"parent_ids": [],
},
{
"event": "on_chain_start",
"data": {"input": 1},
"name": "my_workflow",
"tags": ["graph:step:4"],
"run_id": AnyStr(),
"metadata": {
"thread_id": "1",
"langgraph_step": 4,
"langgraph_node": "my_workflow",
"langgraph_triggers": ("__start__",),
"langgraph_path": ("__pregel_pull", "my_workflow"),
"langgraph_checkpoint_ns": AnyStr(),
},
"parent_ids": [AnyStr()],
},
{
"event": "on_chain_start",
"data": {"input": {"number": 1}},
"name": "my_task",
"tags": ["seq:step:1"],
"run_id": AnyStr(),
"metadata": {
"thread_id": "1",
"langgraph_step": 4,
"langgraph_node": "my_task",
"langgraph_triggers": ("__pregel_push",),
"langgraph_path": (
"__pregel_push",
("__pregel_pull", "my_workflow"),
2,
True,
),
"langgraph_checkpoint_ns": AnyStr(),
},
"parent_ids": [
AnyStr(),
AnyStr(),
],
},
{
"event": "on_chain_stream",
"run_id": AnyStr(),
"name": "my_task",
"tags": ["seq:step:1"],
"metadata": {
"thread_id": "1",
"langgraph_step": 4,
"langgraph_node": "my_task",
"langgraph_triggers": ("__pregel_push",),
"langgraph_path": (
"__pregel_push",
("__pregel_pull", "my_workflow"),
2,
True,
),
"langgraph_checkpoint_ns": AnyStr(),
},
"data": {"chunk": 2},
"parent_ids": [
AnyStr(),
AnyStr(),
],
},
{
"event": "on_chain_end",
"data": {"output": 2, "input": {"number": 1}},
"run_id": AnyStr(),
"name": "my_task",
"tags": ["seq:step:1"],
"metadata": {
"thread_id": "1",
"langgraph_step": 4,
"langgraph_node": "my_task",
"langgraph_triggers": ("__pregel_push",),
"langgraph_path": (
"__pregel_push",
("__pregel_pull", "my_workflow"),
2,
True,
),
"langgraph_checkpoint_ns": AnyStr(),
},
"parent_ids": [
AnyStr(),
AnyStr(),
],
},
{
"event": "on_chain_stream",
"run_id": AnyStr(),
"name": "LangGraph",
"tags": [],
"metadata": {"thread_id": "1"},
"data": {"chunk": {"my_task": 2}},
"parent_ids": [],
},
{
"event": "on_chain_start",
"data": {"input": {"number": 1}},
"name": "task_with_exception",
"tags": ["seq:step:1"],
"run_id": AnyStr(),
"metadata": {
"thread_id": "1",
"langgraph_step": 4,
"langgraph_node": "my_task",
"langgraph_triggers": ("__pregel_push",),
"langgraph_path": (
"__pregel_push",
("__pregel_pull", "my_workflow"),
2,
True,
),
"langgraph_checkpoint_ns": AnyStr(),
},
"parent_ids": [
AnyStr(),
AnyStr(),
],
},
{
"event": "on_chain_start",
"data": {"input": {"number": 1}},
"name": "my_task",
"tags": ["seq:step:1"],
"run_id": AnyStr(),
"metadata": {
"thread_id": "1",
"langgraph_step": 4,
"langgraph_node": "my_task",
"langgraph_triggers": ("__pregel_push",),
"langgraph_path": (
"__pregel_push",
("__pregel_pull", "my_workflow"),
2,
True,
),
"langgraph_checkpoint_ns": AnyStr(),
},
"parent_ids": [
AnyStr(),
AnyStr(),
],
},
{
"event": "on_chain_stream",
"run_id": AnyStr(),
"name": "my_task",
"tags": ["seq:step:1"],
"metadata": {
"thread_id": "1",
"langgraph_step": 4,
"langgraph_node": "my_task",
"langgraph_triggers": ("__pregel_push",),
"langgraph_path": (
"__pregel_push",
("__pregel_pull", "my_workflow"),
2,
True,
),
"langgraph_checkpoint_ns": AnyStr(),
},
"data": {"chunk": 2},
"parent_ids": [
AnyStr(),
AnyStr(),
],
},
{
"event": "on_chain_end",
"data": {"output": 2, "input": {"number": 1}},
"run_id": AnyStr(),
"name": "my_task",
"tags": ["seq:step:1"],
"metadata": {
"thread_id": "1",
"langgraph_step": 4,
"langgraph_node": "my_task",
"langgraph_triggers": ("__pregel_push",),
"langgraph_path": (
"__pregel_push",
("__pregel_pull", "my_workflow"),
2,
True,
),
"langgraph_checkpoint_ns": AnyStr(),
},
"parent_ids": [
AnyStr(),
AnyStr(),
],
},
{
"event": "on_chain_stream",
"run_id": AnyStr(),
"name": "my_workflow",
"tags": ["graph:step:4"],
"metadata": {
"thread_id": "1",
"langgraph_step": 4,
"langgraph_node": "my_workflow",
"langgraph_triggers": ("__start__",),
"langgraph_path": ("__pregel_pull", "my_workflow"),
"langgraph_checkpoint_ns": AnyStr(),
},
"data": {"chunk": "done"},
"parent_ids": [AnyStr()],
},
{
"event": "on_chain_stream",
"run_id": AnyStr(),
"name": "LangGraph",
"tags": [],
"metadata": {"thread_id": "1"},
"data": {"chunk": {"my_task": 2}},
"parent_ids": [],
},
{
"event": "on_chain_end",
"data": {"output": "done", "input": 1},
"run_id": AnyStr(),
"name": "my_workflow",
"tags": ["graph:step:4"],
"metadata": {
"thread_id": "1",
"langgraph_step": 4,
"langgraph_node": "my_workflow",
"langgraph_triggers": ("__start__",),
"langgraph_path": ("__pregel_pull", "my_workflow"),
"langgraph_checkpoint_ns": AnyStr(),
},
"parent_ids": [AnyStr()],
},
{
"event": "on_chain_stream",
"run_id": AnyStr(),
"name": "LangGraph",
"tags": [],
"metadata": {"thread_id": "1"},
"data": {"chunk": {"my_workflow": "done"}},
"parent_ids": [],
},
{
"event": "on_chain_end",
"data": {"output": "done"},
"run_id": AnyStr(),
"name": "LangGraph",
"tags": [],
"metadata": {"thread_id": "1"},
"parent_ids": [],
},
]
@pytest.mark.parametrize("subgraph_persist", [True, False])
async def test_parent_command_goto(
async_checkpointer: BaseCheckpointSaver, subgraph_persist: bool
) -> None:
class State(TypedDict):
dialog_state: Annotated[list[str], operator.add]
async def node_a_child(state):
return {"dialog_state": ["a_child_state"]}
async def node_b_child(state):
return Command(
graph=Command.PARENT,
goto="node_b_parent",
update={"dialog_state": ["b_child_state"]},
)
sub_builder = StateGraph(State)
sub_builder.add_node(node_a_child)
sub_builder.add_node(node_b_child)
sub_builder.add_edge(START, "node_a_child")
sub_builder.add_edge("node_a_child", "node_b_child")
sub_graph = sub_builder.compile(checkpointer=subgraph_persist)
async def node_b_parent(state):
return {"dialog_state": ["node_b_parent"]}
main_builder = StateGraph(State)
main_builder.add_node(node_b_parent)
main_builder.add_edge(START, "subgraph_node")
main_builder.add_node("subgraph_node", sub_graph, destinations=("node_b_parent",))
main_graph = main_builder.compile(async_checkpointer, name="parent")
config = {"configurable": {"thread_id": 1}}
assert await main_graph.ainvoke(
input={"dialog_state": ["init_state"]}, config=config
) == {"dialog_state": ["init_state", "b_child_state", "node_b_parent"]}
+1569 -1567
View File
File diff suppressed because it is too large Load Diff
@@ -240,13 +240,14 @@ def _validate_chat_history(
def create_react_agent(
model: Union[str, LanguageModelLike],
tools: Union[Sequence[Union[BaseTool, Callable]], ToolNode],
tools: Union[Sequence[Union[BaseTool, Callable, dict[str, Any]]], ToolNode],
*,
prompt: Optional[Prompt] = None,
response_format: Optional[
Union[StructuredResponseSchema, tuple[str, StructuredResponseSchema]]
] = None,
pre_model_hook: Optional[RunnableLike] = None,
post_model_hook: Optional[RunnableLike] = None,
state_schema: Optional[StateSchemaType] = None,
config_schema: Optional[Type[Any]] = None,
checkpointer: Optional[Checkpointer] = None,
@@ -321,6 +322,12 @@ def create_react_agent(
...
}
```
post_model_hook: An optional node to add after the `agent` node (i.e., the node that calls the LLM).
Useful for implementing human-in-the-loop, guardrails, validation, or other post-processing.
Post-model hook must be a callable or a runnable that takes in current graph state and returns a state update.
!!! Note
Only available with `version="v2"`.
state_schema: An optional state schema that defines graph state.
Must have `messages` and `remaining_steps` keys.
Defaults to `AgentState` that defines those two keys.
@@ -413,12 +420,13 @@ def create_react_agent(
else AgentState
)
llm_builtin_tools: list[dict] = []
if isinstance(tools, ToolNode):
tool_classes = list(tools.tools_by_name.values())
tool_node = tools
else:
tool_node = ToolNode(tools)
# get the tool functions wrapped in a tool class from the ToolNode
llm_builtin_tools = [t for t in tools if isinstance(t, dict)]
tool_node = ToolNode([t for t in tools if not isinstance(t, dict)])
tool_classes = list(tool_node.tools_by_name.values())
if isinstance(model, str):
@@ -435,8 +443,12 @@ def create_react_agent(
tool_calling_enabled = len(tool_classes) > 0
if _should_bind_tools(model, tool_classes) and tool_calling_enabled:
model = cast(BaseChatModel, model).bind_tools(tool_classes)
if (
_should_bind_tools(model, tool_classes)
and len(tool_classes) > 0
or (len(llm_builtin_tools) > 0)
):
model = cast(BaseChatModel, model).bind_tools(tool_classes + llm_builtin_tools) # type: ignore[operator]
model_runnable = _get_prompt_runnable(prompt) | model
@@ -591,6 +603,10 @@ def create_react_agent(
workflow.set_entry_point(entrypoint)
if post_model_hook is not None:
workflow.add_node("post_model_hook", post_model_hook)
workflow.add_edge("agent", "post_model_hook")
if response_format is not None:
workflow.add_node(
"generate_structured_response",
@@ -598,7 +614,10 @@ def create_react_agent(
generate_structured_response, agenerate_structured_response
),
)
workflow.add_edge("agent", "generate_structured_response")
if post_model_hook is not None:
workflow.add_edge("post_model_hook", "generate_structured_response")
else:
workflow.add_edge("agent", "generate_structured_response")
return workflow.compile(
checkpointer=checkpointer,
@@ -610,17 +629,24 @@ def create_react_agent(
)
# Define the function that determines whether to continue or not
def should_continue(state: StateSchema) -> Union[str, list]:
def should_continue(state: StateSchema) -> Union[str, list[Send]]:
messages = _get_state_value(state, "messages")
last_message = messages[-1]
# If there is no function call, then we finish
if not isinstance(last_message, AIMessage) or not last_message.tool_calls:
return END if response_format is None else "generate_structured_response"
if post_model_hook is not None:
return "post_model_hook"
elif response_format is not None:
return "generate_structured_response"
else:
return END
# Otherwise if there is, we continue
else:
if version == "v1":
return "tools"
elif version == "v2":
if post_model_hook is not None:
return "post_model_hook"
tool_calls = [
tool_node.inject_tool_args(call, state, store) # type: ignore[arg-type]
for call in last_message.tool_calls
@@ -649,6 +675,17 @@ def create_react_agent(
# This means that this node is the first one called
workflow.set_entry_point(entrypoint)
agent_paths = []
post_model_hook_paths = [entrypoint, "tools"]
# Add a post model hook node if post_model_hook is provided
if post_model_hook is not None:
workflow.add_node("post_model_hook", post_model_hook)
agent_paths.append("post_model_hook")
workflow.add_edge("agent", "post_model_hook")
else:
agent_paths.append("tools")
# Add a structured output node if response_format is provided
if response_format is not None:
workflow.add_node(
@@ -657,19 +694,61 @@ def create_react_agent(
generate_structured_response, agenerate_structured_response
),
)
workflow.add_edge("generate_structured_response", END)
should_continue_destinations = ["tools", "generate_structured_response"]
if post_model_hook is not None:
post_model_hook_paths.append("generate_structured_response")
else:
agent_paths.append("generate_structured_response")
else:
should_continue_destinations = ["tools", END]
if post_model_hook is not None:
post_model_hook_paths.append(END)
else:
agent_paths.append(END)
if post_model_hook is not None:
def post_model_hook_router(state: StateSchema) -> Union[str, list[Send]]:
"""Route to the next node after post_model_hook.
Routes to one of:
* "tools": if there are pending tool calls without a corresponding message.
* "generate_structured_response": if no pending tool calls exist and response_format is specified.
* END: if no pending tool calls exist and no response_format is specified.
"""
messages = _get_state_value(state, "messages")
tool_messages = [
m.tool_call_id for m in messages if isinstance(m, ToolMessage)
]
last_ai_message = next(
m for m in reversed(messages) if isinstance(m, AIMessage)
)
pending_tool_calls = [
c for c in last_ai_message.tool_calls if c["id"] not in tool_messages
]
if pending_tool_calls:
pending_tool_calls = [
tool_node.inject_tool_args(call, state, store) # type: ignore[arg-type]
for call in pending_tool_calls
]
return [Send("tools", [tool_call]) for tool_call in pending_tool_calls]
elif isinstance(messages[-1], ToolMessage):
return entrypoint
elif response_format is not None:
return "generate_structured_response"
else:
return END
workflow.add_conditional_edges(
"post_model_hook",
post_model_hook_router, # type: ignore[arg-type]
path_map=post_model_hook_paths,
)
# We now add a conditional edge
workflow.add_conditional_edges(
# First, we define the start node. We use `agent`.
# This means these are the edges taken after the `agent` node is called.
"agent",
# Next, we pass in the function that will determine which node is called next.
should_continue,
path_map=should_continue_destinations,
should_continue, # type: ignore[arg-type]
path_map=agent_paths,
)
def route_tool_responses(state: StateSchema) -> str:
@@ -1,8 +1,4 @@
from typing import (
Literal,
Optional,
Union,
)
from typing import Literal, Optional, Union
from typing_extensions import TypedDict
+10 -7
View File
@@ -431,22 +431,25 @@ class ToolNode(RunnableCallable):
return tool_calls, input_type
else:
input_type = "list"
message: AnyMessage = input[-1]
messages = input
elif isinstance(input, dict) and (messages := input.get(self.messages_key, [])):
input_type = "dict"
message = messages[-1]
elif messages := getattr(input, self.messages_key, None):
elif messages := getattr(input, self.messages_key, []):
# Assume dataclass-like state that can coerce from dict
input_type = "dict"
message = messages[-1]
else:
raise ValueError("No message found in input")
if not isinstance(message, AIMessage):
raise ValueError("Last message is not an AIMessage")
try:
latest_ai_message = next(
m for m in reversed(messages) if isinstance(m, AIMessage)
)
except StopIteration:
raise ValueError("No AIMessage found in input")
tool_calls = [
self.inject_tool_args(call, input, store) for call in message.tool_calls
self.inject_tool_args(call, input, store)
for call in latest_ai_message.tool_calls
]
return tool_calls, input_type
+2 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "langgraph-prebuilt"
version = "0.1.8"
version = "0.2.2"
description = "Library with high-level APIs for creating and executing LangGraph agents and tools."
authors = []
requires-python = ">=3.9"
@@ -32,6 +32,7 @@ dev = [
"langgraph-checkpoint",
"langgraph-checkpoint-sqlite",
"langgraph-checkpoint-postgres",
"syrupy",
]
[tool.uv]
@@ -0,0 +1,173 @@
# serializer version: 1
# name: test_react_agent_graph_structure[None-None-None-tools0]
'''
graph TD;
__start__ --> agent;
agent --> __end__;
'''
# ---
# name: test_react_agent_graph_structure[None-None-None-tools1]
'''
graph TD;
__start__ --> agent;
agent -.-> __end__;
agent -.-> tools;
tools --> agent;
'''
# ---
# name: test_react_agent_graph_structure[None-None-pre_model_hook-tools0]
'''
graph TD;
__start__ --> pre_model_hook;
pre_model_hook --> agent;
agent --> __end__;
'''
# ---
# name: test_react_agent_graph_structure[None-None-pre_model_hook-tools1]
'''
graph TD;
__start__ --> pre_model_hook;
agent -.-> __end__;
agent -.-> tools;
pre_model_hook --> agent;
tools --> pre_model_hook;
'''
# ---
# name: test_react_agent_graph_structure[None-post_model_hook-None-tools0]
'''
graph TD;
__start__ --> agent;
agent --> post_model_hook;
post_model_hook --> __end__;
'''
# ---
# name: test_react_agent_graph_structure[None-post_model_hook-None-tools1]
'''
graph TD;
__start__ --> agent;
agent --> post_model_hook;
post_model_hook -.-> __end__;
post_model_hook -.-> agent;
post_model_hook -.-> tools;
tools --> agent;
'''
# ---
# name: test_react_agent_graph_structure[None-post_model_hook-pre_model_hook-tools0]
'''
graph TD;
__start__ --> pre_model_hook;
agent --> post_model_hook;
pre_model_hook --> agent;
post_model_hook --> __end__;
'''
# ---
# name: test_react_agent_graph_structure[None-post_model_hook-pre_model_hook-tools1]
'''
graph TD;
__start__ --> pre_model_hook;
agent --> post_model_hook;
post_model_hook -.-> __end__;
post_model_hook -.-> pre_model_hook;
post_model_hook -.-> tools;
pre_model_hook --> agent;
tools --> pre_model_hook;
'''
# ---
# name: test_react_agent_graph_structure[ResponseFormat-None-None-tools0]
'''
graph TD;
__start__ --> agent;
agent --> generate_structured_response;
generate_structured_response --> __end__;
'''
# ---
# name: test_react_agent_graph_structure[ResponseFormat-None-None-tools1]
'''
graph TD;
__start__ --> agent;
agent -.-> generate_structured_response;
agent -.-> tools;
tools --> agent;
generate_structured_response --> __end__;
'''
# ---
# name: test_react_agent_graph_structure[ResponseFormat-None-pre_model_hook-tools0]
'''
graph TD;
__start__ --> pre_model_hook;
agent --> generate_structured_response;
pre_model_hook --> agent;
generate_structured_response --> __end__;
'''
# ---
# name: test_react_agent_graph_structure[ResponseFormat-None-pre_model_hook-tools1]
'''
graph TD;
__start__ --> pre_model_hook;
agent -.-> generate_structured_response;
agent -.-> tools;
pre_model_hook --> agent;
tools --> pre_model_hook;
generate_structured_response --> __end__;
'''
# ---
# name: test_react_agent_graph_structure[ResponseFormat-post_model_hook-None-tools0]
'''
graph TD;
__start__ --> agent;
agent --> post_model_hook;
post_model_hook --> generate_structured_response;
generate_structured_response --> __end__;
'''
# ---
# name: test_react_agent_graph_structure[ResponseFormat-post_model_hook-None-tools1]
'''
graph TD;
__start__ --> agent;
agent --> post_model_hook;
post_model_hook -.-> agent;
post_model_hook -.-> generate_structured_response;
post_model_hook -.-> tools;
tools --> agent;
generate_structured_response --> __end__;
'''
# ---
# name: test_react_agent_graph_structure[ResponseFormat-post_model_hook-pre_model_hook-tools0]
'''
graph TD;
__start__ --> pre_model_hook;
agent --> post_model_hook;
post_model_hook --> generate_structured_response;
pre_model_hook --> agent;
generate_structured_response --> __end__;
'''
# ---
# name: test_react_agent_graph_structure[ResponseFormat-post_model_hook-pre_model_hook-tools1]
'''
graph TD;
__start__ --> pre_model_hook;
agent --> post_model_hook;
post_model_hook -.-> generate_structured_response;
post_model_hook -.-> pre_model_hook;
post_model_hook -.-> tools;
pre_model_hook --> agent;
tools --> pre_model_hook;
generate_structured_response --> __end__;
'''
# ---
+201
View File
@@ -1399,3 +1399,204 @@ def test_pre_model_hook() -> None:
AIMessage(content="Hello!", id="1"),
]
}
def test_post_model_hook() -> None:
class FlagState(AgentState):
flag: bool
model = FakeToolCallingModel(tool_calls=[])
def post_model_hook(state: FlagState) -> dict[str, bool]:
return {"flag": True}
pmh_agent = create_react_agent(
model, [], post_model_hook=post_model_hook, state_schema=FlagState
)
assert "post_model_hook" in pmh_agent.nodes
result = pmh_agent.invoke({"messages": [HumanMessage("hi?")], "flag": False})
assert result["flag"] is True
events = list(pmh_agent.stream({"messages": [HumanMessage("hi?")], "flag": False}))
assert events == [
{
"agent": {
"messages": [
AIMessage(
content="hi?",
additional_kwargs={},
response_metadata={},
id="1",
)
]
}
},
{"post_model_hook": {"flag": True}},
]
def test_post_model_hook_with_structured_output() -> None:
class WeatherResponse(BaseModel):
temperature: float = Field(description="The temperature in fahrenheit")
tool_calls = [[{"args": {}, "id": "1", "name": "get_weather"}]]
def get_weather():
"""Get the weather"""
return "The weather is sunny and 75°F."
expected_structured_response = WeatherResponse(temperature=75)
model = FakeToolCallingModel(
tool_calls=tool_calls, structured_response=expected_structured_response
)
class State(AgentState):
flag: bool
structured_response: WeatherResponse
def post_model_hook(state: State) -> Union[dict[str, bool], Command]:
return {"flag": True}
agent = create_react_agent(
model,
[get_weather],
response_format=WeatherResponse,
post_model_hook=post_model_hook,
state_schema=State,
)
assert "post_model_hook" in agent.nodes
assert "generate_structured_response" in agent.nodes
response = agent.invoke(
{"messages": [HumanMessage("What's the weather?")], "flag": False}
)
assert response["flag"] is True
assert response["structured_response"] == expected_structured_response
events = list(
agent.stream({"messages": [HumanMessage("What's the weather?")], "flag": False})
)
assert "generate_structured_response" in events[-1]
assert events == [
{
"agent": {
"messages": [
AIMessage(
content="What's the weather?",
additional_kwargs={},
response_metadata={},
id="2",
tool_calls=[
{
"name": "get_weather",
"args": {},
"id": "1",
"type": "tool_call",
}
],
)
]
}
},
{"post_model_hook": {"flag": True}},
{
"tools": {
"messages": [
_AnyIdToolMessage(
content="The weather is sunny and 75°F.",
name="get_weather",
tool_call_id="1",
),
]
}
},
{
"agent": {
"messages": [
AIMessage(
content="What's the weather?-What's the weather?-The weather is sunny and 75°F.",
additional_kwargs={},
response_metadata={},
id="3",
tool_calls=[
{
"name": "get_weather",
"args": {},
"id": "1",
"type": "tool_call",
}
],
)
]
}
},
{"post_model_hook": {"flag": True}},
{
"generate_structured_response": {
"structured_response": WeatherResponse(temperature=75.0)
}
},
]
@pytest.mark.parametrize(
"state_schema", [AgentStateExtraKey, AgentStateExtraKeyPydantic]
)
def test_create_react_agent_inject_vars_with_post_model_hook(
state_schema: StateSchemaType,
) -> None:
store = InMemoryStore()
namespace = ("test",)
store.put(namespace, "test_key", {"bar": 3})
if issubclass(state_schema, AgentStatePydantic):
def tool1(
some_val: int,
state: Annotated[AgentStateExtraKeyPydantic, InjectedState],
store: Annotated[BaseStore, InjectedStore()],
) -> str:
"""Tool 1 docstring."""
store_val = store.get(namespace, "test_key").value["bar"]
return some_val + state.foo + store_val
else:
def tool1(
some_val: int,
state: Annotated[dict, InjectedState],
store: Annotated[BaseStore, InjectedStore()],
) -> str:
"""Tool 1 docstring."""
store_val = store.get(namespace, "test_key").value["bar"]
return some_val + state["foo"] + store_val
tool_call = {
"name": "tool1",
"args": {"some_val": 1},
"id": "some 0",
"type": "tool_call",
}
def post_model_hook(state: dict) -> None:
return
model = FakeToolCallingModel(tool_calls=[[tool_call], []])
agent = create_react_agent(
model,
[tool1],
state_schema=state_schema,
store=store,
post_model_hook=post_model_hook,
)
input_message = HumanMessage("hi")
result = agent.invoke({"messages": [input_message], "foo": 2})
assert result["messages"] == [
input_message,
AIMessage(content="hi", tool_calls=[tool_call], id="0"),
_AnyIdToolMessage(content="6", name="tool1", tool_call_id="some 0"),
AIMessage("hi-hi-6", id="1"),
]
assert result["foo"] == 2
@@ -0,0 +1,52 @@
from typing import Callable, Union
import pytest
from pydantic import BaseModel
from syrupy import SnapshotAssertion
from langgraph.prebuilt import create_react_agent
from tests.model import FakeToolCallingModel
model = FakeToolCallingModel()
def tool() -> None:
"""Testing tool."""
...
def pre_model_hook() -> None:
"""Pre-model hook."""
...
def post_model_hook() -> None:
"""Post-model hook."""
...
class ResponseFormat(BaseModel):
"""Response format for the agent."""
result: str
@pytest.mark.parametrize("tools", [[], [tool]])
@pytest.mark.parametrize("pre_model_hook", [None, pre_model_hook])
@pytest.mark.parametrize("post_model_hook", [None, post_model_hook])
@pytest.mark.parametrize("response_format", [None, ResponseFormat])
def test_react_agent_graph_structure(
snapshot: SnapshotAssertion,
tools: list[Callable],
pre_model_hook: Union[Callable, None],
post_model_hook: Union[Callable, None],
response_format: Union[type[BaseModel], None],
) -> None:
agent = create_react_agent(
model,
tools=tools,
pre_model_hook=pre_model_hook,
post_model_hook=post_model_hook,
response_format=response_format,
)
assert agent.get_graph().draw_mermaid(with_styles=False) == snapshot
+19 -4
View File
@@ -1,4 +1,5 @@
version = 1
revision = 1
requires-python = ">=3.9"
resolution-markers = [
"python_full_version >= '3.12.4'",
@@ -319,7 +320,7 @@ wheels = [
[[package]]
name = "langgraph"
version = "0.4.5"
version = "0.4.7"
source = { editable = "../langgraph" }
dependencies = [
{ name = "langchain-core" },
@@ -430,7 +431,7 @@ dev = [
[[package]]
name = "langgraph-checkpoint-sqlite"
version = "2.0.9"
version = "2.0.10"
source = { editable = "../checkpoint-sqlite" }
dependencies = [
{ name = "aiosqlite" },
@@ -460,7 +461,7 @@ dev = [
[[package]]
name = "langgraph-prebuilt"
version = "0.1.8"
version = "0.2.2"
source = { editable = "." }
dependencies = [
{ name = "langchain-core" },
@@ -480,6 +481,7 @@ dev = [
{ name = "pytest-mock" },
{ name = "pytest-watcher" },
{ name = "ruff" },
{ name = "syrupy" },
]
[package.metadata]
@@ -501,11 +503,12 @@ dev = [
{ name = "pytest-mock" },
{ name = "pytest-watcher" },
{ name = "ruff" },
{ name = "syrupy" },
]
[[package]]
name = "langgraph-sdk"
version = "0.1.69"
version = "0.1.70"
source = { editable = "../sdk-py" }
dependencies = [
{ name = "httpx" },
@@ -1084,6 +1087,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/6a/98/e8bc58b178266eae2fcf4c9c7a8303a8d41164d781b32d71097924a6bebe/sqlite_vec-0.1.6-py3-none-win_amd64.whl", hash = "sha256:c65bcfd90fa2f41f9000052bcb8bb75d38240b2dae49225389eca6c3136d3f0c", size = 281540 },
]
[[package]]
name = "syrupy"
version = "4.9.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pytest" },
]
sdist = { url = "https://files.pythonhosted.org/packages/8c/f8/022d8704a3314f3e96dbd6bbd16ebe119ce30e35f41aabfa92345652fceb/syrupy-4.9.1.tar.gz", hash = "sha256:b7d0fcadad80a7d2f6c4c71917918e8ebe2483e8c703dfc8d49cdbb01081f9a4", size = 52492 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ec/9d/aef9ec5fd5a4ee2f6a96032c4eda5888c5c7cec65cef6b28c4fc37671d88/syrupy-4.9.1-py3-none-any.whl", hash = "sha256:b94cc12ed0e5e75b448255430af642516842a2374a46936dd2650cfb6dd20eda", size = 52214 },
]
[[package]]
name = "tenacity"
version = "9.1.2"
+4 -3
View File
@@ -447,7 +447,7 @@ wheels = [
[[package]]
name = "langgraph"
version = "0.4.5"
version = "0.4.7"
source = { editable = "../langgraph" }
dependencies = [
{ name = "langchain-core" },
@@ -558,7 +558,7 @@ dev = [
[[package]]
name = "langgraph-prebuilt"
version = "0.1.8"
version = "0.2.2"
source = { editable = "../prebuilt" }
dependencies = [
{ name = "langchain-core" },
@@ -584,6 +584,7 @@ dev = [
{ name = "pytest-mock" },
{ name = "pytest-watcher" },
{ name = "ruff" },
{ name = "syrupy" },
]
[[package]]
@@ -635,7 +636,7 @@ dev = [
[[package]]
name = "langgraph-sdk"
version = "0.1.69"
version = "0.1.70"
source = { editable = "../sdk-py" }
dependencies = [
{ name = "httpx" },
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@langchain/langgraph-sdk",
"version": "0.0.76",
"version": "0.0.78",
"description": "Client library for interacting with the LangGraph API",
"type": "module",
"packageManager": "yarn@1.22.19",
+2
View File
@@ -894,6 +894,7 @@ export class RunsClient<
metadata: payload?.metadata,
stream_mode: payload?.streamMode,
stream_subgraphs: payload?.streamSubgraphs,
stream_resumable: payload?.streamResumable,
feedback_keys: payload?.feedbackKeys,
assistant_id: assistantId,
interrupt_before: payload?.interruptBefore,
@@ -953,6 +954,7 @@ export class RunsClient<
metadata: payload?.metadata,
stream_mode: payload?.streamMode,
stream_subgraphs: payload?.streamSubgraphs,
stream_resumable: payload?.streamResumable,
assistant_id: assistantId,
interrupt_before: payload?.interruptBefore,
interrupt_after: payload?.interruptAfter,
+190 -70
View File
@@ -457,6 +457,11 @@ export interface UseStreamOptions<
*/
onFinish?: (state: ThreadState<StateType>) => void;
/**
* Callback that is called when a new stream is created.
*/
onCreated?: (run: { run_id: string; thread_id: string }) => void;
/**
* Callback that is called when an update event is received.
*/
@@ -502,6 +507,15 @@ export interface UseStreamOptions<
* Callback that is called when the thread ID is updated (ie when a new thread is created).
*/
onThreadId?: (threadId: string) => void;
/** Will reconnect the stream on mount */
reconnectOnMount?: boolean | (() => RunMetadataStorage);
}
interface RunMetadataStorage {
getItem(key: `lg:stream:${string}`): string | null;
setItem(key: `lg:stream:${string}`, value: string): void;
removeItem(key: `lg:stream:${string}`): void;
}
export interface UseStream<
@@ -590,6 +604,11 @@ export interface UseStream<
* The ID of the assistant to use.
*/
assistantId: string;
/**
* Join an active stream.
*/
joinStream: (runId: string) => Promise<void>;
}
type ConfigWithConfigurable<ConfigurableType extends Record<string, unknown>> =
@@ -619,6 +638,7 @@ interface SubmitOptions<
* @default false
*/
streamSubgraphs?: boolean;
streamResumable?: boolean;
}
export function useStream<
@@ -647,7 +667,17 @@ export function useStream<
| ErrorStreamEvent
| FeedbackStreamEvent;
let { assistantId, messagesKey, onError, onFinish } = options;
let { assistantId, messagesKey, onCreated, onError, onFinish } = options;
const reconnectOnMountRef = useRef(options.reconnectOnMount);
const runMetadataStorage = useMemo(() => {
if (typeof window === "undefined") return null;
const storage = reconnectOnMountRef.current;
if (storage === true) return window.sessionStorage;
if (typeof storage === "function") return storage();
return null;
}, []);
messagesKey ??= "messages";
const client = useMemo(
@@ -722,6 +752,7 @@ export function useStream<
// TODO: this should be done on the server to avoid pagination
// TODO: should we permit adapter? SWR / React Query?
// TODO: make this only when branching is expected
const history = useThreadHistory<StateType>(
threadId,
client,
@@ -800,15 +831,23 @@ export function useStream<
);
})();
const stop = useCallback(() => {
const stop = () => {
if (abortRef.current != null) abortRef.current.abort();
abortRef.current = null;
}, []);
const submit = async (
values: UpdateType | null | undefined,
submitOptions?: SubmitOptions<StateType, ConfigurableType>,
) => {
if (runMetadataStorage && threadId) {
const runId = runMetadataStorage.getItem(`lg:stream:${threadId}`);
if (runId) client.runs.cancel(threadId, runId);
runMetadataStorage.removeItem(`lg:stream:${threadId}`);
}
};
async function consumeStream(
action: (signal: AbortSignal) => Promise<{
onSuccess: () => Promise<ThreadState<StateType>[]>;
stream: AsyncGenerator<EventStreamEvent>;
}>,
) {
try {
setIsLoading(true);
setStreamError(undefined);
@@ -816,69 +855,10 @@ export function useStream<
submittingRef.current = true;
abortRef.current = new AbortController();
// Unbranch things
const newPath = submitOptions?.checkpoint?.checkpoint_id
? branchByCheckpoint[submitOptions?.checkpoint?.checkpoint_id]?.branch
: undefined;
if (newPath != null) setBranch(newPath ?? "");
// Assumption: we're setting the initial value
// Used for instant feedback
setStreamValues(() => {
const values = { ...historyValues };
if (submitOptions?.optimisticValues != null) {
return {
...values,
...(typeof submitOptions.optimisticValues === "function"
? submitOptions.optimisticValues(values)
: submitOptions.optimisticValues),
};
}
return values;
});
let usableThreadId = threadId;
if (!usableThreadId) {
const thread = await client.threads.create();
onThreadId(thread.thread_id);
usableThreadId = thread.thread_id;
}
const streamMode = unique([
...(submitOptions?.streamMode ?? []),
...trackStreamModeRef.current,
...callbackStreamMode,
]);
const checkpoint =
submitOptions?.checkpoint ?? threadHead?.checkpoint ?? undefined;
// @ts-expect-error
if (checkpoint != null) delete checkpoint.thread_id;
const run = client.runs.stream(usableThreadId, assistantId, {
input: values as Record<string, unknown>,
config: submitOptions?.config,
command: submitOptions?.command,
interruptBefore: submitOptions?.interruptBefore,
interruptAfter: submitOptions?.interruptAfter,
metadata: submitOptions?.metadata,
multitaskStrategy: submitOptions?.multitaskStrategy,
onCompletion: submitOptions?.onCompletion,
onDisconnect: submitOptions?.onDisconnect ?? "cancel",
signal: abortRef.current.signal,
checkpoint,
streamMode,
streamSubgraphs: submitOptions?.streamSubgraphs,
}) as AsyncGenerator<EventStreamEvent>;
const run = await action(abortRef.current.signal);
let streamError: StreamError | undefined;
for await (const { event, data } of run) {
for await (const { event, data } of run.stream) {
if (event === "error") {
streamError = new StreamError(data);
break;
@@ -930,9 +910,9 @@ export function useStream<
}
// TODO: stream created checkpoints to avoid an unnecessary network request
const result = await history.mutate(usableThreadId);
setStreamValues(null);
const result = await run.onSuccess();
setStreamValues(null);
if (streamError != null) throw streamError;
const lastHead = result.at(0);
@@ -956,8 +936,146 @@ export function useStream<
submittingRef.current = false;
abortRef.current = null;
}
}
const joinStream = async (runId: string, lastEventId?: string) => {
lastEventId ??= "-1";
if (!threadId) return;
await consumeStream(async (signal: AbortSignal) => {
const stream = client.runs.joinStream(threadId, runId, {
signal,
lastEventId,
}) as AsyncGenerator<EventStreamEvent>;
return {
onSuccess: () => {
runMetadataStorage?.removeItem(`lg:stream:${threadId}`);
return history.mutate(threadId);
},
stream,
};
});
};
const submit = async (
values: UpdateType | null | undefined,
submitOptions?: SubmitOptions<StateType, ConfigurableType>,
) => {
await consumeStream(async (signal: AbortSignal) => {
// Unbranch things
const newPath = submitOptions?.checkpoint?.checkpoint_id
? branchByCheckpoint[submitOptions?.checkpoint?.checkpoint_id]?.branch
: undefined;
if (newPath != null) setBranch(newPath ?? "");
// Assumption: we're setting the initial value
// Used for instant feedback
setStreamValues(() => {
const values = { ...historyValues };
if (submitOptions?.optimisticValues != null) {
return {
...values,
...(typeof submitOptions.optimisticValues === "function"
? submitOptions.optimisticValues(values)
: submitOptions.optimisticValues),
};
}
return values;
});
let usableThreadId = threadId;
if (!usableThreadId) {
const thread = await client.threads.create();
onThreadId(thread.thread_id);
usableThreadId = thread.thread_id;
}
const streamMode = unique([
...(submitOptions?.streamMode ?? []),
...trackStreamModeRef.current,
...callbackStreamMode,
]);
const checkpoint =
submitOptions?.checkpoint ?? threadHead?.checkpoint ?? undefined;
// @ts-expect-error
if (checkpoint != null) delete checkpoint.thread_id;
let rejoinKey: `lg:stream:${string}` | undefined;
const stream = client.runs.stream(usableThreadId, assistantId, {
input: values as Record<string, unknown>,
config: submitOptions?.config,
command: submitOptions?.command,
interruptBefore: submitOptions?.interruptBefore,
interruptAfter: submitOptions?.interruptAfter,
metadata: submitOptions?.metadata,
multitaskStrategy: submitOptions?.multitaskStrategy,
onCompletion: submitOptions?.onCompletion,
onDisconnect:
submitOptions?.onDisconnect ??
(runMetadataStorage ? "continue" : "cancel"),
signal,
checkpoint,
streamMode,
streamSubgraphs: submitOptions?.streamSubgraphs,
streamResumable: submitOptions?.streamResumable ?? !!runMetadataStorage,
onRunCreated(params) {
const runParams = {
run_id: params.run_id,
thread_id: params.thread_id ?? usableThreadId,
};
if (runMetadataStorage) {
rejoinKey = `lg:stream:${runParams.thread_id}`;
runMetadataStorage.setItem(rejoinKey, runParams.run_id);
}
onCreated?.(runParams);
},
}) as AsyncGenerator<EventStreamEvent>;
return {
stream,
onSuccess: () => {
if (rejoinKey) runMetadataStorage?.removeItem(rejoinKey);
return history.mutate(usableThreadId);
},
};
});
};
const reconnectKey = useMemo(() => {
if (!runMetadataStorage || isLoading) return undefined;
if (typeof window === "undefined") return undefined;
const runId = runMetadataStorage?.getItem(`lg:stream:${threadId}`);
if (!runId) return undefined;
return { runId, threadId };
}, [runMetadataStorage, isLoading, threadId]);
const shouldReconnect = !!runMetadataStorage;
const reconnectRef = useRef({ threadId, shouldReconnect });
const joinStreamRef = useRef<typeof joinStream>(joinStream);
joinStreamRef.current = joinStream;
useEffect(() => {
// reset shouldReconnect when switching threads
if (reconnectRef.current.threadId !== threadId) {
reconnectRef.current = { threadId, shouldReconnect };
}
}, [threadId, shouldReconnect]);
useEffect(() => {
if (reconnectKey && reconnectRef.current.shouldReconnect) {
reconnectRef.current.shouldReconnect = false;
joinStreamRef.current?.(reconnectKey.runId);
}
}, [reconnectKey]);
const error = streamError ?? historyError;
const values = streamValues ?? historyValues;
@@ -976,6 +1094,8 @@ export function useStream<
stop,
submit,
joinStream,
branch,
setBranch,
+12
View File
@@ -156,6 +156,12 @@ export interface RunsStreamPayload<
*/
streamSubgraphs?: TSubgraphs;
/**
* Whether the stream is considered resumable.
* If true, the stream can be resumed and replayed in its entirety even after disconnection.
*/
streamResumable?: boolean;
/**
* Pass one or more feedbackKeys if you want to request short-lived signed URLs
* for submitting feedback to LangSmith with this key for this run.
@@ -173,6 +179,12 @@ export interface RunsCreatePayload extends RunsInvokePayload {
* Stream output from subgraphs. By default, streams only the top graph.
*/
streamSubgraphs?: boolean;
/**
* Whether the stream is considered resumable.
* If true, the stream can be resumed and replayed in its entirety even after disconnection.
*/
streamResumable?: boolean;
}
export interface CronsCreatePayload extends RunsCreatePayload {
+194 -17
View File
@@ -13,10 +13,12 @@ import asyncio
import functools
import logging
import os
import re
import sys
from collections.abc import AsyncIterator, Iterator, Sequence
from typing import (
Any,
Callable,
Literal,
Optional,
Union,
@@ -49,6 +51,7 @@ from langgraph_sdk.schema import (
OnConflictBehavior,
Run,
RunCreate,
RunCreateMetadata,
RunStatus,
SearchItemsResponse,
SortOrder,
@@ -116,6 +119,27 @@ def _orjson_default(obj: Any) -> Any:
raise TypeError(f"Object of type {type(obj)} is not JSON serializable")
# Compiled regex pattern for extracting run metadata from Content-Location header
_RUN_METADATA_PATTERN = re.compile(
r"(\/threads\/(?P<thread_id>.+))?\/runs\/(?P<run_id>.+)"
)
def _get_run_metadata_from_response(
response: httpx.Response,
) -> Optional[RunCreateMetadata]:
"""Extract run metadata from the response headers."""
if (content_location := response.headers.get("Content-Location")) and (
match := _RUN_METADATA_PATTERN.search(content_location)
):
return RunCreateMetadata(
run_id=match.group("run_id"),
thread_id=match.group("thread_id") or None,
)
return None
def get_client(
*,
url: Optional[str] = None,
@@ -226,9 +250,12 @@ class HttpClient:
*,
params: Optional[QueryParamTypes] = None,
headers: Optional[dict[str, str]] = None,
on_response: Optional[Callable[[httpx.Response], None]] = None,
) -> Any:
"""Send a GET request."""
r = await self.client.get(path, params=params, headers=headers)
if on_response:
on_response(r)
try:
r.raise_for_status()
except httpx.HTTPStatusError as e:
@@ -246,6 +273,7 @@ class HttpClient:
*,
json: Optional[dict],
headers: Optional[dict[str, str]] = None,
on_response: Optional[Callable[[httpx.Response], None]] = None,
) -> Any:
"""Send a POST request."""
if json is not None:
@@ -256,6 +284,8 @@ class HttpClient:
if headers:
request_headers.update(headers)
r = await self.client.post(path, headers=request_headers, content=content)
if on_response:
on_response(r)
try:
r.raise_for_status()
except httpx.HTTPStatusError as e:
@@ -268,13 +298,20 @@ class HttpClient:
return await _adecode_json(r)
async def put(
self, path: str, *, json: dict, headers: Optional[dict[str, str]] = None
self,
path: str,
*,
json: dict,
headers: Optional[dict[str, str]] = None,
on_response: Optional[Callable[[httpx.Response], None]] = None,
) -> Any:
"""Send a PUT request."""
request_headers, content = await _aencode_json(json)
if headers:
request_headers.update(headers)
r = await self.client.put(path, headers=request_headers, content=content)
if on_response:
on_response(r)
try:
r.raise_for_status()
except httpx.HTTPStatusError as e:
@@ -287,13 +324,20 @@ class HttpClient:
return await _adecode_json(r)
async def patch(
self, path: str, *, json: dict, headers: Optional[dict[str, str]] = None
self,
path: str,
*,
json: dict,
headers: Optional[dict[str, str]] = None,
on_response: Optional[Callable[[httpx.Response], None]] = None,
) -> Any:
"""Send a PATCH request."""
request_headers, content = await _aencode_json(json)
if headers:
request_headers.update(headers)
r = await self.client.patch(path, headers=request_headers, content=content)
if on_response:
on_response(r)
try:
r.raise_for_status()
except httpx.HTTPStatusError as e:
@@ -311,9 +355,12 @@ class HttpClient:
*,
json: Optional[Any] = None,
headers: Optional[dict[str, str]] = None,
on_response: Optional[Callable[[httpx.Response], None]] = None,
) -> None:
"""Send a DELETE request."""
r = await self.client.request("DELETE", path, json=json, headers=headers)
if on_response:
on_response(r)
try:
r.raise_for_status()
except httpx.HTTPStatusError as e:
@@ -332,6 +379,7 @@ class HttpClient:
json: Optional[dict] = None,
params: Optional[QueryParamTypes] = None,
headers: Optional[dict[str, str]] = None,
on_response: Optional[Callable[[httpx.Response], None]] = None,
) -> AsyncIterator[StreamPart]:
"""Stream results using SSE."""
request_headers, content = await _aencode_json(json)
@@ -344,6 +392,8 @@ class HttpClient:
async with self.client.stream(
method, path, headers=request_headers, content=content, params=params
) as res:
if on_response:
on_response(res)
# check status
try:
res.raise_for_status()
@@ -1455,6 +1505,7 @@ class RunsClient:
command: Optional[Command] = None,
stream_mode: Union[StreamMode, Sequence[StreamMode]] = "values",
stream_subgraphs: bool = False,
stream_resumable: bool = False,
metadata: Optional[dict] = None,
config: Optional[Config] = None,
checkpoint: Optional[Checkpoint] = None,
@@ -1469,6 +1520,7 @@ class RunsClient:
if_not_exists: Optional[IfNotExists] = None,
after_seconds: Optional[int] = None,
headers: Optional[dict[str, str]] = None,
on_run_created: Optional[Callable[[RunCreateMetadata], None]] = None,
) -> AsyncIterator[StreamPart]: ...
@overload
@@ -1481,6 +1533,7 @@ class RunsClient:
command: Optional[Command] = None,
stream_mode: Union[StreamMode, Sequence[StreamMode]] = "values",
stream_subgraphs: bool = False,
stream_resumable: bool = False,
metadata: Optional[dict] = None,
config: Optional[Config] = None,
checkpoint_during: Optional[bool] = None,
@@ -1493,6 +1546,7 @@ class RunsClient:
webhook: Optional[str] = None,
after_seconds: Optional[int] = None,
headers: Optional[dict[str, str]] = None,
on_run_created: Optional[Callable[[RunCreateMetadata], None]] = None,
) -> AsyncIterator[StreamPart]: ...
def stream(
@@ -1504,6 +1558,7 @@ class RunsClient:
command: Optional[Command] = None,
stream_mode: Union[StreamMode, Sequence[StreamMode]] = "values",
stream_subgraphs: bool = False,
stream_resumable: bool = False,
metadata: Optional[dict] = None,
config: Optional[Config] = None,
checkpoint: Optional[Checkpoint] = None,
@@ -1519,6 +1574,7 @@ class RunsClient:
if_not_exists: Optional[IfNotExists] = None,
after_seconds: Optional[int] = None,
headers: Optional[dict[str, str]] = None,
on_run_created: Optional[Callable[[RunCreateMetadata], None]] = None,
) -> AsyncIterator[StreamPart]:
"""Create a run and stream the results.
@@ -1531,6 +1587,8 @@ class RunsClient:
command: A command to execute. Cannot be combined with input.
stream_mode: The stream mode(s) to use.
stream_subgraphs: Whether to stream output from subgraphs.
stream_resumable: Whether the stream is considered resumable.
If true, the stream can be resumed and replayed in its entirety even after disconnection.
metadata: Metadata to assign to the run.
config: The configuration for the assistant.
checkpoint: The checkpoint to resume from.
@@ -1549,6 +1607,7 @@ class RunsClient:
Must be either 'reject' (raise error if missing), or 'create' (create new thread).
after_seconds: The number of seconds to wait before starting the run.
Use to schedule future runs.
on_run_created: Callback when a run is created.
Returns:
AsyncIterator[StreamPart]: Asynchronous iterator of stream results.
@@ -1593,6 +1652,7 @@ class RunsClient:
"metadata": metadata,
"stream_mode": stream_mode,
"stream_subgraphs": stream_subgraphs,
"stream_resumable": stream_resumable,
"assistant_id": assistant_id,
"interrupt_before": interrupt_before,
"interrupt_after": interrupt_after,
@@ -1612,11 +1672,18 @@ class RunsClient:
if thread_id is not None
else "/runs/stream"
)
def on_response(res: httpx.Response):
"""Callback function to handle the response."""
if on_run_created and (metadata := _get_run_metadata_from_response(res)):
on_run_created(metadata)
return self.http.stream(
endpoint,
"POST",
json={k: v for k, v in payload.items() if v is not None},
headers=headers,
on_response=on_response if on_run_created else None,
)
@overload
@@ -1629,6 +1696,7 @@ class RunsClient:
command: Optional[Command] = None,
stream_mode: Union[StreamMode, Sequence[StreamMode]] = "values",
stream_subgraphs: bool = False,
stream_resumable: bool = False,
metadata: Optional[dict] = None,
checkpoint_during: Optional[bool] = None,
config: Optional[Config] = None,
@@ -1639,6 +1707,7 @@ class RunsClient:
if_not_exists: Optional[IfNotExists] = None,
after_seconds: Optional[int] = None,
headers: Optional[dict[str, str]] = None,
on_run_created: Optional[Callable[[RunCreateMetadata], None]] = None,
) -> Run: ...
@overload
@@ -1651,6 +1720,7 @@ class RunsClient:
command: Optional[Command] = None,
stream_mode: Union[StreamMode, Sequence[StreamMode]] = "values",
stream_subgraphs: bool = False,
stream_resumable: bool = False,
metadata: Optional[dict] = None,
config: Optional[Config] = None,
checkpoint: Optional[Checkpoint] = None,
@@ -1663,6 +1733,7 @@ class RunsClient:
if_not_exists: Optional[IfNotExists] = None,
after_seconds: Optional[int] = None,
headers: Optional[dict[str, str]] = None,
on_run_created: Optional[Callable[[RunCreateMetadata], None]] = None,
) -> Run: ...
async def create(
@@ -1674,6 +1745,7 @@ class RunsClient:
command: Optional[Command] = None,
stream_mode: Union[StreamMode, Sequence[StreamMode]] = "values",
stream_subgraphs: bool = False,
stream_resumable: bool = False,
metadata: Optional[dict] = None,
config: Optional[Config] = None,
checkpoint: Optional[Checkpoint] = None,
@@ -1687,6 +1759,7 @@ class RunsClient:
on_completion: Optional[OnCompletionBehavior] = None,
after_seconds: Optional[int] = None,
headers: Optional[dict[str, str]] = None,
on_run_created: Optional[Callable[[RunCreateMetadata], None]] = None,
) -> Run:
"""Create a background run.
@@ -1699,6 +1772,8 @@ class RunsClient:
command: A command to execute. Cannot be combined with input.
stream_mode: The stream mode(s) to use.
stream_subgraphs: Whether to stream output from subgraphs.
stream_resumable: Whether the stream is considered resumable.
If true, the stream can be resumed and replayed in its entirety even after disconnection.
metadata: Metadata to assign to the run.
config: The configuration for the assistant.
checkpoint: The checkpoint to resume from.
@@ -1715,6 +1790,7 @@ class RunsClient:
after_seconds: The number of seconds to wait before starting the run.
Use to schedule future runs.
headers: Optional custom headers to include with the request.
on_run_created: Optional callback to call when a run is created.
Returns:
Run: The created background run.
@@ -1794,6 +1870,7 @@ class RunsClient:
),
"stream_mode": stream_mode,
"stream_subgraphs": stream_subgraphs,
"stream_resumable": stream_resumable,
"config": config,
"metadata": metadata,
"assistant_id": assistant_id,
@@ -1809,10 +1886,18 @@ class RunsClient:
"after_seconds": after_seconds,
}
payload = {k: v for k, v in payload.items() if v is not None}
if thread_id:
return await self.http.post(f"/threads/{thread_id}/runs", json=payload)
else:
return await self.http.post("/runs", json=payload)
def on_response(res: httpx.Response):
"""Callback function to handle the response."""
if on_run_created and (metadata := _get_run_metadata_from_response(res)):
on_run_created(metadata)
return await self.http.post(
f"/threads/{thread_id}/runs" if thread_id else "/runs",
json=payload,
headers=headers,
on_response=on_response if on_run_created else None,
)
async def create_batch(self, payloads: list[RunCreate]) -> list[Run]:
"""Create a batch of stateless background runs."""
@@ -1845,6 +1930,7 @@ class RunsClient:
after_seconds: Optional[int] = None,
raise_error: bool = True,
headers: Optional[dict[str, str]] = None,
on_run_created: Optional[Callable[[RunCreateMetadata], None]] = None,
) -> Union[list[dict], dict[str, Any]]: ...
@overload
@@ -1867,6 +1953,7 @@ class RunsClient:
after_seconds: Optional[int] = None,
raise_error: bool = True,
headers: Optional[dict[str, str]] = None,
on_run_created: Optional[Callable[[RunCreateMetadata], None]] = None,
) -> Union[list[dict], dict[str, Any]]: ...
async def wait(
@@ -1891,6 +1978,7 @@ class RunsClient:
after_seconds: Optional[int] = None,
raise_error: bool = True,
headers: Optional[dict[str, str]] = None,
on_run_created: Optional[Callable[[RunCreateMetadata], None]] = None,
) -> Union[list[dict], dict[str, Any]]:
"""Create a run, wait until it finishes and return the final state.
@@ -1919,6 +2007,7 @@ class RunsClient:
after_seconds: The number of seconds to wait before starting the run.
Use to schedule future runs.
headers: Optional custom headers to include with the request.
on_run_created: Optional callback to call when a run is created.
Returns:
Union[list[dict], dict[str, Any]]: The output of the run.
@@ -1995,10 +2084,17 @@ class RunsClient:
endpoint = (
f"/threads/{thread_id}/runs/wait" if thread_id is not None else "/runs/wait"
)
def on_response(res: httpx.Response):
"""Callback function to handle the response."""
if on_run_created and (metadata := _get_run_metadata_from_response(res)):
on_run_created(metadata)
response = await self.http.post(
endpoint,
json={k: v for k, v in payload.items() if v is not None},
headers=headers,
on_response=on_response if on_run_created else None,
)
if (
raise_error
@@ -2160,6 +2256,7 @@ class RunsClient:
cancel_on_disconnect: bool = False,
stream_mode: Optional[Union[StreamMode, Sequence[StreamMode]]] = None,
headers: Optional[dict[str, str]] = None,
last_event_id: Optional[str] = None,
) -> AsyncIterator[StreamPart]:
"""Stream output from a run in real-time, until the run is done.
Output is not buffered, so any output produced before this call will
@@ -2197,7 +2294,11 @@ class RunsClient:
"cancel_on_disconnect": cancel_on_disconnect,
"stream_mode": stream_mode,
},
headers=headers,
headers={
**({"Last-Event-ID": last_event_id} if last_event_id else {}),
**(headers or {}),
}
or None,
)
async def delete(
@@ -2875,9 +2976,12 @@ class SyncHttpClient:
*,
params: Optional[QueryParamTypes] = None,
headers: Optional[dict[str, str]] = None,
on_response: Optional[Callable[[httpx.Response], None]] = None,
) -> Any:
"""Send a GET request."""
r = self.client.get(path, params=params, headers=headers)
if on_response:
on_response(r)
try:
r.raise_for_status()
except httpx.HTTPStatusError as e:
@@ -2895,6 +2999,7 @@ class SyncHttpClient:
*,
json: Optional[dict],
headers: Optional[dict[str, str]] = None,
on_response: Optional[Callable[[httpx.Response], None]] = None,
) -> Any:
"""Send a POST request."""
if json is not None:
@@ -2904,6 +3009,8 @@ class SyncHttpClient:
if headers:
request_headers.update(headers)
r = self.client.post(path, headers=request_headers, content=content)
if on_response:
on_response(r)
try:
r.raise_for_status()
except httpx.HTTPStatusError as e:
@@ -2916,7 +3023,12 @@ class SyncHttpClient:
return _decode_json(r)
def put(
self, path: str, *, json: dict, headers: Optional[dict[str, str]] = None
self,
path: str,
*,
json: dict,
headers: Optional[dict[str, str]] = None,
on_response: Optional[Callable[[httpx.Response], None]] = None,
) -> Any:
"""Send a PUT request."""
request_headers, content = _encode_json(json)
@@ -2924,6 +3036,8 @@ class SyncHttpClient:
request_headers.update(headers)
r = self.client.put(path, headers=request_headers, content=content)
if on_response:
on_response(r)
try:
r.raise_for_status()
except httpx.HTTPStatusError as e:
@@ -2936,13 +3050,20 @@ class SyncHttpClient:
return _decode_json(r)
def patch(
self, path: str, *, json: dict, headers: Optional[dict[str, str]] = None
self,
path: str,
*,
json: dict,
headers: Optional[dict[str, str]] = None,
on_response: Optional[Callable[[httpx.Response], None]] = None,
) -> Any:
"""Send a PATCH request."""
request_headers, content = _encode_json(json)
if headers:
request_headers.update(headers)
r = self.client.patch(path, headers=request_headers, content=content)
if on_response:
on_response(r)
try:
r.raise_for_status()
except httpx.HTTPStatusError as e:
@@ -2960,9 +3081,12 @@ class SyncHttpClient:
*,
json: Optional[Any] = None,
headers: Optional[dict[str, str]] = None,
on_response: Optional[Callable[[httpx.Response], None]] = None,
) -> None:
"""Send a DELETE request."""
r = self.client.request("DELETE", path, json=json, headers=headers)
if on_response:
on_response(r)
try:
r.raise_for_status()
except httpx.HTTPStatusError as e:
@@ -2981,6 +3105,7 @@ class SyncHttpClient:
json: Optional[dict] = None,
params: Optional[QueryParamTypes] = None,
headers: Optional[dict[str, str]] = None,
on_response: Optional[Callable[[httpx.Response], None]] = None,
) -> Iterator[StreamPart]:
"""Stream the results of a request using SSE."""
request_headers, content = _encode_json(json)
@@ -2991,6 +3116,8 @@ class SyncHttpClient:
with self.client.stream(
method, path, headers=request_headers, content=content, params=params
) as res:
if on_response:
on_response(res)
# check status
try:
res.raise_for_status()
@@ -4086,6 +4213,7 @@ class SyncRunsClient:
if_not_exists: Optional[IfNotExists] = None,
after_seconds: Optional[int] = None,
headers: Optional[dict[str, str]] = None,
on_run_created: Optional[Callable[[RunCreateMetadata], None]] = None,
) -> Iterator[StreamPart]: ...
@overload
@@ -4098,6 +4226,7 @@ class SyncRunsClient:
command: Optional[Command] = None,
stream_mode: Union[StreamMode, Sequence[StreamMode]] = "values",
stream_subgraphs: bool = False,
stream_resumable: bool = False,
metadata: Optional[dict] = None,
config: Optional[Config] = None,
checkpoint_during: Optional[bool] = None,
@@ -4110,6 +4239,7 @@ class SyncRunsClient:
webhook: Optional[str] = None,
after_seconds: Optional[int] = None,
headers: Optional[dict[str, str]] = None,
on_run_created: Optional[Callable[[RunCreateMetadata], None]] = None,
) -> Iterator[StreamPart]: ...
def stream(
@@ -4121,6 +4251,7 @@ class SyncRunsClient:
command: Optional[Command] = None,
stream_mode: Union[StreamMode, Sequence[StreamMode]] = "values",
stream_subgraphs: bool = False,
stream_resumable: bool = False,
metadata: Optional[dict] = None,
config: Optional[Config] = None,
checkpoint: Optional[Checkpoint] = None,
@@ -4136,6 +4267,7 @@ class SyncRunsClient:
if_not_exists: Optional[IfNotExists] = None,
after_seconds: Optional[int] = None,
headers: Optional[dict[str, str]] = None,
on_run_created: Optional[Callable[[RunCreateMetadata], None]] = None,
) -> Iterator[StreamPart]:
"""Create a run and stream the results.
@@ -4148,6 +4280,8 @@ class SyncRunsClient:
command: The command to execute.
stream_mode: The stream mode(s) to use.
stream_subgraphs: Whether to stream output from subgraphs.
stream_resumable: Whether the stream is considered resumable.
If true, the stream can be resumed and replayed in its entirety even after disconnection.
metadata: Metadata to assign to the run.
config: The configuration for the assistant.
checkpoint: The checkpoint to resume from.
@@ -4167,6 +4301,7 @@ class SyncRunsClient:
after_seconds: The number of seconds to wait before starting the run.
Use to schedule future runs.
headers: Optional custom headers to include with the request.
on_run_created: Optional callback to call when a run is created.
Returns:
Iterator[StreamPart]: Iterator of stream results.
@@ -4208,6 +4343,7 @@ class SyncRunsClient:
"metadata": metadata,
"stream_mode": stream_mode,
"stream_subgraphs": stream_subgraphs,
"stream_resumable": stream_resumable,
"assistant_id": assistant_id,
"interrupt_before": interrupt_before,
"interrupt_after": interrupt_after,
@@ -4227,11 +4363,18 @@ class SyncRunsClient:
if thread_id is not None
else "/runs/stream"
)
def on_response(res: httpx.Response):
"""Callback function to handle the response."""
if on_run_created and (metadata := _get_run_metadata_from_response(res)):
on_run_created(metadata)
return self.http.stream(
endpoint,
"POST",
json={k: v for k, v in payload.items() if v is not None},
headers=headers,
on_response=on_response if on_run_created else None,
)
@overload
@@ -4244,6 +4387,7 @@ class SyncRunsClient:
command: Optional[Command] = None,
stream_mode: Union[StreamMode, Sequence[StreamMode]] = "values",
stream_subgraphs: bool = False,
stream_resumable: bool = False,
metadata: Optional[dict] = None,
config: Optional[Config] = None,
checkpoint_during: Optional[bool] = None,
@@ -4254,6 +4398,7 @@ class SyncRunsClient:
if_not_exists: Optional[IfNotExists] = None,
after_seconds: Optional[int] = None,
headers: Optional[dict[str, str]] = None,
on_run_created: Optional[Callable[[RunCreateMetadata], None]] = None,
) -> Run: ...
@overload
@@ -4266,6 +4411,7 @@ class SyncRunsClient:
command: Optional[Command] = None,
stream_mode: Union[StreamMode, Sequence[StreamMode]] = "values",
stream_subgraphs: bool = False,
stream_resumable: bool = False,
metadata: Optional[dict] = None,
config: Optional[Config] = None,
checkpoint: Optional[Checkpoint] = None,
@@ -4278,6 +4424,7 @@ class SyncRunsClient:
if_not_exists: Optional[IfNotExists] = None,
after_seconds: Optional[int] = None,
headers: Optional[dict[str, str]] = None,
on_run_created: Optional[Callable[[RunCreateMetadata], None]] = None,
) -> Run: ...
def create(
@@ -4289,6 +4436,7 @@ class SyncRunsClient:
command: Optional[Command] = None,
stream_mode: Union[StreamMode, Sequence[StreamMode]] = "values",
stream_subgraphs: bool = False,
stream_resumable: bool = False,
metadata: Optional[dict] = None,
config: Optional[Config] = None,
checkpoint: Optional[Checkpoint] = None,
@@ -4302,6 +4450,7 @@ class SyncRunsClient:
if_not_exists: Optional[IfNotExists] = None,
after_seconds: Optional[int] = None,
headers: Optional[dict[str, str]] = None,
on_run_created: Optional[Callable[[RunCreateMetadata], None]] = None,
) -> Run:
"""Create a background run.
@@ -4314,6 +4463,8 @@ class SyncRunsClient:
command: The command to execute.
stream_mode: The stream mode(s) to use.
stream_subgraphs: Whether to stream output from subgraphs.
stream_resumable: Whether the stream is considered resumable.
If true, the stream can be resumed and replayed in its entirety even after disconnection.
metadata: Metadata to assign to the run.
config: The configuration for the assistant.
checkpoint: The checkpoint to resume from.
@@ -4330,6 +4481,7 @@ class SyncRunsClient:
after_seconds: The number of seconds to wait before starting the run.
Use to schedule future runs.
headers: Optional custom headers to include with the request.
on_run_created: Optional callback to call when a run is created.
Returns:
Run: The created background run.
@@ -4409,6 +4561,7 @@ class SyncRunsClient:
),
"stream_mode": stream_mode,
"stream_subgraphs": stream_subgraphs,
"stream_resumable": stream_resumable,
"config": config,
"metadata": metadata,
"assistant_id": assistant_id,
@@ -4424,12 +4577,18 @@ class SyncRunsClient:
"after_seconds": after_seconds,
}
payload = {k: v for k, v in payload.items() if v is not None}
if thread_id:
return self.http.post(
f"/threads/{thread_id}/runs", json=payload, headers=headers
)
else:
return self.http.post("/runs", json=payload, headers=headers)
def on_response(res: httpx.Response):
"""Callback function to handle the response."""
if on_run_created and (metadata := _get_run_metadata_from_response(res)):
on_run_created(metadata)
return self.http.post(
f"/threads/{thread_id}/runs" if thread_id else "/runs",
json=payload,
headers=headers,
on_response=on_response if on_run_created else None,
)
def create_batch(
self, payloads: list[RunCreate], *, headers: Optional[dict[str, str]] = None
@@ -4463,6 +4622,7 @@ class SyncRunsClient:
if_not_exists: Optional[IfNotExists] = None,
after_seconds: Optional[int] = None,
headers: Optional[dict[str, str]] = None,
on_run_created: Optional[Callable[[RunCreateMetadata], None]] = None,
) -> Union[list[dict], dict[str, Any]]: ...
@overload
@@ -4484,6 +4644,7 @@ class SyncRunsClient:
if_not_exists: Optional[IfNotExists] = None,
after_seconds: Optional[int] = None,
headers: Optional[dict[str, str]] = None,
on_run_created: Optional[Callable[[RunCreateMetadata], None]] = None,
) -> Union[list[dict], dict[str, Any]]: ...
def wait(
@@ -4507,6 +4668,7 @@ class SyncRunsClient:
if_not_exists: Optional[IfNotExists] = None,
after_seconds: Optional[int] = None,
headers: Optional[dict[str, str]] = None,
on_run_created: Optional[Callable[[RunCreateMetadata], None]] = None,
) -> Union[list[dict], dict[str, Any]]:
"""Create a run, wait until it finishes and return the final state.
@@ -4535,6 +4697,7 @@ class SyncRunsClient:
after_seconds: The number of seconds to wait before starting the run.
Use to schedule future runs.
headers: Optional custom headers to include with the request.
on_run_created: Optional callback to call when a run is created.
Returns:
Union[list[dict], dict[str, Any]]: The output of the run.
@@ -4609,6 +4772,12 @@ class SyncRunsClient:
"on_completion": on_completion,
"after_seconds": after_seconds,
}
def on_response(res: httpx.Response):
"""Callback function to handle the response."""
if on_run_created and (metadata := _get_run_metadata_from_response(res)):
on_run_created(metadata)
endpoint = (
f"/threads/{thread_id}/runs/wait" if thread_id is not None else "/runs/wait"
)
@@ -4616,6 +4785,7 @@ class SyncRunsClient:
endpoint,
json={k: v for k, v in payload.items() if v is not None},
headers=headers,
on_response=on_response if on_run_created else None,
)
def list(
@@ -4764,6 +4934,7 @@ class SyncRunsClient:
stream_mode: Optional[Union[StreamMode, Sequence[StreamMode]]] = None,
cancel_on_disconnect: bool = False,
headers: Optional[dict[str, str]] = None,
last_event_id: Optional[str] = None,
) -> Iterator[StreamPart]:
"""Stream output from a run in real-time, until the run is done.
Output is not buffered, so any output produced before this call will
@@ -4800,7 +4971,11 @@ class SyncRunsClient:
"stream_mode": stream_mode,
"cancel_on_disconnect": cancel_on_disconnect,
},
headers=headers,
headers={
**({"Last-Event-ID": last_event_id} if last_event_id else {}),
**(headers or {}),
}
or None,
)
def delete(
@@ -5392,7 +5567,9 @@ def get_asgi_transport() -> type[httpx.ASGITransport]:
TimeoutTypes = Union[
Optional[float],
None,
float,
tuple[Optional[float], Optional[float]],
tuple[Optional[float], Optional[float], Optional[float], Optional[float]],
httpx.Timeout,
]
+10
View File
@@ -435,3 +435,13 @@ class Command(TypedDict, total=False):
"""Value to resume execution with after an interruption.
Used in conjunction with interrupt() to implement control flow.
"""
class RunCreateMetadata(TypedDict):
"""Metadata for a run creation request."""
run_id: str
"""The ID of the run."""
thread_id: Optional[str]
"""The ID of the thread."""
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "langgraph-sdk"
version = "0.1.69"
version = "0.1.70"
description = "SDK for interacting with LangGraph API"
authors = []
requires-python = ">=3.9"
+224 -224
View File
@@ -1,5 +1,5 @@
version = 1
revision = 1
revision = 2
requires-python = ">=3.9"
[[package]]
@@ -12,43 +12,43 @@ dependencies = [
{ name = "sniffio" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 }
sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 },
{ url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" },
]
[[package]]
name = "certifi"
version = "2025.4.26"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705 }
sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload-time = "2025-04-26T02:12:29.51Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618 },
{ url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" },
]
[[package]]
name = "codespell"
version = "2.4.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/15/e0/709453393c0ea77d007d907dd436b3ee262e28b30995ea1aa36c6ffbccaf/codespell-2.4.1.tar.gz", hash = "sha256:299fcdcb09d23e81e35a671bbe746d5ad7e8385972e65dbb833a2eaac33c01e5", size = 344740 }
sdist = { url = "https://files.pythonhosted.org/packages/15/e0/709453393c0ea77d007d907dd436b3ee262e28b30995ea1aa36c6ffbccaf/codespell-2.4.1.tar.gz", hash = "sha256:299fcdcb09d23e81e35a671bbe746d5ad7e8385972e65dbb833a2eaac33c01e5", size = 344740, upload-time = "2025-01-28T18:52:39.411Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/20/01/b394922252051e97aab231d416c86da3d8a6d781eeadcdca1082867de64e/codespell-2.4.1-py3-none-any.whl", hash = "sha256:3dadafa67df7e4a3dbf51e0d7315061b80d265f9552ebd699b3dd6834b47e425", size = 344501 },
{ url = "https://files.pythonhosted.org/packages/20/01/b394922252051e97aab231d416c86da3d8a6d781eeadcdca1082867de64e/codespell-2.4.1-py3-none-any.whl", hash = "sha256:3dadafa67df7e4a3dbf51e0d7315061b80d265f9552ebd699b3dd6834b47e425", size = 344501, upload-time = "2025-01-28T18:52:37.057Z" },
]
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
]
[[package]]
name = "docopt"
version = "0.6.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/55/8f8cab2afd404cf578136ef2cc5dfb50baa1761b68c9da1fb1e4eed343c9/docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491", size = 25901 }
sdist = { url = "https://files.pythonhosted.org/packages/a2/55/8f8cab2afd404cf578136ef2cc5dfb50baa1761b68c9da1fb1e4eed343c9/docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491", size = 25901, upload-time = "2014-06-16T11:18:57.406Z" }
[[package]]
name = "exceptiongroup"
@@ -57,18 +57,18 @@ source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749 }
sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674 },
{ url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" },
]
[[package]]
name = "h11"
version = "0.16.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250 }
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 },
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
]
[[package]]
@@ -79,9 +79,9 @@ dependencies = [
{ name = "certifi" },
{ name = "h11" },
]
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484 }
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784 },
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
]
[[package]]
@@ -94,32 +94,32 @@ dependencies = [
{ name = "httpcore" },
{ name = "idna" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 }
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 },
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
]
[[package]]
name = "idna"
version = "3.10"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
]
[[package]]
name = "iniconfig"
version = "2.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 }
sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 },
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" },
]
[[package]]
name = "langgraph-sdk"
version = "0.1.69"
version = "0.1.70"
source = { editable = "." }
dependencies = [
{ name = "httpx" },
@@ -163,145 +163,145 @@ dependencies = [
{ name = "tomli", marker = "python_full_version < '3.11'" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ce/43/d5e49a86afa64bd3839ea0d5b9c7103487007d728e1293f52525d6d5486a/mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43", size = 3239717 }
sdist = { url = "https://files.pythonhosted.org/packages/ce/43/d5e49a86afa64bd3839ea0d5b9c7103487007d728e1293f52525d6d5486a/mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43", size = 3239717, upload-time = "2025-02-05T03:50:34.655Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/68/f8/65a7ce8d0e09b6329ad0c8d40330d100ea343bd4dd04c4f8ae26462d0a17/mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13", size = 10738433 },
{ url = "https://files.pythonhosted.org/packages/b4/95/9c0ecb8eacfe048583706249439ff52105b3f552ea9c4024166c03224270/mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559", size = 9861472 },
{ url = "https://files.pythonhosted.org/packages/84/09/9ec95e982e282e20c0d5407bc65031dfd0f0f8ecc66b69538296e06fcbee/mypy-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be68172e9fd9ad8fb876c6389f16d1c1b5f100ffa779f77b1fb2176fcc9ab95b", size = 11611424 },
{ url = "https://files.pythonhosted.org/packages/78/13/f7d14e55865036a1e6a0a69580c240f43bc1f37407fe9235c0d4ef25ffb0/mypy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7be1e46525adfa0d97681432ee9fcd61a3964c2446795714699a998d193f1a3", size = 12365450 },
{ url = "https://files.pythonhosted.org/packages/48/e1/301a73852d40c241e915ac6d7bcd7fedd47d519246db2d7b86b9d7e7a0cb/mypy-1.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2e2c2e6d3593f6451b18588848e66260ff62ccca522dd231cd4dd59b0160668b", size = 12551765 },
{ url = "https://files.pythonhosted.org/packages/77/ba/c37bc323ae5fe7f3f15a28e06ab012cd0b7552886118943e90b15af31195/mypy-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:6983aae8b2f653e098edb77f893f7b6aca69f6cffb19b2cc7443f23cce5f4828", size = 9274701 },
{ url = "https://files.pythonhosted.org/packages/03/bc/f6339726c627bd7ca1ce0fa56c9ae2d0144604a319e0e339bdadafbbb599/mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f", size = 10662338 },
{ url = "https://files.pythonhosted.org/packages/e2/90/8dcf506ca1a09b0d17555cc00cd69aee402c203911410136cd716559efe7/mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5", size = 9787540 },
{ url = "https://files.pythonhosted.org/packages/05/05/a10f9479681e5da09ef2f9426f650d7b550d4bafbef683b69aad1ba87457/mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e", size = 11538051 },
{ url = "https://files.pythonhosted.org/packages/e9/9a/1f7d18b30edd57441a6411fcbc0c6869448d1a4bacbaee60656ac0fc29c8/mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c", size = 12286751 },
{ url = "https://files.pythonhosted.org/packages/72/af/19ff499b6f1dafcaf56f9881f7a965ac2f474f69f6f618b5175b044299f5/mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f", size = 12421783 },
{ url = "https://files.pythonhosted.org/packages/96/39/11b57431a1f686c1aed54bf794870efe0f6aeca11aca281a0bd87a5ad42c/mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f", size = 9265618 },
{ url = "https://files.pythonhosted.org/packages/98/3a/03c74331c5eb8bd025734e04c9840532226775c47a2c39b56a0c8d4f128d/mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd", size = 10793981 },
{ url = "https://files.pythonhosted.org/packages/f0/1a/41759b18f2cfd568848a37c89030aeb03534411eef981df621d8fad08a1d/mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f", size = 9749175 },
{ url = "https://files.pythonhosted.org/packages/12/7e/873481abf1ef112c582db832740f4c11b2bfa510e829d6da29b0ab8c3f9c/mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464", size = 11455675 },
{ url = "https://files.pythonhosted.org/packages/b3/d0/92ae4cde706923a2d3f2d6c39629134063ff64b9dedca9c1388363da072d/mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee", size = 12410020 },
{ url = "https://files.pythonhosted.org/packages/46/8b/df49974b337cce35f828ba6fda228152d6db45fed4c86ba56ffe442434fd/mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e", size = 12498582 },
{ url = "https://files.pythonhosted.org/packages/13/50/da5203fcf6c53044a0b699939f31075c45ae8a4cadf538a9069b165c1050/mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22", size = 9366614 },
{ url = "https://files.pythonhosted.org/packages/6a/9b/fd2e05d6ffff24d912f150b87db9e364fa8282045c875654ce7e32fffa66/mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445", size = 10788592 },
{ url = "https://files.pythonhosted.org/packages/74/37/b246d711c28a03ead1fd906bbc7106659aed7c089d55fe40dd58db812628/mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d", size = 9753611 },
{ url = "https://files.pythonhosted.org/packages/a6/ac/395808a92e10cfdac8003c3de9a2ab6dc7cde6c0d2a4df3df1b815ffd067/mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5", size = 11438443 },
{ url = "https://files.pythonhosted.org/packages/d2/8b/801aa06445d2de3895f59e476f38f3f8d610ef5d6908245f07d002676cbf/mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036", size = 12402541 },
{ url = "https://files.pythonhosted.org/packages/c7/67/5a4268782eb77344cc613a4cf23540928e41f018a9a1ec4c6882baf20ab8/mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357", size = 12494348 },
{ url = "https://files.pythonhosted.org/packages/83/3e/57bb447f7bbbfaabf1712d96f9df142624a386d98fb026a761532526057e/mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf", size = 9373648 },
{ url = "https://files.pythonhosted.org/packages/5a/fa/79cf41a55b682794abe71372151dbbf856e3008f6767057229e6649d294a/mypy-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e601a7fa172c2131bff456bb3ee08a88360760d0d2f8cbd7a75a65497e2df078", size = 10737129 },
{ url = "https://files.pythonhosted.org/packages/d3/33/dd8feb2597d648de29e3da0a8bf4e1afbda472964d2a4a0052203a6f3594/mypy-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:712e962a6357634fef20412699a3655c610110e01cdaa6180acec7fc9f8513ba", size = 9856335 },
{ url = "https://files.pythonhosted.org/packages/e4/b5/74508959c1b06b96674b364ffeb7ae5802646b32929b7701fc6b18447592/mypy-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95579473af29ab73a10bada2f9722856792a36ec5af5399b653aa28360290a5", size = 11611935 },
{ url = "https://files.pythonhosted.org/packages/6c/53/da61b9d9973efcd6507183fdad96606996191657fe79701b2c818714d573/mypy-1.15.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f8722560a14cde92fdb1e31597760dc35f9f5524cce17836c0d22841830fd5b", size = 12365827 },
{ url = "https://files.pythonhosted.org/packages/c1/72/965bd9ee89540c79a25778cc080c7e6ef40aa1eeac4d52cec7eae6eb5228/mypy-1.15.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fbb8da62dc352133d7d7ca90ed2fb0e9d42bb1a32724c287d3c76c58cbaa9c2", size = 12541924 },
{ url = "https://files.pythonhosted.org/packages/46/d0/f41645c2eb263e6c77ada7d76f894c580c9ddb20d77f0c24d34273a4dab2/mypy-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:d10d994b41fb3497719bbf866f227b3489048ea4bbbb5015357db306249f7980", size = 9271176 },
{ url = "https://files.pythonhosted.org/packages/09/4e/a7d65c7322c510de2c409ff3828b03354a7c43f5a8ed458a7a131b41c7b9/mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e", size = 2221777 },
{ url = "https://files.pythonhosted.org/packages/68/f8/65a7ce8d0e09b6329ad0c8d40330d100ea343bd4dd04c4f8ae26462d0a17/mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13", size = 10738433, upload-time = "2025-02-05T03:49:29.145Z" },
{ url = "https://files.pythonhosted.org/packages/b4/95/9c0ecb8eacfe048583706249439ff52105b3f552ea9c4024166c03224270/mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559", size = 9861472, upload-time = "2025-02-05T03:49:16.986Z" },
{ url = "https://files.pythonhosted.org/packages/84/09/9ec95e982e282e20c0d5407bc65031dfd0f0f8ecc66b69538296e06fcbee/mypy-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be68172e9fd9ad8fb876c6389f16d1c1b5f100ffa779f77b1fb2176fcc9ab95b", size = 11611424, upload-time = "2025-02-05T03:49:46.908Z" },
{ url = "https://files.pythonhosted.org/packages/78/13/f7d14e55865036a1e6a0a69580c240f43bc1f37407fe9235c0d4ef25ffb0/mypy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7be1e46525adfa0d97681432ee9fcd61a3964c2446795714699a998d193f1a3", size = 12365450, upload-time = "2025-02-05T03:50:05.89Z" },
{ url = "https://files.pythonhosted.org/packages/48/e1/301a73852d40c241e915ac6d7bcd7fedd47d519246db2d7b86b9d7e7a0cb/mypy-1.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2e2c2e6d3593f6451b18588848e66260ff62ccca522dd231cd4dd59b0160668b", size = 12551765, upload-time = "2025-02-05T03:49:33.56Z" },
{ url = "https://files.pythonhosted.org/packages/77/ba/c37bc323ae5fe7f3f15a28e06ab012cd0b7552886118943e90b15af31195/mypy-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:6983aae8b2f653e098edb77f893f7b6aca69f6cffb19b2cc7443f23cce5f4828", size = 9274701, upload-time = "2025-02-05T03:49:38.981Z" },
{ url = "https://files.pythonhosted.org/packages/03/bc/f6339726c627bd7ca1ce0fa56c9ae2d0144604a319e0e339bdadafbbb599/mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f", size = 10662338, upload-time = "2025-02-05T03:50:17.287Z" },
{ url = "https://files.pythonhosted.org/packages/e2/90/8dcf506ca1a09b0d17555cc00cd69aee402c203911410136cd716559efe7/mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5", size = 9787540, upload-time = "2025-02-05T03:49:51.21Z" },
{ url = "https://files.pythonhosted.org/packages/05/05/a10f9479681e5da09ef2f9426f650d7b550d4bafbef683b69aad1ba87457/mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e", size = 11538051, upload-time = "2025-02-05T03:50:20.885Z" },
{ url = "https://files.pythonhosted.org/packages/e9/9a/1f7d18b30edd57441a6411fcbc0c6869448d1a4bacbaee60656ac0fc29c8/mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c", size = 12286751, upload-time = "2025-02-05T03:49:42.408Z" },
{ url = "https://files.pythonhosted.org/packages/72/af/19ff499b6f1dafcaf56f9881f7a965ac2f474f69f6f618b5175b044299f5/mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f", size = 12421783, upload-time = "2025-02-05T03:49:07.707Z" },
{ url = "https://files.pythonhosted.org/packages/96/39/11b57431a1f686c1aed54bf794870efe0f6aeca11aca281a0bd87a5ad42c/mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f", size = 9265618, upload-time = "2025-02-05T03:49:54.581Z" },
{ url = "https://files.pythonhosted.org/packages/98/3a/03c74331c5eb8bd025734e04c9840532226775c47a2c39b56a0c8d4f128d/mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd", size = 10793981, upload-time = "2025-02-05T03:50:28.25Z" },
{ url = "https://files.pythonhosted.org/packages/f0/1a/41759b18f2cfd568848a37c89030aeb03534411eef981df621d8fad08a1d/mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f", size = 9749175, upload-time = "2025-02-05T03:50:13.411Z" },
{ url = "https://files.pythonhosted.org/packages/12/7e/873481abf1ef112c582db832740f4c11b2bfa510e829d6da29b0ab8c3f9c/mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464", size = 11455675, upload-time = "2025-02-05T03:50:31.421Z" },
{ url = "https://files.pythonhosted.org/packages/b3/d0/92ae4cde706923a2d3f2d6c39629134063ff64b9dedca9c1388363da072d/mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee", size = 12410020, upload-time = "2025-02-05T03:48:48.705Z" },
{ url = "https://files.pythonhosted.org/packages/46/8b/df49974b337cce35f828ba6fda228152d6db45fed4c86ba56ffe442434fd/mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e", size = 12498582, upload-time = "2025-02-05T03:49:03.628Z" },
{ url = "https://files.pythonhosted.org/packages/13/50/da5203fcf6c53044a0b699939f31075c45ae8a4cadf538a9069b165c1050/mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22", size = 9366614, upload-time = "2025-02-05T03:50:00.313Z" },
{ url = "https://files.pythonhosted.org/packages/6a/9b/fd2e05d6ffff24d912f150b87db9e364fa8282045c875654ce7e32fffa66/mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445", size = 10788592, upload-time = "2025-02-05T03:48:55.789Z" },
{ url = "https://files.pythonhosted.org/packages/74/37/b246d711c28a03ead1fd906bbc7106659aed7c089d55fe40dd58db812628/mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d", size = 9753611, upload-time = "2025-02-05T03:48:44.581Z" },
{ url = "https://files.pythonhosted.org/packages/a6/ac/395808a92e10cfdac8003c3de9a2ab6dc7cde6c0d2a4df3df1b815ffd067/mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5", size = 11438443, upload-time = "2025-02-05T03:49:25.514Z" },
{ url = "https://files.pythonhosted.org/packages/d2/8b/801aa06445d2de3895f59e476f38f3f8d610ef5d6908245f07d002676cbf/mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036", size = 12402541, upload-time = "2025-02-05T03:49:57.623Z" },
{ url = "https://files.pythonhosted.org/packages/c7/67/5a4268782eb77344cc613a4cf23540928e41f018a9a1ec4c6882baf20ab8/mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357", size = 12494348, upload-time = "2025-02-05T03:48:52.361Z" },
{ url = "https://files.pythonhosted.org/packages/83/3e/57bb447f7bbbfaabf1712d96f9df142624a386d98fb026a761532526057e/mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf", size = 9373648, upload-time = "2025-02-05T03:49:11.395Z" },
{ url = "https://files.pythonhosted.org/packages/5a/fa/79cf41a55b682794abe71372151dbbf856e3008f6767057229e6649d294a/mypy-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e601a7fa172c2131bff456bb3ee08a88360760d0d2f8cbd7a75a65497e2df078", size = 10737129, upload-time = "2025-02-05T03:50:24.509Z" },
{ url = "https://files.pythonhosted.org/packages/d3/33/dd8feb2597d648de29e3da0a8bf4e1afbda472964d2a4a0052203a6f3594/mypy-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:712e962a6357634fef20412699a3655c610110e01cdaa6180acec7fc9f8513ba", size = 9856335, upload-time = "2025-02-05T03:49:36.398Z" },
{ url = "https://files.pythonhosted.org/packages/e4/b5/74508959c1b06b96674b364ffeb7ae5802646b32929b7701fc6b18447592/mypy-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95579473af29ab73a10bada2f9722856792a36ec5af5399b653aa28360290a5", size = 11611935, upload-time = "2025-02-05T03:49:14.154Z" },
{ url = "https://files.pythonhosted.org/packages/6c/53/da61b9d9973efcd6507183fdad96606996191657fe79701b2c818714d573/mypy-1.15.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f8722560a14cde92fdb1e31597760dc35f9f5524cce17836c0d22841830fd5b", size = 12365827, upload-time = "2025-02-05T03:48:59.458Z" },
{ url = "https://files.pythonhosted.org/packages/c1/72/965bd9ee89540c79a25778cc080c7e6ef40aa1eeac4d52cec7eae6eb5228/mypy-1.15.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fbb8da62dc352133d7d7ca90ed2fb0e9d42bb1a32724c287d3c76c58cbaa9c2", size = 12541924, upload-time = "2025-02-05T03:50:03.12Z" },
{ url = "https://files.pythonhosted.org/packages/46/d0/f41645c2eb263e6c77ada7d76f894c580c9ddb20d77f0c24d34273a4dab2/mypy-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:d10d994b41fb3497719bbf866f227b3489048ea4bbbb5015357db306249f7980", size = 9271176, upload-time = "2025-02-05T03:50:10.86Z" },
{ url = "https://files.pythonhosted.org/packages/09/4e/a7d65c7322c510de2c409ff3828b03354a7c43f5a8ed458a7a131b41c7b9/mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e", size = 2221777, upload-time = "2025-02-05T03:50:08.348Z" },
]
[[package]]
name = "mypy-extensions"
version = "1.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343 }
sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 },
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
]
[[package]]
name = "orjson"
version = "3.10.18"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/81/0b/fea456a3ffe74e70ba30e01ec183a9b26bec4d497f61dcfce1b601059c60/orjson-3.10.18.tar.gz", hash = "sha256:e8da3947d92123eda795b68228cafe2724815621fe35e8e320a9e9593a4bcd53", size = 5422810 }
sdist = { url = "https://files.pythonhosted.org/packages/81/0b/fea456a3ffe74e70ba30e01ec183a9b26bec4d497f61dcfce1b601059c60/orjson-3.10.18.tar.gz", hash = "sha256:e8da3947d92123eda795b68228cafe2724815621fe35e8e320a9e9593a4bcd53", size = 5422810, upload-time = "2025-04-29T23:30:08.423Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/27/16/2ceb9fb7bc2b11b1e4a3ea27794256e93dee2309ebe297fd131a778cd150/orjson-3.10.18-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a45e5d68066b408e4bc383b6e4ef05e717c65219a9e1390abc6155a520cac402", size = 248927 },
{ url = "https://files.pythonhosted.org/packages/3d/e1/d3c0a2bba5b9906badd121da449295062b289236c39c3a7801f92c4682b0/orjson-3.10.18-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be3b9b143e8b9db05368b13b04c84d37544ec85bb97237b3a923f076265ec89c", size = 136995 },
{ url = "https://files.pythonhosted.org/packages/d7/51/698dd65e94f153ee5ecb2586c89702c9e9d12f165a63e74eb9ea1299f4e1/orjson-3.10.18-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9b0aa09745e2c9b3bf779b096fa71d1cc2d801a604ef6dd79c8b1bfef52b2f92", size = 132893 },
{ url = "https://files.pythonhosted.org/packages/b3/e5/155ce5a2c43a85e790fcf8b985400138ce5369f24ee6770378ee6b691036/orjson-3.10.18-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53a245c104d2792e65c8d225158f2b8262749ffe64bc7755b00024757d957a13", size = 137017 },
{ url = "https://files.pythonhosted.org/packages/46/bb/6141ec3beac3125c0b07375aee01b5124989907d61c72c7636136e4bd03e/orjson-3.10.18-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f9495ab2611b7f8a0a8a505bcb0f0cbdb5469caafe17b0e404c3c746f9900469", size = 138290 },
{ url = "https://files.pythonhosted.org/packages/77/36/6961eca0b66b7809d33c4ca58c6bd4c23a1b914fb23aba2fa2883f791434/orjson-3.10.18-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73be1cbcebadeabdbc468f82b087df435843c809cd079a565fb16f0f3b23238f", size = 142828 },
{ url = "https://files.pythonhosted.org/packages/8b/2f/0c646d5fd689d3be94f4d83fa9435a6c4322c9b8533edbb3cd4bc8c5f69a/orjson-3.10.18-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe8936ee2679e38903df158037a2f1c108129dee218975122e37847fb1d4ac68", size = 132806 },
{ url = "https://files.pythonhosted.org/packages/ea/af/65907b40c74ef4c3674ef2bcfa311c695eb934710459841b3c2da212215c/orjson-3.10.18-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7115fcbc8525c74e4c2b608129bef740198e9a120ae46184dac7683191042056", size = 135005 },
{ url = "https://files.pythonhosted.org/packages/c7/d1/68bd20ac6a32cd1f1b10d23e7cc58ee1e730e80624e3031d77067d7150fc/orjson-3.10.18-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:771474ad34c66bc4d1c01f645f150048030694ea5b2709b87d3bda273ffe505d", size = 413418 },
{ url = "https://files.pythonhosted.org/packages/31/31/c701ec0bcc3e80e5cb6e319c628ef7b768aaa24b0f3b4c599df2eaacfa24/orjson-3.10.18-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:7c14047dbbea52886dd87169f21939af5d55143dad22d10db6a7514f058156a8", size = 153288 },
{ url = "https://files.pythonhosted.org/packages/d9/31/5e1aa99a10893a43cfc58009f9da840990cc8a9ebb75aa452210ba18587e/orjson-3.10.18-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:641481b73baec8db14fdf58f8967e52dc8bda1f2aba3aa5f5c1b07ed6df50b7f", size = 137181 },
{ url = "https://files.pythonhosted.org/packages/bf/8c/daba0ac1b8690011d9242a0f37235f7d17df6d0ad941021048523b76674e/orjson-3.10.18-cp310-cp310-win32.whl", hash = "sha256:607eb3ae0909d47280c1fc657c4284c34b785bae371d007595633f4b1a2bbe06", size = 142694 },
{ url = "https://files.pythonhosted.org/packages/16/62/8b687724143286b63e1d0fab3ad4214d54566d80b0ba9d67c26aaf28a2f8/orjson-3.10.18-cp310-cp310-win_amd64.whl", hash = "sha256:8770432524ce0eca50b7efc2a9a5f486ee0113a5fbb4231526d414e6254eba92", size = 134600 },
{ url = "https://files.pythonhosted.org/packages/97/c7/c54a948ce9a4278794f669a353551ce7db4ffb656c69a6e1f2264d563e50/orjson-3.10.18-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e0a183ac3b8e40471e8d843105da6fbe7c070faab023be3b08188ee3f85719b8", size = 248929 },
{ url = "https://files.pythonhosted.org/packages/9e/60/a9c674ef1dd8ab22b5b10f9300e7e70444d4e3cda4b8258d6c2488c32143/orjson-3.10.18-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:5ef7c164d9174362f85238d0cd4afdeeb89d9e523e4651add6a5d458d6f7d42d", size = 133364 },
{ url = "https://files.pythonhosted.org/packages/c1/4e/f7d1bdd983082216e414e6d7ef897b0c2957f99c545826c06f371d52337e/orjson-3.10.18-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afd14c5d99cdc7bf93f22b12ec3b294931518aa019e2a147e8aa2f31fd3240f7", size = 136995 },
{ url = "https://files.pythonhosted.org/packages/17/89/46b9181ba0ea251c9243b0c8ce29ff7c9796fa943806a9c8b02592fce8ea/orjson-3.10.18-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b672502323b6cd133c4af6b79e3bea36bad2d16bca6c1f645903fce83909a7a", size = 132894 },
{ url = "https://files.pythonhosted.org/packages/ca/dd/7bce6fcc5b8c21aef59ba3c67f2166f0a1a9b0317dcca4a9d5bd7934ecfd/orjson-3.10.18-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51f8c63be6e070ec894c629186b1c0fe798662b8687f3d9fdfa5e401c6bd7679", size = 137016 },
{ url = "https://files.pythonhosted.org/packages/1c/4a/b8aea1c83af805dcd31c1f03c95aabb3e19a016b2a4645dd822c5686e94d/orjson-3.10.18-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f9478ade5313d724e0495d167083c6f3be0dd2f1c9c8a38db9a9e912cdaf947", size = 138290 },
{ url = "https://files.pythonhosted.org/packages/36/d6/7eb05c85d987b688707f45dcf83c91abc2251e0dd9fb4f7be96514f838b1/orjson-3.10.18-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:187aefa562300a9d382b4b4eb9694806e5848b0cedf52037bb5c228c61bb66d4", size = 142829 },
{ url = "https://files.pythonhosted.org/packages/d2/78/ddd3ee7873f2b5f90f016bc04062713d567435c53ecc8783aab3a4d34915/orjson-3.10.18-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da552683bc9da222379c7a01779bddd0ad39dd699dd6300abaf43eadee38334", size = 132805 },
{ url = "https://files.pythonhosted.org/packages/8c/09/c8e047f73d2c5d21ead9c180203e111cddeffc0848d5f0f974e346e21c8e/orjson-3.10.18-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e450885f7b47a0231979d9c49b567ed1c4e9f69240804621be87c40bc9d3cf17", size = 135008 },
{ url = "https://files.pythonhosted.org/packages/0c/4b/dccbf5055ef8fb6eda542ab271955fc1f9bf0b941a058490293f8811122b/orjson-3.10.18-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5e3c9cc2ba324187cd06287ca24f65528f16dfc80add48dc99fa6c836bb3137e", size = 413419 },
{ url = "https://files.pythonhosted.org/packages/8a/f3/1eac0c5e2d6d6790bd2025ebfbefcbd37f0d097103d76f9b3f9302af5a17/orjson-3.10.18-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:50ce016233ac4bfd843ac5471e232b865271d7d9d44cf9d33773bcd883ce442b", size = 153292 },
{ url = "https://files.pythonhosted.org/packages/1f/b4/ef0abf64c8f1fabf98791819ab502c2c8c1dc48b786646533a93637d8999/orjson-3.10.18-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b3ceff74a8f7ffde0b2785ca749fc4e80e4315c0fd887561144059fb1c138aa7", size = 137182 },
{ url = "https://files.pythonhosted.org/packages/a9/a3/6ea878e7b4a0dc5c888d0370d7752dcb23f402747d10e2257478d69b5e63/orjson-3.10.18-cp311-cp311-win32.whl", hash = "sha256:fdba703c722bd868c04702cac4cb8c6b8ff137af2623bc0ddb3b3e6a2c8996c1", size = 142695 },
{ url = "https://files.pythonhosted.org/packages/79/2a/4048700a3233d562f0e90d5572a849baa18ae4e5ce4c3ba6247e4ece57b0/orjson-3.10.18-cp311-cp311-win_amd64.whl", hash = "sha256:c28082933c71ff4bc6ccc82a454a2bffcef6e1d7379756ca567c772e4fb3278a", size = 134603 },
{ url = "https://files.pythonhosted.org/packages/03/45/10d934535a4993d27e1c84f1810e79ccf8b1b7418cef12151a22fe9bb1e1/orjson-3.10.18-cp311-cp311-win_arm64.whl", hash = "sha256:a6c7c391beaedd3fa63206e5c2b7b554196f14debf1ec9deb54b5d279b1b46f5", size = 131400 },
{ url = "https://files.pythonhosted.org/packages/21/1a/67236da0916c1a192d5f4ccbe10ec495367a726996ceb7614eaa687112f2/orjson-3.10.18-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:50c15557afb7f6d63bc6d6348e0337a880a04eaa9cd7c9d569bcb4e760a24753", size = 249184 },
{ url = "https://files.pythonhosted.org/packages/b3/bc/c7f1db3b1d094dc0c6c83ed16b161a16c214aaa77f311118a93f647b32dc/orjson-3.10.18-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:356b076f1662c9813d5fa56db7d63ccceef4c271b1fb3dd522aca291375fcf17", size = 133279 },
{ url = "https://files.pythonhosted.org/packages/af/84/664657cd14cc11f0d81e80e64766c7ba5c9b7fc1ec304117878cc1b4659c/orjson-3.10.18-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:559eb40a70a7494cd5beab2d73657262a74a2c59aff2068fdba8f0424ec5b39d", size = 136799 },
{ url = "https://files.pythonhosted.org/packages/9a/bb/f50039c5bb05a7ab024ed43ba25d0319e8722a0ac3babb0807e543349978/orjson-3.10.18-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f3c29eb9a81e2fbc6fd7ddcfba3e101ba92eaff455b8d602bf7511088bbc0eae", size = 132791 },
{ url = "https://files.pythonhosted.org/packages/93/8c/ee74709fc072c3ee219784173ddfe46f699598a1723d9d49cbc78d66df65/orjson-3.10.18-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6612787e5b0756a171c7d81ba245ef63a3533a637c335aa7fcb8e665f4a0966f", size = 137059 },
{ url = "https://files.pythonhosted.org/packages/6a/37/e6d3109ee004296c80426b5a62b47bcadd96a3deab7443e56507823588c5/orjson-3.10.18-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ac6bd7be0dcab5b702c9d43d25e70eb456dfd2e119d512447468f6405b4a69c", size = 138359 },
{ url = "https://files.pythonhosted.org/packages/4f/5d/387dafae0e4691857c62bd02839a3bf3fa648eebd26185adfac58d09f207/orjson-3.10.18-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9f72f100cee8dde70100406d5c1abba515a7df926d4ed81e20a9730c062fe9ad", size = 142853 },
{ url = "https://files.pythonhosted.org/packages/27/6f/875e8e282105350b9a5341c0222a13419758545ae32ad6e0fcf5f64d76aa/orjson-3.10.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9dca85398d6d093dd41dc0983cbf54ab8e6afd1c547b6b8a311643917fbf4e0c", size = 133131 },
{ url = "https://files.pythonhosted.org/packages/48/b2/73a1f0b4790dcb1e5a45f058f4f5dcadc8a85d90137b50d6bbc6afd0ae50/orjson-3.10.18-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:22748de2a07fcc8781a70edb887abf801bb6142e6236123ff93d12d92db3d406", size = 134834 },
{ url = "https://files.pythonhosted.org/packages/56/f5/7ed133a5525add9c14dbdf17d011dd82206ca6840811d32ac52a35935d19/orjson-3.10.18-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3a83c9954a4107b9acd10291b7f12a6b29e35e8d43a414799906ea10e75438e6", size = 413368 },
{ url = "https://files.pythonhosted.org/packages/11/7c/439654221ed9c3324bbac7bdf94cf06a971206b7b62327f11a52544e4982/orjson-3.10.18-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:303565c67a6c7b1f194c94632a4a39918e067bd6176a48bec697393865ce4f06", size = 153359 },
{ url = "https://files.pythonhosted.org/packages/48/e7/d58074fa0cc9dd29a8fa2a6c8d5deebdfd82c6cfef72b0e4277c4017563a/orjson-3.10.18-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:86314fdb5053a2f5a5d881f03fca0219bfdf832912aa88d18676a5175c6916b5", size = 137466 },
{ url = "https://files.pythonhosted.org/packages/57/4d/fe17581cf81fb70dfcef44e966aa4003360e4194d15a3f38cbffe873333a/orjson-3.10.18-cp312-cp312-win32.whl", hash = "sha256:187ec33bbec58c76dbd4066340067d9ece6e10067bb0cc074a21ae3300caa84e", size = 142683 },
{ url = "https://files.pythonhosted.org/packages/e6/22/469f62d25ab5f0f3aee256ea732e72dc3aab6d73bac777bd6277955bceef/orjson-3.10.18-cp312-cp312-win_amd64.whl", hash = "sha256:f9f94cf6d3f9cd720d641f8399e390e7411487e493962213390d1ae45c7814fc", size = 134754 },
{ url = "https://files.pythonhosted.org/packages/10/b0/1040c447fac5b91bc1e9c004b69ee50abb0c1ffd0d24406e1350c58a7fcb/orjson-3.10.18-cp312-cp312-win_arm64.whl", hash = "sha256:3d600be83fe4514944500fa8c2a0a77099025ec6482e8087d7659e891f23058a", size = 131218 },
{ url = "https://files.pythonhosted.org/packages/04/f0/8aedb6574b68096f3be8f74c0b56d36fd94bcf47e6c7ed47a7bd1474aaa8/orjson-3.10.18-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:69c34b9441b863175cc6a01f2935de994025e773f814412030f269da4f7be147", size = 249087 },
{ url = "https://files.pythonhosted.org/packages/bc/f7/7118f965541aeac6844fcb18d6988e111ac0d349c9b80cda53583e758908/orjson-3.10.18-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:1ebeda919725f9dbdb269f59bc94f861afbe2a27dce5608cdba2d92772364d1c", size = 133273 },
{ url = "https://files.pythonhosted.org/packages/fb/d9/839637cc06eaf528dd8127b36004247bf56e064501f68df9ee6fd56a88ee/orjson-3.10.18-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5adf5f4eed520a4959d29ea80192fa626ab9a20b2ea13f8f6dc58644f6927103", size = 136779 },
{ url = "https://files.pythonhosted.org/packages/2b/6d/f226ecfef31a1f0e7d6bf9a31a0bbaf384c7cbe3fce49cc9c2acc51f902a/orjson-3.10.18-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7592bb48a214e18cd670974f289520f12b7aed1fa0b2e2616b8ed9e069e08595", size = 132811 },
{ url = "https://files.pythonhosted.org/packages/73/2d/371513d04143c85b681cf8f3bce743656eb5b640cb1f461dad750ac4b4d4/orjson-3.10.18-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f872bef9f042734110642b7a11937440797ace8c87527de25e0c53558b579ccc", size = 137018 },
{ url = "https://files.pythonhosted.org/packages/69/cb/a4d37a30507b7a59bdc484e4a3253c8141bf756d4e13fcc1da760a0b00cb/orjson-3.10.18-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0315317601149c244cb3ecef246ef5861a64824ccbcb8018d32c66a60a84ffbc", size = 138368 },
{ url = "https://files.pythonhosted.org/packages/1e/ae/cd10883c48d912d216d541eb3db8b2433415fde67f620afe6f311f5cd2ca/orjson-3.10.18-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0da26957e77e9e55a6c2ce2e7182a36a6f6b180ab7189315cb0995ec362e049", size = 142840 },
{ url = "https://files.pythonhosted.org/packages/6d/4c/2bda09855c6b5f2c055034c9eda1529967b042ff8d81a05005115c4e6772/orjson-3.10.18-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb70d489bc79b7519e5803e2cc4c72343c9dc1154258adf2f8925d0b60da7c58", size = 133135 },
{ url = "https://files.pythonhosted.org/packages/13/4a/35971fd809a8896731930a80dfff0b8ff48eeb5d8b57bb4d0d525160017f/orjson-3.10.18-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e9e86a6af31b92299b00736c89caf63816f70a4001e750bda179e15564d7a034", size = 134810 },
{ url = "https://files.pythonhosted.org/packages/99/70/0fa9e6310cda98365629182486ff37a1c6578e34c33992df271a476ea1cd/orjson-3.10.18-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:c382a5c0b5931a5fc5405053d36c1ce3fd561694738626c77ae0b1dfc0242ca1", size = 413491 },
{ url = "https://files.pythonhosted.org/packages/32/cb/990a0e88498babddb74fb97855ae4fbd22a82960e9b06eab5775cac435da/orjson-3.10.18-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8e4b2ae732431127171b875cb2668f883e1234711d3c147ffd69fe5be51a8012", size = 153277 },
{ url = "https://files.pythonhosted.org/packages/92/44/473248c3305bf782a384ed50dd8bc2d3cde1543d107138fd99b707480ca1/orjson-3.10.18-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d808e34ddb24fc29a4d4041dcfafbae13e129c93509b847b14432717d94b44f", size = 137367 },
{ url = "https://files.pythonhosted.org/packages/ad/fd/7f1d3edd4ffcd944a6a40e9f88af2197b619c931ac4d3cfba4798d4d3815/orjson-3.10.18-cp313-cp313-win32.whl", hash = "sha256:ad8eacbb5d904d5591f27dee4031e2c1db43d559edb8f91778efd642d70e6bea", size = 142687 },
{ url = "https://files.pythonhosted.org/packages/4b/03/c75c6ad46be41c16f4cfe0352a2d1450546f3c09ad2c9d341110cd87b025/orjson-3.10.18-cp313-cp313-win_amd64.whl", hash = "sha256:aed411bcb68bf62e85588f2a7e03a6082cc42e5a2796e06e72a962d7c6310b52", size = 134794 },
{ url = "https://files.pythonhosted.org/packages/c2/28/f53038a5a72cc4fd0b56c1eafb4ef64aec9685460d5ac34de98ca78b6e29/orjson-3.10.18-cp313-cp313-win_arm64.whl", hash = "sha256:f54c1385a0e6aba2f15a40d703b858bedad36ded0491e55d35d905b2c34a4cc3", size = 131186 },
{ url = "https://files.pythonhosted.org/packages/df/db/69488acaa2316788b7e171f024912c6fe8193aa2e24e9cfc7bc41c3669ba/orjson-3.10.18-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c95fae14225edfd699454e84f61c3dd938df6629a00c6ce15e704f57b58433bb", size = 249301 },
{ url = "https://files.pythonhosted.org/packages/23/21/d816c44ec5d1482c654e1d23517d935bb2716e1453ff9380e861dc6efdd3/orjson-3.10.18-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5232d85f177f98e0cefabb48b5e7f60cff6f3f0365f9c60631fecd73849b2a82", size = 136786 },
{ url = "https://files.pythonhosted.org/packages/a5/9f/f68d8a9985b717e39ba7bf95b57ba173fcd86aeca843229ec60d38f1faa7/orjson-3.10.18-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2783e121cafedf0d85c148c248a20470018b4ffd34494a68e125e7d5857655d1", size = 132711 },
{ url = "https://files.pythonhosted.org/packages/b5/63/447f5955439bf7b99bdd67c38a3f689d140d998ac58e3b7d57340520343c/orjson-3.10.18-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e54ee3722caf3db09c91f442441e78f916046aa58d16b93af8a91500b7bbf273", size = 136841 },
{ url = "https://files.pythonhosted.org/packages/68/9e/4855972f2be74097242e4681ab6766d36638a079e09d66f3d6a5d1188ce7/orjson-3.10.18-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2daf7e5379b61380808c24f6fc182b7719301739e4271c3ec88f2984a2d61f89", size = 138082 },
{ url = "https://files.pythonhosted.org/packages/08/0f/e68431e53a39698d2355faf1f018c60a3019b4b54b4ea6be9dc6b8208a3d/orjson-3.10.18-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f39b371af3add20b25338f4b29a8d6e79a8c7ed0e9dd49e008228a065d07781", size = 142618 },
{ url = "https://files.pythonhosted.org/packages/32/da/bdcfff239ddba1b6ef465efe49d7e43cc8c30041522feba9fd4241d47c32/orjson-3.10.18-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b819ed34c01d88c6bec290e6842966f8e9ff84b7694632e88341363440d4cc0", size = 132627 },
{ url = "https://files.pythonhosted.org/packages/0c/28/bc634da09bbe972328f615b0961f1e7d91acb3cc68bddbca9e8dd64e8e24/orjson-3.10.18-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2f6c57debaef0b1aa13092822cbd3698a1fb0209a9ea013a969f4efa36bdea57", size = 134832 },
{ url = "https://files.pythonhosted.org/packages/1d/d2/e8ac0c2d0ec782ed8925b4eb33f040cee1f1fbd1d8b268aeb84b94153e49/orjson-3.10.18-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:755b6d61ffdb1ffa1e768330190132e21343757c9aa2308c67257cc81a1a6f5a", size = 413161 },
{ url = "https://files.pythonhosted.org/packages/28/f0/397e98c352a27594566e865999dc6b88d6f37d5bbb87b23c982af24114c4/orjson-3.10.18-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ce8d0a875a85b4c8579eab5ac535fb4b2a50937267482be402627ca7e7570ee3", size = 153012 },
{ url = "https://files.pythonhosted.org/packages/93/bf/2c7334caeb48bdaa4cae0bde17ea417297ee136598653b1da7ae1f98c785/orjson-3.10.18-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57b5d0673cbd26781bebc2bf86f99dd19bd5a9cb55f71cc4f66419f6b50f3d77", size = 136999 },
{ url = "https://files.pythonhosted.org/packages/35/72/4827b1c0c31621c2aa1e661a899cdd2cfac0565c6cd7131890daa4ef7535/orjson-3.10.18-cp39-cp39-win32.whl", hash = "sha256:951775d8b49d1d16ca8818b1f20c4965cae9157e7b562a2ae34d3967b8f21c8e", size = 142560 },
{ url = "https://files.pythonhosted.org/packages/72/91/ef8e76868e7eed478887c82f60607a8abf58dadd24e95817229a4b2e2639/orjson-3.10.18-cp39-cp39-win_amd64.whl", hash = "sha256:fdd9d68f83f0bc4406610b1ac68bdcded8c5ee58605cc69e643a06f4d075f429", size = 134455 },
{ url = "https://files.pythonhosted.org/packages/27/16/2ceb9fb7bc2b11b1e4a3ea27794256e93dee2309ebe297fd131a778cd150/orjson-3.10.18-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a45e5d68066b408e4bc383b6e4ef05e717c65219a9e1390abc6155a520cac402", size = 248927, upload-time = "2025-04-29T23:28:08.643Z" },
{ url = "https://files.pythonhosted.org/packages/3d/e1/d3c0a2bba5b9906badd121da449295062b289236c39c3a7801f92c4682b0/orjson-3.10.18-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be3b9b143e8b9db05368b13b04c84d37544ec85bb97237b3a923f076265ec89c", size = 136995, upload-time = "2025-04-29T23:28:11.503Z" },
{ url = "https://files.pythonhosted.org/packages/d7/51/698dd65e94f153ee5ecb2586c89702c9e9d12f165a63e74eb9ea1299f4e1/orjson-3.10.18-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9b0aa09745e2c9b3bf779b096fa71d1cc2d801a604ef6dd79c8b1bfef52b2f92", size = 132893, upload-time = "2025-04-29T23:28:12.751Z" },
{ url = "https://files.pythonhosted.org/packages/b3/e5/155ce5a2c43a85e790fcf8b985400138ce5369f24ee6770378ee6b691036/orjson-3.10.18-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53a245c104d2792e65c8d225158f2b8262749ffe64bc7755b00024757d957a13", size = 137017, upload-time = "2025-04-29T23:28:14.498Z" },
{ url = "https://files.pythonhosted.org/packages/46/bb/6141ec3beac3125c0b07375aee01b5124989907d61c72c7636136e4bd03e/orjson-3.10.18-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f9495ab2611b7f8a0a8a505bcb0f0cbdb5469caafe17b0e404c3c746f9900469", size = 138290, upload-time = "2025-04-29T23:28:16.211Z" },
{ url = "https://files.pythonhosted.org/packages/77/36/6961eca0b66b7809d33c4ca58c6bd4c23a1b914fb23aba2fa2883f791434/orjson-3.10.18-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73be1cbcebadeabdbc468f82b087df435843c809cd079a565fb16f0f3b23238f", size = 142828, upload-time = "2025-04-29T23:28:18.065Z" },
{ url = "https://files.pythonhosted.org/packages/8b/2f/0c646d5fd689d3be94f4d83fa9435a6c4322c9b8533edbb3cd4bc8c5f69a/orjson-3.10.18-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe8936ee2679e38903df158037a2f1c108129dee218975122e37847fb1d4ac68", size = 132806, upload-time = "2025-04-29T23:28:19.782Z" },
{ url = "https://files.pythonhosted.org/packages/ea/af/65907b40c74ef4c3674ef2bcfa311c695eb934710459841b3c2da212215c/orjson-3.10.18-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7115fcbc8525c74e4c2b608129bef740198e9a120ae46184dac7683191042056", size = 135005, upload-time = "2025-04-29T23:28:21.367Z" },
{ url = "https://files.pythonhosted.org/packages/c7/d1/68bd20ac6a32cd1f1b10d23e7cc58ee1e730e80624e3031d77067d7150fc/orjson-3.10.18-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:771474ad34c66bc4d1c01f645f150048030694ea5b2709b87d3bda273ffe505d", size = 413418, upload-time = "2025-04-29T23:28:23.097Z" },
{ url = "https://files.pythonhosted.org/packages/31/31/c701ec0bcc3e80e5cb6e319c628ef7b768aaa24b0f3b4c599df2eaacfa24/orjson-3.10.18-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:7c14047dbbea52886dd87169f21939af5d55143dad22d10db6a7514f058156a8", size = 153288, upload-time = "2025-04-29T23:28:25.02Z" },
{ url = "https://files.pythonhosted.org/packages/d9/31/5e1aa99a10893a43cfc58009f9da840990cc8a9ebb75aa452210ba18587e/orjson-3.10.18-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:641481b73baec8db14fdf58f8967e52dc8bda1f2aba3aa5f5c1b07ed6df50b7f", size = 137181, upload-time = "2025-04-29T23:28:26.318Z" },
{ url = "https://files.pythonhosted.org/packages/bf/8c/daba0ac1b8690011d9242a0f37235f7d17df6d0ad941021048523b76674e/orjson-3.10.18-cp310-cp310-win32.whl", hash = "sha256:607eb3ae0909d47280c1fc657c4284c34b785bae371d007595633f4b1a2bbe06", size = 142694, upload-time = "2025-04-29T23:28:28.092Z" },
{ url = "https://files.pythonhosted.org/packages/16/62/8b687724143286b63e1d0fab3ad4214d54566d80b0ba9d67c26aaf28a2f8/orjson-3.10.18-cp310-cp310-win_amd64.whl", hash = "sha256:8770432524ce0eca50b7efc2a9a5f486ee0113a5fbb4231526d414e6254eba92", size = 134600, upload-time = "2025-04-29T23:28:29.422Z" },
{ url = "https://files.pythonhosted.org/packages/97/c7/c54a948ce9a4278794f669a353551ce7db4ffb656c69a6e1f2264d563e50/orjson-3.10.18-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e0a183ac3b8e40471e8d843105da6fbe7c070faab023be3b08188ee3f85719b8", size = 248929, upload-time = "2025-04-29T23:28:30.716Z" },
{ url = "https://files.pythonhosted.org/packages/9e/60/a9c674ef1dd8ab22b5b10f9300e7e70444d4e3cda4b8258d6c2488c32143/orjson-3.10.18-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:5ef7c164d9174362f85238d0cd4afdeeb89d9e523e4651add6a5d458d6f7d42d", size = 133364, upload-time = "2025-04-29T23:28:32.392Z" },
{ url = "https://files.pythonhosted.org/packages/c1/4e/f7d1bdd983082216e414e6d7ef897b0c2957f99c545826c06f371d52337e/orjson-3.10.18-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afd14c5d99cdc7bf93f22b12ec3b294931518aa019e2a147e8aa2f31fd3240f7", size = 136995, upload-time = "2025-04-29T23:28:34.024Z" },
{ url = "https://files.pythonhosted.org/packages/17/89/46b9181ba0ea251c9243b0c8ce29ff7c9796fa943806a9c8b02592fce8ea/orjson-3.10.18-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b672502323b6cd133c4af6b79e3bea36bad2d16bca6c1f645903fce83909a7a", size = 132894, upload-time = "2025-04-29T23:28:35.318Z" },
{ url = "https://files.pythonhosted.org/packages/ca/dd/7bce6fcc5b8c21aef59ba3c67f2166f0a1a9b0317dcca4a9d5bd7934ecfd/orjson-3.10.18-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51f8c63be6e070ec894c629186b1c0fe798662b8687f3d9fdfa5e401c6bd7679", size = 137016, upload-time = "2025-04-29T23:28:36.674Z" },
{ url = "https://files.pythonhosted.org/packages/1c/4a/b8aea1c83af805dcd31c1f03c95aabb3e19a016b2a4645dd822c5686e94d/orjson-3.10.18-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f9478ade5313d724e0495d167083c6f3be0dd2f1c9c8a38db9a9e912cdaf947", size = 138290, upload-time = "2025-04-29T23:28:38.3Z" },
{ url = "https://files.pythonhosted.org/packages/36/d6/7eb05c85d987b688707f45dcf83c91abc2251e0dd9fb4f7be96514f838b1/orjson-3.10.18-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:187aefa562300a9d382b4b4eb9694806e5848b0cedf52037bb5c228c61bb66d4", size = 142829, upload-time = "2025-04-29T23:28:39.657Z" },
{ url = "https://files.pythonhosted.org/packages/d2/78/ddd3ee7873f2b5f90f016bc04062713d567435c53ecc8783aab3a4d34915/orjson-3.10.18-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da552683bc9da222379c7a01779bddd0ad39dd699dd6300abaf43eadee38334", size = 132805, upload-time = "2025-04-29T23:28:40.969Z" },
{ url = "https://files.pythonhosted.org/packages/8c/09/c8e047f73d2c5d21ead9c180203e111cddeffc0848d5f0f974e346e21c8e/orjson-3.10.18-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e450885f7b47a0231979d9c49b567ed1c4e9f69240804621be87c40bc9d3cf17", size = 135008, upload-time = "2025-04-29T23:28:42.284Z" },
{ url = "https://files.pythonhosted.org/packages/0c/4b/dccbf5055ef8fb6eda542ab271955fc1f9bf0b941a058490293f8811122b/orjson-3.10.18-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5e3c9cc2ba324187cd06287ca24f65528f16dfc80add48dc99fa6c836bb3137e", size = 413419, upload-time = "2025-04-29T23:28:43.673Z" },
{ url = "https://files.pythonhosted.org/packages/8a/f3/1eac0c5e2d6d6790bd2025ebfbefcbd37f0d097103d76f9b3f9302af5a17/orjson-3.10.18-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:50ce016233ac4bfd843ac5471e232b865271d7d9d44cf9d33773bcd883ce442b", size = 153292, upload-time = "2025-04-29T23:28:45.573Z" },
{ url = "https://files.pythonhosted.org/packages/1f/b4/ef0abf64c8f1fabf98791819ab502c2c8c1dc48b786646533a93637d8999/orjson-3.10.18-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b3ceff74a8f7ffde0b2785ca749fc4e80e4315c0fd887561144059fb1c138aa7", size = 137182, upload-time = "2025-04-29T23:28:47.229Z" },
{ url = "https://files.pythonhosted.org/packages/a9/a3/6ea878e7b4a0dc5c888d0370d7752dcb23f402747d10e2257478d69b5e63/orjson-3.10.18-cp311-cp311-win32.whl", hash = "sha256:fdba703c722bd868c04702cac4cb8c6b8ff137af2623bc0ddb3b3e6a2c8996c1", size = 142695, upload-time = "2025-04-29T23:28:48.564Z" },
{ url = "https://files.pythonhosted.org/packages/79/2a/4048700a3233d562f0e90d5572a849baa18ae4e5ce4c3ba6247e4ece57b0/orjson-3.10.18-cp311-cp311-win_amd64.whl", hash = "sha256:c28082933c71ff4bc6ccc82a454a2bffcef6e1d7379756ca567c772e4fb3278a", size = 134603, upload-time = "2025-04-29T23:28:50.442Z" },
{ url = "https://files.pythonhosted.org/packages/03/45/10d934535a4993d27e1c84f1810e79ccf8b1b7418cef12151a22fe9bb1e1/orjson-3.10.18-cp311-cp311-win_arm64.whl", hash = "sha256:a6c7c391beaedd3fa63206e5c2b7b554196f14debf1ec9deb54b5d279b1b46f5", size = 131400, upload-time = "2025-04-29T23:28:51.838Z" },
{ url = "https://files.pythonhosted.org/packages/21/1a/67236da0916c1a192d5f4ccbe10ec495367a726996ceb7614eaa687112f2/orjson-3.10.18-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:50c15557afb7f6d63bc6d6348e0337a880a04eaa9cd7c9d569bcb4e760a24753", size = 249184, upload-time = "2025-04-29T23:28:53.612Z" },
{ url = "https://files.pythonhosted.org/packages/b3/bc/c7f1db3b1d094dc0c6c83ed16b161a16c214aaa77f311118a93f647b32dc/orjson-3.10.18-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:356b076f1662c9813d5fa56db7d63ccceef4c271b1fb3dd522aca291375fcf17", size = 133279, upload-time = "2025-04-29T23:28:55.055Z" },
{ url = "https://files.pythonhosted.org/packages/af/84/664657cd14cc11f0d81e80e64766c7ba5c9b7fc1ec304117878cc1b4659c/orjson-3.10.18-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:559eb40a70a7494cd5beab2d73657262a74a2c59aff2068fdba8f0424ec5b39d", size = 136799, upload-time = "2025-04-29T23:28:56.828Z" },
{ url = "https://files.pythonhosted.org/packages/9a/bb/f50039c5bb05a7ab024ed43ba25d0319e8722a0ac3babb0807e543349978/orjson-3.10.18-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f3c29eb9a81e2fbc6fd7ddcfba3e101ba92eaff455b8d602bf7511088bbc0eae", size = 132791, upload-time = "2025-04-29T23:28:58.751Z" },
{ url = "https://files.pythonhosted.org/packages/93/8c/ee74709fc072c3ee219784173ddfe46f699598a1723d9d49cbc78d66df65/orjson-3.10.18-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6612787e5b0756a171c7d81ba245ef63a3533a637c335aa7fcb8e665f4a0966f", size = 137059, upload-time = "2025-04-29T23:29:00.129Z" },
{ url = "https://files.pythonhosted.org/packages/6a/37/e6d3109ee004296c80426b5a62b47bcadd96a3deab7443e56507823588c5/orjson-3.10.18-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ac6bd7be0dcab5b702c9d43d25e70eb456dfd2e119d512447468f6405b4a69c", size = 138359, upload-time = "2025-04-29T23:29:01.704Z" },
{ url = "https://files.pythonhosted.org/packages/4f/5d/387dafae0e4691857c62bd02839a3bf3fa648eebd26185adfac58d09f207/orjson-3.10.18-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9f72f100cee8dde70100406d5c1abba515a7df926d4ed81e20a9730c062fe9ad", size = 142853, upload-time = "2025-04-29T23:29:03.576Z" },
{ url = "https://files.pythonhosted.org/packages/27/6f/875e8e282105350b9a5341c0222a13419758545ae32ad6e0fcf5f64d76aa/orjson-3.10.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9dca85398d6d093dd41dc0983cbf54ab8e6afd1c547b6b8a311643917fbf4e0c", size = 133131, upload-time = "2025-04-29T23:29:05.753Z" },
{ url = "https://files.pythonhosted.org/packages/48/b2/73a1f0b4790dcb1e5a45f058f4f5dcadc8a85d90137b50d6bbc6afd0ae50/orjson-3.10.18-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:22748de2a07fcc8781a70edb887abf801bb6142e6236123ff93d12d92db3d406", size = 134834, upload-time = "2025-04-29T23:29:07.35Z" },
{ url = "https://files.pythonhosted.org/packages/56/f5/7ed133a5525add9c14dbdf17d011dd82206ca6840811d32ac52a35935d19/orjson-3.10.18-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3a83c9954a4107b9acd10291b7f12a6b29e35e8d43a414799906ea10e75438e6", size = 413368, upload-time = "2025-04-29T23:29:09.301Z" },
{ url = "https://files.pythonhosted.org/packages/11/7c/439654221ed9c3324bbac7bdf94cf06a971206b7b62327f11a52544e4982/orjson-3.10.18-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:303565c67a6c7b1f194c94632a4a39918e067bd6176a48bec697393865ce4f06", size = 153359, upload-time = "2025-04-29T23:29:10.813Z" },
{ url = "https://files.pythonhosted.org/packages/48/e7/d58074fa0cc9dd29a8fa2a6c8d5deebdfd82c6cfef72b0e4277c4017563a/orjson-3.10.18-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:86314fdb5053a2f5a5d881f03fca0219bfdf832912aa88d18676a5175c6916b5", size = 137466, upload-time = "2025-04-29T23:29:12.26Z" },
{ url = "https://files.pythonhosted.org/packages/57/4d/fe17581cf81fb70dfcef44e966aa4003360e4194d15a3f38cbffe873333a/orjson-3.10.18-cp312-cp312-win32.whl", hash = "sha256:187ec33bbec58c76dbd4066340067d9ece6e10067bb0cc074a21ae3300caa84e", size = 142683, upload-time = "2025-04-29T23:29:13.865Z" },
{ url = "https://files.pythonhosted.org/packages/e6/22/469f62d25ab5f0f3aee256ea732e72dc3aab6d73bac777bd6277955bceef/orjson-3.10.18-cp312-cp312-win_amd64.whl", hash = "sha256:f9f94cf6d3f9cd720d641f8399e390e7411487e493962213390d1ae45c7814fc", size = 134754, upload-time = "2025-04-29T23:29:15.338Z" },
{ url = "https://files.pythonhosted.org/packages/10/b0/1040c447fac5b91bc1e9c004b69ee50abb0c1ffd0d24406e1350c58a7fcb/orjson-3.10.18-cp312-cp312-win_arm64.whl", hash = "sha256:3d600be83fe4514944500fa8c2a0a77099025ec6482e8087d7659e891f23058a", size = 131218, upload-time = "2025-04-29T23:29:17.324Z" },
{ url = "https://files.pythonhosted.org/packages/04/f0/8aedb6574b68096f3be8f74c0b56d36fd94bcf47e6c7ed47a7bd1474aaa8/orjson-3.10.18-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:69c34b9441b863175cc6a01f2935de994025e773f814412030f269da4f7be147", size = 249087, upload-time = "2025-04-29T23:29:19.083Z" },
{ url = "https://files.pythonhosted.org/packages/bc/f7/7118f965541aeac6844fcb18d6988e111ac0d349c9b80cda53583e758908/orjson-3.10.18-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:1ebeda919725f9dbdb269f59bc94f861afbe2a27dce5608cdba2d92772364d1c", size = 133273, upload-time = "2025-04-29T23:29:20.602Z" },
{ url = "https://files.pythonhosted.org/packages/fb/d9/839637cc06eaf528dd8127b36004247bf56e064501f68df9ee6fd56a88ee/orjson-3.10.18-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5adf5f4eed520a4959d29ea80192fa626ab9a20b2ea13f8f6dc58644f6927103", size = 136779, upload-time = "2025-04-29T23:29:22.062Z" },
{ url = "https://files.pythonhosted.org/packages/2b/6d/f226ecfef31a1f0e7d6bf9a31a0bbaf384c7cbe3fce49cc9c2acc51f902a/orjson-3.10.18-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7592bb48a214e18cd670974f289520f12b7aed1fa0b2e2616b8ed9e069e08595", size = 132811, upload-time = "2025-04-29T23:29:23.602Z" },
{ url = "https://files.pythonhosted.org/packages/73/2d/371513d04143c85b681cf8f3bce743656eb5b640cb1f461dad750ac4b4d4/orjson-3.10.18-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f872bef9f042734110642b7a11937440797ace8c87527de25e0c53558b579ccc", size = 137018, upload-time = "2025-04-29T23:29:25.094Z" },
{ url = "https://files.pythonhosted.org/packages/69/cb/a4d37a30507b7a59bdc484e4a3253c8141bf756d4e13fcc1da760a0b00cb/orjson-3.10.18-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0315317601149c244cb3ecef246ef5861a64824ccbcb8018d32c66a60a84ffbc", size = 138368, upload-time = "2025-04-29T23:29:26.609Z" },
{ url = "https://files.pythonhosted.org/packages/1e/ae/cd10883c48d912d216d541eb3db8b2433415fde67f620afe6f311f5cd2ca/orjson-3.10.18-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0da26957e77e9e55a6c2ce2e7182a36a6f6b180ab7189315cb0995ec362e049", size = 142840, upload-time = "2025-04-29T23:29:28.153Z" },
{ url = "https://files.pythonhosted.org/packages/6d/4c/2bda09855c6b5f2c055034c9eda1529967b042ff8d81a05005115c4e6772/orjson-3.10.18-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb70d489bc79b7519e5803e2cc4c72343c9dc1154258adf2f8925d0b60da7c58", size = 133135, upload-time = "2025-04-29T23:29:29.726Z" },
{ url = "https://files.pythonhosted.org/packages/13/4a/35971fd809a8896731930a80dfff0b8ff48eeb5d8b57bb4d0d525160017f/orjson-3.10.18-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e9e86a6af31b92299b00736c89caf63816f70a4001e750bda179e15564d7a034", size = 134810, upload-time = "2025-04-29T23:29:31.269Z" },
{ url = "https://files.pythonhosted.org/packages/99/70/0fa9e6310cda98365629182486ff37a1c6578e34c33992df271a476ea1cd/orjson-3.10.18-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:c382a5c0b5931a5fc5405053d36c1ce3fd561694738626c77ae0b1dfc0242ca1", size = 413491, upload-time = "2025-04-29T23:29:33.315Z" },
{ url = "https://files.pythonhosted.org/packages/32/cb/990a0e88498babddb74fb97855ae4fbd22a82960e9b06eab5775cac435da/orjson-3.10.18-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8e4b2ae732431127171b875cb2668f883e1234711d3c147ffd69fe5be51a8012", size = 153277, upload-time = "2025-04-29T23:29:34.946Z" },
{ url = "https://files.pythonhosted.org/packages/92/44/473248c3305bf782a384ed50dd8bc2d3cde1543d107138fd99b707480ca1/orjson-3.10.18-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d808e34ddb24fc29a4d4041dcfafbae13e129c93509b847b14432717d94b44f", size = 137367, upload-time = "2025-04-29T23:29:36.52Z" },
{ url = "https://files.pythonhosted.org/packages/ad/fd/7f1d3edd4ffcd944a6a40e9f88af2197b619c931ac4d3cfba4798d4d3815/orjson-3.10.18-cp313-cp313-win32.whl", hash = "sha256:ad8eacbb5d904d5591f27dee4031e2c1db43d559edb8f91778efd642d70e6bea", size = 142687, upload-time = "2025-04-29T23:29:38.292Z" },
{ url = "https://files.pythonhosted.org/packages/4b/03/c75c6ad46be41c16f4cfe0352a2d1450546f3c09ad2c9d341110cd87b025/orjson-3.10.18-cp313-cp313-win_amd64.whl", hash = "sha256:aed411bcb68bf62e85588f2a7e03a6082cc42e5a2796e06e72a962d7c6310b52", size = 134794, upload-time = "2025-04-29T23:29:40.349Z" },
{ url = "https://files.pythonhosted.org/packages/c2/28/f53038a5a72cc4fd0b56c1eafb4ef64aec9685460d5ac34de98ca78b6e29/orjson-3.10.18-cp313-cp313-win_arm64.whl", hash = "sha256:f54c1385a0e6aba2f15a40d703b858bedad36ded0491e55d35d905b2c34a4cc3", size = 131186, upload-time = "2025-04-29T23:29:41.922Z" },
{ url = "https://files.pythonhosted.org/packages/df/db/69488acaa2316788b7e171f024912c6fe8193aa2e24e9cfc7bc41c3669ba/orjson-3.10.18-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c95fae14225edfd699454e84f61c3dd938df6629a00c6ce15e704f57b58433bb", size = 249301, upload-time = "2025-04-29T23:29:44.719Z" },
{ url = "https://files.pythonhosted.org/packages/23/21/d816c44ec5d1482c654e1d23517d935bb2716e1453ff9380e861dc6efdd3/orjson-3.10.18-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5232d85f177f98e0cefabb48b5e7f60cff6f3f0365f9c60631fecd73849b2a82", size = 136786, upload-time = "2025-04-29T23:29:46.517Z" },
{ url = "https://files.pythonhosted.org/packages/a5/9f/f68d8a9985b717e39ba7bf95b57ba173fcd86aeca843229ec60d38f1faa7/orjson-3.10.18-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2783e121cafedf0d85c148c248a20470018b4ffd34494a68e125e7d5857655d1", size = 132711, upload-time = "2025-04-29T23:29:48.605Z" },
{ url = "https://files.pythonhosted.org/packages/b5/63/447f5955439bf7b99bdd67c38a3f689d140d998ac58e3b7d57340520343c/orjson-3.10.18-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e54ee3722caf3db09c91f442441e78f916046aa58d16b93af8a91500b7bbf273", size = 136841, upload-time = "2025-04-29T23:29:50.31Z" },
{ url = "https://files.pythonhosted.org/packages/68/9e/4855972f2be74097242e4681ab6766d36638a079e09d66f3d6a5d1188ce7/orjson-3.10.18-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2daf7e5379b61380808c24f6fc182b7719301739e4271c3ec88f2984a2d61f89", size = 138082, upload-time = "2025-04-29T23:29:51.992Z" },
{ url = "https://files.pythonhosted.org/packages/08/0f/e68431e53a39698d2355faf1f018c60a3019b4b54b4ea6be9dc6b8208a3d/orjson-3.10.18-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f39b371af3add20b25338f4b29a8d6e79a8c7ed0e9dd49e008228a065d07781", size = 142618, upload-time = "2025-04-29T23:29:53.642Z" },
{ url = "https://files.pythonhosted.org/packages/32/da/bdcfff239ddba1b6ef465efe49d7e43cc8c30041522feba9fd4241d47c32/orjson-3.10.18-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b819ed34c01d88c6bec290e6842966f8e9ff84b7694632e88341363440d4cc0", size = 132627, upload-time = "2025-04-29T23:29:55.318Z" },
{ url = "https://files.pythonhosted.org/packages/0c/28/bc634da09bbe972328f615b0961f1e7d91acb3cc68bddbca9e8dd64e8e24/orjson-3.10.18-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2f6c57debaef0b1aa13092822cbd3698a1fb0209a9ea013a969f4efa36bdea57", size = 134832, upload-time = "2025-04-29T23:29:56.985Z" },
{ url = "https://files.pythonhosted.org/packages/1d/d2/e8ac0c2d0ec782ed8925b4eb33f040cee1f1fbd1d8b268aeb84b94153e49/orjson-3.10.18-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:755b6d61ffdb1ffa1e768330190132e21343757c9aa2308c67257cc81a1a6f5a", size = 413161, upload-time = "2025-04-29T23:29:59.148Z" },
{ url = "https://files.pythonhosted.org/packages/28/f0/397e98c352a27594566e865999dc6b88d6f37d5bbb87b23c982af24114c4/orjson-3.10.18-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ce8d0a875a85b4c8579eab5ac535fb4b2a50937267482be402627ca7e7570ee3", size = 153012, upload-time = "2025-04-29T23:30:01.066Z" },
{ url = "https://files.pythonhosted.org/packages/93/bf/2c7334caeb48bdaa4cae0bde17ea417297ee136598653b1da7ae1f98c785/orjson-3.10.18-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57b5d0673cbd26781bebc2bf86f99dd19bd5a9cb55f71cc4f66419f6b50f3d77", size = 136999, upload-time = "2025-04-29T23:30:02.93Z" },
{ url = "https://files.pythonhosted.org/packages/35/72/4827b1c0c31621c2aa1e661a899cdd2cfac0565c6cd7131890daa4ef7535/orjson-3.10.18-cp39-cp39-win32.whl", hash = "sha256:951775d8b49d1d16ca8818b1f20c4965cae9157e7b562a2ae34d3967b8f21c8e", size = 142560, upload-time = "2025-04-29T23:30:04.805Z" },
{ url = "https://files.pythonhosted.org/packages/72/91/ef8e76868e7eed478887c82f60607a8abf58dadd24e95817229a4b2e2639/orjson-3.10.18-cp39-cp39-win_amd64.whl", hash = "sha256:fdd9d68f83f0bc4406610b1ac68bdcded8c5ee58605cc69e643a06f4d075f429", size = 134455, upload-time = "2025-04-29T23:30:06.588Z" },
]
[[package]]
name = "packaging"
version = "25.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 }
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 },
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
]
[[package]]
name = "pluggy"
version = "1.6.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 }
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 },
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
]
[[package]]
@@ -316,9 +316,9 @@ dependencies = [
{ name = "pluggy" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 }
sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 },
{ url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" },
]
[[package]]
@@ -329,9 +329,9 @@ dependencies = [
{ name = "pytest" },
{ name = "typing-extensions", marker = "python_full_version < '3.10'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/8e/c4/453c52c659521066969523e87d85d54139bbd17b78f09532fb8eb8cdb58e/pytest_asyncio-0.26.0.tar.gz", hash = "sha256:c4df2a697648241ff39e7f0e4a73050b03f123f760673956cf0d72a4990e312f", size = 54156 }
sdist = { url = "https://files.pythonhosted.org/packages/8e/c4/453c52c659521066969523e87d85d54139bbd17b78f09532fb8eb8cdb58e/pytest_asyncio-0.26.0.tar.gz", hash = "sha256:c4df2a697648241ff39e7f0e4a73050b03f123f760673956cf0d72a4990e312f", size = 54156, upload-time = "2025-03-25T06:22:28.883Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/20/7f/338843f449ace853647ace35870874f69a764d251872ed1b4de9f234822c/pytest_asyncio-0.26.0-py3-none-any.whl", hash = "sha256:7b51ed894f4fbea1340262bdae5135797ebbe21d8638978e35d31c6d19f72fb0", size = 19694 },
{ url = "https://files.pythonhosted.org/packages/20/7f/338843f449ace853647ace35870874f69a764d251872ed1b4de9f234822c/pytest_asyncio-0.26.0-py3-none-any.whl", hash = "sha256:7b51ed894f4fbea1340262bdae5135797ebbe21d8638978e35d31c6d19f72fb0", size = 19694, upload-time = "2025-03-25T06:22:27.807Z" },
]
[[package]]
@@ -341,9 +341,9 @@ source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pytest" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c6/90/a955c3ab35ccd41ad4de556596fa86685bf4fc5ffcc62d22d856cfd4e29a/pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0", size = 32814 }
sdist = { url = "https://files.pythonhosted.org/packages/c6/90/a955c3ab35ccd41ad4de556596fa86685bf4fc5ffcc62d22d856cfd4e29a/pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0", size = 32814, upload-time = "2024-03-21T22:14:04.964Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f2/3b/b26f90f74e2986a82df6e7ac7e319b8ea7ccece1caec9f8ab6104dc70603/pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f", size = 9863 },
{ url = "https://files.pythonhosted.org/packages/f2/3b/b26f90f74e2986a82df6e7ac7e319b8ea7ccece1caec9f8ab6104dc70603/pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f", size = 9863, upload-time = "2024-03-21T22:14:02.694Z" },
]
[[package]]
@@ -356,123 +356,123 @@ dependencies = [
{ name = "pytest" },
{ name = "watchdog" },
]
sdist = { url = "https://files.pythonhosted.org/packages/36/47/ab65fc1d682befc318c439940f81a0de1026048479f732e84fe714cd69c0/pytest-watch-4.2.0.tar.gz", hash = "sha256:06136f03d5b361718b8d0d234042f7b2f203910d8568f63df2f866b547b3d4b9", size = 16340 }
sdist = { url = "https://files.pythonhosted.org/packages/36/47/ab65fc1d682befc318c439940f81a0de1026048479f732e84fe714cd69c0/pytest-watch-4.2.0.tar.gz", hash = "sha256:06136f03d5b361718b8d0d234042f7b2f203910d8568f63df2f866b547b3d4b9", size = 16340, upload-time = "2018-05-20T19:52:16.194Z" }
[[package]]
name = "ruff"
version = "0.11.10"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e8/4c/4a3c5a97faaae6b428b336dcca81d03ad04779f8072c267ad2bd860126bf/ruff-0.11.10.tar.gz", hash = "sha256:d522fb204b4959909ecac47da02830daec102eeb100fb50ea9554818d47a5fa6", size = 4165632 }
sdist = { url = "https://files.pythonhosted.org/packages/e8/4c/4a3c5a97faaae6b428b336dcca81d03ad04779f8072c267ad2bd860126bf/ruff-0.11.10.tar.gz", hash = "sha256:d522fb204b4959909ecac47da02830daec102eeb100fb50ea9554818d47a5fa6", size = 4165632, upload-time = "2025-05-15T14:08:56.76Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2f/9f/596c628f8824a2ce4cd12b0f0b4c0629a62dfffc5d0f742c19a1d71be108/ruff-0.11.10-py3-none-linux_armv6l.whl", hash = "sha256:859a7bfa7bc8888abbea31ef8a2b411714e6a80f0d173c2a82f9041ed6b50f58", size = 10316243 },
{ url = "https://files.pythonhosted.org/packages/3c/38/c1e0b77ab58b426f8c332c1d1d3432d9fc9a9ea622806e208220cb133c9e/ruff-0.11.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:968220a57e09ea5e4fd48ed1c646419961a0570727c7e069842edd018ee8afed", size = 11083636 },
{ url = "https://files.pythonhosted.org/packages/23/41/b75e15961d6047d7fe1b13886e56e8413be8467a4e1be0a07f3b303cd65a/ruff-0.11.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1067245bad978e7aa7b22f67113ecc6eb241dca0d9b696144256c3a879663bca", size = 10441624 },
{ url = "https://files.pythonhosted.org/packages/b6/2c/e396b6703f131406db1811ea3d746f29d91b41bbd43ad572fea30da1435d/ruff-0.11.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4854fd09c7aed5b1590e996a81aeff0c9ff51378b084eb5a0b9cd9518e6cff2", size = 10624358 },
{ url = "https://files.pythonhosted.org/packages/bd/8c/ee6cca8bdaf0f9a3704796022851a33cd37d1340bceaf4f6e991eb164e2e/ruff-0.11.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b4564e9f99168c0f9195a0fd5fa5928004b33b377137f978055e40008a082c5", size = 10176850 },
{ url = "https://files.pythonhosted.org/packages/e9/ce/4e27e131a434321b3b7c66512c3ee7505b446eb1c8a80777c023f7e876e6/ruff-0.11.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b6a9cc5b62c03cc1fea0044ed8576379dbaf751d5503d718c973d5418483641", size = 11759787 },
{ url = "https://files.pythonhosted.org/packages/58/de/1e2e77fc72adc7cf5b5123fd04a59ed329651d3eab9825674a9e640b100b/ruff-0.11.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:607ecbb6f03e44c9e0a93aedacb17b4eb4f3563d00e8b474298a201622677947", size = 12430479 },
{ url = "https://files.pythonhosted.org/packages/07/ed/af0f2340f33b70d50121628ef175523cc4c37619e98d98748c85764c8d88/ruff-0.11.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b3a522fa389402cd2137df9ddefe848f727250535c70dafa840badffb56b7a4", size = 11919760 },
{ url = "https://files.pythonhosted.org/packages/24/09/d7b3d3226d535cb89234390f418d10e00a157b6c4a06dfbe723e9322cb7d/ruff-0.11.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f071b0deed7e9245d5820dac235cbdd4ef99d7b12ff04c330a241ad3534319f", size = 14041747 },
{ url = "https://files.pythonhosted.org/packages/62/b3/a63b4e91850e3f47f78795e6630ee9266cb6963de8f0191600289c2bb8f4/ruff-0.11.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a60e3a0a617eafba1f2e4186d827759d65348fa53708ca547e384db28406a0b", size = 11550657 },
{ url = "https://files.pythonhosted.org/packages/46/63/a4f95c241d79402ccdbdb1d823d156c89fbb36ebfc4289dce092e6c0aa8f/ruff-0.11.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:da8ec977eaa4b7bf75470fb575bea2cb41a0e07c7ea9d5a0a97d13dbca697bf2", size = 10489671 },
{ url = "https://files.pythonhosted.org/packages/6a/9b/c2238bfebf1e473495659c523d50b1685258b6345d5ab0b418ca3f010cd7/ruff-0.11.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ddf8967e08227d1bd95cc0851ef80d2ad9c7c0c5aab1eba31db49cf0a7b99523", size = 10160135 },
{ url = "https://files.pythonhosted.org/packages/ba/ef/ba7251dd15206688dbfba7d413c0312e94df3b31b08f5d695580b755a899/ruff-0.11.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5a94acf798a82db188f6f36575d80609072b032105d114b0f98661e1679c9125", size = 11170179 },
{ url = "https://files.pythonhosted.org/packages/73/9f/5c336717293203ba275dbfa2ea16e49b29a9fd9a0ea8b6febfc17e133577/ruff-0.11.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3afead355f1d16d95630df28d4ba17fb2cb9c8dfac8d21ced14984121f639bad", size = 11626021 },
{ url = "https://files.pythonhosted.org/packages/d9/2b/162fa86d2639076667c9aa59196c020dc6d7023ac8f342416c2f5ec4bda0/ruff-0.11.10-py3-none-win32.whl", hash = "sha256:dc061a98d32a97211af7e7f3fa1d4ca2fcf919fb96c28f39551f35fc55bdbc19", size = 10494958 },
{ url = "https://files.pythonhosted.org/packages/24/f3/66643d8f32f50a4b0d09a4832b7d919145ee2b944d43e604fbd7c144d175/ruff-0.11.10-py3-none-win_amd64.whl", hash = "sha256:5cc725fbb4d25b0f185cb42df07ab6b76c4489b4bfb740a175f3a59c70e8a224", size = 11650285 },
{ url = "https://files.pythonhosted.org/packages/95/3a/2e8704d19f376c799748ff9cb041225c1d59f3e7711bc5596c8cfdc24925/ruff-0.11.10-py3-none-win_arm64.whl", hash = "sha256:ef69637b35fb8b210743926778d0e45e1bffa850a7c61e428c6b971549b5f5d1", size = 10765278 },
{ url = "https://files.pythonhosted.org/packages/2f/9f/596c628f8824a2ce4cd12b0f0b4c0629a62dfffc5d0f742c19a1d71be108/ruff-0.11.10-py3-none-linux_armv6l.whl", hash = "sha256:859a7bfa7bc8888abbea31ef8a2b411714e6a80f0d173c2a82f9041ed6b50f58", size = 10316243, upload-time = "2025-05-15T14:08:12.884Z" },
{ url = "https://files.pythonhosted.org/packages/3c/38/c1e0b77ab58b426f8c332c1d1d3432d9fc9a9ea622806e208220cb133c9e/ruff-0.11.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:968220a57e09ea5e4fd48ed1c646419961a0570727c7e069842edd018ee8afed", size = 11083636, upload-time = "2025-05-15T14:08:16.551Z" },
{ url = "https://files.pythonhosted.org/packages/23/41/b75e15961d6047d7fe1b13886e56e8413be8467a4e1be0a07f3b303cd65a/ruff-0.11.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1067245bad978e7aa7b22f67113ecc6eb241dca0d9b696144256c3a879663bca", size = 10441624, upload-time = "2025-05-15T14:08:19.032Z" },
{ url = "https://files.pythonhosted.org/packages/b6/2c/e396b6703f131406db1811ea3d746f29d91b41bbd43ad572fea30da1435d/ruff-0.11.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4854fd09c7aed5b1590e996a81aeff0c9ff51378b084eb5a0b9cd9518e6cff2", size = 10624358, upload-time = "2025-05-15T14:08:21.542Z" },
{ url = "https://files.pythonhosted.org/packages/bd/8c/ee6cca8bdaf0f9a3704796022851a33cd37d1340bceaf4f6e991eb164e2e/ruff-0.11.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b4564e9f99168c0f9195a0fd5fa5928004b33b377137f978055e40008a082c5", size = 10176850, upload-time = "2025-05-15T14:08:23.682Z" },
{ url = "https://files.pythonhosted.org/packages/e9/ce/4e27e131a434321b3b7c66512c3ee7505b446eb1c8a80777c023f7e876e6/ruff-0.11.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b6a9cc5b62c03cc1fea0044ed8576379dbaf751d5503d718c973d5418483641", size = 11759787, upload-time = "2025-05-15T14:08:25.733Z" },
{ url = "https://files.pythonhosted.org/packages/58/de/1e2e77fc72adc7cf5b5123fd04a59ed329651d3eab9825674a9e640b100b/ruff-0.11.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:607ecbb6f03e44c9e0a93aedacb17b4eb4f3563d00e8b474298a201622677947", size = 12430479, upload-time = "2025-05-15T14:08:28.013Z" },
{ url = "https://files.pythonhosted.org/packages/07/ed/af0f2340f33b70d50121628ef175523cc4c37619e98d98748c85764c8d88/ruff-0.11.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b3a522fa389402cd2137df9ddefe848f727250535c70dafa840badffb56b7a4", size = 11919760, upload-time = "2025-05-15T14:08:30.956Z" },
{ url = "https://files.pythonhosted.org/packages/24/09/d7b3d3226d535cb89234390f418d10e00a157b6c4a06dfbe723e9322cb7d/ruff-0.11.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f071b0deed7e9245d5820dac235cbdd4ef99d7b12ff04c330a241ad3534319f", size = 14041747, upload-time = "2025-05-15T14:08:33.297Z" },
{ url = "https://files.pythonhosted.org/packages/62/b3/a63b4e91850e3f47f78795e6630ee9266cb6963de8f0191600289c2bb8f4/ruff-0.11.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a60e3a0a617eafba1f2e4186d827759d65348fa53708ca547e384db28406a0b", size = 11550657, upload-time = "2025-05-15T14:08:35.639Z" },
{ url = "https://files.pythonhosted.org/packages/46/63/a4f95c241d79402ccdbdb1d823d156c89fbb36ebfc4289dce092e6c0aa8f/ruff-0.11.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:da8ec977eaa4b7bf75470fb575bea2cb41a0e07c7ea9d5a0a97d13dbca697bf2", size = 10489671, upload-time = "2025-05-15T14:08:38.437Z" },
{ url = "https://files.pythonhosted.org/packages/6a/9b/c2238bfebf1e473495659c523d50b1685258b6345d5ab0b418ca3f010cd7/ruff-0.11.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ddf8967e08227d1bd95cc0851ef80d2ad9c7c0c5aab1eba31db49cf0a7b99523", size = 10160135, upload-time = "2025-05-15T14:08:41.247Z" },
{ url = "https://files.pythonhosted.org/packages/ba/ef/ba7251dd15206688dbfba7d413c0312e94df3b31b08f5d695580b755a899/ruff-0.11.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5a94acf798a82db188f6f36575d80609072b032105d114b0f98661e1679c9125", size = 11170179, upload-time = "2025-05-15T14:08:43.762Z" },
{ url = "https://files.pythonhosted.org/packages/73/9f/5c336717293203ba275dbfa2ea16e49b29a9fd9a0ea8b6febfc17e133577/ruff-0.11.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3afead355f1d16d95630df28d4ba17fb2cb9c8dfac8d21ced14984121f639bad", size = 11626021, upload-time = "2025-05-15T14:08:46.451Z" },
{ url = "https://files.pythonhosted.org/packages/d9/2b/162fa86d2639076667c9aa59196c020dc6d7023ac8f342416c2f5ec4bda0/ruff-0.11.10-py3-none-win32.whl", hash = "sha256:dc061a98d32a97211af7e7f3fa1d4ca2fcf919fb96c28f39551f35fc55bdbc19", size = 10494958, upload-time = "2025-05-15T14:08:49.601Z" },
{ url = "https://files.pythonhosted.org/packages/24/f3/66643d8f32f50a4b0d09a4832b7d919145ee2b944d43e604fbd7c144d175/ruff-0.11.10-py3-none-win_amd64.whl", hash = "sha256:5cc725fbb4d25b0f185cb42df07ab6b76c4489b4bfb740a175f3a59c70e8a224", size = 11650285, upload-time = "2025-05-15T14:08:52.392Z" },
{ url = "https://files.pythonhosted.org/packages/95/3a/2e8704d19f376c799748ff9cb041225c1d59f3e7711bc5596c8cfdc24925/ruff-0.11.10-py3-none-win_arm64.whl", hash = "sha256:ef69637b35fb8b210743926778d0e45e1bffa850a7c61e428c6b971549b5f5d1", size = 10765278, upload-time = "2025-05-15T14:08:54.56Z" },
]
[[package]]
name = "sniffio"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 }
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 },
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
]
[[package]]
name = "tomli"
version = "2.2.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 }
sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 },
{ url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 },
{ url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 },
{ url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 },
{ url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 },
{ url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 },
{ url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 },
{ url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 },
{ url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 },
{ url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 },
{ url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 },
{ url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 },
{ url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 },
{ url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 },
{ url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 },
{ url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 },
{ url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 },
{ url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 },
{ url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 },
{ url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 },
{ url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 },
{ url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 },
{ url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 },
{ url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 },
{ url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 },
{ url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 },
{ url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 },
{ url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 },
{ url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 },
{ url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 },
{ url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 },
{ url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" },
{ url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" },
{ url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" },
{ url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" },
{ url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" },
{ url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" },
{ url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" },
{ url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" },
{ url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" },
{ url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" },
{ url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" },
{ url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" },
{ url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" },
{ url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" },
{ url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" },
{ url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" },
{ url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" },
{ url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" },
{ url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" },
{ url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" },
{ url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" },
{ url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" },
{ url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" },
{ url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" },
{ url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" },
{ url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" },
{ url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" },
{ url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" },
{ url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" },
{ url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" },
{ url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" },
]
[[package]]
name = "typing-extensions"
version = "4.13.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 }
sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 },
{ url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" },
]
[[package]]
name = "watchdog"
version = "6.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220 }
sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0c/56/90994d789c61df619bfc5ce2ecdabd5eeff564e1eb47512bd01b5e019569/watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26", size = 96390 },
{ url = "https://files.pythonhosted.org/packages/55/46/9a67ee697342ddf3c6daa97e3a587a56d6c4052f881ed926a849fcf7371c/watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112", size = 88389 },
{ url = "https://files.pythonhosted.org/packages/44/65/91b0985747c52064d8701e1075eb96f8c40a79df889e59a399453adfb882/watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3", size = 89020 },
{ url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393 },
{ url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392 },
{ url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019 },
{ url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471 },
{ url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449 },
{ url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054 },
{ url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480 },
{ url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451 },
{ url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057 },
{ url = "https://files.pythonhosted.org/packages/05/52/7223011bb760fce8ddc53416beb65b83a3ea6d7d13738dde75eeb2c89679/watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8", size = 96390 },
{ url = "https://files.pythonhosted.org/packages/9c/62/d2b21bc4e706d3a9d467561f487c2938cbd881c69f3808c43ac1ec242391/watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a", size = 88386 },
{ url = "https://files.pythonhosted.org/packages/ea/22/1c90b20eda9f4132e4603a26296108728a8bfe9584b006bd05dd94548853/watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c", size = 89017 },
{ url = "https://files.pythonhosted.org/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881", size = 87902 },
{ url = "https://files.pythonhosted.org/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11", size = 88380 },
{ url = "https://files.pythonhosted.org/packages/5b/79/69f2b0e8d3f2afd462029031baafb1b75d11bb62703f0e1022b2e54d49ee/watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa", size = 87903 },
{ url = "https://files.pythonhosted.org/packages/e2/2b/dc048dd71c2e5f0f7ebc04dd7912981ec45793a03c0dc462438e0591ba5d/watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e", size = 88381 },
{ url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079 },
{ url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078 },
{ url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076 },
{ url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077 },
{ url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078 },
{ url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077 },
{ url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078 },
{ url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065 },
{ url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070 },
{ url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067 },
{ url = "https://files.pythonhosted.org/packages/0c/56/90994d789c61df619bfc5ce2ecdabd5eeff564e1eb47512bd01b5e019569/watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26", size = 96390, upload-time = "2024-11-01T14:06:24.793Z" },
{ url = "https://files.pythonhosted.org/packages/55/46/9a67ee697342ddf3c6daa97e3a587a56d6c4052f881ed926a849fcf7371c/watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112", size = 88389, upload-time = "2024-11-01T14:06:27.112Z" },
{ url = "https://files.pythonhosted.org/packages/44/65/91b0985747c52064d8701e1075eb96f8c40a79df889e59a399453adfb882/watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3", size = 89020, upload-time = "2024-11-01T14:06:29.876Z" },
{ url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393, upload-time = "2024-11-01T14:06:31.756Z" },
{ url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392, upload-time = "2024-11-01T14:06:32.99Z" },
{ url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019, upload-time = "2024-11-01T14:06:34.963Z" },
{ url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" },
{ url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" },
{ url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" },
{ url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" },
{ url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" },
{ url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" },
{ url = "https://files.pythonhosted.org/packages/05/52/7223011bb760fce8ddc53416beb65b83a3ea6d7d13738dde75eeb2c89679/watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8", size = 96390, upload-time = "2024-11-01T14:06:49.325Z" },
{ url = "https://files.pythonhosted.org/packages/9c/62/d2b21bc4e706d3a9d467561f487c2938cbd881c69f3808c43ac1ec242391/watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a", size = 88386, upload-time = "2024-11-01T14:06:50.536Z" },
{ url = "https://files.pythonhosted.org/packages/ea/22/1c90b20eda9f4132e4603a26296108728a8bfe9584b006bd05dd94548853/watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c", size = 89017, upload-time = "2024-11-01T14:06:51.717Z" },
{ url = "https://files.pythonhosted.org/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881", size = 87902, upload-time = "2024-11-01T14:06:53.119Z" },
{ url = "https://files.pythonhosted.org/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11", size = 88380, upload-time = "2024-11-01T14:06:55.19Z" },
{ url = "https://files.pythonhosted.org/packages/5b/79/69f2b0e8d3f2afd462029031baafb1b75d11bb62703f0e1022b2e54d49ee/watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa", size = 87903, upload-time = "2024-11-01T14:06:57.052Z" },
{ url = "https://files.pythonhosted.org/packages/e2/2b/dc048dd71c2e5f0f7ebc04dd7912981ec45793a03c0dc462438e0591ba5d/watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e", size = 88381, upload-time = "2024-11-01T14:06:58.193Z" },
{ url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" },
{ url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" },
{ url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" },
{ url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" },
{ url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" },
{ url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" },
{ url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" },
{ url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" },
{ url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" },
{ url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" },
]