mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-19 22:25:44 +02:00
Compare commits
29
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8a9aec9d8 | ||
|
|
e05cef7732 | ||
|
|
ac328c3fd8 | ||
|
|
ebd7977936 | ||
|
|
a6381c32b0 | ||
|
|
294d346650 | ||
|
|
866c8009dc | ||
|
|
73bed2cf7c | ||
|
|
4cffe58065 | ||
|
|
e73964a971 | ||
|
|
de91f21f6b | ||
|
|
41eed326b8 | ||
|
|
946d23213d | ||
|
|
c78197a583 | ||
|
|
50756207ee | ||
|
|
903cec0cfa | ||
|
|
f63952595d | ||
|
|
4a252bd03a | ||
|
|
f0f329d9e1 | ||
|
|
77306c5142 | ||
|
|
eba18c3213 | ||
|
|
a1c856c088 | ||
|
|
92010f84ec | ||
|
|
3b8f3f9de3 | ||
|
|
6dcff8a839 | ||
|
|
666279a241 | ||
|
|
2172bc89ed | ||
|
|
4138ef9c43 | ||
|
|
0ff181b7ce |
+119
-122
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
@@ -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'
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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, we’ll show how to trigger a webhook after streaming a run.
|
||||
|
||||
@@ -25,36 +25,39 @@ In this guide, we’ll 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
|
||||
|
||||
|
||||
@@ -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 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. |
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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, it’s 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.
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
|
||||
Generated
+1
-1
@@ -308,7 +308,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-checkpoint"
|
||||
version = "2.0.26"
|
||||
version = "2.1.0"
|
||||
source = { editable = "../checkpoint" }
|
||||
dependencies = [
|
||||
{ name = "langchain-core" },
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Generated
+1
-1
@@ -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 ---
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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": {},
|
||||
},
|
||||
|
||||
@@ -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=(
|
||||
|
||||
@@ -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=(
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": {},
|
||||
},
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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 == []
|
||||
|
||||
|
||||
Generated
+1
-1
@@ -320,7 +320,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "langgraph"
|
||||
version = "0.5.0rc0"
|
||||
version = "0.5.0rc1"
|
||||
source = { editable = "../langgraph" }
|
||||
dependencies = [
|
||||
{ name = "langchain-core" },
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user