Compare commits

..
Author SHA1 Message Date
Sam Crowder b2d39a1227 Update changelog via LangGraph Server Changelog Bot 2025-07-30 16:01:03 -07:00
Lauren Hirata SinghandGitHub 9d4dd066e5 docs: Revert "docs: Delete LGP nav Items from docs" (#5753)
Reverts langchain-ai/langgraph#5743
2025-07-30 18:15:19 -04:00
Lauren Hirata SinghandGitHub 81027b2b80 docs: fix redirects to external pages (#5752) 2025-07-30 16:52:13 -04:00
Sydney RunkleandGitHub 296bf5f75e fix(docs): context docs formatting (#5751) 2025-07-30 16:41:49 -04:00
Sydney RunkleandGitHub f43c806736 release(langgraph): 0.6.2 (#5750) 2025-07-30 20:35:27 +00:00
Sydney RunkleandGitHub 36f444dcb6 release(prebuilt): 0.6.2 (#5749) 2025-07-30 20:27:10 +00:00
Sydney RunkleandGitHub 781a115f92 fix(prebuilt): assign context_schema to config_schema with correct condition (#5746) 2025-07-30 20:08:45 +00:00
95d056e735 docs: Delete LGP nav Items from docs (#5743)
Remove the files and nav for LGP docs in the mkdocs site.

---------

Co-authored-by: Lauren Hirata Singh <lauren@langchain.dev>
2025-07-30 15:49:54 -04:00
open-swe[bot]GitHubopen-swe[bot] <open-swe@users.noreply.github.com>Sydney RunkleSydney RunkleEugene Yurtsev
64adb2bab3 feat: Add context coercion for LangGraph runtime (#5736)
Fixes #5735

Implement context coercion functionality for LangGraph runtime to
improve API usability.

Key changes:
- Added `_coerce_context` function in `pregel/main.py`
- Supports coercion for:
  - Pydantic BaseModel
  - Dataclasses
  - TypedDict
- Comprehensive test coverage added in `tests/test_runtime.py`
- Handles edge cases like None context and missing fields

The implementation allows users to pass dictionaries as context, which
will be automatically converted to the expected schema type, making the
API more flexible and user-friendly.

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: Sydney Runkle <sydneymarierunkle@gmail.com>
Co-authored-by: Sydney Runkle <54324534+sydney-runkle@users.noreply.github.com>
Co-authored-by: Eugene Yurtsev <eyurtsev@gmail.com>
2025-07-30 19:08:45 +00:00
Syed Baqar AbbasandGitHub e87f0fb0cd docs: The notebook redirects to a page that does not exist (#5638)
**docs: The notebook redirects to a page that does not exist**
- **Description:** The current documentation redirects to a file that
does not exist. I have removed the doc to avoid confusion.
  - **Issue:** Fixes #5637
2025-07-30 18:26:28 +00:00
c70f283f83 chore(examples): remove outdated HITL notebooks pointing to 404s (#5731)
**Description:**  
Removed 4 broken notebooks in `examples/human_in_the_loop/` that
referenced missing files (list below). These notebooks displayed
redirect messages but the new paths are either invalid or don’t contain
content.
Filenames:
1. examples/human_in_the_loop/dynamic_breakpoints.ipynb
2. examples/human_in_the_loop/edit-graph-state.ipynb
3. examples/human_in_the_loop/review-tool-calls.ipynb
4. examples/human_in_the_loop/time-travel.ipynb


**Issue:**  
Closes #5642

**Dependencies:**  
None

Co-authored-by: gawhaarya <gawhaneaarya@gmail.com>
2025-07-30 18:25:55 +00:00
Sydney RunkleandGitHub 1d6b0c36e9 docs: [LangGraph Server Changelog Bot] Changelog updates for new version(s) (#5732) 2025-07-30 14:20:35 -04:00
Sam Crowder 9f48bb0b61 Update changelog via LangGraph Server Changelog Bot 2025-07-30 08:39:34 -07:00
19 changed files with 509 additions and 236 deletions
+120 -12
View File
@@ -127,6 +127,79 @@ REDIRECT_MAP = {
"how-tos/human_in_the_loop/breakpoints.md": "how-tos/human_in_the_loop/add-human-in-the-loop.md",
"cloud/how-tos/human_in_the_loop_breakpoint.md": "cloud/how-tos/add-human-in-the-loop.md",
"how-tos/human_in_the_loop/edit-graph-state.ipynb": "how-tos/human_in_the_loop/time-travel.md",
# mintlify
# "tutorials/auth/getting_started.md": "https://docs.langchain.com/langgraph-platform/",
# "tutorials/auth/resource_auth.md": "https://docs.langchain.com/langgraph-platform/",
# "tutorials/auth/add_auth_server.md": "https://docs.langchain.com/langgraph-platform/",
# "how-tos/use-remote-graph.md": "https://docs.langchain.com/langgraph-platform/",
# "how-tos/autogen-integration.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/how-tos/use_stream_react.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/how-tos/generative_ui_react.md": "https://docs.langchain.com/langgraph-platform/",
# "concepts/langgraph_platform.md": "https://docs.langchain.com/langgraph-platform/",
# "concepts/langgraph_components.md": "https://docs.langchain.com/langgraph-platform/",
# "concepts/langgraph_server.md": "https://docs.langchain.com/langgraph-platform/",
# "concepts/langgraph_data_plane.md": "https://docs.langchain.com/langgraph-platform/",
# "concepts/langgraph_control_plane.md": "https://docs.langchain.com/langgraph-platform/",
# "concepts/langgraph_cli.md": "https://docs.langchain.com/langgraph-platform/",
# "concepts/langgraph_studio.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/how-tos/studio/quick_start.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/how-tos/invoke_studio.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/how-tos/studio/manage_assistants.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/how-tos/threads_studio.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/how-tos/iterate_graph_studio.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/how-tos/studio/run_evals.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/how-tos/clone_traces_studio.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/how-tos/datasets_studio.md": "https://docs.langchain.com/langgraph-platform/",
# "concepts/sdk.md": "https://docs.langchain.com/langgraph-platform/",
# "concepts/plans.md": "https://docs.langchain.com/langgraph-platform/",
# "concepts/application_structure.md": "https://docs.langchain.com/langgraph-platform/",
# "concepts/scalability_and_resilience.md": "https://docs.langchain.com/langgraph-platform/",
# "concepts/auth.md": "https://docs.langchain.com/langgraph-platform/",
# "how-tos/auth/custom_auth.md": "https://docs.langchain.com/langgraph-platform/",
# "how-tos/auth/openapi_security.md": "https://docs.langchain.com/langgraph-platform/",
# "concepts/assistants.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/how-tos/configuration_cloud.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/how-tos/use_threads.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/how-tos/background_run.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/how-tos/same-thread.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/how-tos/cron_jobs.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/how-tos/stateless_runs.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/how-tos/configurable_headers.md": "https://docs.langchain.com/langgraph-platform/",
# "concepts/double_texting.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/how-tos/interrupt_concurrent.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/how-tos/rollback_concurrent.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/how-tos/reject_concurrent.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/how-tos/enqueue_concurrent.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/concepts/webhooks.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/how-tos/webhooks.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/concepts/cron_jobs.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/how-tos/cron_jobs.md": "https://docs.langchain.com/langgraph-platform/",
# "how-tos/http/custom_lifespan.md": "https://docs.langchain.com/langgraph-platform/",
# "how-tos/http/custom_middleware.md": "https://docs.langchain.com/langgraph-platform/",
# "how-tos/http/custom_routes.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/concepts/data_storage_and_privacy.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/deployment/semantic_search.md": "https://docs.langchain.com/langgraph-platform/",
# "how-tos/ttl/configure_ttl.md": "https://docs.langchain.com/langgraph-platform/",
# "concepts/deployment_options.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/quick_start.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/deployment/setup.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/deployment/setup_pyproject.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/deployment/setup_javascript.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/deployment/custom_docker.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/deployment/graph_rebuild.md": "https://docs.langchain.com/langgraph-platform/",
# "concepts/langgraph_cloud.md": "https://docs.langchain.com/langgraph-platform/",
# "concepts/langgraph_self_hosted_data_plane.md": "https://docs.langchain.com/langgraph-platform/",
# "concepts/langgraph_self_hosted_control_plane.md": "https://docs.langchain.com/langgraph-platform/",
# "concepts/langgraph_standalone_container.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/deployment/cloud.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/deployment/self_hosted_data_plane.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/deployment/self_hosted_control_plane.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/deployment/standalone_container.md": "https://docs.langchain.com/langgraph-platform/",
# "concepts/server-mcp.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/how-tos/human_in_the_loop_time_travel.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/how-tos/add-human-in-the-loop.md": "https://docs.langchain.com/langgraph-platform/",
# "cloud/how-tos/streaming.md": "https://docs.langchain.com/langgraph-platform/",
}
@@ -482,16 +555,51 @@ def on_post_page(html: str, page: Page, config: MkDocsConfig) -> str:
def on_post_build(config):
use_directory_urls = config.get("use_directory_urls")
for page_old, page_new in REDIRECT_MAP.items():
# Convert .ipynb to .md for path calculation
page_old = page_old.replace(".ipynb", ".md")
page_new = page_new.replace(".ipynb", ".md")
page_new_before_hash, hash, suffix = page_new.partition("#")
old_html_path = File(page_old, "", "", use_directory_urls).dest_path.replace(
os.sep, "/"
)
new_html_path = File(page_new_before_hash, "", "", True).url
new_html_path = (
posixpath.relpath(new_html_path, start=posixpath.dirname(old_html_path))
+ hash
+ suffix
)
_write_html(config["site_dir"], old_html_path, new_html_path)
# Calculate the HTML path for the old page (whether it exists or not)
if use_directory_urls:
# With directory URLs: /path/to/page/ becomes /path/to/page/index.html
if page_old.endswith(".md"):
old_html_path = page_old[:-3] + "/index.html"
else:
old_html_path = page_old + "/index.html"
else:
# Without directory URLs: /path/to/page.md becomes /path/to/page.html
if page_old.endswith(".md"):
old_html_path = page_old[:-3] + ".html"
else:
old_html_path = page_old + ".html"
if isinstance(page_new, str) and page_new.startswith("http"):
# Handle external redirects
_write_html(config["site_dir"], old_html_path, page_new)
else:
# Handle internal redirects
page_new = page_new.replace(".ipynb", ".md")
page_new_before_hash, hash, suffix = page_new.partition("#")
# Try to get the new path using File class, but fallback to manual calculation
try:
new_html_path = File(page_new_before_hash, "", "", True).url
new_html_path = (
posixpath.relpath(new_html_path, start=posixpath.dirname(old_html_path))
+ hash
+ suffix
)
except:
# Fallback: calculate relative path manually
if use_directory_urls:
if page_new_before_hash.endswith(".md"):
new_html_path = page_new_before_hash[:-3] + "/"
else:
new_html_path = page_new_before_hash + "/"
else:
if page_new_before_hash.endswith(".md"):
new_html_path = page_new_before_hash[:-3] + ".html"
else:
new_html_path = page_new_before_hash + ".html"
new_html_path += hash + suffix
_write_html(config["site_dir"], old_html_path, new_html_path)
+4 -4
View File
@@ -3,11 +3,11 @@
**Context engineering** is the practice of building dynamic systems that provide the right information and tools, in the right format, so that an AI application can accomplish a task. Context can be characterized along two key dimensions:
1. By **mutability**:
- **Static context**: Immutable data that doesn't change during execution (e.g., user metadata, database connections, tools)
- **Dynamic context**: Mutable data that evolves as the application runs (e.g., conversation history, intermediate results, tool call observations)
- **Static context**: Immutable data that doesn't change during execution (e.g., user metadata, database connections, tools)
- **Dynamic context**: Mutable data that evolves as the application runs (e.g., conversation history, intermediate results, tool call observations)
2. By **lifetime**:
- **Runtime context**: Data scoped to a single run or invocation
- **Cross-conversation context**: Data that persists across multiple conversations or sessions
- **Runtime context**: Data scoped to a single run or invocation
- **Cross-conversation context**: Data that persists across multiple conversations or sessions
!!! tip "Runtime context vs LLM context"
@@ -4,6 +4,16 @@
---
## v0.2.113 (2025-07-30)
- Improved thread search pagination by refining response headers for better navigation and accuracy.
## v0.2.112 (2025-07-30)
- Ensured asynchronous handling for sync logging methods and added a linter to prevent future issues.
- Fixed an issue where JavaScript tasks were not populating correctly in graphs.
## v0.2.111 (2025-07-29)
- Started the heartbeat immediately upon connection to prevent JS graph streaming errors during long startups.
## v0.2.110 (2025-07-29)
- Added interrupts as default values for all operations except streams to maintain consistent behavior.
+1 -2
View File
@@ -383,5 +383,4 @@ extra_css:
- stylesheets/version_admonitions.css
- stylesheets/logos.css
- stylesheets/sticky_navigation.css
- stylesheets/agent_graph_widget.css
- stylesheets/agent_graph_widget.css
+1 -1
View File
@@ -360,5 +360,5 @@ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
{% endblock %}
{% block announce %}
Our <a href="https://academy.langchain.com/courses/ambient-agents/?utm_medium=internal&utm_source=docs&utm_campaign=q2-2025_ambient-agents_co" target="_blank">Building Ambient Agents with LangGraph</a> course is now available on LangChain Academy!
LangGraph Platform docs are moving! Find the LangGraph Platform docs at the new <a href="https://docs.langchain.com/langgraph-platform" target="_blank">LangChain Docs</a> site!
{% endblock %}
@@ -1,33 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "b3cec425",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/human_in_the_loop/dynamic_breakpoints.ipynb"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -1,33 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "4876215f",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/human_in_the_loop/edit-graph-state.ipynb"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.8"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -1,33 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "b162f1bd",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/human_in_the_loop/review-tool-calls.ipynb"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -1,33 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "84c5f6f1",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/how-tos/human_in_the_loop/time-travel.ipynb"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -1,33 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "5eb637a4",
"metadata": {},
"source": [
"This file has been moved to https://github.com/langchain-ai/langgraph/blob/main/docs/docs/tutorials/multi_agent/agent_supervisor.ipynb"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.9"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+30 -42
View File
@@ -72,7 +72,7 @@ from langgraph._internal._runnable import (
RunnableSeq,
coerce_to_runnable,
)
from langgraph._internal._typing import DeprecatedKwargs
from langgraph._internal._typing import MISSING, DeprecatedKwargs
from langgraph.cache.base import BaseCache
from langgraph.channels.base import BaseChannel
from langgraph.channels.topic import Topic
@@ -636,7 +636,10 @@ class Pregel(
name: str = "LangGraph",
**deprecated_kwargs: Unpack[DeprecatedKwargs],
) -> None:
if config_type := deprecated_kwargs.get("config_type"):
if (
config_type := deprecated_kwargs.get("config_type"),
MISSING,
) is not MISSING:
warnings.warn(
"`config_type` is deprecated and will be removed. Please use `context_schema` instead.",
category=LangGraphDeprecatedSinceV10,
@@ -3227,45 +3230,30 @@ def _output(
def _coerce_context(
context_schema: type[ContextT] | None, context: ContextT | dict[str, Any] | None
context_schema: type[ContextT] | None, context: Any
) -> ContextT | None:
"""Coerce dict context to typed context schema (dataclass, pydantic model, etc.)"""
if context is None or context_schema is None:
"""Coerce context input to the appropriate schema type.
If context is a dict and context_schema is a dataclass or pydantic model, we coerce.
Else, we return the context as-is.
Args:
context_schema: The schema type to coerce to (BaseModel, dataclass, or TypedDict)
context: The context value to coerce
Returns:
The coerced context value or None if context is None
"""
if context is None:
return None
if context_schema is None:
return context
# If context is a dict and schema is not a dict type, coerce it
if isinstance(context, dict):
from inspect import isclass
from typing_extensions import is_typeddict
from dataclasses import is_dataclass
from pydantic import BaseModel
# Check if the schema is a typed dict, dataclass, or pydantic model
if (
(isclass(context_schema) and issubclass(context_schema, BaseModel)) or
is_typeddict(context_schema) or
is_dataclass(context_schema)
):
try:
return context_schema(**context)
except Exception as e:
raise ValueError(
f"Failed to coerce context dict to {context_schema}: {e}"
) from e
# For non-dict contexts, check type compatibility (but avoid isinstance with TypedDict)
# If it's not a dict and the schema is a TypedDict, we can't do much validation
# For dataclasses and pydantic models, we can check type
if not isinstance(context, dict):
from inspect import isclass
from typing_extensions import is_typeddict
from dataclasses import is_dataclass
from pydantic import BaseModel
# Only check isinstance for non-TypedDict schemas
if not is_typeddict(context_schema):
if isclass(context_schema) and isinstance(context, context_schema):
return context
# Return as-is if no coercion is needed/possible
return context
schema_is_class = issubclass(context_schema, BaseModel) or is_dataclass(
context_schema
)
if isinstance(context, dict) and schema_is_class:
return context_schema(**context) # type: ignore[misc]
return cast(ContextT, context)
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "langgraph"
version = "0.6.1"
version = "0.6.2"
description = "Building stateful, multi-actor applications with LLMs"
authors = []
requires-python = ">=3.9"
+19 -2
View File
@@ -102,6 +102,7 @@ def test_config_schema_deprecation() -> None:
match="`config_schema` is deprecated and will be removed. Please use `context_schema` instead.",
):
builder = StateGraph(PlainState, config_schema=PlainState)
assert builder.context_schema == PlainState
builder.add_node("test_node", lambda state: state)
builder.set_entry_point("test_node")
@@ -111,7 +112,7 @@ def test_config_schema_deprecation() -> None:
LangGraphDeprecatedSinceV10,
match="`config_schema` is deprecated. Use `get_context_jsonschema` for the relevant schema instead.",
):
graph.config_schema()
assert graph.config_schema() is not None
with pytest.warns(
LangGraphDeprecatedSinceV10,
@@ -120,6 +121,21 @@ def test_config_schema_deprecation() -> None:
graph.get_config_jsonschema()
@pytest.mark.filterwarnings("ignore:`config_schema` is deprecated")
def test_config_schema_deprecation_on_entrypoint() -> None:
with pytest.warns(
LangGraphDeprecatedSinceV10,
match="`config_schema` is deprecated and will be removed. Please use `context_schema` instead.",
):
@entrypoint(config_schema=PlainState) # type: ignore[arg-type]
def my_entrypoint(state: PlainState) -> PlainState:
return state
assert my_entrypoint.context_schema == PlainState
assert my_entrypoint.config_schema() is not None
def test_config_type_deprecation_pregel(mocker: MockerFixture) -> None:
add_one = mocker.Mock(side_effect=lambda x: x + 1)
chain = NodeBuilder().subscribe_only("input").do(add_one).write_to("output")
@@ -128,7 +144,7 @@ def test_config_type_deprecation_pregel(mocker: MockerFixture) -> None:
LangGraphDeprecatedSinceV10,
match="`config_type` is deprecated and will be removed. Please use `context_schema` instead.",
):
Pregel(
instance = Pregel(
nodes={
"one": chain,
},
@@ -140,6 +156,7 @@ def test_config_type_deprecation_pregel(mocker: MockerFixture) -> None:
output_channels="output",
config_type=PlainState,
)
assert instance.context_schema == PlainState
@pytest.mark.filterwarnings("ignore:`interrupt_id` is deprecated. Use `id` instead.")
+283
View File
@@ -1,6 +1,8 @@
from dataclasses import dataclass
from typing import Any
import pytest
from pydantic import BaseModel, ValidationError
from typing_extensions import TypedDict
from langgraph.graph import END, START, StateGraph
@@ -106,3 +108,284 @@ def test_runtime_propogated_to_subgraph() -> None:
context = Context(username="Alice")
result = graph.invoke({}, context=context)
assert result == {"subgraph": "Alice!", "main": "Alice!"}
def test_context_coercion_dataclass() -> None:
"""Test that dict context is coerced to dataclass."""
@dataclass
class Context:
api_key: str
timeout: int = 30
class State(TypedDict):
message: str
def node_with_context(state: State, runtime: Runtime[Context]) -> dict[str, Any]:
return {
"message": f"api_key: {runtime.context.api_key}, timeout: {runtime.context.timeout}"
}
graph = StateGraph(state_schema=State, context_schema=Context)
graph.add_node("node", node_with_context)
graph.add_edge(START, "node")
graph.add_edge("node", END)
compiled = graph.compile()
# Test dict coercion with all fields
result = compiled.invoke(
{"message": "test"}, context={"api_key": "sk_test", "timeout": 60}
)
assert result == {"message": "api_key: sk_test, timeout: 60"}
# Test dict coercion with default field
result = compiled.invoke({"message": "test"}, context={"api_key": "sk_test2"})
assert result == {"message": "api_key: sk_test2, timeout: 30"}
# Test with actual dataclass instance (should still work)
result = compiled.invoke(
{"message": "test"}, context=Context(api_key="sk_test3", timeout=90)
)
assert result == {"message": "api_key: sk_test3, timeout: 90"}
def test_context_coercion_pydantic() -> None:
"""Test that dict context is coerced to Pydantic model."""
class Context(BaseModel):
api_key: str
timeout: int = 30
tags: list[str] = []
class State(TypedDict):
message: str
def node_with_context(state: State, runtime: Runtime[Context]) -> dict[str, Any]:
return {
"message": f"api_key: {runtime.context.api_key}, timeout: {runtime.context.timeout}, tags: {runtime.context.tags}"
}
graph = StateGraph(state_schema=State, context_schema=Context)
graph.add_node("node", node_with_context)
graph.add_edge(START, "node")
graph.add_edge("node", END)
compiled = graph.compile()
# Test dict coercion with all fields
result = compiled.invoke(
{"message": "test"},
context={"api_key": "sk_test", "timeout": 60, "tags": ["prod", "v2"]},
)
assert result == {"message": "api_key: sk_test, timeout: 60, tags: ['prod', 'v2']"}
# Test dict coercion with defaults
result = compiled.invoke({"message": "test"}, context={"api_key": "sk_test2"})
assert result == {"message": "api_key: sk_test2, timeout: 30, tags: []"}
# Test with actual Pydantic instance (should still work)
result = compiled.invoke(
{"message": "test"},
context=Context(api_key="sk_test3", timeout=90, tags=["test"]),
)
assert result == {"message": "api_key: sk_test3, timeout: 90, tags: ['test']"}
def test_context_coercion_typeddict() -> None:
"""Test that dict context with TypedDict schema passes through as-is."""
class Context(TypedDict):
api_key: str
timeout: int
class State(TypedDict):
message: str
def node_with_context(state: State, runtime: Runtime[Context]) -> dict[str, Any]:
# TypedDict context is just a dict at runtime
return {
"message": f"api_key: {runtime.context['api_key']}, timeout: {runtime.context['timeout']}"
}
graph = StateGraph(state_schema=State, context_schema=Context)
graph.add_node("node", node_with_context)
graph.add_edge(START, "node")
graph.add_edge("node", END)
compiled = graph.compile()
# Test dict passes through for TypedDict
result = compiled.invoke(
{"message": "test"}, context={"api_key": "sk_test", "timeout": 60}
)
assert result == {"message": "api_key: sk_test, timeout: 60"}
def test_context_coercion_none() -> None:
"""Test that None context is handled properly."""
@dataclass
class Context:
api_key: str
class State(TypedDict):
message: str
def node_without_context(state: State, runtime: Runtime[Context]) -> dict[str, Any]:
# Should be None when no context provided
return {"message": f"context is None: {runtime.context is None}"}
graph = StateGraph(state_schema=State, context_schema=Context)
graph.add_node("node", node_without_context)
graph.add_edge(START, "node")
graph.add_edge("node", END)
compiled = graph.compile()
# Test with None context
result = compiled.invoke({"message": "test"}, context=None)
assert result == {"message": "context is None: True"}
# Test without context parameter (defaults to None)
result = compiled.invoke({"message": "test"})
assert result == {"message": "context is None: True"}
def test_context_coercion_errors() -> None:
"""Test error handling for invalid context."""
@dataclass
class Context:
api_key: str # Required field
class State(TypedDict):
message: str
def node_with_context(state: State, runtime: Runtime[Context]) -> dict[str, Any]:
return {"message": "should not reach here"}
graph = StateGraph(state_schema=State, context_schema=Context)
graph.add_node("node", node_with_context)
graph.add_edge(START, "node")
graph.add_edge("node", END)
compiled = graph.compile()
# Test missing required field
with pytest.raises(TypeError):
compiled.invoke({"message": "test"}, context={"timeout": 60})
# Test invalid dict keys
with pytest.raises(TypeError):
compiled.invoke(
{"message": "test"}, context={"api_key": "test", "invalid_field": "value"}
)
@pytest.mark.anyio
async def test_context_coercion_async() -> None:
"""Test context coercion with async methods."""
@dataclass
class Context:
api_key: str
async_mode: bool = True
class State(TypedDict):
message: str
async def async_node(state: State, runtime: Runtime[Context]) -> dict[str, Any]:
return {
"message": f"async api_key: {runtime.context.api_key}, async_mode: {runtime.context.async_mode}"
}
graph = StateGraph(state_schema=State, context_schema=Context)
graph.add_node("node", async_node)
graph.add_edge(START, "node")
graph.add_edge("node", END)
compiled = graph.compile()
# Test dict coercion with ainvoke
result = await compiled.ainvoke(
{"message": "test"}, context={"api_key": "sk_async", "async_mode": False}
)
assert result == {"message": "async api_key: sk_async, async_mode: False"}
# Test dict coercion with astream
chunks = []
async for chunk in compiled.astream(
{"message": "test"}, context={"api_key": "sk_stream"}
):
chunks.append(chunk)
# Find the chunk with our node output
node_output = None
for chunk in chunks:
if "node" in chunk:
node_output = chunk["node"]
break
assert node_output == {"message": "async api_key: sk_stream, async_mode: True"}
def test_context_coercion_stream() -> None:
"""Test context coercion with sync stream method."""
@dataclass
class Context:
api_key: str
stream_mode: str = "default"
class State(TypedDict):
message: str
def node_with_context(state: State, runtime: Runtime[Context]) -> dict[str, Any]:
return {
"message": f"stream api_key: {runtime.context.api_key}, mode: {runtime.context.stream_mode}"
}
graph = StateGraph(state_schema=State, context_schema=Context)
graph.add_node("node", node_with_context)
graph.add_edge(START, "node")
graph.add_edge("node", END)
compiled = graph.compile()
# Test dict coercion with stream
chunks = []
for chunk in compiled.stream(
{"message": "test"}, context={"api_key": "sk_stream", "stream_mode": "fast"}
):
chunks.append(chunk)
# Find the chunk with our node output
node_output = None
for chunk in chunks:
if "node" in chunk:
node_output = chunk["node"]
break
assert node_output == {"message": "stream api_key: sk_stream, mode: fast"}
def test_context_coercion_pydantic_validation_errors() -> None:
"""Test that Pydantic validation errors are raised."""
class Context(BaseModel):
api_key: str
timeout: int
class State(TypedDict):
message: str
def node_with_context(state: State, runtime: Runtime[Context]) -> dict[str, Any]:
return {
"message": f"api_key: {runtime.context.api_key}, timeout: {runtime.context.timeout}"
}
graph = StateGraph(state_schema=State, context_schema=Context)
graph.add_node("node", node_with_context)
graph.add_edge(START, "node")
graph.add_edge("node", END)
compiled = graph.compile()
with pytest.raises(ValidationError):
compiled.invoke(
{"message": "test"}, context={"api_key": "sk_test", "timeout": "not_an_int"}
)
+2 -2
View File
@@ -1192,7 +1192,7 @@ wheels = [
[[package]]
name = "langgraph"
version = "0.6.1"
version = "0.6.2"
source = { editable = "." }
dependencies = [
{ name = "langchain-core" },
@@ -1433,7 +1433,7 @@ dev = [
[[package]]
name = "langgraph-prebuilt"
version = "0.6.1"
version = "0.6.2"
source = { editable = "../prebuilt" }
dependencies = [
{ name = "langchain-core" },
@@ -459,11 +459,11 @@ def create_react_agent(
config_schema := deprecated_kwargs.pop("config_schema", MISSING)
) is not MISSING:
warn(
"`config_schema` is no longer supported. Use `context_schema` instead.",
"`config_schema` is deprecated and will be removed. Please use `context_schema` instead.",
category=LangGraphDeprecatedSinceV10,
)
if context_schema is not None:
if context_schema is None:
context_schema = config_schema
if version not in ("v1", "v2"):
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "langgraph-prebuilt"
version = "0.6.1"
version = "0.6.2"
description = "Library with high-level APIs for creating and executing LangGraph agents and tools."
authors = []
requires-python = ">=3.9"
+33
View File
@@ -0,0 +1,33 @@
import pytest
from typing_extensions import TypedDict
from langgraph.prebuilt import create_react_agent
from langgraph.warnings import LangGraphDeprecatedSinceV10
from tests.model import FakeToolCallingModel
class Config(TypedDict):
model: str
@pytest.mark.filterwarnings("ignore:`config_schema` is deprecated")
@pytest.mark.filterwarnings("ignore:`get_config_jsonschema` is deprecated")
def test_config_schema_deprecation() -> None:
with pytest.warns(
LangGraphDeprecatedSinceV10,
match="`config_schema` is deprecated and will be removed. Please use `context_schema` instead.",
):
agent = create_react_agent(FakeToolCallingModel(), [], config_schema=Config)
assert agent.context_schema == Config
with pytest.warns(
LangGraphDeprecatedSinceV10,
match="`config_schema` is deprecated. Use `get_context_jsonschema` for the relevant schema instead.",
):
assert agent.config_schema() is not None
with pytest.warns(
LangGraphDeprecatedSinceV10,
match="`get_config_jsonschema` is deprecated. Use `get_context_jsonschema` instead.",
):
assert agent.get_config_jsonschema() is not None
+2 -2
View File
@@ -316,7 +316,7 @@ wheels = [
[[package]]
name = "langgraph"
version = "0.6.1"
version = "0.6.2"
source = { editable = "../langgraph" }
dependencies = [
{ name = "langchain-core" },
@@ -460,7 +460,7 @@ dev = [
[[package]]
name = "langgraph-prebuilt"
version = "0.6.1"
version = "0.6.2"
source = { editable = "." }
dependencies = [
{ name = "langchain-core" },