Compare commits

..
Author SHA1 Message Date
Sydney Runkle ce6d8800d8 claude fix 2025-07-30 13:41:52 -04:00
19 changed files with 236 additions and 509 deletions
+12 -120
View File
@@ -127,79 +127,6 @@ 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/",
}
@@ -555,51 +482,16 @@ 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")
# 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)
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)
+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,16 +4,6 @@
---
## 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.
+2 -1
View File
@@ -383,4 +383,5 @@ 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 %}
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!
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!
{% endblock %}
@@ -0,0 +1,33 @@
{
"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
}
@@ -0,0 +1,33 @@
{
"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
}
@@ -0,0 +1,33 @@
{
"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
}
@@ -0,0 +1,33 @@
{
"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
}
@@ -0,0 +1,33 @@
{
"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
}
+42 -30
View File
@@ -72,7 +72,7 @@ from langgraph._internal._runnable import (
RunnableSeq,
coerce_to_runnable,
)
from langgraph._internal._typing import MISSING, DeprecatedKwargs
from langgraph._internal._typing import DeprecatedKwargs
from langgraph.cache.base import BaseCache
from langgraph.channels.base import BaseChannel
from langgraph.channels.topic import Topic
@@ -636,10 +636,7 @@ class Pregel(
name: str = "LangGraph",
**deprecated_kwargs: Unpack[DeprecatedKwargs],
) -> None:
if (
config_type := deprecated_kwargs.get("config_type"),
MISSING,
) is not MISSING:
if config_type := deprecated_kwargs.get("config_type"):
warnings.warn(
"`config_type` is deprecated and will be removed. Please use `context_schema` instead.",
category=LangGraphDeprecatedSinceV10,
@@ -3230,30 +3227,45 @@ def _output(
def _coerce_context(
context_schema: type[ContextT] | None, context: Any
context_schema: type[ContextT] | None, context: ContextT | dict[str, Any] | None
) -> ContextT | 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:
"""Coerce dict context to typed context schema (dataclass, pydantic model, etc.)"""
if context is None or context_schema is None:
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)
# 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
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "langgraph"
version = "0.6.2"
version = "0.6.1"
description = "Building stateful, multi-actor applications with LLMs"
authors = []
requires-python = ">=3.9"
+2 -19
View File
@@ -102,7 +102,6 @@ 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")
@@ -112,7 +111,7 @@ def test_config_schema_deprecation() -> None:
LangGraphDeprecatedSinceV10,
match="`config_schema` is deprecated. Use `get_context_jsonschema` for the relevant schema instead.",
):
assert graph.config_schema() is not None
graph.config_schema()
with pytest.warns(
LangGraphDeprecatedSinceV10,
@@ -121,21 +120,6 @@ 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")
@@ -144,7 +128,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.",
):
instance = Pregel(
Pregel(
nodes={
"one": chain,
},
@@ -156,7 +140,6 @@ 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,8 +1,6 @@
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
@@ -108,284 +106,3 @@ 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.2"
version = "0.6.1"
source = { editable = "." }
dependencies = [
{ name = "langchain-core" },
@@ -1433,7 +1433,7 @@ dev = [
[[package]]
name = "langgraph-prebuilt"
version = "0.6.2"
version = "0.6.1"
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 deprecated and will be removed. Please use `context_schema` instead.",
"`config_schema` is no longer supported. Use `context_schema` instead.",
category=LangGraphDeprecatedSinceV10,
)
if context_schema is None:
if context_schema is not 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.2"
version = "0.6.1"
description = "Library with high-level APIs for creating and executing LangGraph agents and tools."
authors = []
requires-python = ">=3.9"
-33
View File
@@ -1,33 +0,0 @@
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.2"
version = "0.6.1"
source = { editable = "../langgraph" }
dependencies = [
{ name = "langchain-core" },
@@ -460,7 +460,7 @@ dev = [
[[package]]
name = "langgraph-prebuilt"
version = "0.6.2"
version = "0.6.1"
source = { editable = "." }
dependencies = [
{ name = "langchain-core" },