Compare commits

...
Author SHA1 Message Date
Sydney Runkle c8a9aec9d8 fix signature 2025-06-24 09:08:08 -04:00
Sydney Runkle e05cef7732 support invokable signature 2025-06-24 08:31:58 -04:00
Vedant PanchalandGitHub ac328c3fd8 [fix] snippet for Agents as a Tool (#5181)
snippet had a missing decorator that might lead the user to confuse whether it is really a tool
2025-06-24 11:44:32 +00:00
Nuno CamposandGitHub ebd7977936 Revert change to default value of checkpoint_during arg (#5177) 2025-06-23 18:12:19 -07:00
Nuno Campos a6381c32b0 Revert change to default value of checkpoint_during arg 2025-06-23 18:05:46 -07:00
Nuno CamposandGitHub 294d346650 Fix bug where Command(update=) could be ignored if there was a 2nd interrupt after it (#5175) 2025-06-23 17:35:36 -07:00
Nuno Campos 866c8009dc Fix bug where Command(update=) could be ignored if there was a 2nd interrupt after it
- writes from the null task (ie. from outside tasks) should be accummulated across invocations
2025-06-23 17:23:53 -07:00
Rauf ParchievandGitHub 73bed2cf7c Update workflows.md (#5116) 2025-06-23 23:54:45 +00:00
Eugene YurtsevandGitHub 4cffe58065 docs: fix tab syntax errors and admonition syntax errors (#5091) 2025-06-23 19:53:54 -04:00
nikhildigdeandGitHub e73964a971 Update custom_routes.md (#5110)
Updated the statement about app.py
2025-06-23 23:53:25 +00:00
William Fu-Hinthorn de91f21f6b Update cli config doc on pip_installer 2025-06-23 16:40:07 -07:00
David DuongandGitHub 41eed326b8 Disable values update on use stream in interrupt events (#5041) 2025-06-24 00:08:14 +02:00
Mason DaughertyandGitHub 946d23213d docs: add Homebrew install option to CLI docs (#5160)
Add Homebrew install option to CLI docs
2025-06-23 11:33:12 -04:00
hari-dhanushkodiandGitHub c78197a583 add more docs for lgp deployment metrics (#5151) 2025-06-23 07:12:06 -07:00
Lauren Hirata SinghandGitHub 50756207ee docs: fix a typo 'prebuit' to 'prebuilt' in notebook_hooks.py (#5154) 2025-06-23 09:44:45 -04:00
OfirTeneJunoandGitHub 903cec0cfa Merge branch 'main' into interrupt-use-stream-values 2025-06-23 13:25:35 +03:00
foie0222 f63952595d fix: a typo prebuit to prebuilt 2025-06-21 17:49:40 +09:00
Andrew NguonlyandGitHub 4a252bd03a docs: Remove note for LANGSMITH_TRACING environment variable (#5147)
Remove note for LANGSMITH_TRACING.
2025-06-20 13:36:29 -07:00
Eugene YurtsevandGitHub f0f329d9e1 docs: Add conditional js/python rendering (#5128)
# Overview

Adding conditional rendering logic to co-locate js and python documentation.

* `:::` conditional syntax can be used to switch between python only or js only content.
* Contains simple unit tests for `:::`
* PR adds set up for a way to implement a context switch between languages, but it will not be enabled until JS content is merged in.
* Contains a script that can add javascript documentation 

Implementation of: https://github.com/langchain-ai/langgraph/pull/5118

## Example

Example of conditional rendering / compilation.


```markdown

### Config (static context)

Config is for immutable data like user metadata or API keys. Use
when you have values that don't change mid-run.

Specify configuration using a key called **"configurable"** which is reserved
for this purpose:


:::python

This content will only be rendered for the python site.
:::

:::js
this content will only be rendered for the js / ts site.
:::

```
2025-06-20 16:20:16 -04:00
OfirTeneJunoandGitHub 77306c5142 Merge branch 'main' into interrupt-use-stream-values 2025-06-19 14:06:24 +03:00
Nuno CamposandGitHub eba18c3213 Reduce extraneous keys in checkpoint.metadata (#5133) 2025-06-17 17:46:45 -07:00
Nuno Campos a1c856c088 Reduce extraneous keys in checkpoint.metadata
- Leave it up to each checkpointer implementation to decide whether to merge in configurable/metadata (previously PregelLoop would do some of this always)
- Never copy over internal langgraph keys into checkpoint.metadata (these are redundant/misleading to include)
2025-06-17 17:40:18 -07:00
Josh RogersandGitHub 92010f84ec Adding disable_mcp field to cli docs (#5132) 2025-06-17 16:22:50 -07:00
Nuno CamposandGitHub 3b8f3f9de3 If FuturesDict callback has been GCed, don't call it (#5131) 2025-06-17 14:03:16 -07:00
Nuno Campos 6dcff8a839 If FuturesDict callback has been GCed, don't call it 2025-06-17 13:47:47 -07:00
OfirTeneJunoandGitHub 666279a241 Merge branch 'main' into interrupt-use-stream-values 2025-06-11 09:53:25 +03:00
OfirTeneJuno 2172bc89ed remove yarn 2025-06-10 17:37:27 +03:00
OfirTeneJuno 4138ef9c43 Change to continue 2025-06-10 17:33:52 +03:00
OfirTeneJuno 0ff181b7ce Disable values update on use stream in interrupt events 2025-06-10 17:21:17 +03:00
44 changed files with 698 additions and 710 deletions
+119 -122
View File
@@ -1,157 +1,154 @@
"""Add typescript translation to a given markdown file."""
"""Translate Python markdown to TypeScript and/or consolidate Python-JS markdown into a single document."""
import argparse
import re
import requests
from langchain_anthropic import ChatAnthropic
# Load reference TypeScript snippets
URL = "https://gist.githubusercontent.com/eyurtsev/e7486731415463a9bc5b4682358859c8/raw/b5a5fda9c7e3387cfcb781f25082814d43675d50/gistfile1.txt"
response = requests.get(URL)
response.raise_for_status()
reference_snippets = response.text
model = ChatAnthropic(model="claude-3-5-sonnet-latest")
# Initialize model
model = ChatAnthropic(model="claude-sonnet-4-0", max_tokens=64_000)
TRANSLATION_PROMPT = (
"You are a helpful assistant that translates Python-based technical "
"documentation written in Markdown to equivalent TypeScript-based documentation. "
"The input is a Markdown file written in mkdocs format. It contains "
"Python code snippets embedded in prose. "
"Your task is to rewrite the content by translating the Python code to "
"idiomatic TypeScript, using the provided TypeScript reference snippets "
"to ensure accurate and consistent usage (e.g., correct imports, function "
"names, and patterns). "
"Remove the original Python code and replace it with the corresponding "
"TypeScript version. "
"Do not alter the surrounding prose unless a change is necessary to "
"reflect differences between Python and TypeScript. "
"Preserve the structure and formatting of the original Markdown document. "
"Do not make stylistic or structural changes unless they directly support "
"the translation. "
"Use the reference TypeScript snippets as guidance whenever possible to "
"maintain alignment with existing conventions.\n\n"
f"Here are the reference TypeScript snippets:\n\n{reference_snippets}\n\n"
)
CONSOLIDATION_PROMPT = (
"You are a helpful assistant that consolidates parallel Python and JavaScript (TypeScript) technical documentation "
"written in Markdown into a single unified Markdown document. "
"The input consists of two documents: the first is for Python users, and the second is for JavaScript/TypeScript users. "
"Your task is to merge these into one Markdown file using language-specific fenced blocks to separate the content where needed. "
"Use the following syntax to distinguish content for each language:\n\n"
":::python\n"
"# Python-specific content\n"
":::\n\n"
":::js\n"
"# JavaScript/TypeScript-specific content\n"
":::\n\n"
"Follow these consolidation rules:\n"
"- When content (prose or code) is the same or nearly identical in both versions, include it only once—outside of any fenced block.\n"
"- When content differs between the Python and JS versions, wrap each version in its corresponding fenced block.\n"
"- Prefer **paragraph-level separation** of language-specific content. Do not combine Python and JS snippets or terminology in the same sentence or paragraph using conditional phrases.\n"
" For example, avoid inline constructs like:\n"
" `The :::python add_messages ::: :::js reducer ::: function...`\n"
" Instead, write two distinct paragraphs:\n\n"
" :::python\n"
" The `add_messages` function in our `State` will append the LLM's response messages to whatever messages are already in the state.\n"
" ::: \n\n"
" :::js\n"
" The `reducer` function in our `StateAnnotation` will append the LLM's response messages to whatever messages are already in the state.\n"
" :::\n\n"
"- Preserve the overall structure, ordering, and formatting of the original Markdown documents.\n"
"- Do not rephrase or unify content unless it is logically and semantically identical.\n"
"- Use the fenced blocks for both prose and code as needed, and ensure output is clean, readable Markdown suitable for tools that parse these directives.\n"
"Your goal is to produce a cleanly merged documentation file that serves both Python and JavaScript users without redundancy, while maximizing clarity and separation of language-specific details."
)
def _get_tqdm():
try:
from tqdm import tqdm
except ImportError:
# If not available return a simple identity function
def tqdm(iterable, *args, **kwargs):
return iterable
return tqdm
_tqdm = _get_tqdm()
opening_pattern = re.compile(r"^\s*```python(?:\s+.*)?\s*$")
closing_pattern = re.compile(r"^\s*```\s*$")
def extract_python_snippets(markdown: str) -> list[str]:
"""
Extract all python code blocks (including their fence lines) from the markdown content.
A python block is defined as any block that starts with a line containing an opening fence
with '```python' (optionally with extra parameters) and ends with a closing fence '```'.
"""
snippets = []
inside_block = False
current_snippet = []
for line in markdown.splitlines(keepends=True):
if not inside_block:
if opening_pattern.match(line):
inside_block = True
current_snippet = [line]
else:
current_snippet.append(line)
if closing_pattern.match(line):
inside_block = False
snippets.append("".join(current_snippet))
current_snippet = []
return snippets
def translate_snippet(python_snippet: str) -> str:
"""Translate a python code block into a TypeScript code block using Langchain.
The response is expected to be a properly fenced TypeScript code block (i.e.
starting with ```typescript and ending with ```).
"""
ai_message = model.invoke(
def translate_python_to_ts(markdown_content: str) -> str:
response = model.invoke(
[
{
"role": "system",
"content": (
f"You have access to the following up-to-date example TypeScript code "
f"snippets that show examples of building with langgraph "
f"and langchain:\n\n{reference_snippets}\n\n"
"Use this context to translate the following Python code to equivalent "
"TypeScript. Ensure that your output is a valid fenced TypeScript "
"code block (i.e. starts with ```typescript and ends with ```)."
),
},
{
"role": "user",
"content": f"Translate this Python snippet to TypeScript:\n\n{python_snippet}",
"content": TRANSLATION_PROMPT,
"cache_control": {"type": "ephemeral"},
},
{"role": "user", "content": markdown_content},
]
)
# Use a regular expression to search for a TypeScript code block in the response.
pattern = r"```typescript\s*(.*?)\s*```"
match = re.search(pattern, ai_message.content, re.DOTALL)
if match:
# Reconstruct the code block with proper fences.
typescript_code = match.group(1).strip()
return f"```typescript\n{typescript_code}\n```"
else:
raise ValueError("No TypeScript code block found in the model's response.")
return response.content
def insert_translations_into_markdown(
markdown: str, typescript_snippets: list[str]
) -> str:
"""Walks through the original markdown content and, after each
Python snippet block, inserts the corresponding translated TypeScript snippet.
It assumes that the ordering of the Python snippets
(from extract_python_snippets) matches the order they appear in the markdown.
"""
output_lines = []
lines = markdown.splitlines(keepends=True)
inside_block = False
snippet_index = 0
for line in lines:
output_lines.append(line)
if not inside_block and opening_pattern.match(line):
# We've encountered the start of a python code block.
inside_block = True
elif inside_block:
if closing_pattern.match(line):
# End of a python snippet block.
inside_block = False
if snippet_index < len(typescript_snippets):
# Insert an extra newline for clarity, then the translated TypeScript snippet.
output_lines.append("\n")
output_lines.append(typescript_snippets[snippet_index])
output_lines.append("\n")
snippet_index += 1
return "".join(output_lines)
def consolidate_python_and_ts(combined_content: str) -> str:
response = model.invoke(
[
{
"role": "system",
"content": CONSOLIDATION_PROMPT,
"cache_control": {"type": "ephemeral"},
},
{"role": "user", "content": combined_content},
]
)
return response.content
def main(file_path: str) -> None:
# Read the markdown file.
with open(file_path, "r") as f:
def main(file_path: str, translate_only: bool, consolidate_only: bool) -> None:
with open(file_path, "r", encoding="utf-8") as f:
markdown_content = f.read()
# 1. Extract all Python snippets.
python_snippets = extract_python_snippets(markdown_content)[:1]
if translate_only:
translated = translate_python_to_ts(markdown_content)
output_path = file_path.replace(".md", ".translated.md")
with open(output_path, "w", encoding="utf-8") as f:
f.write(translated)
print(f"Translated JS/TS version written to: {output_path}")
# 2. Translate each Python snippet to TypeScript.
typescript_snippets = []
# Replace with .batch() for faster translation
for python_snippet in _tqdm(python_snippets):
ts_snippet = translate_snippet(python_snippet)
typescript_snippets.append(ts_snippet)
elif consolidate_only:
consolidated = consolidate_python_and_ts(markdown_content)
with open(file_path, "w", encoding="utf-8") as f:
f.write(consolidated)
print(f"Consolidated content written to: {file_path}")
# 3. Insert the TypeScript translations after their respective Python snippets.
updated_markdown = insert_translations_into_markdown(
markdown_content, typescript_snippets
)
# Overwrite the original markdown file with the updated content.
with open(file_path, "w") as f:
f.write(updated_markdown)
else:
# Default behavior: translate first, then consolidate both
translated = translate_python_to_ts(markdown_content)
combined = f"{markdown_content.strip()}\n\n\n{translated.strip()}"
consolidated = consolidate_python_and_ts(combined)
with open(file_path, "w", encoding="utf-8") as f:
f.write(consolidated)
print(f"Translated and consolidated content written to: {file_path}")
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Translate Python snippets in a markdown file to TypeScript and insert them after each Python snippet."
description=(
"Translate Python markdown to TypeScript and/or consolidate "
"Python-JS markdown into one file."
)
)
parser.add_argument("file_path", type=str, help="Path to the markdown file.")
parser.add_argument(
"--translate-only",
action="store_true",
help="Only generate the JS translation.",
)
parser.add_argument(
"--consolidate-only",
action="store_true",
help="Only consolidate pre-paired Python and JS content.",
)
args = parser.parse_args()
main(args.file_path)
if args.translate_only and args.consolidate_only:
raise ValueError(
"Cannot use both --translate-only and --consolidate-only at the same time."
)
main(
args.file_path,
translate_only=args.translate_only,
consolidate_only=args.consolidate_only,
)
+10 -6
View File
@@ -3,19 +3,21 @@
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
from typing import TypedDict, List, Optional
import yaml
from langchain.chat_models import init_chat_model
from langchain_core.rate_limiters import InMemoryRateLimiter
from mkdocs.structure.files import File
from mkdocs.structure.pages import Page
from pydantic import BaseModel, Field
from yaml import SafeLoader
from _scripts.notebook_hooks import _on_page_markdown_with_config
from _scripts.notebook_hooks import (
_on_page_markdown_with_config,
_apply_conditional_rendering,
)
HERE = os.path.dirname(os.path.abspath(__file__))
# Get source directory (parent of HERE / docs)
@@ -211,7 +213,9 @@ async def process_nav_items(nav_items: list[NavItem]) -> list[NavItem]:
# 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://"))
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]
+5
View File
@@ -0,0 +1,5 @@
JS_LINK_MAP = {
"langgraph.types.interrupt": "https://langchain-ai.github.io/langgraphjs/reference/functions/langgraph.interrupt-2.html",
"create_react_agent": "https://langchain-ai.github.io/langgraphjs/reference/functions/langgraph_prebuilt.createReactAgent.html",
"langgraph.types.Command": "https://langchain-ai.github.io/langgraphjs/reference/classes/langgraph.Command.html",
}
+72 -1
View File
@@ -16,6 +16,7 @@ from mkdocs.structure.pages import Page
from _scripts.generate_api_reference_links import update_markdown_with_imports
from _scripts.notebook_convert import convert_notebook
from _scripts.link_map import JS_LINK_MAP
logger = logging.getLogger(__name__)
logging.basicConfig()
@@ -86,7 +87,7 @@ REDIRECT_MAP = {
"cloud/how-tos/stream_events.md": "cloud/how-tos/streaming.md#stream-events",
"cloud/how-tos/stream_debug.md": "cloud/how-tos/streaming.md#debug",
"cloud/how-tos/stream_multiple.md": "cloud/how-tos/streaming.md#stream-multiple-modes",
# prebuit redirects
# prebuilt redirects
"how-tos/create-react-agent.ipynb": "agents/agents.md#basic-configuration",
"how-tos/create-react-agent-memory.ipynb": "agents/memory.md",
"how-tos/create-react-agent-system-prompt.ipynb": "agents/context.md#prompts",
@@ -158,6 +159,62 @@ def _add_path_to_code_blocks(markdown: str, page: Page) -> str:
return code_block_pattern.sub(replace_code_block_header, markdown)
def _resolve_cross_references(md_text: str, link_map: dict[str, str]) -> str:
"""Replace [title][identifier] with [title](url) using language-specific link_map.
Args:
md_text: The markdown text to process.
link_map: mapping of identifier to URL.
Returns:
The processed markdown text with cross-references resolved.
"""
# Pattern to match [title][identifier]
pattern = re.compile(r"\[([^\]]+)\]\[([^\]]+)\]")
def replace_reference(match: re.Match) -> str:
"""Replace the matched reference with the corresponding URL."""
title, identifier = match.group(1), match.group(2)
url = link_map.get(identifier)
if url:
return f"[{title}]({url})"
else:
# Leave it unchanged if not found
return match.group(0)
return pattern.sub(replace_reference, md_text)
def _apply_conditional_rendering(md_text: str, target_language: str) -> str:
if target_language not in {"python", "js"}:
raise ValueError("target_language must be 'python' or 'js'")
pattern = re.compile(
r"(?P<indent>[ \t]*):::(?P<language>\w+)\s*\n"
r"(?P<content>((?:.*\n)*?))" # Capture the content inside the block
r"(?P=indent):::" # Match closing with the same indentation
)
def replace_conditional_blocks(match: re.Match) -> str:
"""Keep active conditionals."""
language = match.group("language")
content = match.group("content")
if language not in {"python", "js"}:
# If the language is not supported, return the original block
return match.group(0)
if language == target_language:
return content
# If the language does not match, return an empty string
return ""
processed = pattern.sub(replace_conditional_blocks, md_text)
return processed
def _highlight_code_blocks(markdown: str) -> str:
"""Find code blocks with highlight comments and add hl_lines attribute.
@@ -257,6 +314,20 @@ def _on_page_markdown_with_config(
# Apply highlight comments to code blocks
markdown = _highlight_code_blocks(markdown)
# Apply conditional rendering for code blocks
target_language = kwargs.get("target_language", "python")
markdown = _apply_conditional_rendering(markdown, target_language)
if target_language == "js":
markdown = _resolve_cross_references(markdown, JS_LINK_MAP)
elif target_language == "python":
# Via a dedicated plugin
pass
else:
raise ValueError(
f"Unsupported target language: {target_language}. "
"Supported languages are 'python' and 'js'."
)
# Add file path as an attribute to code blocks that are executable.
# This file path is used to associate fixtures with the executable code
# which can be used in CI to test the docs without making network requests.
@@ -212,6 +212,7 @@ We have now created an assistant called "Open AI Assistant" that has `model_name
Output:
```
Receiving event of type: metadata
{'run_id': '1ef6746e-5893-67b1-978a-0f1cd4060e16'}
@@ -219,6 +220,7 @@ Output:
Receiving event of type: updates
{'agent': {'messages': [{'content': 'I was created by OpenAI, a research organization focused on developing and advancing artificial intelligence technology.', 'additional_kwargs': {}, 'response_metadata': {'finish_reason': 'stop', 'model_name': 'gpt-4o-2024-05-13', 'system_fingerprint': 'fp_157b3831f5'}, 'type': 'ai', 'name': None, 'id': 'run-e1a6b25c-8416-41f2-9981-f9cfe043f414', 'example': False, 'tool_calls': [], 'invalid_tool_calls': [], 'usage_metadata': None}]}}
```
### LangGraph Platform UI
@@ -231,9 +233,11 @@ Inside your deployment, select the "Assistants" tab. For the assistant you would
To edit the assistant, use the `update` method. This will create a new version of the assistant with the provided edits. See the [Python](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/python_sdk_ref/#langgraph_sdk.client.AssistantsClient.update) and [JS](https://langchain-ai.github.io/langgraph/cloud/reference/sdk/js_ts_sdk_ref/#update) SDK reference docs for more information.
!!! note "Note"
You must pass in the ENTIRE config (and metadata if you are using it). The update endpoint creates new versions completely from scratch and does not rely on previous versions.
You must pass in the ENTIRE config (and metadata if you are using it). The update endpoint creates new versions completely from scratch and does not rely on previous versions.
For example, to update your assistant's system prompt:
=== "Python"
```python
@@ -247,5 +247,7 @@ Verify that the original, interrupted run was interrupted
Output:
```
'interrupted'
```
+14 -7
View File
@@ -73,9 +73,11 @@ langgraph dev --debug-port 5678
Then attach your preferred debugger:
=== "VS Code"
Add this configuration to `launch.json`:
`json
{
Add this configuration to `launch.json`:
```json
{
"name": "Attach to LangGraph",
"type": "debugpy",
"request": "attach",
@@ -83,11 +85,16 @@ Add this configuration to `launch.json`:
"host": "0.0.0.0",
"port": 5678
}
}
`
Specify the port number you chose in the previous step.
}
```
=== "PyCharm" 1. Go to Run → Edit Configurations 2. Click + and select "Python Debug Server" 3. Set IDE host name: `localhost` 4. Set port: `5678` (or the port number you chose in the previous step) 5. Click "OK" and start debugging
=== "PyCharm"
1. Go to Run → Edit Configurations
2. Click + and select "Python Debug Server"
3. Set IDE host name: `localhost`
4. Set port: `5678` (or the port number you chose in the previous step)
5. Click "OK" and start debugging
## Troubleshooting
+74 -68
View File
@@ -8,15 +8,15 @@ Currently, the SDK does not provide built-in support for defining webhook endpoi
The following API endpoints accept a `webhook` parameter:
| Operation | HTTP Method | Endpoint |
|-----------|------------|----------|
| Create Run | `POST` | `/thread/{thread_id}/runs` |
| Create Thread Cron | `POST` | `/thread/{thread_id}/runs/crons` |
| Stream Run | `POST` | `/thread/{thread_id}/runs/stream` |
| Wait Run | `POST` | `/thread/{thread_id}/runs/wait` |
| Create Cron | `POST` | `/runs/crons` |
| Stream Run Stateless | `POST` | `/runs/stream` |
| Wait Run Stateless | `POST` | `/runs/wait` |
| Operation | HTTP Method | Endpoint |
|----------------------|-------------|-----------------------------------|
| Create Run | `POST` | `/thread/{thread_id}/runs` |
| Create Thread Cron | `POST` | `/thread/{thread_id}/runs/crons` |
| Stream Run | `POST` | `/thread/{thread_id}/runs/stream` |
| Wait Run | `POST` | `/thread/{thread_id}/runs/wait` |
| Create Cron | `POST` | `/runs/crons` |
| Stream Run Stateless | `POST` | `/runs/stream` |
| Wait Run Stateless | `POST` | `/runs/wait` |
In this guide, well show how to trigger a webhook after streaming a run.
@@ -25,36 +25,39 @@ In this guide, well show how to trigger a webhook after streaming a run.
Before making API calls, set up your assistant and thread.
=== "Python"
```python
from langgraph_sdk import get_client
client = get_client(url=<DEPLOYMENT_URL>)
assistant_id = "agent"
thread = await client.threads.create()
print(thread)
```
```python
from langgraph_sdk import get_client
client = get_client(url=<DEPLOYMENT_URL>)
assistant_id = "agent"
thread = await client.threads.create()
print(thread)
```
=== "JavaScript"
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
const assistantID = "agent";
const thread = await client.threads.create();
console.log(thread);
```
```js
import { Client } from "@langchain/langgraph-sdk";
const client = new Client({ apiUrl: <DEPLOYMENT_URL> });
const assistantID = "agent";
const thread = await client.threads.create();
console.log(thread);
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/assistants/search \
--header 'Content-Type: application/json' \
--data '{ "limit": 10, "offset": 0 }' | jq -c 'map(select(.config == null or .config == {})) | .[0]' && \
curl --request POST \
--url <DEPLOYMENT_URL>/threads \
--header 'Content-Type: application/json' \
--data '{}'
```
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/assistants/search \
--header 'Content-Type: application/json' \
--data '{ "limit": 10, "offset": 0 }' | jq -c 'map(select(.config == null or .config == {})) | .[0]' && \
curl --request POST \
--url <DEPLOYMENT_URL>/threads \
--header 'Content-Type: application/json' \
--data '{}'
```
Example response:
@@ -77,48 +80,51 @@ To use a webhook, specify the `webhook` parameter in your API request. When the
For example, if your server listens for webhook events at `https://my-server.app/my-webhook-endpoint`, include this in your request:
=== "Python"
```python
input = { "messages": [{ "role": "user", "content": "Hello!" }] }
async for chunk in client.runs.stream(
thread_id=thread["thread_id"],
assistant_id=assistant_id,
input=input,
stream_mode="events",
webhook="https://my-server.app/my-webhook-endpoint"
):
pass
```
```python
input = { "messages": [{ "role": "user", "content": "Hello!" }] }
async for chunk in client.runs.stream(
thread_id=thread["thread_id"],
assistant_id=assistant_id,
input=input,
stream_mode="events",
webhook="https://my-server.app/my-webhook-endpoint"
):
pass
```
=== "JavaScript"
```js
const input = { messages: [{ role: "human", content: "Hello!" }] };
const streamResponse = client.runs.stream(
thread["thread_id"],
assistantID,
{
input: input,
webhook: "https://my-server.app/my-webhook-endpoint"
}
);
```js
const input = { messages: [{ role: "human", content: "Hello!" }] };
for await (const chunk of streamResponse) {
// Handle stream output
}
```
const streamResponse = client.runs.stream(
thread["thread_id"],
assistantID,
{
input: input,
webhook: "https://my-server.app/my-webhook-endpoint"
}
);
for await (const chunk of streamResponse) {
// Handle stream output
}
```
=== "CURL"
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
--header 'Content-Type: application/json' \
--data '{
"assistant_id": <ASSISTANT_ID>,
"input": {"messages": [{"role": "user", "content": "Hello!"}]},
"webhook": "https://my-server.app/my-webhook-endpoint"
}'
```
```bash
curl --request POST \
--url <DEPLOYMENT_URL>/threads/<THREAD_ID>/runs/stream \
--header 'Content-Type: application/json' \
--data '{
"assistant_id": <ASSISTANT_ID>,
"input": {"messages": [{"role": "user", "content": "Hello!"}]},
"webhook": "https://my-server.app/my-webhook-endpoint"
}'
```
## Webhook payload
+7 -6
View File
@@ -50,9 +50,10 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
| <span style="white-space: nowrap;">`python_version`</span> | `3.11`, `3.12`, or `3.13`. Defaults to `3.11`. |
| <span style="white-space: nowrap;">`node_version`</span> | Specify `node_version: 20` to use LangGraph.js. |
| <span style="white-space: nowrap;">`pip_config_file`</span> | Path to `pip` config file. |
| <span style="white-space: nowrap;">`pip_installer`</span> | _(Added in v0.3)_ Optional. Python package installer selector. It can be set to `"auto"`, `"pip"`, or `"uv"`. From version&nbsp;0.3 onward the default strategy is to run `uv pip`, which typically delivers faster builds while remaining a drop-in replacement. In the uncommon situation where `uv` cannot handle your dependency graph or the structure of your `pyproject.toml`, specify `"pip"` here to revert to the earlier behaviour. |
| <span style="white-space: nowrap;">`dockerfile_lines`</span> | Array of additional lines to add to Dockerfile following the import from parent image. |
| <span style="white-space: nowrap;">`checkpointer`</span> | Configuration for the checkpointer. Contains a `ttl` field which is an object with the following keys: <ul><li>`strategy`: How to handle expired checkpoints (e.g., `"delete"`).</li><li>`sweep_interval_minutes`: How often to check for expired checkpoints (integer).</li><li>`default_ttl`: Default time-to-live for checkpoints in **minutes** (integer). Defines how long checkpoints are kept before the specified strategy is applied.</li></ul> |
| <span style="white-space: nowrap;">`http`</span> | HTTP server configuration with the following fields: <ul><li>`app`: Path to custom Starlette/FastAPI app (e.g., `"./src/agent/webapp.py:app"`). See [custom routes guide](../../how-tos/http/custom_routes.md).</li><li>`disable_assistants`: Disable `/assistants` routes</li><li>`disable_threads`: Disable `/threads` routes</li><li>`disable_runs`: Disable `/runs` routes</li><li>`disable_store`: Disable `/store` routes</li><li>`disable_meta`: Disable `/ok`, `/info`, `/metrics`, and `/docs` routes</li><li>`cors`: CORS configuration with fields for `allow_origins`, `allow_methods`, `allow_headers`, etc.</li><li>`configurable_headers`: Define which request headers to exclude or include as a run's configurable values.</li></ul> |
| <span style="white-space: nowrap;">`http`</span> | HTTP server configuration with the following fields: <ul><li>`app`: Path to custom Starlette/FastAPI app (e.g., `"./src/agent/webapp.py:app"`). See [custom routes guide](../../how-tos/http/custom_routes.md).</li><li>`disable_assistants`: Disable `/assistants` routes</li><li>`disable_threads`: Disable `/threads` routes</li><li>`disable_runs`: Disable `/runs` routes</li><li>`disable_store`: Disable `/store` routes</li><li>`disable_meta`: Disable `/ok`, `/info`, `/metrics`, and `/docs` routes</li><li>`disable_mcp`: Disable `/mcp` routes</li><li>`cors`: CORS configuration with fields for `allow_origins`, `allow_methods`, `allow_headers`, etc.</li><li>`configurable_headers`: Define which request headers to exclude or include as a run's configurable values.</li></ul> |
=== "JS"
@@ -128,7 +129,7 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
- `cohere:embed-english-v3.0`: 1024
- `cohere:embed-english-light-v3.0`: 384
- `cohere:embed-multilingual-v3.0`: 1024
- `cohere:embed-multilingual-light-v3.0`: 384
- `cohere:embed-multilingual-light-v3.0`: 384
#### Semantic search with a custom embedding function
@@ -361,8 +362,8 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
**Options**
| Option | Default | Description |
| -------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| Option | Default | Description |
| -------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------- |
| `--platform TEXT` | | Target platform(s) to build the Docker image for. Example: `langgraph build --platform linux/amd64,linux/arm64` |
| `-t, --tag TEXT` | | **Required**. Tag for the Docker image. Example: `langgraph build -t my-image` |
| `--pull / --no-pull` | `--pull` | Build with latest remote Docker image. Use `--no-pull` for running the LangGraph Platform API server with locally built images. |
@@ -381,8 +382,8 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
**Options**
| Option | Default | Description |
| -------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| Option | Default | Description |
| -------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------- |
| `--platform TEXT` | | Target platform(s) to build the Docker image for. Example: `langgraph build --platform linux/amd64,linux/arm64` |
| `-t, --tag TEXT` | | **Required**. Tag for the Docker image. Example: `langgraph build -t my-image` |
| `--no-pull` | | Use locally built images. Defaults to `false` to build with latest remote Docker image. |
+2 -3
View File
@@ -50,11 +50,10 @@ Set this environment variable to have a deployment send traces to a self-hosted
## `LANGSMITH_TRACING`
!!! info "Only for Self-Hosted Data Plane, Self-Hosted Control Plane, and Standalone Container"
Disabling LangSmith tracing is only available for [Self-Hosted Data Plane](../../concepts/langgraph_self_hosted_data_plane.md), [Self-Hosted Control Plane](../../concepts/langgraph_self_hosted_control_plane.md), and [Standalone Container](../../concepts/langgraph_standalone_container.md) deployments.
Set `LANGSMITH_TRACING` to `false` to disable tracing to LangSmith.
Defaults to `true`.
## `LOG_LEVEL`
Configure [log level](https://docs.python.org/3/library/logging.html#logging-levels). Defaults to `INFO`.
+6 -1
View File
@@ -9,13 +9,18 @@ search:
## Installation
The LangGraph CLI can be installed via pip:
The LangGraph CLI can be installed via pip or [Homebrew](https://brew.sh/):
=== "pip"
```bash
pip install langgraph-cli
```
=== "Homebrew"
```bash
brew install langgraph-cli
```
## Commands
LangGraph CLI provides the following core functionality:
@@ -19,7 +19,7 @@ From the control plane UI, you can:
- Update a deployment.
- Update environment variables for a deployment.
- View build and server logs of a deployment.
- View deployment metrics like CPU and memory usage.
- View deployment metrics such as CPU and memory usage.
- Delete a deployment.
The Control Plane UI is embedded in [LangSmith](https://docs.smith.langchain.com/langgraph_cloud).
@@ -95,6 +95,8 @@ After a deployment is ready, the control plane monitors the deployment and recor
- CPU and memory usage of the deployment.
- Number of container restarts.
- Number of replicas (this will increase with [autoscaling](../concepts/langgraph_data_plane.md#autoscaling)).
- [Postgres](../concepts/langgraph_data_plane.md#postgres) CPU, memory usage, and disk usage.
These metrics are displayed as charts in the Control Plane UI.
+2 -1
View File
@@ -87,6 +87,7 @@ One of the most common agent types is a [tool-calling agent](../agents/overview.
```python
from langchain_core.tools import tool
@tool
def transfer_to_bob():
"""Transfer to bob."""
return Command(
@@ -414,4 +415,4 @@ There are two high-level approaches to achieve that:
An agent might need to have a different state schema from the rest of the agents. For example, a search agent might only need to keep track of queries and retrieved documents. There are two ways to achieve this in LangGraph:
- Define [subgraph](./subgraphs.md) agents with a separate state schema. If there are no shared state keys (channels) between the subgraph and the parent graph, its important to [add input / output transformations](../how-tos/subgraph.ipynb#different-state-schemas) so that the parent graph knows how to communicate with the subgraphs.
- Define agent node functions with a [private input state schema](../how-tos/graph-api.ipynb/#pass-private-state-between-nodes) that is distinct from the overall graph state schema. This allows passing information that is only needed for executing that particular agent.
- Define agent node functions with a [private input state schema](../how-tos/graph-api.ipynb/#pass-private-state-between-nodes) that is distinct from the overall graph state schema. This allows passing information that is only needed for executing that particular agent.
+2 -2
View File
@@ -34,7 +34,7 @@ def read_root():
## Configure `langgraph.json`
Add the following to your `langgraph.json` configuration file. Make sure the path points to the `app.py` file you created above.
Add the following to your `langgraph.json` configuration file. Make sure the path points to the FastAPI application instance `app` in the `webapp.py` file you created above.
```json
{
@@ -71,4 +71,4 @@ You can deploy this app as-is to LangGraph Platform or to your self-hosted platf
## Next steps
Now that you've added a custom route to your deployment, you can use this same technique to further customize how your server behaves, such as defining custom [custom middleware](./custom_middleware.md) and [custom lifespan events](./custom_lifespan.md).
Now that you've added a custom route to your deployment, you can use this same technique to further customize how your server behaves, such as defining custom [custom middleware](./custom_middleware.md) and [custom lifespan events](./custom_lifespan.md).
+1 -1
View File
@@ -648,7 +648,7 @@ With orchestrator-worker, an orchestrator breaks down a task and delegates each
Because orchestrator-worker workflows are common, LangGraph **has the `Send` API to support this**. It lets you dynamically create worker nodes and send each one a specific input. Each worker has its own state, and all worker outputs are written to a *shared state key* that is accessible to the orchestrator graph. This gives the orchestrator access to all worker output and allows it to synthesize them into a final output. As you can see below, we iterate over a list of sections and `Send` each to a worker node. See further documentation [here](https://langchain-ai.github.io/langgraph/how-tos/map-reduce/) and [here](https://langchain-ai.github.io/langgraph/concepts/low_level/#send).
```python
from langgraph.constants import Send
from langgraph.types import Send
# Graph state
+41
View File
@@ -0,0 +1,41 @@
.lang-python,
.lang-javascript {
display: none;
}
.language-switcher-global {
display: flex;
align-items: center;
padding-left: 0.5rem;
margin-right: 0.5rem;
}
/* Style the select to match the header */
.language-switcher-global select {
appearance: none;
font: inherit;
border: none;
padding: 0.25rem 0.6rem;
cursor: pointer;
outline: none;
font-weight: bolder;
}
/* Hover/focus effect */
.language-switcher-global select:hover,
.language-switcher-global select:focus {
text-decoration: underline;
}
/* Theme-specific overrides */
html[data-md-color-scheme="default"] .language-switcher-global select,
html[data-md-color-scheme="default"] .language-switcher-global option {
color: #333;
background-color: transparent;
}
html[data-md-color-scheme="slate"] .language-switcher-global select,
html[data-md-color-scheme="slate"] .language-switcher-global option {
color: #eee;
background-color: transparent;
}
+38
View File
@@ -0,0 +1,38 @@
function applyLanguageSwitching() {
const selector = document.getElementById("global-language-selector");
const langBlocks = {
python: document.querySelectorAll(".lang-python"),
javascript: document.querySelectorAll(".lang-javascript"),
};
const setLanguage = (lang) => {
for (const [key, blocks] of Object.entries(langBlocks)) {
blocks.forEach((block) => {
block.style.display = key === lang ? "block" : "none";
});
}
localStorage.setItem("preferredLang", lang);
};
const saved = localStorage.getItem("preferredLang") || "python";
if (selector) {
selector.value = saved;
selector.addEventListener("change", (e) => setLanguage(e.target.value));
}
setLanguage(saved);
}
// Run on initial load
document.addEventListener("DOMContentLoaded", applyLanguageSwitching);
// Re-run after client-side navigation (MkDocs Material)
document.addEventListener("pjax:success", applyLanguageSwitching);
// Optional: observe DOM changes (e.g., for late-loaded content)
if (window.MutationObserver) {
const observer = new MutationObserver(() => applyLanguageSwitching());
observer.observe(document.body, { childList: true, subtree: true });
}
@@ -0,0 +1,6 @@
<div class="md-header__button language-switcher-global" title="Select Language">
<select id="global-language-selector" aria-label="Select Language">
<option value="python">🐍 Python</option>
<option value="javascript">⚡️ JavaScript</option>
</select>
</div>
@@ -0,0 +1,22 @@
from _scripts.notebook_hooks import _apply_conditional_rendering
CONDITIONAL_RENDERING = """
above
:::js
js-content
:::
between
:::python
python-content
:::
below
"""
def test_conditional_rendering() -> None:
"""Test logic for conditional rendering of content."""
output = _apply_conditional_rendering(CONDITIONAL_RENDERING, "js")
assert output.strip() == "above\njs-content\n\nbetween\n\nbelow"
output = _apply_conditional_rendering(CONDITIONAL_RENDERING, "python")
assert output.strip() == "above\n\nbetween\npython-content\n\nbelow"
@@ -228,7 +228,6 @@ async def test_combined_metadata(saver_name: str, test_data) -> None:
checkpoint = await saver.aget_tuple(config)
assert checkpoint.metadata == {
**metadata,
"thread_id": "thread-2",
"run_id": "my_run_id",
}
@@ -210,7 +210,6 @@ def test_combined_metadata(saver_name: str, test_data) -> None:
checkpoint = saver.get_tuple(config)
assert checkpoint.metadata == {
**metadata,
"thread_id": "thread-2",
"run_id": "my_run_id",
}
+1 -1
View File
@@ -308,7 +308,7 @@ wheels = [
[[package]]
name = "langgraph-checkpoint"
version = "2.0.26"
version = "2.1.0"
source = { editable = "../checkpoint" }
dependencies = [
{ name = "langchain-core" },
+2 -10
View File
@@ -71,7 +71,6 @@ class TestAsyncSqliteSaver:
checkpoint = await saver.aget_tuple(config)
assert checkpoint is not None and checkpoint.metadata == {
**self.metadata_2,
"thread_id": "thread-2",
"run_id": "my_run_id",
}
@@ -92,18 +91,11 @@ class TestAsyncSqliteSaver:
search_results_1 = [c async for c in saver.alist(None, filter=query_1)]
assert len(search_results_1) == 1
assert search_results_1[0].metadata == {
"thread_id": "thread-1",
"thread_ts": "1",
**self.metadata_1,
}
assert search_results_1[0].metadata == self.metadata_1
search_results_2 = [c async for c in saver.alist(None, filter=query_2)]
assert len(search_results_2) == 1
assert search_results_2[0].metadata == {
"thread_id": "thread-2",
**self.metadata_2,
}
assert search_results_2[0].metadata == self.metadata_2
search_results_3 = [c async for c in saver.alist(None, filter=query_3)]
assert len(search_results_3) == 3
+2 -10
View File
@@ -72,7 +72,6 @@ class TestSqliteSaver:
checkpoint = saver.get_tuple(config)
assert checkpoint is not None and checkpoint.metadata == {
**self.metadata_2,
"thread_id": "thread-2",
"run_id": "my_run_id",
}
@@ -95,18 +94,11 @@ class TestSqliteSaver:
search_results_1 = list(saver.list(None, filter=query_1))
assert len(search_results_1) == 1
assert search_results_1[0].metadata == {
"thread_id": "thread-1",
"thread_ts": "1",
**self.metadata_1,
}
assert search_results_1[0].metadata == self.metadata_1
search_results_2 = list(saver.list(None, filter=query_2))
assert len(search_results_2) == 1
assert search_results_2[0].metadata == {
"thread_id": "thread-2",
**self.metadata_2,
}
assert search_results_2[0].metadata == self.metadata_2
search_results_3 = list(saver.list(None, filter=query_3))
assert len(search_results_3) == 3
+1 -1
View File
@@ -320,7 +320,7 @@ wheels = [
[[package]]
name = "langgraph-checkpoint"
version = "2.0.26"
version = "2.1.0"
source = { editable = "../checkpoint" }
dependencies = [
{ name = "langchain-core" },
@@ -392,11 +392,10 @@ def get_checkpoint_metadata(
for obj in (config.get("metadata"), config.get("configurable")):
if not obj:
continue
for key in obj:
for key, v in obj.items():
if key in metadata or key in EXCLUDED_METADATA_KEYS or key.startswith("__"):
continue
v = obj[key]
if isinstance(v, str):
elif isinstance(v, str):
metadata[key] = v.replace("\u0000", "")
elif isinstance(v, (int, bool, float)):
metadata[key] = v
@@ -413,9 +412,16 @@ Each Checkpointer implementation should use this mapping in put_writes.
WRITES_IDX_MAP = {ERROR: -1, SCHEDULED: -2, INTERRUPT: -3, RESUME: -4}
EXCLUDED_METADATA_KEYS = {
"thread_id",
"thread_ts",
"checkpoint_id",
"checkpoint_ns",
"checkpoint_map",
"langgraph_step",
"langgraph_node",
"langgraph_triggers",
"langgraph_path",
"langgraph_checkpoint_ns",
}
# --- below are deprecated utilities used by past versions of LangGraph ---
+4 -19
View File
@@ -75,7 +75,6 @@ class TestMemorySaver:
assert checkpoint is not None
assert checkpoint.metadata == {
**self.metadata_2,
"thread_id": "thread-2",
"run_id": "my_run_id",
}
@@ -112,18 +111,11 @@ class TestMemorySaver:
search_results_1 = list(self.memory_saver.list(None, filter=query_1))
assert len(search_results_1) == 1
assert search_results_1[0].metadata == {
"thread_id": "thread-1",
"thread_ts": "1",
**self.metadata_1,
}
assert search_results_1[0].metadata == self.metadata_1
search_results_2 = list(self.memory_saver.list(None, filter=query_2))
assert len(search_results_2) == 1
assert search_results_2[0].metadata == {
"thread_id": "thread-2",
**self.metadata_2,
}
assert search_results_2[0].metadata == self.metadata_2
search_results_3 = list(self.memory_saver.list(None, filter=query_3))
assert len(search_results_3) == 3
@@ -178,20 +170,13 @@ class TestMemorySaver:
c async for c in self.memory_saver.alist(None, filter=query_1)
]
assert len(search_results_1) == 1
assert search_results_1[0].metadata == {
"thread_id": "thread-1",
"thread_ts": "1",
**self.metadata_1,
}
assert search_results_1[0].metadata == self.metadata_1
search_results_2 = [
c async for c in self.memory_saver.alist(None, filter=query_2)
]
assert len(search_results_2) == 1
assert search_results_2[0].metadata == {
"thread_id": "thread-2",
**self.metadata_2,
}
assert search_results_2[0].metadata == self.metadata_2
search_results_3 = [
c async for c in self.memory_saver.alist(None, filter=query_3)
+5
View File
@@ -330,6 +330,11 @@ class HttpConfig(TypedDict, total=False):
disable_store: bool
"""Optional. If True, /store routes are removed, disabling direct store interactions via HTTP.
Default is False.
"""
disable_mcp: bool
"""Optional. If True, /mcp routes are removed, disabling the MCP server.
Default is False.
"""
disable_meta: bool
+4
View File
@@ -499,6 +499,10 @@
"type": "boolean",
"description": "Optional. If True, /assistants routes are removed from the server.\n\nDefault is False (meaning /assistants is enabled).\n"
},
"disable_mcp": {
"type": "boolean",
"description": "Optional. If True, /mcp routes are removed, disabling the MCP server.\n\nDefault is False.\n"
},
"disable_meta": {
"type": "boolean",
"description": "Optional. If True, all meta endpoints (/ok, /info, /metrics, /docs) are disabled.\n\nDefault is False.\n"
+4
View File
@@ -499,6 +499,10 @@
"type": "boolean",
"description": "Optional. If True, /assistants routes are removed from the server.\n\nDefault is False (meaning /assistants is enabled).\n"
},
"disable_mcp": {
"type": "boolean",
"description": "Optional. If True, /mcp routes are removed, disabling the MCP server.\n\nDefault is False.\n"
},
"disable_meta": {
"type": "boolean",
"description": "Optional. If True, all meta endpoints (/ok, /info, /metrics, /docs) are disabled.\n\nDefault is False.\n"
+11 -2
View File
@@ -148,6 +148,15 @@ class _NodeWithConfigWriterStore(Protocol[StateT_contra]):
) -> Any: ...
class _Invokable(Protocol[StateT_contra]):
def invoke(
self,
input: StateT_contra,
config: RunnableConfig | None = None,
**kwargs: Any,
) -> Any: ...
# TODO: we probably don't want to explicitly support the config / store signatures once
# we move to adding a context arg. Maybe what we do is we add support for kwargs with param spec
# this is purely for typing purposes though, so can easily change in the coming weeks.
@@ -160,7 +169,7 @@ StateNode: TypeAlias = Union[
_NodeWithConfigWriter[StateT_contra],
_NodeWithConfigStore[StateT_contra],
_NodeWithConfigWriterStore[StateT_contra],
Runnable[StateT_contra, Any],
_Invokable[StateT_contra],
]
@@ -536,7 +545,7 @@ class StateGraph(Generic[StateT, InputT, OutputT]):
if input_schema is not None:
self._add_schema(input_schema)
self.nodes[node] = StateNodeSpec(
coerce_to_runnable(action, name=node, trace=False),
coerce_to_runnable(action, name=node, trace=False), # type: ignore[arg-type]
metadata,
input=input_schema or self.state_schema,
retry_policy=retry_policy,
+8 -16
View File
@@ -1415,10 +1415,8 @@ class Pregel(PregelProtocol[StateT, InputT, OutputT], Generic[StateT, InputT, Ou
)
},
)
checkpoint_metadata = config["metadata"]
if saved:
checkpoint_config = patch_configurable(config, saved.config[CONF])
checkpoint_metadata = {**saved.metadata, **checkpoint_metadata}
channels, managed = channels_from_checkpoint(
self.channels,
checkpoint,
@@ -1483,7 +1481,6 @@ class Pregel(PregelProtocol[StateT, InputT, OutputT], Generic[StateT, InputT, Ou
checkpoint_config,
create_checkpoint(checkpoint, None, step),
{
**checkpoint_metadata,
"source": "update",
"step": step + 1,
"parents": saved.metadata.get("parents", {}) if saved else {},
@@ -1506,7 +1503,6 @@ class Pregel(PregelProtocol[StateT, InputT, OutputT], Generic[StateT, InputT, Ou
checkpoint_config,
next_checkpoint,
{
**checkpoint_metadata,
"source": "update",
"step": step + 1,
"parents": saved.metadata.get("parents", {}) if saved else {},
@@ -1543,9 +1539,11 @@ class Pregel(PregelProtocol[StateT, InputT, OutputT], Generic[StateT, InputT, Ou
checkpoint_config,
create_checkpoint(checkpoint, channels, next_step),
{
**checkpoint_metadata,
"source": "input",
"step": next_step,
"parents": saved.metadata.get("parents", {})
if saved
else {},
},
get_new_channel_versions(
checkpoint_previous_versions,
@@ -1581,7 +1579,6 @@ class Pregel(PregelProtocol[StateT, InputT, OutputT], Generic[StateT, InputT, Ou
saved.parent_config or saved.config if saved else checkpoint_config,
next_checkpoint,
{
**checkpoint_metadata,
"source": "fork",
"step": step + 1,
"parents": saved.metadata.get("parents", {}) if saved else {},
@@ -1743,7 +1740,6 @@ class Pregel(PregelProtocol[StateT, InputT, OutputT], Generic[StateT, InputT, Ou
checkpoint_config,
checkpoint,
{
**checkpoint_metadata,
"source": "update",
"step": step + 1,
"parents": saved.metadata.get("parents", {}) if saved else {},
@@ -1837,10 +1833,8 @@ class Pregel(PregelProtocol[StateT, InputT, OutputT], Generic[StateT, InputT, Ou
)
},
)
checkpoint_metadata = config["metadata"]
if saved:
checkpoint_config = patch_configurable(config, saved.config[CONF])
checkpoint_metadata = {**saved.metadata, **checkpoint_metadata}
channels, managed = channels_from_checkpoint(
self.channels,
checkpoint,
@@ -1903,7 +1897,6 @@ class Pregel(PregelProtocol[StateT, InputT, OutputT], Generic[StateT, InputT, Ou
checkpoint_config,
create_checkpoint(checkpoint, None, step),
{
**checkpoint_metadata,
"source": "update",
"step": step + 1,
"parents": saved.metadata.get("parents", {}) if saved else {},
@@ -1926,7 +1919,6 @@ class Pregel(PregelProtocol[StateT, InputT, OutputT], Generic[StateT, InputT, Ou
checkpoint_config,
next_checkpoint,
{
**checkpoint_metadata,
"source": "update",
"step": step + 1,
"parents": saved.metadata.get("parents", {}) if saved else {},
@@ -1963,9 +1955,11 @@ class Pregel(PregelProtocol[StateT, InputT, OutputT], Generic[StateT, InputT, Ou
checkpoint_config,
create_checkpoint(checkpoint, channels, next_step),
{
**checkpoint_metadata,
"source": "input",
"step": next_step,
"parents": saved.metadata.get("parents", {})
if saved
else {},
},
get_new_channel_versions(
checkpoint_previous_versions,
@@ -2001,7 +1995,6 @@ class Pregel(PregelProtocol[StateT, InputT, OutputT], Generic[StateT, InputT, Ou
saved.parent_config or saved.config if saved else checkpoint_config,
next_checkpoint,
{
**checkpoint_metadata,
"source": "fork",
"step": step + 1,
"parents": saved.metadata.get("parents", {}) if saved else {},
@@ -2161,7 +2154,6 @@ class Pregel(PregelProtocol[StateT, InputT, OutputT], Generic[StateT, InputT, Ou
checkpoint_config,
checkpoint,
{
**checkpoint_metadata,
"source": "update",
"step": step + 1,
"parents": saved.metadata.get("parents", {}) if saved else {},
@@ -2420,7 +2412,7 @@ class Pregel(PregelProtocol[StateT, InputT, OutputT], Generic[StateT, InputT, Ou
debug=debug,
checkpoint_during=checkpoint_during
if checkpoint_during is not None
else config[CONF].get(CONFIG_KEY_CHECKPOINT_DURING, False),
else config[CONF].get(CONFIG_KEY_CHECKPOINT_DURING, True),
trigger_to_nodes=self.trigger_to_nodes,
migrate_checkpoint=self._migrate_checkpoint,
retry_policy=self.retry_policy,
@@ -2664,7 +2656,7 @@ class Pregel(PregelProtocol[StateT, InputT, OutputT], Generic[StateT, InputT, Ou
debug=debug,
checkpoint_during=checkpoint_during
if checkpoint_during is not None
else config[CONF].get(CONFIG_KEY_CHECKPOINT_DURING, False),
else config[CONF].get(CONFIG_KEY_CHECKPOINT_DURING, True),
trigger_to_nodes=self.trigger_to_nodes,
migrate_checkpoint=self._migrate_checkpoint,
retry_policy=self.retry_policy,
+18 -12
View File
@@ -30,7 +30,6 @@ from typing_extensions import ParamSpec, Self
from langgraph.cache.base import BaseCache
from langgraph.channels.base import BaseChannel
from langgraph.checkpoint.base import (
EXCLUDED_METADATA_KEYS,
WRITES_IDX_MAP,
BaseCheckpointSaver,
ChannelVersions,
@@ -314,10 +313,22 @@ class PregelLoop:
# deduplicate writes to special channels, last write wins
if all(w[0] in WRITES_IDX_MAP for w in writes):
writes = list({w[0]: w for w in writes}.values())
# remove existing writes for this task
self.checkpoint_pending_writes = [
w for w in self.checkpoint_pending_writes if w[0] != task_id
]
if task_id == NULL_TASK_ID:
# writes for the null task are accumulated
self.checkpoint_pending_writes = [
w
for w in self.checkpoint_pending_writes
if w[0] != task_id or w[1] not in WRITES_IDX_MAP
]
writes_to_save: WritesT = [
w[1:] for w in self.checkpoint_pending_writes if w[0] == task_id
] + list(writes)
else:
# remove existing writes for this task
self.checkpoint_pending_writes = [
w for w in self.checkpoint_pending_writes if w[0] != task_id
]
writes_to_save = writes
# save writes
self.checkpoint_pending_writes.extend((task_id, c, v) for c, v in writes)
if self.checkpoint_during and self.checkpointer_put_writes is not None:
@@ -338,7 +349,7 @@ class PregelLoop:
self.submit(
self.checkpointer_put_writes,
config,
writes,
writes_to_save,
task_id,
task_path_str(task.path) if task else "",
)
@@ -346,7 +357,7 @@ class PregelLoop:
self.submit(
self.checkpointer_put_writes,
config,
writes,
writes_to_save,
task_id,
)
# output writes
@@ -721,11 +732,6 @@ class PregelLoop:
)
# bail if no checkpointer
if do_checkpoint and self._checkpointer_put_after_previous is not None:
for k, v in self.config["metadata"].items():
if k in EXCLUDED_METADATA_KEYS:
continue
metadata.setdefault(k, v) # type: ignore
self.prev_checkpoint_config = (
self.checkpoint_config
if CONFIG_KEY_CHECKPOINT_ID in self.checkpoint_config[CONF]
+2 -1
View File
@@ -104,7 +104,8 @@ class FuturesDict(Generic[F, E], dict[F, Optional[PregelExecutableTask]]):
fut: F,
) -> None:
try:
self.callback()(task, _exception(fut)) # type: ignore[misc]
if cb := self.callback():
cb(task, _exception(fut))
finally:
with self.lock:
self.done.add(fut)
+3 -9
View File
@@ -1,7 +1,5 @@
from __future__ import annotations
from typing import Union
from typing_extensions import TypeVar
from langgraph._typing import StateLike
@@ -19,12 +17,8 @@ InputT = TypeVar("InputT", bound=StateLike, default=StateT)
Defaults to `StateT`.
"""
ResolvedInputT = TypeVar("ResolvedInputT", bound=StateLike)
"""Type variable used to represent the resolved input to a state graph.
OutputT = TypeVar("OutputT", bound=StateLike, default=StateT)
"""Type variable used to represent the output of a state graph.
No default.
Defaults to `StateT`.
"""
OutputT = TypeVar("OutputT", bound=Union[StateLike, None], default=StateT)
"""Type variable used to represent the output of a state graph."""
@@ -43,7 +43,6 @@ def get_expected_history(*, exc_task_results: int = 0) -> list[StateSnapshot]:
"source": "loop",
"step": 4,
"parents": {},
"thread_id": "1",
},
created_at=AnyStr(),
parent_config={
@@ -73,7 +72,6 @@ def get_expected_history(*, exc_task_results: int = 0) -> list[StateSnapshot]:
"source": "loop",
"step": 3,
"parents": {},
"thread_id": "1",
},
created_at=AnyStr(),
parent_config={
@@ -131,7 +129,6 @@ def get_expected_history(*, exc_task_results: int = 0) -> list[StateSnapshot]:
"source": "loop",
"step": 2,
"parents": {},
"thread_id": "1",
},
created_at=AnyStr(),
parent_config={
@@ -168,7 +165,6 @@ def get_expected_history(*, exc_task_results: int = 0) -> list[StateSnapshot]:
"source": "loop",
"step": 1,
"parents": {},
"thread_id": "1",
},
created_at=AnyStr(),
parent_config={
@@ -218,7 +214,6 @@ def get_expected_history(*, exc_task_results: int = 0) -> list[StateSnapshot]:
"source": "loop",
"step": 0,
"parents": {},
"thread_id": "1",
},
created_at=AnyStr(),
parent_config={
@@ -257,7 +252,6 @@ def get_expected_history(*, exc_task_results: int = 0) -> list[StateSnapshot]:
"source": "input",
"step": -1,
"parents": {},
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=None,
@@ -343,7 +337,6 @@ SAVED_CHECKPOINTS = {
"source": "loop",
"step": 4,
"parents": {},
"thread_id": "1",
},
parent_config={
"configurable": {
@@ -404,7 +397,6 @@ SAVED_CHECKPOINTS = {
"source": "loop",
"step": 3,
"parents": {},
"thread_id": "1",
},
parent_config={
"configurable": {
@@ -480,7 +472,6 @@ SAVED_CHECKPOINTS = {
"source": "loop",
"step": 2,
"parents": {},
"thread_id": "1",
},
parent_config={
"configurable": {
@@ -532,7 +523,6 @@ SAVED_CHECKPOINTS = {
"source": "loop",
"step": 1,
"parents": {},
"thread_id": "1",
},
parent_config={
"configurable": {
@@ -587,7 +577,6 @@ SAVED_CHECKPOINTS = {
"source": "loop",
"step": 0,
"parents": {},
"thread_id": "1",
},
parent_config={
"configurable": {
@@ -636,7 +625,6 @@ SAVED_CHECKPOINTS = {
"source": "input",
"step": -1,
"parents": {},
"thread_id": "1",
},
parent_config=None,
pending_writes=[
@@ -720,7 +708,6 @@ SAVED_CHECKPOINTS = {
},
metadata={
"source": "loop",
"thread_id": "1",
"step": 4,
"parents": {},
},
@@ -782,7 +769,6 @@ SAVED_CHECKPOINTS = {
},
metadata={
"source": "loop",
"thread_id": "1",
"step": 3,
"parents": {},
},
@@ -861,7 +847,6 @@ SAVED_CHECKPOINTS = {
},
metadata={
"source": "loop",
"thread_id": "1",
"step": 2,
"parents": {},
},
@@ -917,7 +902,6 @@ SAVED_CHECKPOINTS = {
},
metadata={
"source": "loop",
"thread_id": "1",
"step": 1,
"parents": {},
},
@@ -977,7 +961,6 @@ SAVED_CHECKPOINTS = {
},
metadata={
"source": "loop",
"thread_id": "1",
"step": 0,
"parents": {},
},
@@ -1026,7 +1009,6 @@ SAVED_CHECKPOINTS = {
},
metadata={
"source": "input",
"thread_id": "1",
"step": -1,
"parents": {},
},
@@ -1112,7 +1094,6 @@ SAVED_CHECKPOINTS = {
},
metadata={
"source": "loop",
"thread_id": "1",
"step": 4,
"parents": {},
},
@@ -1174,7 +1155,6 @@ SAVED_CHECKPOINTS = {
},
metadata={
"source": "loop",
"thread_id": "1",
"step": 3,
"parents": {},
},
@@ -1253,7 +1233,6 @@ SAVED_CHECKPOINTS = {
},
metadata={
"source": "loop",
"thread_id": "1",
"step": 2,
"parents": {},
},
@@ -1309,7 +1288,6 @@ SAVED_CHECKPOINTS = {
},
metadata={
"source": "loop",
"thread_id": "1",
"step": 1,
"parents": {},
},
@@ -1369,7 +1347,6 @@ SAVED_CHECKPOINTS = {
},
metadata={
"source": "loop",
"thread_id": "1",
"step": 0,
"parents": {},
},
@@ -1418,7 +1395,6 @@ SAVED_CHECKPOINTS = {
},
metadata={
"source": "input",
"thread_id": "1",
"step": -1,
"parents": {},
},
+81 -179
View File
@@ -126,7 +126,6 @@ def test_invoke_two_processes_in_out_interrupt(
"parents": {},
"source": "loop",
"step": 6,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=history[1].config,
@@ -147,7 +146,6 @@ def test_invoke_two_processes_in_out_interrupt(
"parents": {},
"source": "loop",
"step": 5,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=history[2].config,
@@ -168,7 +166,6 @@ def test_invoke_two_processes_in_out_interrupt(
"parents": {},
"source": "input",
"step": 4,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=history[3].config,
@@ -189,7 +186,6 @@ def test_invoke_two_processes_in_out_interrupt(
"parents": {},
"source": "loop",
"step": 3,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=history[4].config,
@@ -210,7 +206,6 @@ def test_invoke_two_processes_in_out_interrupt(
"parents": {},
"source": "input",
"step": 2,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=history[5].config,
@@ -231,7 +226,6 @@ def test_invoke_two_processes_in_out_interrupt(
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=history[6].config,
@@ -252,7 +246,6 @@ def test_invoke_two_processes_in_out_interrupt(
"parents": {},
"source": "loop",
"step": 0,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=history[7].config,
@@ -273,7 +266,6 @@ def test_invoke_two_processes_in_out_interrupt(
"parents": {},
"source": "input",
"step": -1,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=None,
@@ -342,7 +334,6 @@ def test_fork_always_re_runs_nodes(
"parents": {},
"source": "loop",
"step": 5,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=history[1].config,
@@ -363,7 +354,6 @@ def test_fork_always_re_runs_nodes(
"parents": {},
"source": "loop",
"step": 4,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=history[2].config,
@@ -384,7 +374,6 @@ def test_fork_always_re_runs_nodes(
"parents": {},
"source": "loop",
"step": 3,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=history[3].config,
@@ -405,7 +394,6 @@ def test_fork_always_re_runs_nodes(
"parents": {},
"source": "loop",
"step": 2,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=history[4].config,
@@ -426,7 +414,6 @@ def test_fork_always_re_runs_nodes(
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=history[5].config,
@@ -447,7 +434,6 @@ def test_fork_always_re_runs_nodes(
"parents": {},
"source": "loop",
"step": 0,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=history[6].config,
@@ -468,7 +454,6 @@ def test_fork_always_re_runs_nodes(
"parents": {},
"source": "input",
"step": -1,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=None,
@@ -679,7 +664,10 @@ def test_conditional_state_graph(
config = {"configurable": {"thread_id": "1"}}
assert [
c for c in app_w_interrupt.stream({"input": "what is weather in sf"}, config)
c
for c in app_w_interrupt.stream(
{"input": "what is weather in sf"}, config, checkpoint_during=False
)
] == [
{
"agent": {
@@ -714,7 +702,6 @@ def test_conditional_state_graph(
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "1",
},
parent_config=None,
interrupts=(),
@@ -754,7 +741,6 @@ def test_conditional_state_graph(
"parents": {},
"source": "update",
"step": 2,
"thread_id": "1",
},
parent_config=(
list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config
@@ -830,7 +816,6 @@ def test_conditional_state_graph(
"parents": {},
"source": "update",
"step": 5,
"thread_id": "1",
},
parent_config=(
list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config
@@ -849,7 +834,10 @@ def test_conditional_state_graph(
llm.i = 0 # reset the llm
assert [
c for c in app_w_interrupt.stream({"input": "what is weather in sf"}, config)
c
for c in app_w_interrupt.stream(
{"input": "what is weather in sf"}, config, checkpoint_during=False
)
] == [
{
"agent": {
@@ -882,7 +870,6 @@ def test_conditional_state_graph(
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "2",
},
parent_config=None,
interrupts=(),
@@ -922,7 +909,6 @@ def test_conditional_state_graph(
"parents": {},
"source": "update",
"step": 2,
"thread_id": "2",
},
parent_config=(
list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config
@@ -998,7 +984,6 @@ def test_conditional_state_graph(
"parents": {},
"source": "update",
"step": 5,
"thread_id": "2",
},
parent_config=(
list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config
@@ -1016,7 +1001,10 @@ def test_conditional_state_graph(
llm.i = 0 # reset the llm
assert [
c for c in app_w_interrupt.stream({"input": "what is weather in sf"}, config)
c
for c in app_w_interrupt.stream(
{"input": "what is weather in sf"}, config, checkpoint_during=False
)
] == [
{"__interrupt__": ()},
]
@@ -1039,7 +1027,6 @@ def test_conditional_state_graph(
"parents": {},
"source": "loop",
"step": 0,
"thread_id": "3",
},
parent_config=None,
interrupts=(),
@@ -1077,7 +1064,6 @@ def test_conditional_state_graph(
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "3",
},
parent_config=(
list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config
@@ -1133,7 +1119,6 @@ def test_conditional_state_graph(
"parents": {},
"source": "loop",
"step": 2,
"thread_id": "3",
},
parent_config=(
list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config
@@ -1163,7 +1148,10 @@ def test_conditional_state_graph(
llm.i = 0 # reset the llm
assert [
c for c in app_w_interrupt.stream({"input": "what is weather in sf"}, config)
c
for c in app_w_interrupt.stream(
{"input": "what is weather in sf"}, config, checkpoint_during=False
)
] == [
{
"agent": {
@@ -1196,7 +1184,6 @@ def test_conditional_state_graph(
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "4",
},
parent_config=None,
interrupts=(),
@@ -1250,7 +1237,6 @@ def test_conditional_state_graph(
"parents": {},
"source": "loop",
"step": 2,
"thread_id": "4",
},
parent_config=(
list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config
@@ -1863,7 +1849,9 @@ def test_state_graph_packets(
assert [
c
for c in app_w_interrupt.stream(
{"messages": HumanMessage(content="what is weather in sf")}, config
{"messages": HumanMessage(content="what is weather in sf")},
config,
checkpoint_during=False,
)
] == [
{
@@ -1915,7 +1903,6 @@ def test_state_graph_packets(
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "1",
},
parent_config=None,
interrupts=(),
@@ -1960,7 +1947,6 @@ def test_state_graph_packets(
"parents": {},
"source": "update",
"step": 2,
"thread_id": "1",
},
parent_config=(
[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config
@@ -2056,7 +2042,6 @@ def test_state_graph_packets(
"parents": {},
"source": "loop",
"step": 4,
"thread_id": "1",
},
parent_config=(
[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config
@@ -2110,7 +2095,6 @@ def test_state_graph_packets(
"parents": {},
"source": "update",
"step": 5,
"thread_id": "1",
},
parent_config=(
[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config
@@ -2130,7 +2114,9 @@ def test_state_graph_packets(
assert [
c
for c in app_w_interrupt.stream(
{"messages": HumanMessage(content="what is weather in sf")}, config
{"messages": HumanMessage(content="what is weather in sf")},
config,
checkpoint_during=False,
)
] == [
{
@@ -2182,7 +2168,6 @@ def test_state_graph_packets(
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "2",
},
parent_config=None,
interrupts=(),
@@ -2221,7 +2206,6 @@ def test_state_graph_packets(
"parents": {},
"source": "update",
"step": 2,
"thread_id": "2",
},
parent_config=(
[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config
@@ -2317,7 +2301,6 @@ def test_state_graph_packets(
"parents": {},
"source": "loop",
"step": 4,
"thread_id": "2",
},
parent_config=(
[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config
@@ -2371,7 +2354,6 @@ def test_state_graph_packets(
"parents": {},
"source": "update",
"step": 5,
"thread_id": "2",
},
parent_config=(
[*app_w_interrupt.checkpointer.list(config, limit=2)][-1].config
@@ -2600,7 +2582,10 @@ def test_message_graph(
config = {"configurable": {"thread_id": "1"}}
assert [
c for c in app_w_interrupt.stream(("human", "what is weather in sf"), config)
c
for c in app_w_interrupt.stream(
("human", "what is weather in sf"), config, checkpoint_during=False
)
] == [
{
"agent": AIMessage(
@@ -2647,7 +2632,6 @@ def test_message_graph(
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "1",
},
parent_config=None,
interrupts=(),
@@ -2682,7 +2666,6 @@ def test_message_graph(
"parents": {},
"source": "update",
"step": 2,
"thread_id": "1",
},
parent_config=(
list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config
@@ -2761,7 +2744,6 @@ def test_message_graph(
"parents": {},
"source": "loop",
"step": 4,
"thread_id": "1",
},
parent_config=(
list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config
@@ -2810,7 +2792,6 @@ def test_message_graph(
"parents": {},
"source": "update",
"step": 5,
"thread_id": "1",
},
parent_config=(
list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config
@@ -2825,7 +2806,12 @@ def test_message_graph(
config = {"configurable": {"thread_id": "2"}}
model.i = 0 # reset the llm
assert [c for c in app_w_interrupt.stream("what is weather in sf", config)] == [
assert [
c
for c in app_w_interrupt.stream(
"what is weather in sf", config, checkpoint_during=False
)
] == [
{
"agent": AIMessage(
content="",
@@ -2871,7 +2857,6 @@ def test_message_graph(
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "2",
},
parent_config=None,
interrupts=(),
@@ -2912,7 +2897,6 @@ def test_message_graph(
"parents": {},
"source": "update",
"step": 2,
"thread_id": "2",
},
parent_config=(
list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config
@@ -2991,7 +2975,6 @@ def test_message_graph(
"parents": {},
"source": "loop",
"step": 4,
"thread_id": "2",
},
parent_config=(
list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config
@@ -3041,7 +3024,6 @@ def test_message_graph(
"parents": {},
"source": "update",
"step": 5,
"thread_id": "2",
},
parent_config=(
list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config
@@ -3091,7 +3073,6 @@ def test_message_graph(
"parents": {},
"source": "update",
"step": 6,
"thread_id": "2",
},
parent_config=(
list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config
@@ -3323,7 +3304,10 @@ def test_root_graph(
config = {"configurable": {"thread_id": "1"}}
assert [
c for c in app_w_interrupt.stream(("human", "what is weather in sf"), config)
c
for c in app_w_interrupt.stream(
("human", "what is weather in sf"), config, checkpoint_during=False
)
] == [
{
"agent": AIMessage(
@@ -3370,7 +3354,6 @@ def test_root_graph(
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "1",
},
parent_config=None,
interrupts=(),
@@ -3405,7 +3388,6 @@ def test_root_graph(
"parents": {},
"source": "update",
"step": 2,
"thread_id": "1",
},
parent_config=(
list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config
@@ -3485,7 +3467,6 @@ def test_root_graph(
"parents": {},
"source": "loop",
"step": 4,
"thread_id": "1",
},
parent_config=(
list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config
@@ -3535,7 +3516,6 @@ def test_root_graph(
"parents": {},
"source": "update",
"step": 5,
"thread_id": "1",
},
parent_config=(
list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config
@@ -3550,7 +3530,12 @@ def test_root_graph(
config = {"configurable": {"thread_id": "2"}}
model.i = 0 # reset the llm
assert [c for c in app_w_interrupt.stream("what is weather in sf", config)] == [
assert [
c
for c in app_w_interrupt.stream(
"what is weather in sf", config, checkpoint_during=False
)
] == [
{
"agent": AIMessage(
content="",
@@ -3596,7 +3581,6 @@ def test_root_graph(
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "2",
},
parent_config=None,
interrupts=(),
@@ -3637,7 +3621,6 @@ def test_root_graph(
"parents": {},
"source": "update",
"step": 2,
"thread_id": "2",
},
parent_config=(
list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config
@@ -3717,7 +3700,6 @@ def test_root_graph(
"parents": {},
"source": "loop",
"step": 4,
"thread_id": "2",
},
parent_config=(
list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config
@@ -3766,7 +3748,6 @@ def test_root_graph(
"parents": {},
"source": "update",
"step": 5,
"thread_id": "2",
},
parent_config=(
list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config
@@ -3816,7 +3797,6 @@ def test_root_graph(
"parents": {},
"source": "update",
"step": 6,
"thread_id": "2",
},
parent_config=(
list(app_w_interrupt.checkpointer.list(config, limit=2))[-1].config
@@ -3897,7 +3877,6 @@ def test_root_graph(
"parents": {},
"source": "update",
"step": 6,
"thread_id": "2",
},
parent_config=(list(new_app.checkpointer.list(config, limit=2))[-1].config),
interrupts=(),
@@ -4240,7 +4219,9 @@ def test_dynamic_interrupt(sync_checkpointer: BaseCheckpointSaver) -> None:
# flow: interrupt -> clear tasks
thread1 = {"configurable": {"thread_id": "1"}}
# stop when about to enter node
assert tool_two.invoke({"my_key": "value ⛰️", "market": "DE"}, thread1) == {
assert tool_two.invoke(
{"my_key": "value ⛰️", "market": "DE"}, thread1, checkpoint_during=False
) == {
"my_key": "value ⛰️",
"market": "DE",
"__interrupt__": [
@@ -4253,7 +4234,6 @@ def test_dynamic_interrupt(sync_checkpointer: BaseCheckpointSaver) -> None:
"parents": {},
"source": "loop",
"step": 0,
"thread_id": "1",
},
]
@@ -4286,7 +4266,6 @@ def test_dynamic_interrupt(sync_checkpointer: BaseCheckpointSaver) -> None:
"parents": {},
"source": "loop",
"step": 0,
"thread_id": "1",
},
parent_config=None,
interrupts=(
@@ -4316,7 +4295,6 @@ def test_dynamic_interrupt(sync_checkpointer: BaseCheckpointSaver) -> None:
"parents": {},
"source": "update",
"step": 1,
"thread_id": "1",
},
parent_config=(list(tool_two.checkpointer.list(thread1, limit=2))[-1].config),
interrupts=(),
@@ -4408,7 +4386,9 @@ def test_copy_checkpoint(sync_checkpointer: BaseCheckpointSaver) -> None:
# flow: interrupt -> clear tasks
thread1 = {"configurable": {"thread_id": "1"}}
# stop when about to enter node
assert tool_two.invoke({"my_key": "value ⛰️", "market": "DE"}, thread1) == {
assert tool_two.invoke(
{"my_key": "value ⛰️", "market": "DE"}, thread1, checkpoint_during=False
) == {
"my_key": "value ⛰️ one",
"market": "DE",
"__interrupt__": [
@@ -4420,7 +4400,6 @@ def test_copy_checkpoint(sync_checkpointer: BaseCheckpointSaver) -> None:
"parents": {},
"source": "loop",
"step": 0,
"thread_id": "1",
},
]
@@ -4459,7 +4438,6 @@ def test_copy_checkpoint(sync_checkpointer: BaseCheckpointSaver) -> None:
"parents": {},
"source": "loop",
"step": 0,
"thread_id": "1",
},
parent_config=None,
interrupts=(
@@ -4505,7 +4483,6 @@ def test_copy_checkpoint(sync_checkpointer: BaseCheckpointSaver) -> None:
"parents": {},
"source": "fork",
"step": 1,
"thread_id": "1",
},
parent_config=([*tool_two.checkpointer.list(thread1, limit=2)][-1].config),
interrupts=(),
@@ -4597,7 +4574,9 @@ def test_dynamic_interrupt_subgraph(sync_checkpointer: BaseCheckpointSaver) -> N
# flow: interrupt -> clear tasks
thread1 = {"configurable": {"thread_id": "1"}}
# stop when about to enter node
assert tool_two.invoke({"my_key": "value ⛰️", "market": "DE"}, thread1) == {
assert tool_two.invoke(
{"my_key": "value ⛰️", "market": "DE"}, thread1, checkpoint_during=False
) == {
"my_key": "value ⛰️",
"market": "DE",
"__interrupt__": [
@@ -4619,7 +4598,6 @@ def test_dynamic_interrupt_subgraph(sync_checkpointer: BaseCheckpointSaver) -> N
"parents": {},
"source": "loop",
"step": 0,
"thread_id": "1",
},
]
@@ -4658,7 +4636,6 @@ def test_dynamic_interrupt_subgraph(sync_checkpointer: BaseCheckpointSaver) -> N
"parents": {},
"source": "loop",
"step": 0,
"thread_id": "1",
},
parent_config=None,
interrupts=(
@@ -4688,7 +4665,6 @@ def test_dynamic_interrupt_subgraph(sync_checkpointer: BaseCheckpointSaver) -> N
"parents": {},
"source": "update",
"step": 1,
"thread_id": "1",
},
parent_config=(
list(
@@ -4819,7 +4795,6 @@ def test_send_dedupe_on_resume(
},
metadata={
"source": "loop",
"thread_id": "1",
"step": 4,
"parents": {},
},
@@ -4855,7 +4830,6 @@ def test_send_dedupe_on_resume(
},
metadata={
"source": "loop",
"thread_id": "1",
"step": 3,
"parents": {},
},
@@ -4898,7 +4872,6 @@ def test_send_dedupe_on_resume(
},
metadata={
"source": "loop",
"thread_id": "1",
"step": 2,
"parents": {},
},
@@ -4953,7 +4926,6 @@ def test_send_dedupe_on_resume(
},
metadata={
"source": "loop",
"thread_id": "1",
"step": 1,
"parents": {},
},
@@ -5008,7 +4980,6 @@ def test_send_dedupe_on_resume(
},
metadata={
"source": "loop",
"thread_id": "1",
"step": 0,
"parents": {},
},
@@ -5045,7 +5016,6 @@ def test_send_dedupe_on_resume(
},
metadata={
"source": "input",
"thread_id": "1",
"step": -1,
"parents": {},
},
@@ -5123,7 +5093,7 @@ def test_nested_graph_state(sync_checkpointer: BaseCheckpointSaver) -> None:
app = graph.compile(checkpointer=sync_checkpointer)
config = {"configurable": {"thread_id": "1"}}
app.invoke({"my_key": "my value"}, config, debug=True)
app.invoke({"my_key": "my value"}, config, checkpoint_during=False)
# test state w/ nested subgraph state (right after interrupt)
# first get_state without subgraph state
expected = StateSnapshot(
@@ -5148,7 +5118,6 @@ def test_nested_graph_state(sync_checkpointer: BaseCheckpointSaver) -> None:
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=None,
@@ -5193,12 +5162,6 @@ def test_nested_graph_state(sync_checkpointer: BaseCheckpointSaver) -> None:
},
"source": "loop",
"step": 1,
"thread_id": "1",
"langgraph_node": "inner",
"langgraph_path": [PULL, "inner"],
"langgraph_step": 2,
"langgraph_triggers": ["branch:to:inner"],
"langgraph_checkpoint_ns": AnyStr("inner:"),
},
created_at=AnyStr(),
parent_config=None,
@@ -5218,7 +5181,6 @@ def test_nested_graph_state(sync_checkpointer: BaseCheckpointSaver) -> None:
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=None,
@@ -5245,12 +5207,6 @@ def test_nested_graph_state(sync_checkpointer: BaseCheckpointSaver) -> None:
"source": "loop",
"step": 1,
"parents": {"": AnyStr()},
"thread_id": "1",
"langgraph_node": "inner",
"langgraph_path": [PULL, "inner"],
"langgraph_step": 2,
"langgraph_triggers": ["branch:to:inner"],
"langgraph_checkpoint_ns": AnyStr("inner:"),
},
created_at=AnyStr(),
parent_config=None,
@@ -5261,7 +5217,7 @@ def test_nested_graph_state(sync_checkpointer: BaseCheckpointSaver) -> None:
assert child_history == expected_child_history
# resume
app.invoke(None, config, debug=True)
app.invoke(None, config, checkpoint_during=False)
# test state w/ nested subgraph state (after resuming from interrupt)
assert app.get_state(config) == StateSnapshot(
values={"my_key": "hi my value here and there and back again"},
@@ -5278,7 +5234,6 @@ def test_nested_graph_state(sync_checkpointer: BaseCheckpointSaver) -> None:
"parents": {},
"source": "loop",
"step": 3,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=(
@@ -5310,7 +5265,6 @@ def test_nested_graph_state(sync_checkpointer: BaseCheckpointSaver) -> None:
"parents": {},
"source": "loop",
"step": 3,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=(
@@ -5349,7 +5303,6 @@ def test_nested_graph_state(sync_checkpointer: BaseCheckpointSaver) -> None:
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=None,
@@ -5417,7 +5370,12 @@ def test_doubly_nested_graph_state(
# test invoke w/ nested interrupt
config = {"configurable": {"thread_id": "1"}}
assert [c for c in app.stream({"my_key": "my value"}, config, subgraphs=True)] == [
assert [
c
for c in app.stream(
{"my_key": "my value"}, config, subgraphs=True, checkpoint_during=False
)
] == [
((), {"parent_1": {"my_key": "hi my value"}}),
(
(AnyStr("child:"), AnyStr("child_1:")),
@@ -5454,7 +5412,6 @@ def test_doubly_nested_graph_state(
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=None,
@@ -5491,15 +5448,9 @@ def test_doubly_nested_graph_state(
}
},
metadata={
"langgraph_checkpoint_ns": AnyStr("child:"),
"langgraph_node": "child",
"langgraph_path": ["__pregel_pull", "child"],
"langgraph_step": 2,
"langgraph_triggers": ["branch:to:child"],
"parents": {"": AnyStr()},
"source": "loop",
"step": 0,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=None,
@@ -5539,12 +5490,6 @@ def test_doubly_nested_graph_state(
),
"source": "loop",
"step": 1,
"thread_id": "1",
"langgraph_checkpoint_ns": AnyStr("child:"),
"langgraph_node": "child_1",
"langgraph_path": [PULL, AnyStr("child_1")],
"langgraph_step": 1,
"langgraph_triggers": ["branch:to:child_1"],
},
created_at=AnyStr(),
parent_config=None,
@@ -5600,17 +5545,6 @@ def test_doubly_nested_graph_state(
),
"source": "loop",
"step": 1,
"thread_id": "1",
"langgraph_checkpoint_ns": AnyStr("child:"),
"langgraph_node": "child_1",
"langgraph_path": [
PULL,
AnyStr("child_1"),
],
"langgraph_step": 1,
"langgraph_triggers": [
"branch:to:child_1",
],
},
created_at=AnyStr(),
parent_config=None,
@@ -5633,12 +5567,6 @@ def test_doubly_nested_graph_state(
"parents": {"": AnyStr()},
"source": "loop",
"step": 0,
"thread_id": "1",
"langgraph_node": "child",
"langgraph_path": [PULL, AnyStr("child")],
"langgraph_step": 2,
"langgraph_triggers": ["branch:to:child"],
"langgraph_checkpoint_ns": AnyStr("child:"),
},
created_at=AnyStr(),
parent_config=None,
@@ -5658,14 +5586,15 @@ def test_doubly_nested_graph_state(
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=None,
interrupts=(),
)
# # resume
assert [c for c in app.stream(None, config, subgraphs=True)] == [
assert [
c for c in app.stream(None, config, subgraphs=True, checkpoint_during=False)
] == [
(
(AnyStr("child:"), AnyStr("child_1:")),
{"grandchild_2": {"my_key": "hi my value here and there"}},
@@ -5693,7 +5622,6 @@ def test_doubly_nested_graph_state(
"parents": {},
"source": "loop",
"step": 3,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=(
@@ -5727,7 +5655,6 @@ def test_doubly_nested_graph_state(
"parents": {},
"source": "loop",
"step": 3,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config={
@@ -5767,7 +5694,6 @@ def test_doubly_nested_graph_state(
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=None,
@@ -5794,12 +5720,6 @@ def test_doubly_nested_graph_state(
"source": "loop",
"step": 0,
"parents": {"": AnyStr()},
"thread_id": "1",
"langgraph_node": "child",
"langgraph_path": [PULL, AnyStr("child")],
"langgraph_step": 2,
"langgraph_triggers": ["branch:to:child"],
"langgraph_checkpoint_ns": AnyStr("child:"),
},
created_at=AnyStr(),
parent_config=None,
@@ -5849,15 +5769,6 @@ def test_doubly_nested_graph_state(
AnyStr("child:"): AnyStr(),
}
),
"thread_id": "1",
"langgraph_checkpoint_ns": AnyStr("child:"),
"langgraph_node": "child_1",
"langgraph_path": [
PULL,
AnyStr("child_1"),
],
"langgraph_step": 1,
"langgraph_triggers": ["branch:to:child_1"],
},
created_at=AnyStr(),
parent_config=None,
@@ -6040,7 +5951,9 @@ def test_send_react_interrupt(
foo_called = 0
graph = builder.compile(checkpointer=sync_checkpointer, interrupt_before=["foo"])
thread1 = {"configurable": {"thread_id": "2"}}
assert graph.invoke({"messages": [HumanMessage("hello")]}, thread1) == {
assert graph.invoke(
{"messages": [HumanMessage("hello")]}, thread1, checkpoint_during=False
) == {
"messages": [
_AnyIdHumanMessage(content="hello"),
_AnyIdAIMessage(
@@ -6089,7 +6002,6 @@ def test_send_react_interrupt(
"step": 1,
"source": "loop",
"parents": {},
"thread_id": "2",
},
created_at=AnyStr(),
parent_config=None,
@@ -6135,7 +6047,6 @@ def test_send_react_interrupt(
"step": 2,
"source": "update",
"parents": {},
"thread_id": "2",
},
created_at=AnyStr(),
parent_config=(
@@ -6164,7 +6075,9 @@ def test_send_react_interrupt(
foo_called = 0
graph = builder.compile(checkpointer=sync_checkpointer, interrupt_before=["foo"])
thread1 = {"configurable": {"thread_id": "3"}}
assert graph.invoke({"messages": [HumanMessage("hello")]}, thread1) == {
assert graph.invoke(
{"messages": [HumanMessage("hello")]}, thread1, checkpoint_during=False
) == {
"messages": [
_AnyIdHumanMessage(content="hello"),
_AnyIdAIMessage(
@@ -6213,7 +6126,6 @@ def test_send_react_interrupt(
"step": 1,
"source": "loop",
"parents": {},
"thread_id": "3",
},
created_at=AnyStr(),
parent_config=None,
@@ -6280,7 +6192,6 @@ def test_send_react_interrupt(
"step": 2,
"source": "update",
"parents": {},
"thread_id": "3",
},
created_at=AnyStr(),
parent_config=(
@@ -6430,7 +6341,9 @@ def test_send_react_interrupt_control(
foo_called = 0
graph = builder.compile(checkpointer=sync_checkpointer, interrupt_before=["foo"])
thread1 = {"configurable": {"thread_id": "2"}}
assert graph.invoke({"messages": [HumanMessage("hello")]}, thread1) == {
assert graph.invoke(
{"messages": [HumanMessage("hello")]}, thread1, checkpoint_during=False
) == {
"messages": [
_AnyIdHumanMessage(content="hello"),
_AnyIdAIMessage(
@@ -6479,7 +6392,6 @@ def test_send_react_interrupt_control(
"step": 1,
"source": "loop",
"parents": {},
"thread_id": "2",
},
created_at=AnyStr(),
parent_config=None,
@@ -6525,7 +6437,6 @@ def test_send_react_interrupt_control(
"step": 2,
"source": "update",
"parents": {},
"thread_id": "2",
},
created_at=AnyStr(),
parent_config=(
@@ -6686,7 +6597,11 @@ def test_weather_subgraph(
assert [
c
for c in graph.stream(
inputs, config=config, stream_mode="updates", subgraphs=True
inputs,
config=config,
stream_mode="updates",
subgraphs=True,
checkpoint_during=False,
)
] == [
((), {"router_node": {"route": "weather"}}),
@@ -6713,7 +6628,6 @@ def test_weather_subgraph(
"source": "loop",
"step": 1,
"parents": {},
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=None,
@@ -6770,7 +6684,11 @@ def test_weather_subgraph(
assert [
c
for c in graph.stream(
inputs, config=config, stream_mode="updates", subgraphs=True
inputs,
config=config,
stream_mode="updates",
subgraphs=True,
checkpoint_during=False,
)
] == [
((), {"router_node": {"route": "weather"}}),
@@ -6795,7 +6713,6 @@ def test_weather_subgraph(
"source": "loop",
"step": 1,
"parents": {},
"thread_id": "14",
},
created_at=AnyStr(),
parent_config=None,
@@ -6829,12 +6746,6 @@ def test_weather_subgraph(
"source": "loop",
"step": 1,
"parents": {"": AnyStr()},
"thread_id": "14",
"langgraph_node": "weather_graph",
"langgraph_path": [PULL, "weather_graph"],
"langgraph_step": 2,
"langgraph_triggers": ["branch:to:weather_graph"],
"langgraph_checkpoint_ns": AnyStr("weather_graph:"),
},
created_at=AnyStr(),
parent_config=None,
@@ -6874,7 +6785,6 @@ def test_weather_subgraph(
"source": "loop",
"step": 1,
"parents": {},
"thread_id": "14",
},
created_at=AnyStr(),
parent_config=None,
@@ -6909,14 +6819,6 @@ def test_weather_subgraph(
"step": 2,
"source": "update",
"parents": {"": AnyStr()},
"thread_id": "14",
"checkpoint_id": AnyStr(),
"checkpoint_ns": AnyStr("weather_graph:"),
"langgraph_node": "weather_graph",
"langgraph_path": [PULL, "weather_graph"],
"langgraph_step": 2,
"langgraph_triggers": ["branch:to:weather_graph"],
"langgraph_checkpoint_ns": AnyStr("weather_graph:"),
},
created_at=AnyStr(),
parent_config=(
+33 -133
View File
@@ -119,7 +119,6 @@ async def test_invoke_two_processes_in_out_interrupt(
"parents": {},
"source": "loop",
"step": 6,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=history[1].config,
@@ -140,7 +139,6 @@ async def test_invoke_two_processes_in_out_interrupt(
"parents": {},
"source": "loop",
"step": 5,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=history[2].config,
@@ -161,7 +159,6 @@ async def test_invoke_two_processes_in_out_interrupt(
"parents": {},
"source": "input",
"step": 4,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=history[3].config,
@@ -182,7 +179,6 @@ async def test_invoke_two_processes_in_out_interrupt(
"parents": {},
"source": "loop",
"step": 3,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=history[4].config,
@@ -203,7 +199,6 @@ async def test_invoke_two_processes_in_out_interrupt(
"parents": {},
"source": "input",
"step": 2,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=history[5].config,
@@ -224,7 +219,6 @@ async def test_invoke_two_processes_in_out_interrupt(
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=history[6].config,
@@ -245,7 +239,6 @@ async def test_invoke_two_processes_in_out_interrupt(
"parents": {},
"source": "loop",
"step": 0,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=history[7].config,
@@ -266,7 +259,6 @@ async def test_invoke_two_processes_in_out_interrupt(
"parents": {},
"source": "input",
"step": -1,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=None,
@@ -342,7 +334,6 @@ async def test_fork_always_re_runs_nodes(
"parents": {},
"source": "loop",
"step": 5,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=history[1].config,
@@ -363,7 +354,6 @@ async def test_fork_always_re_runs_nodes(
"parents": {},
"source": "loop",
"step": 4,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=history[2].config,
@@ -384,7 +374,6 @@ async def test_fork_always_re_runs_nodes(
"parents": {},
"source": "loop",
"step": 3,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=history[3].config,
@@ -405,7 +394,6 @@ async def test_fork_always_re_runs_nodes(
"parents": {},
"source": "loop",
"step": 2,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=history[4].config,
@@ -426,7 +414,6 @@ async def test_fork_always_re_runs_nodes(
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=history[5].config,
@@ -447,7 +434,6 @@ async def test_fork_always_re_runs_nodes(
"parents": {},
"source": "loop",
"step": 0,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=history[6].config,
@@ -468,7 +454,6 @@ async def test_fork_always_re_runs_nodes(
"parents": {},
"source": "input",
"step": -1,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=None,
@@ -698,7 +683,7 @@ async def test_conditional_graph_state(async_checkpointer: BaseCheckpointSaver)
assert [
c
async for c in app_w_interrupt.astream(
{"input": "what is weather in sf"}, config
{"input": "what is weather in sf"}, config, checkpoint_during=False
)
] == [
{
@@ -736,7 +721,6 @@ async def test_conditional_graph_state(async_checkpointer: BaseCheckpointSaver)
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "1",
},
parent_config=None,
interrupts=(),
@@ -776,7 +760,6 @@ async def test_conditional_graph_state(async_checkpointer: BaseCheckpointSaver)
"parents": {},
"source": "update",
"step": 2,
"thread_id": "1",
},
parent_config=(
[c async for c in app_w_interrupt.checkpointer.alist(config, limit=2)][
@@ -854,7 +837,6 @@ async def test_conditional_graph_state(async_checkpointer: BaseCheckpointSaver)
"parents": {},
"source": "update",
"step": 5,
"thread_id": "1",
},
parent_config=(
[c async for c in app_w_interrupt.checkpointer.alist(config, limit=2)][
@@ -876,7 +858,7 @@ async def test_conditional_graph_state(async_checkpointer: BaseCheckpointSaver)
assert [
c
async for c in app_w_interrupt.astream(
{"input": "what is weather in sf"}, config
{"input": "what is weather in sf"}, config, checkpoint_during=False
)
] == [
{
@@ -912,7 +894,6 @@ async def test_conditional_graph_state(async_checkpointer: BaseCheckpointSaver)
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "2",
},
parent_config=None,
interrupts=(),
@@ -952,7 +933,6 @@ async def test_conditional_graph_state(async_checkpointer: BaseCheckpointSaver)
"parents": {},
"source": "update",
"step": 2,
"thread_id": "2",
},
parent_config=[
c async for c in app_w_interrupt.checkpointer.alist(config, limit=2)
@@ -1028,7 +1008,6 @@ async def test_conditional_graph_state(async_checkpointer: BaseCheckpointSaver)
"parents": {},
"source": "update",
"step": 5,
"thread_id": "2",
},
parent_config=[
c async for c in app_w_interrupt.checkpointer.alist(config, limit=2)
@@ -1595,7 +1574,9 @@ async def test_state_graph_packets(async_checkpointer: BaseCheckpointSaver) -> N
assert [
c
async for c in app_w_interrupt.astream(
{"messages": HumanMessage(content="what is weather in sf")}, config
{"messages": HumanMessage(content="what is weather in sf")},
config,
checkpoint_during=False,
)
] == [
{
@@ -1647,7 +1628,6 @@ async def test_state_graph_packets(async_checkpointer: BaseCheckpointSaver) -> N
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "1",
},
parent_config=None,
interrupts=(),
@@ -1685,7 +1665,6 @@ async def test_state_graph_packets(async_checkpointer: BaseCheckpointSaver) -> N
"parents": {},
"source": "update",
"step": 2,
"thread_id": "1",
},
parent_config=(
[c async for c in app_w_interrupt.checkpointer.alist(config, limit=2)][
@@ -1778,7 +1757,6 @@ async def test_state_graph_packets(async_checkpointer: BaseCheckpointSaver) -> N
"parents": {},
"source": "loop",
"step": 4,
"thread_id": "1",
},
parent_config=(
[c async for c in app_w_interrupt.checkpointer.alist(config, limit=2)][
@@ -1826,7 +1804,6 @@ async def test_state_graph_packets(async_checkpointer: BaseCheckpointSaver) -> N
"parents": {},
"source": "update",
"step": 5,
"thread_id": "1",
},
parent_config=(
[c async for c in app_w_interrupt.checkpointer.alist(config, limit=2)][
@@ -1848,7 +1825,9 @@ async def test_state_graph_packets(async_checkpointer: BaseCheckpointSaver) -> N
assert [
c
async for c in app_w_interrupt.astream(
{"messages": HumanMessage(content="what is weather in sf")}, config
{"messages": HumanMessage(content="what is weather in sf")},
config,
checkpoint_during=False,
)
] == [
{
@@ -1894,7 +1873,6 @@ async def test_state_graph_packets(async_checkpointer: BaseCheckpointSaver) -> N
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "2",
},
parent_config=None,
interrupts=(),
@@ -1932,7 +1910,6 @@ async def test_state_graph_packets(async_checkpointer: BaseCheckpointSaver) -> N
"parents": {},
"source": "update",
"step": 2,
"thread_id": "2",
},
parent_config=(
[c async for c in app_w_interrupt.checkpointer.alist(config, limit=2)][
@@ -2025,7 +2002,6 @@ async def test_state_graph_packets(async_checkpointer: BaseCheckpointSaver) -> N
"parents": {},
"source": "loop",
"step": 4,
"thread_id": "2",
},
parent_config=(
[c async for c in app_w_interrupt.checkpointer.alist(config, limit=2)][
@@ -2073,7 +2049,6 @@ async def test_state_graph_packets(async_checkpointer: BaseCheckpointSaver) -> N
"parents": {},
"source": "update",
"step": 5,
"thread_id": "2",
},
parent_config=(
[c async for c in app_w_interrupt.checkpointer.alist(config, limit=2)][
@@ -2279,7 +2254,9 @@ async def test_message_graph(async_checkpointer: BaseCheckpointSaver) -> None:
assert [
c
async for c in app_w_interrupt.astream(
HumanMessage(content="what is weather in sf"), config
HumanMessage(content="what is weather in sf"),
config,
checkpoint_during=False,
)
] == [
{
@@ -2322,7 +2299,6 @@ async def test_message_graph(async_checkpointer: BaseCheckpointSaver) -> None:
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "1",
},
parent_config=None,
interrupts=(),
@@ -2358,7 +2334,6 @@ async def test_message_graph(async_checkpointer: BaseCheckpointSaver) -> None:
"parents": {},
"source": "update",
"step": 2,
"thread_id": "1",
},
parent_config=(
[c async for c in app_w_interrupt.checkpointer.alist(config, limit=2)][
@@ -2434,7 +2409,6 @@ async def test_message_graph(async_checkpointer: BaseCheckpointSaver) -> None:
"parents": {},
"source": "loop",
"step": 4,
"thread_id": "1",
},
parent_config=(
[c async for c in app_w_interrupt.checkpointer.alist(config, limit=2)][
@@ -2480,7 +2454,6 @@ async def test_message_graph(async_checkpointer: BaseCheckpointSaver) -> None:
"parents": {},
"source": "update",
"step": 5,
"thread_id": "1",
},
parent_config=(
[c async for c in app_w_interrupt.checkpointer.alist(config, limit=2)][
@@ -2766,7 +2739,7 @@ async def test_nested_graph_state(async_checkpointer: BaseCheckpointSaver) -> No
app = graph.compile(checkpointer=async_checkpointer)
config = {"configurable": {"thread_id": "1"}}
await app.ainvoke({"my_key": "my value"}, config, debug=True)
await app.ainvoke({"my_key": "my value"}, config, checkpoint_during=False)
# test state w/ nested subgraph state (right after interrupt)
# first get_state without subgraph state
expected = StateSnapshot(
@@ -2791,7 +2764,6 @@ async def test_nested_graph_state(async_checkpointer: BaseCheckpointSaver) -> No
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=None,
@@ -2835,12 +2807,6 @@ async def test_nested_graph_state(async_checkpointer: BaseCheckpointSaver) -> No
},
"source": "loop",
"step": 1,
"thread_id": "1",
"langgraph_node": "inner",
"langgraph_path": [PULL, "inner"],
"langgraph_step": 2,
"langgraph_triggers": ["branch:to:inner"],
"langgraph_checkpoint_ns": AnyStr("inner:"),
},
created_at=AnyStr(),
parent_config=None,
@@ -2860,7 +2826,6 @@ async def test_nested_graph_state(async_checkpointer: BaseCheckpointSaver) -> No
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=None,
@@ -2894,12 +2859,6 @@ async def test_nested_graph_state(async_checkpointer: BaseCheckpointSaver) -> No
"source": "loop",
"step": 1,
"parents": {"": AnyStr()},
"thread_id": "1",
"langgraph_node": "inner",
"langgraph_path": [PULL, "inner"],
"langgraph_step": 2,
"langgraph_triggers": ["branch:to:inner"],
"langgraph_checkpoint_ns": AnyStr("inner:"),
},
created_at=AnyStr(),
parent_config=None,
@@ -2911,7 +2870,7 @@ async def test_nested_graph_state(async_checkpointer: BaseCheckpointSaver) -> No
assert child_history == expected_child_history
# resume
await app.ainvoke(None, config, debug=True)
await app.ainvoke(None, config, checkpoint_during=False)
# test state w/ nested subgraph state (after resuming from interrupt)
assert await app.aget_state(config) == StateSnapshot(
values={"my_key": "hi my value here and there and back again"},
@@ -2928,7 +2887,6 @@ async def test_nested_graph_state(async_checkpointer: BaseCheckpointSaver) -> No
"parents": {},
"source": "loop",
"step": 3,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=(
@@ -2960,7 +2918,6 @@ async def test_nested_graph_state(async_checkpointer: BaseCheckpointSaver) -> No
"parents": {},
"source": "loop",
"step": 3,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=(
@@ -3002,7 +2959,6 @@ async def test_nested_graph_state(async_checkpointer: BaseCheckpointSaver) -> No
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=None,
@@ -3071,7 +3027,10 @@ async def test_doubly_nested_graph_state(
# test invoke w/ nested interrupt
config = {"configurable": {"thread_id": "1"}}
assert [
c async for c in app.astream({"my_key": "my value"}, config, subgraphs=True)
c
async for c in app.astream(
{"my_key": "my value"}, config, subgraphs=True, checkpoint_during=False
)
] == [
((), {"parent_1": {"my_key": "hi my value"}}),
(
@@ -3109,7 +3068,6 @@ async def test_doubly_nested_graph_state(
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=None,
@@ -3146,15 +3104,9 @@ async def test_doubly_nested_graph_state(
}
},
metadata={
"langgraph_checkpoint_ns": AnyStr("child:"),
"langgraph_node": "child",
"langgraph_path": ["__pregel_pull", "child"],
"langgraph_step": 2,
"langgraph_triggers": ["branch:to:child"],
"parents": {"": AnyStr()},
"source": "loop",
"step": 0,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=None,
@@ -3194,14 +3146,6 @@ async def test_doubly_nested_graph_state(
),
"source": "loop",
"step": 1,
"thread_id": "1",
"langgraph_checkpoint_ns": AnyStr("child:"),
"langgraph_node": "child_1",
"langgraph_path": [PULL, AnyStr("child_1")],
"langgraph_step": 1,
"langgraph_triggers": [
"branch:to:child_1",
],
},
created_at=AnyStr(),
parent_config=None,
@@ -3257,17 +3201,6 @@ async def test_doubly_nested_graph_state(
),
"source": "loop",
"step": 1,
"thread_id": "1",
"langgraph_checkpoint_ns": AnyStr("child:"),
"langgraph_node": "child_1",
"langgraph_path": [
PULL,
AnyStr("child_1"),
],
"langgraph_step": 1,
"langgraph_triggers": [
"branch:to:child_1",
],
},
created_at=AnyStr(),
parent_config=None,
@@ -3290,14 +3223,6 @@ async def test_doubly_nested_graph_state(
"parents": {"": AnyStr()},
"source": "loop",
"step": 0,
"thread_id": "1",
"langgraph_node": "child",
"langgraph_path": [PULL, AnyStr("child")],
"langgraph_step": 2,
"langgraph_triggers": [
"branch:to:child",
],
"langgraph_checkpoint_ns": AnyStr("child:"),
},
created_at=AnyStr(),
parent_config=None,
@@ -3317,14 +3242,18 @@ async def test_doubly_nested_graph_state(
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=None,
interrupts=(),
)
# resume
assert [c async for c in app.astream(None, config, subgraphs=True)] == [
assert [
c
async for c in app.astream(
None, config, subgraphs=True, checkpoint_during=False
)
] == [
(
(AnyStr("child:"), AnyStr("child_1:")),
{"grandchild_2": {"my_key": "hi my value here and there"}},
@@ -3355,7 +3284,6 @@ async def test_doubly_nested_graph_state(
"parents": {},
"source": "loop",
"step": 3,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=(
@@ -3389,7 +3317,6 @@ async def test_doubly_nested_graph_state(
"parents": {},
"source": "loop",
"step": 3,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config={
@@ -3428,7 +3355,6 @@ async def test_doubly_nested_graph_state(
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=None,
@@ -3457,12 +3383,6 @@ async def test_doubly_nested_graph_state(
"source": "loop",
"step": 0,
"parents": {"": AnyStr()},
"thread_id": "1",
"langgraph_node": "child",
"langgraph_path": [PULL, AnyStr("child")],
"langgraph_step": 2,
"langgraph_triggers": ["branch:to:child"],
"langgraph_checkpoint_ns": AnyStr("child:"),
},
created_at=AnyStr(),
parent_config=None,
@@ -3514,17 +3434,6 @@ async def test_doubly_nested_graph_state(
AnyStr("child:"): AnyStr(),
}
),
"thread_id": "1",
"langgraph_checkpoint_ns": AnyStr("child:"),
"langgraph_node": "child_1",
"langgraph_path": [
PULL,
AnyStr("child_1"),
],
"langgraph_step": 1,
"langgraph_triggers": [
"branch:to:child_1",
],
},
created_at=AnyStr(),
parent_config=None,
@@ -3748,7 +3657,11 @@ async def test_weather_subgraph(
assert [
c
async for c in graph.astream(
inputs, config=config, stream_mode="updates", subgraphs=True
inputs,
config=config,
stream_mode="updates",
subgraphs=True,
checkpoint_during=False,
)
] == [
((), {"router_node": {"route": "weather"}}),
@@ -3775,7 +3688,6 @@ async def test_weather_subgraph(
"source": "loop",
"step": 1,
"parents": {},
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=None,
@@ -3834,7 +3746,11 @@ async def test_weather_subgraph(
assert [
c
async for c in graph.astream(
inputs, config=config, stream_mode="updates", subgraphs=True
inputs,
config=config,
stream_mode="updates",
subgraphs=True,
checkpoint_during=False,
)
] == [
((), {"router_node": {"route": "weather"}}),
@@ -3859,7 +3775,6 @@ async def test_weather_subgraph(
"source": "loop",
"step": 1,
"parents": {},
"thread_id": "14",
},
created_at=AnyStr(),
parent_config=None,
@@ -3893,12 +3808,6 @@ async def test_weather_subgraph(
"source": "loop",
"step": 1,
"parents": {"": AnyStr()},
"thread_id": "14",
"langgraph_node": "weather_graph",
"langgraph_path": [PULL, "weather_graph"],
"langgraph_step": 2,
"langgraph_triggers": ["branch:to:weather_graph"],
"langgraph_checkpoint_ns": AnyStr("weather_graph:"),
},
created_at=AnyStr(),
parent_config=None,
@@ -3938,7 +3847,6 @@ async def test_weather_subgraph(
"source": "loop",
"step": 1,
"parents": {},
"thread_id": "14",
},
created_at=AnyStr(),
parent_config=None,
@@ -3974,14 +3882,6 @@ async def test_weather_subgraph(
"step": 2,
"source": "update",
"parents": {"": AnyStr()},
"thread_id": "14",
"checkpoint_id": AnyStr(),
"checkpoint_ns": AnyStr("weather_graph:"),
"langgraph_node": "weather_graph",
"langgraph_path": [PULL, "weather_graph"],
"langgraph_step": 2,
"langgraph_triggers": ["branch:to:weather_graph"],
"langgraph_checkpoint_ns": AnyStr("weather_graph:"),
},
created_at=AnyStr(),
parent_config=(
+22 -19
View File
@@ -904,7 +904,6 @@ def test_pending_writes_resume(
"parents": {},
"source": "loop",
"step": 0,
"thread_id": "1",
}
# get_state with checkpoint_id should not apply any pending writes
state = graph.get_state(state.config)
@@ -994,7 +993,6 @@ def test_pending_writes_resume(
"parents": {},
"step": 1,
"source": "loop",
"thread_id": "1",
},
parent_config={
"configurable": {
@@ -1042,7 +1040,6 @@ def test_pending_writes_resume(
"parents": {},
"step": 0,
"source": "loop",
"thread_id": "1",
},
parent_config={
"configurable": {
@@ -1094,7 +1091,6 @@ def test_pending_writes_resume(
"parents": {},
"step": -1,
"source": "input",
"thread_id": "1",
},
parent_config=None,
pending_writes=UnsortedSequence(
@@ -2058,7 +2054,6 @@ def test_in_one_fan_out_state_graph_waiting_edge(
"parents": {},
"source": "update",
"step": 4,
"thread_id": "2",
},
parent_config=expected_parent_config,
interrupts=(),
@@ -2328,7 +2323,6 @@ def test_in_one_fan_out_state_graph_defer_node(
"parents": {},
"source": "update",
"step": 4,
"thread_id": "2",
},
parent_config=expected_parent_config,
interrupts=(),
@@ -3930,7 +3924,6 @@ def test_checkpoint_metadata(sync_checkpointer: BaseCheckpointSaver) -> None:
# assert that checkpoint metadata contains the run's configurable fields
chkpnt_metadata_1 = sync_checkpointer.get_tuple(config).metadata
assert chkpnt_metadata_1["thread_id"] == "1"
assert chkpnt_metadata_1["test_config_1"] == "foo"
assert chkpnt_metadata_1["test_config_2"] == "bar"
@@ -3939,7 +3932,6 @@ def test_checkpoint_metadata(sync_checkpointer: BaseCheckpointSaver) -> None:
# on how the graph is constructed.
chkpnt_tuples_1 = sync_checkpointer.list(config)
for chkpnt_tuple in chkpnt_tuples_1:
assert chkpnt_tuple.metadata["thread_id"] == "1"
assert chkpnt_tuple.metadata["test_config_1"] == "foo"
assert chkpnt_tuple.metadata["test_config_2"] == "bar"
@@ -3959,7 +3951,6 @@ def test_checkpoint_metadata(sync_checkpointer: BaseCheckpointSaver) -> None:
# assert that checkpoint metadata contains the run's configurable fields
chkpnt_metadata_2 = sync_checkpointer.get_tuple(config).metadata
assert chkpnt_metadata_2["thread_id"] == "2"
assert chkpnt_metadata_2["test_config_3"] == "foo"
assert chkpnt_metadata_2["test_config_4"] == "bar"
@@ -3977,7 +3968,6 @@ def test_checkpoint_metadata(sync_checkpointer: BaseCheckpointSaver) -> None:
# assert that checkpoint metadata contains the run's configurable fields
chkpnt_metadata_3 = sync_checkpointer.get_tuple(config).metadata
assert chkpnt_metadata_3["thread_id"] == "2"
assert chkpnt_metadata_3["test_config_3"] == "foo"
assert chkpnt_metadata_3["test_config_4"] == "bar"
@@ -3986,7 +3976,6 @@ def test_checkpoint_metadata(sync_checkpointer: BaseCheckpointSaver) -> None:
# on how the graph is constructed.
chkpnt_tuples_2 = sync_checkpointer.list(config)
for chkpnt_tuple in chkpnt_tuples_2:
assert chkpnt_tuple.metadata["thread_id"] == "2"
assert chkpnt_tuple.metadata["test_config_3"] == "foo"
assert chkpnt_tuple.metadata["test_config_4"] == "bar"
@@ -4822,7 +4811,9 @@ def test_parent_command(
config = {"configurable": {"thread_id": "1"}}
assert graph.invoke({"messages": [("user", "get user name")]}, config) == {
assert graph.invoke(
{"messages": [("user", "get user name")]}, config, checkpoint_during=False
) == {
"messages": [
_AnyIdHumanMessage(
content="get user name", additional_kwargs={}, response_metadata={}
@@ -4849,7 +4840,6 @@ def test_parent_command(
},
metadata={
"source": "loop",
"thread_id": "1",
"step": 1,
"parents": {},
},
@@ -4921,7 +4911,7 @@ def test_interrupt_multiple(sync_checkpointer: BaseCheckpointSaver):
assert [
event
for event in graph.stream(
Command(resume="answer 1", update={"my_key": "foofoo"}), thread1
Command(resume="answer 1", update={"my_key": " foofoo "}), thread1
)
] == [
{
@@ -4936,8 +4926,14 @@ def test_interrupt_multiple(sync_checkpointer: BaseCheckpointSaver):
}
]
assert [event for event in graph.stream(Command(resume="answer 2"), thread1)] == [
{"node": {"my_key": "answer 1 answer 2"}},
assert [
event
for event in graph.stream(
Command(resume="answer 2"), thread1, stream_mode="values"
)
] == [
{"my_key": "DE foofoo "},
{"my_key": "DE foofoo answer 1 answer 2"},
]
@@ -5545,7 +5541,10 @@ def test_falsy_return_from_task(sync_checkpointer: BaseCheckpointSaver):
configurable = {"configurable": {"thread_id": uuid.uuid4()}}
assert [
chunk for chunk in graph.stream({"a": 5}, configurable, stream_mode="debug")
chunk
for chunk in graph.stream(
{"a": 5}, configurable, stream_mode="debug", checkpoint_during=False
)
] == [
{
"payload": {
@@ -5647,7 +5646,12 @@ def test_falsy_return_from_task(sync_checkpointer: BaseCheckpointSaver):
]
assert [
c
for c in graph.stream(Command(resume="123"), configurable, stream_mode="debug")
for c in graph.stream(
Command(resume="123"),
configurable,
stream_mode="debug",
checkpoint_during=False,
)
] == [
{
"payload": {
@@ -5662,7 +5666,6 @@ def test_falsy_return_from_task(sync_checkpointer: BaseCheckpointSaver):
"parents": {},
"source": "input",
"step": -1,
"thread_id": AnyStr(),
},
"next": [
"graph",
+30 -44
View File
@@ -274,7 +274,9 @@ async def test_checkpoint_put_after_cancellation() -> None:
thread1 = {"configurable": {"thread_id": "1"}}
# start the task
t = asyncio.create_task(graph.ainvoke({"hello": "world"}, thread1))
t = asyncio.create_task(
graph.ainvoke({"hello": "world"}, thread1, checkpoint_during=False)
)
# cancel after 0.2 seconds
await asyncio.sleep(0.2)
t.cancel()
@@ -340,7 +342,7 @@ async def test_checkpoint_put_after_cancellation_stream_anext() -> None:
thread1 = {"configurable": {"thread_id": "1"}}
# start the task
s = graph.astream({"hello": "world"}, thread1)
s = graph.astream({"hello": "world"}, thread1, checkpoint_during=False)
t = asyncio.create_task(s.__anext__())
# cancel after 0.2 seconds
await asyncio.sleep(0.2)
@@ -408,7 +410,11 @@ async def test_checkpoint_put_after_cancellation_stream_events_anext() -> None:
# start the task
s = graph.astream_events(
{"hello": "world"}, thread1, version="v2", include_names=["LangGraph"]
{"hello": "world"},
thread1,
version="v2",
include_names=["LangGraph"],
checkpoint_during=False,
)
# skip first event (happens right away)
await s.__anext__()
@@ -595,7 +601,9 @@ async def test_dynamic_interrupt(async_checkpointer: BaseCheckpointSaver) -> Non
# stop when about to enter node
assert [
c
async for c in tool_two.astream({"my_key": "value ⛰️", "market": "DE"}, thread1)
async for c in tool_two.astream(
{"my_key": "value ⛰️", "market": "DE"}, thread1, checkpoint_during=False
)
] == [
{
"__interrupt__": (
@@ -612,7 +620,6 @@ async def test_dynamic_interrupt(async_checkpointer: BaseCheckpointSaver) -> Non
"parents": {},
"source": "loop",
"step": 0,
"thread_id": "1",
},
]
tup = await tool_two.checkpointer.aget_tuple(thread1)
@@ -639,7 +646,6 @@ async def test_dynamic_interrupt(async_checkpointer: BaseCheckpointSaver) -> Non
"parents": {},
"source": "loop",
"step": 0,
"thread_id": "1",
},
parent_config=None,
interrupts=(
@@ -665,7 +671,6 @@ async def test_dynamic_interrupt(async_checkpointer: BaseCheckpointSaver) -> Non
"parents": {},
"source": "update",
"step": 1,
"thread_id": "1",
},
parent_config=(
[c async for c in tool_two.checkpointer.alist(thread1, limit=2)][-1].config
@@ -768,7 +773,9 @@ async def test_dynamic_interrupt_subgraph(
# stop when about to enter node
assert [
c
async for c in tool_two.astream({"my_key": "value ⛰️", "market": "DE"}, thread1)
async for c in tool_two.astream(
{"my_key": "value ⛰️", "market": "DE"}, thread1, checkpoint_during=False
)
] == [
{
"__interrupt__": (
@@ -785,7 +792,6 @@ async def test_dynamic_interrupt_subgraph(
"parents": {},
"source": "loop",
"step": 0,
"thread_id": "1",
},
]
tup = await tool_two.checkpointer.aget_tuple(thread1)
@@ -818,7 +824,6 @@ async def test_dynamic_interrupt_subgraph(
"parents": {},
"source": "loop",
"step": 0,
"thread_id": "1",
},
parent_config=None,
interrupts=(
@@ -844,7 +849,6 @@ async def test_dynamic_interrupt_subgraph(
"parents": {},
"source": "update",
"step": 1,
"thread_id": "1",
},
parent_config=(
[c async for c in tool_two.checkpointer.alist(thread1root, limit=2)][
@@ -946,7 +950,9 @@ async def test_copy_checkpoint(async_checkpointer: BaseCheckpointSaver) -> None:
# flow: interrupt -> clear tasks
thread1 = {"configurable": {"thread_id": "1"}}
# stop when about to enter node
assert await tool_two.ainvoke({"my_key": "value ⛰️", "market": "DE"}, thread1) == {
assert await tool_two.ainvoke(
{"my_key": "value ⛰️", "market": "DE"}, thread1, checkpoint_during=False
) == {
"my_key": "value ⛰️ one",
"market": "DE",
"__interrupt__": [
@@ -963,7 +969,6 @@ async def test_copy_checkpoint(async_checkpointer: BaseCheckpointSaver) -> None:
"parents": {},
"source": "loop",
"step": 0,
"thread_id": "1",
},
]
@@ -1000,7 +1005,6 @@ async def test_copy_checkpoint(async_checkpointer: BaseCheckpointSaver) -> None:
"parents": {},
"source": "loop",
"step": 0,
"thread_id": "1",
},
parent_config=None,
interrupts=(
@@ -1039,7 +1043,6 @@ async def test_copy_checkpoint(async_checkpointer: BaseCheckpointSaver) -> None:
"parents": {},
"source": "fork",
"step": 1,
"thread_id": "1",
},
parent_config=(
[c async for c in tool_two.checkpointer.alist(thread1, limit=2)][-1].config
@@ -1217,7 +1220,6 @@ async def test_cancel_graph_astream(async_checkpointer: BaseCheckpointSaver) ->
"parents": {},
"source": "loop",
"step": 0,
"thread_id": "1",
}
@@ -1292,7 +1294,6 @@ async def test_cancel_graph_astream_events_v2(
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "2",
}
@@ -1815,7 +1816,6 @@ async def test_pending_writes_resume(
"parents": {},
"source": "loop",
"step": 0,
"thread_id": "1",
}
# get_state with checkpoint_id should not apply any pending writes
state = await graph.aget_state(state.config)
@@ -1905,7 +1905,6 @@ async def test_pending_writes_resume(
"parents": {},
"step": 1,
"source": "loop",
"thread_id": "1",
},
parent_config={
"configurable": {
@@ -1953,7 +1952,6 @@ async def test_pending_writes_resume(
"parents": {},
"step": 0,
"source": "loop",
"thread_id": "1",
},
parent_config={
"configurable": {
@@ -2001,7 +1999,6 @@ async def test_pending_writes_resume(
"parents": {},
"step": -1,
"source": "input",
"thread_id": "1",
},
parent_config=None,
pending_writes=UnsortedSequence(
@@ -2601,7 +2598,6 @@ async def test_send_dedupe_on_resume(
},
metadata={
"source": "loop",
"thread_id": "1",
"step": 4,
"parents": {},
},
@@ -2637,7 +2633,6 @@ async def test_send_dedupe_on_resume(
},
metadata={
"source": "loop",
"thread_id": "1",
"step": 3,
"parents": {},
},
@@ -2680,7 +2675,6 @@ async def test_send_dedupe_on_resume(
},
metadata={
"source": "loop",
"thread_id": "1",
"step": 2,
"parents": {},
},
@@ -2735,7 +2729,6 @@ async def test_send_dedupe_on_resume(
},
metadata={
"source": "loop",
"thread_id": "1",
"step": 1,
"parents": {},
},
@@ -2790,7 +2783,6 @@ async def test_send_dedupe_on_resume(
},
metadata={
"source": "loop",
"thread_id": "1",
"step": 0,
"parents": {},
},
@@ -2827,7 +2819,6 @@ async def test_send_dedupe_on_resume(
},
metadata={
"source": "input",
"thread_id": "1",
"step": -1,
"parents": {},
},
@@ -2957,7 +2948,9 @@ async def test_send_react_interrupt(async_checkpointer: BaseCheckpointSaver) ->
foo_called = 0
graph = builder.compile(checkpointer=async_checkpointer, interrupt_before=["foo"])
thread1 = {"configurable": {"thread_id": "2"}}
assert await graph.ainvoke({"messages": [HumanMessage("hello")]}, thread1) == {
assert await graph.ainvoke(
{"messages": [HumanMessage("hello")]}, thread1, checkpoint_during=False
) == {
"messages": [
_AnyIdHumanMessage(content="hello"),
_AnyIdAIMessage(
@@ -3006,7 +2999,6 @@ async def test_send_react_interrupt(async_checkpointer: BaseCheckpointSaver) ->
"step": 1,
"source": "loop",
"parents": {},
"thread_id": "2",
},
created_at=AnyStr(),
parent_config=None,
@@ -3052,7 +3044,6 @@ async def test_send_react_interrupt(async_checkpointer: BaseCheckpointSaver) ->
"step": 2,
"source": "update",
"parents": {},
"thread_id": "2",
},
created_at=AnyStr(),
parent_config=(
@@ -3081,7 +3072,9 @@ async def test_send_react_interrupt(async_checkpointer: BaseCheckpointSaver) ->
foo_called = 0
graph = builder.compile(checkpointer=async_checkpointer, interrupt_before=["foo"])
thread1 = {"configurable": {"thread_id": "3"}}
assert await graph.ainvoke({"messages": [HumanMessage("hello")]}, thread1) == {
assert await graph.ainvoke(
{"messages": [HumanMessage("hello")]}, thread1, checkpoint_during=False
) == {
"messages": [
_AnyIdHumanMessage(content="hello"),
_AnyIdAIMessage(
@@ -3130,7 +3123,6 @@ async def test_send_react_interrupt(async_checkpointer: BaseCheckpointSaver) ->
"step": 1,
"source": "loop",
"parents": {},
"thread_id": "3",
},
created_at=AnyStr(),
parent_config=None,
@@ -3197,7 +3189,6 @@ async def test_send_react_interrupt(async_checkpointer: BaseCheckpointSaver) ->
"step": 2,
"source": "update",
"parents": {},
"thread_id": "3",
},
created_at=AnyStr(),
parent_config=(
@@ -3346,7 +3337,9 @@ async def test_send_react_interrupt_control(
foo_called = 0
graph = builder.compile(checkpointer=async_checkpointer, interrupt_before=["foo"])
thread1 = {"configurable": {"thread_id": "2"}}
assert await graph.ainvoke({"messages": [HumanMessage("hello")]}, thread1) == {
assert await graph.ainvoke(
{"messages": [HumanMessage("hello")]}, thread1, checkpoint_during=False
) == {
"messages": [
_AnyIdHumanMessage(content="hello"),
_AnyIdAIMessage(
@@ -3395,7 +3388,6 @@ async def test_send_react_interrupt_control(
"step": 1,
"source": "loop",
"parents": {},
"thread_id": "2",
},
created_at=AnyStr(),
parent_config=None,
@@ -3441,7 +3433,6 @@ async def test_send_react_interrupt_control(
"step": 2,
"source": "update",
"parents": {},
"thread_id": "2",
},
created_at=AnyStr(),
parent_config=(
@@ -4387,7 +4378,6 @@ async def test_in_one_fan_out_state_graph_waiting_edge_custom_state_class(
"parents": {},
"source": "loop",
"step": 4,
"thread_id": "1",
},
created_at=AnyStr(),
parent_config=(
@@ -5646,7 +5636,6 @@ async def test_checkpoint_metadata(async_checkpointer: BaseCheckpointSaver) -> N
# assert that checkpoint metadata contains the run's configurable fields
chkpnt_metadata_1 = (await async_checkpointer.aget_tuple(config)).metadata
assert chkpnt_metadata_1["thread_id"] == "1"
assert chkpnt_metadata_1["test_config_1"] == "foo"
assert chkpnt_metadata_1["test_config_2"] == "bar"
@@ -5655,7 +5644,6 @@ async def test_checkpoint_metadata(async_checkpointer: BaseCheckpointSaver) -> N
# on how the graph is constructed.
chkpnt_tuples_1 = async_checkpointer.alist(config)
async for chkpnt_tuple in chkpnt_tuples_1:
assert chkpnt_tuple.metadata["thread_id"] == "1"
assert chkpnt_tuple.metadata["test_config_1"] == "foo"
assert chkpnt_tuple.metadata["test_config_2"] == "bar"
@@ -5675,7 +5663,6 @@ async def test_checkpoint_metadata(async_checkpointer: BaseCheckpointSaver) -> N
# assert that checkpoint metadata contains the run's configurable fields
chkpnt_metadata_2 = (await async_checkpointer.aget_tuple(config)).metadata
assert chkpnt_metadata_2["thread_id"] == "2"
assert chkpnt_metadata_2["test_config_3"] == "foo"
assert chkpnt_metadata_2["test_config_4"] == "bar"
@@ -5693,7 +5680,6 @@ async def test_checkpoint_metadata(async_checkpointer: BaseCheckpointSaver) -> N
# assert that checkpoint metadata contains the run's configurable fields
chkpnt_metadata_3 = (await async_checkpointer.aget_tuple(config)).metadata
assert chkpnt_metadata_3["thread_id"] == "2"
assert chkpnt_metadata_3["test_config_3"] == "foo"
assert chkpnt_metadata_3["test_config_4"] == "bar"
@@ -5702,7 +5688,6 @@ async def test_checkpoint_metadata(async_checkpointer: BaseCheckpointSaver) -> N
# on how the graph is constructed.
chkpnt_tuples_2 = async_checkpointer.alist(config)
async for chkpnt_tuple in chkpnt_tuples_2:
assert chkpnt_tuple.metadata["thread_id"] == "2"
assert chkpnt_tuple.metadata["test_config_3"] == "foo"
assert chkpnt_tuple.metadata["test_config_4"] == "bar"
@@ -6110,7 +6095,9 @@ async def test_parent_command(
config = {"configurable": {"thread_id": "1"}}
assert await graph.ainvoke({"messages": [("user", "get user name")]}, config) == {
assert await graph.ainvoke(
{"messages": [("user", "get user name")]}, config, checkpoint_during=False
) == {
"messages": [
_AnyIdHumanMessage(
content="get user name", additional_kwargs={}, response_metadata={}
@@ -6139,7 +6126,6 @@ async def test_parent_command(
},
metadata={
"source": "loop",
"thread_id": "1",
"step": 1,
"parents": {},
},
+19 -1
View File
@@ -1,6 +1,6 @@
from dataclasses import dataclass
from operator import add
from typing import Annotated, Any
from typing import Annotated, Any, Union
from langchain_core.runnables import RunnableConfig
from pydantic import BaseModel
@@ -103,3 +103,21 @@ def test_input_state_specified() -> None:
new_graph.invoke({"something": 1})
new_graph.invoke({"something": 2, "info": ["hello", "world"]}) # type: ignore[arg-type]
def test_invokeable_node_signature() -> None:
class State(TypedDict):
info: Annotated[list[str], add]
graph_builder = StateGraph(State)
class RunnableIsh:
def invoke(
self,
input: State,
config: Union[RunnableConfig, None] = None,
**kwargs: Any,
) -> dict[str, str]:
return {}
graph_builder.add_node("runnable", RunnableIsh())
-2
View File
@@ -91,7 +91,6 @@ def test_no_prompt(sync_checkpointer: BaseCheckpointSaver, version: str) -> None
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "123",
}
assert saved.pending_writes == []
@@ -118,7 +117,6 @@ async def test_no_prompt_async(async_checkpointer: BaseCheckpointSaver) -> None:
"parents": {},
"source": "loop",
"step": 1,
"thread_id": "123",
}
assert saved.pending_writes == []
+1 -1
View File
@@ -320,7 +320,7 @@ wheels = [
[[package]]
name = "langgraph"
version = "0.5.0rc0"
version = "0.5.0rc1"
source = { editable = "../langgraph" }
dependencies = [
{ name = "langchain-core" },
+7 -1
View File
@@ -894,7 +894,13 @@ export function useStream<
if (event === "events") options.onLangChainEvent?.(data);
if (event === "debug") options.onDebugEvent?.(data);
if (event === "values") setStreamValues(data);
if (event === "values") {
if ("__interrupt__" in data) {
// don't update values on interrupt values event
continue;
}
setStreamValues(data);
}
if (event === "messages") {
const [serialized] = data;