Compare commits

..
Author SHA1 Message Date
William Fu-Hinthorn 36d1e96398 chore: respect default values for schemas 2026-02-19 07:17:02 -08:00
William Fu-Hinthorn a2912cef67 chore: use defaults for channel values 2026-02-18 20:12:54 -08:00
Lauren Hirata SinghandGitHub c4f5861166 chore(docs): Add new redirects file(s) so we can update/add (#6778)
Adds redirects for all old LangGraph docs URLs to docs.langchain.com
using meta refresh and GitHub Pages
2026-02-18 14:35:38 -05:00
Parker J. RuleandGitHub 172238b2d5 fix(sdk-py): allow reset of config/context in assistants update (#6862)
This distinguishes an empty `config`/`context` (`{}`) from a null
`config`/`context`, and therefore allows resetting these fields.

(Easiest to test this E2E on the server side; will create a separate PR
for that.)
2026-02-18 18:48:07 +00:00
William FHandGitHub 095da17833 chore: state_updated_at sort by (#6857) 2026-02-18 10:41:23 -08:00
John KennedyandGitHub e931c68669 fix: actual fix for workflow (#6854)
Another attempt at fixing `_integration_test.yml`
2026-02-17 11:31:08 -08:00
John KennedyandGitHub 666c224c2d chore: bump orjson (#6852)
The orjson.dumps function in orjson thru 3.11.4 does not limit recursion
for deeply nested JSON documents.
2026-02-17 10:30:58 -08:00
William FHandGitHub 21a6f41e0a chore: add testpypi index (#6853) 2026-02-17 10:24:39 -08:00
32 changed files with 932 additions and 67 deletions
+5 -3
View File
@@ -31,6 +31,8 @@ jobs:
workdir: libs/cli/examples/graphs_reqs_b
tag: langgraph-test-d
name: "CLI integration test"
env:
HAS_LANGSMITH_API_KEY: ${{ secrets.LANGSMITH_API_KEY != '' }}
defaults:
run:
working-directory: libs/cli
@@ -58,7 +60,7 @@ jobs:
run: |
langgraph build -t ${{ matrix.example.tag }}
- name: Test service ${{ matrix.example.name }}
if: ${{ steps.changed-files.outputs.all && secrets.LANGSMITH_API_KEY != '' }}
if: ${{ steps.changed-files.outputs.all && env.HAS_LANGSMITH_API_KEY == 'true' }}
working-directory: ${{ matrix.example.workdir }}
env:
LANGSMITH_API_KEY: ${{ secrets.LANGSMITH_API_KEY }}
@@ -89,7 +91,7 @@ jobs:
run: |
langgraph build -t langgraph-test-g -c apps/agent/langgraph.json
- name: Test Python monorepo service
if: ${{ steps.changed-files.outputs.all && matrix.example.name == 'A' && secrets.LANGSMITH_API_KEY != '' }}
if: ${{ steps.changed-files.outputs.all && matrix.example.name == 'A' && env.HAS_LANGSMITH_API_KEY == 'true' }}
working-directory: libs/cli/python-monorepo-example
env:
LANGSMITH_API_KEY: ${{ secrets.LANGSMITH_API_KEY }}
@@ -104,7 +106,7 @@ jobs:
run: |
langgraph build -t langgraph-test-h
- name: Test prerelease reqs service
if: ${{ steps.changed-files.outputs.all && matrix.example.name == 'A' && secrets.LANGSMITH_API_KEY != '' }}
if: ${{ steps.changed-files.outputs.all && matrix.example.name == 'A' && env.HAS_LANGSMITH_API_KEY == 'true' }}
working-directory: libs/cli/examples/graph_prerelease_reqs
env:
LANGSMITH_API_KEY: ${{ secrets.LANGSMITH_API_KEY }}
+49
View File
@@ -0,0 +1,49 @@
name: Deploy Redirects to GitHub Pages
on:
push:
branches:
- main
paths:
- 'docs/**'
- '.github/workflows/deploy-redirects.yml'
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Generate redirect files
run: python docs/generate_redirects.py
- name: Setup Pages
uses: actions/configure-pages@v4
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: 'docs/_site'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
+1
View File
@@ -0,0 +1 @@
_site/
+142
View File
@@ -0,0 +1,142 @@
#!/usr/bin/env python3
"""
Generate HTML redirect files from redirects.json.
Usage:
python generate_redirects.py
This script reads redirects.json and generates individual HTML files
for each redirect path. Each HTML file uses meta refresh (0 delay)
which is SEO-friendly and treated similarly to 301 redirects by Google.
To add new redirects, simply edit redirects.json and re-run this script.
"""
import json
import os
from pathlib import Path
# Default fallback URL for any path not in the redirect map
DEFAULT_REDIRECT = "https://docs.langchain.com/oss/python/langgraph/overview"
HTML_TEMPLATE = """<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Redirecting...</title>
<link rel="canonical" href="{url}">
<meta name="robots" content="noindex">
<script>var anchor=window.location.hash.substr(1);location.href="{url}"+(anchor?"#"+anchor:"")</script>
<meta http-equiv="refresh" content="0; url={url}">
</head>
<body>
Redirecting...
</body>
</html>
"""
ROOT_HTML_TEMPLATE = """<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Redirecting to LangGraph Documentation</title>
<link rel="canonical" href="{url}">
<meta name="robots" content="noindex">
<script>var anchor=window.location.hash.substr(1);location.href="{url}"+(anchor?"#"+anchor:"")</script>
<meta http-equiv="refresh" content="0; url={url}">
</head>
<body>
<h1>Documentation has moved</h1>
<p>The LangGraph documentation has moved to <a href="{url}">docs.langchain.com</a>.</p>
<p>Redirecting you now...</p>
</body>
</html>
"""
CATCHALL_404_TEMPLATE = """<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Redirecting to LangGraph Documentation</title>
<link rel="canonical" href="{default_url}">
<meta name="robots" content="noindex">
<script>
// Catchall redirect for any unmapped paths
window.location.replace("{default_url}");
</script>
<meta http-equiv="refresh" content="0; url={default_url}">
</head>
<body>
<h1>Documentation has moved</h1>
<p>The LangGraph documentation has moved to <a href="{default_url}">docs.langchain.com</a>.</p>
<p>Redirecting you now...</p>
</body>
</html>
"""
def generate_redirects():
script_dir = Path(__file__).parent
output_dir = script_dir / "_site"
# Load redirects
with open(script_dir / "redirects.json") as f:
redirects = json.load(f)
# Clean output directory
if output_dir.exists():
import shutil
shutil.rmtree(output_dir)
output_dir.mkdir(parents=True)
# Generate individual HTML files for each redirect
for old_path, new_url in redirects.items():
# Remove leading slash and create directory structure
path = old_path.lstrip("/")
# Check if path has a file extension (e.g., .txt, .xml)
# If so, create the file directly instead of a directory with index.html
path_obj = Path(path)
has_extension = path_obj.suffix and len(path_obj.suffix) <= 5
if not path:
html_path = output_dir / "index.html"
elif has_extension:
# For files with extensions, create the file directly
html_path = output_dir / path
else:
# For directory-style URLs, create index.html inside
html_path = output_dir / path / "index.html"
# Create parent directories
html_path.parent.mkdir(parents=True, exist_ok=True)
# Write the redirect HTML
html_path.write_text(HTML_TEMPLATE.format(url=new_url))
print(f"Created: {html_path}")
# Create root index.html
root_index = output_dir / "index.html"
if not root_index.exists():
root_index.write_text(ROOT_HTML_TEMPLATE.format(url=DEFAULT_REDIRECT))
print(f"Created: {root_index}")
# Create 404.html for catchall
catchall_404 = output_dir / "404.html"
catchall_404.write_text(CATCHALL_404_TEMPLATE.format(default_url=DEFAULT_REDIRECT))
print(f"Created: {catchall_404}")
# Copy static files (like llms.txt) that can't be redirected via HTML
static_files = ["llms.txt"]
for static_file in static_files:
src = script_dir / static_file
if src.exists():
dst = output_dir / static_file
dst.write_text(src.read_text())
print(f"Copied: {dst}")
print(f"\nGenerated {len(redirects)} redirect files in {output_dir}")
if __name__ == "__main__":
generate_redirects()
+35
View File
@@ -0,0 +1,35 @@
# LangGraph
LangGraph documentation has moved to docs.langchain.com.
## Overview
- [LangGraph Overview](https://docs.langchain.com/oss/python/langgraph/overview): Introduction to LangGraph, a library for building stateful, multi-actor applications with LLMs.
- [Why LangGraph?](https://docs.langchain.com/oss/python/langgraph/why-langgraph): Motivation for LangGraph and its key features.
## Core Concepts
- [Graph API](https://docs.langchain.com/oss/python/langgraph/graph-api): Learn how to define state, create nodes, and connect them with edges.
- [Streaming](https://docs.langchain.com/oss/python/langgraph/streaming): Stream outputs from your graph for better UX.
- [Persistence](https://docs.langchain.com/oss/python/langgraph/persistence): Add memory and checkpointing to your graphs.
- [Add Memory](https://docs.langchain.com/oss/python/langgraph/add-memory): Implement short-term and long-term memory.
- [Workflows & Agents](https://docs.langchain.com/oss/python/langgraph/workflows-agents): Build agents and workflows with LangGraph.
## How-To Guides
- [Use Subgraphs](https://docs.langchain.com/oss/python/langgraph/use-subgraphs): Compose graphs using subgraphs.
- [Observability](https://docs.langchain.com/oss/python/langgraph/observability): Add tracing and debugging to your graphs.
- [Common Errors](https://docs.langchain.com/oss/python/langgraph/common-errors): Troubleshoot common LangGraph errors.
## Tutorials
- [Agentic RAG](https://docs.langchain.com/oss/python/langgraph/agentic-rag): Build an agentic RAG system with LangGraph.
- [SQL Agent](https://docs.langchain.com/oss/python/langgraph/sql-agent): Create a SQL agent with LangGraph.
## Reference
- [API Reference](https://reference.langchain.com/python/langgraph/): Complete API documentation for LangGraph.
## LangGraph Platform
For deploying LangGraph applications in production, see the [LangSmith documentation](https://docs.langchain.com/langsmith/agent-server).
+296
View File
@@ -0,0 +1,296 @@
{
"/how-tos/stream-values": "https://docs.langchain.com/oss/python/langgraph/streaming",
"/how-tos/stream-updates": "https://docs.langchain.com/oss/python/langgraph/streaming",
"/how-tos/streaming-content": "https://docs.langchain.com/oss/python/langgraph/streaming",
"/how-tos/stream-multiple": "https://docs.langchain.com/oss/python/langgraph/streaming",
"/how-tos/streaming-tokens-without-langchain": "https://docs.langchain.com/oss/python/langgraph/streaming",
"/how-tos/streaming-from-final-node": "https://docs.langchain.com/oss/python/langgraph/streaming",
"/how-tos/streaming-events-from-within-tools-without-langchain": "https://docs.langchain.com/oss/python/langgraph/streaming",
"/how-tos/state-reducers": "https://docs.langchain.com/oss/python/langgraph/graph-api#define-and-update-state",
"/how-tos/sequence": "https://docs.langchain.com/oss/python/langgraph/graph-api#create-a-sequence-of-steps",
"/how-tos/branching": "https://docs.langchain.com/oss/python/langgraph/graph-api#create-branches",
"/how-tos/recursion-limit": "https://docs.langchain.com/oss/python/langgraph/graph-api#create-and-control-loops",
"/how-tos/visualization": "https://docs.langchain.com/oss/python/langgraph/graph-api#visualize-your-graph",
"/how-tos/input_output_schema": "https://docs.langchain.com/oss/python/langgraph/graph-api#define-input-and-output-schemas",
"/how-tos/pass_private_state": "https://docs.langchain.com/oss/python/langgraph/graph-api#pass-private-state-between-nodes",
"/how-tos/state-model": "https://docs.langchain.com/oss/python/langgraph/graph-api#use-pydantic-models-for-graph-state",
"/how-tos/map-reduce": "https://docs.langchain.com/oss/python/langgraph/graph-api#map-reduce-and-the-send-api",
"/how-tos/command": "https://docs.langchain.com/oss/python/langgraph/graph-api#combine-control-flow-and-state-updates-with-command",
"/how-tos/configuration": "https://docs.langchain.com/oss/python/langgraph/graph-api#add-runtime-configuration",
"/how-tos/node-retries": "https://docs.langchain.com/oss/python/langgraph/graph-api#add-retry-policies",
"/how-tos/return-when-recursion-limit-hits": "https://docs.langchain.com/oss/python/langgraph/graph-api#impose-a-recursion-limit",
"/how-tos/async": "https://docs.langchain.com/oss/python/langgraph/graph-api#async",
"/how-tos/memory/manage-conversation-history": "https://docs.langchain.com/oss/python/langgraph/add-memory",
"/how-tos/memory/delete-messages": "https://docs.langchain.com/oss/python/langgraph/add-memory#delete-messages",
"/how-tos/memory/add-summary-conversation-history": "https://docs.langchain.com/oss/python/langgraph/add-memory#summarize-messages",
"/how-tos/memory": "https://docs.langchain.com/oss/python/langgraph/add-memory",
"/agents/memory": "https://docs.langchain.com/oss/python/langgraph/add-memory",
"/how-tos/subgraph-transform-state": "https://docs.langchain.com/oss/python/langgraph/use-subgraphs#different-state-schemas",
"/how-tos/subgraphs-manage-state": "https://docs.langchain.com/oss/python/langgraph/use-subgraphs#add-persistence",
"/how-tos/persistence_postgres": "https://docs.langchain.com/oss/python/langgraph/add-memory#use-in-production",
"/how-tos/persistence_mongodb": "https://docs.langchain.com/oss/python/langgraph/add-memory#use-in-production",
"/how-tos/persistence_redis": "https://docs.langchain.com/oss/python/langgraph/add-memory#use-in-production",
"/how-tos/subgraph-persistence": "https://docs.langchain.com/oss/python/langgraph/add-memory#use-with-subgraphs",
"/how-tos/cross-thread-persistence": "https://docs.langchain.com/oss/python/langgraph/add-memory#add-long-term-memory",
"/cloud/how-tos/copy_threads": "https://docs.langchain.com/langsmith/use-threads",
"/cloud/how-tos/check-thread-status": "https://docs.langchain.com/langsmith/use-threads",
"/cloud/concepts/threads": "https://docs.langchain.com/oss/python/langgraph/persistence#threads",
"/how-tos/persistence": "https://docs.langchain.com/oss/python/langgraph/add-memory",
"/how-tos/tool-calling-errors": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
"/how-tos/pass-config-to-tools": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
"/how-tos/pass-run-time-values-to-tools": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
"/how-tos/update-state-from-tools": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
"/agents/tools": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
"/how-tos/agent-handoffs": "https://docs.langchain.com/oss/python/langgraph/graph-api",
"/how-tos/multi-agent-network": "https://docs.langchain.com/oss/python/langgraph/graph-api",
"/how-tos/multi-agent-multi-turn-convo": "https://docs.langchain.com/oss/python/langgraph/graph-api",
"/cloud/index": "https://docs.langchain.com/oss/python/langgraph/overview",
"/cloud/how-tos/index": "https://docs.langchain.com/langsmith/home",
"/cloud/concepts/api": "https://docs.langchain.com/langsmith/agent-server",
"/cloud/concepts/cloud": "https://docs.langchain.com/langsmith/cloud",
"/cloud/faq/studio": "https://docs.langchain.com/langsmith/studio",
"/cloud/how-tos/human_in_the_loop_edit_state": "https://docs.langchain.com/langsmith/add-human-in-the-loop",
"/cloud/how-tos/human_in_the_loop_user_input": "https://docs.langchain.com/langsmith/add-human-in-the-loop",
"/concepts/platform_architecture": "https://docs.langchain.com/langsmith/cloud#architecture",
"/cloud/how-tos/stream_values": "https://docs.langchain.com/langsmith/streaming",
"/cloud/how-tos/stream_updates": "https://docs.langchain.com/langsmith/streaming",
"/cloud/how-tos/stream_messages": "https://docs.langchain.com/langsmith/streaming",
"/cloud/how-tos/stream_events": "https://docs.langchain.com/langsmith/streaming",
"/cloud/how-tos/stream_debug": "https://docs.langchain.com/langsmith/streaming",
"/cloud/how-tos/stream_multiple": "https://docs.langchain.com/langsmith/streaming",
"/cloud/concepts/streaming": "https://docs.langchain.com/oss/python/langgraph/streaming",
"/agents/streaming": "https://docs.langchain.com/oss/python/langgraph/streaming",
"/how-tos/create-react-agent": "https://docs.langchain.com/oss/python/langchain/agents#basic-configuration",
"/how-tos/create-react-agent-memory": "https://docs.langchain.com/oss/python/langgraph/add-memory",
"/how-tos/create-react-agent-system-prompt": "https://docs.langchain.com/oss/python/langgraph/add-memory",
"/how-tos/create-react-agent-structured-output": "https://docs.langchain.com/oss/python/langchain/agents#structured-output",
"/prebuilt": "https://docs.langchain.com/oss/python/langchain/agents",
"/reference/prebuilt": "https://reference.langchain.com/python/langgraph/agents/",
"/concepts/high_level": "https://docs.langchain.com/oss/python/langgraph/overview",
"/concepts/index": "https://docs.langchain.com/oss/python/langgraph/overview",
"/concepts/v0-human-in-the-loop": "https://docs.langchain.com/oss/python/langgraph/interrupts",
"/how-tos/index": "https://docs.langchain.com/oss/python/langgraph/overview",
"/tutorials/introduction": "https://docs.langchain.com/oss/python/langgraph/overview",
"/agents/deployment": "https://docs.langchain.com/oss/python/langgraph/local-server",
"/how-tos/deploy-self-hosted": "https://docs.langchain.com/langsmith/platform-setup",
"/concepts/self_hosted": "https://docs.langchain.com/langsmith/platform-setup",
"/tutorials/deployment": "https://docs.langchain.com/langsmith/deployments",
"/cloud/how-tos/assistant_versioning": "https://docs.langchain.com/langsmith/configuration-cloud",
"/cloud/concepts/runs": "https://docs.langchain.com/langsmith/assistants#execution",
"/how-tos/wait-user-input-functional": "https://docs.langchain.com/oss/python/langgraph/functional-api",
"/how-tos/review-tool-calls-functional": "https://docs.langchain.com/oss/python/langgraph/functional-api",
"/how-tos/create-react-agent-hitl": "https://docs.langchain.com/oss/python/langgraph/interrupts",
"/agents/human-in-the-loop": "https://docs.langchain.com/oss/python/langgraph/interrupts",
"/how-tos/human_in_the_loop/dynamic_breakpoints": "https://docs.langchain.com/oss/python/langgraph/interrupts",
"/concepts/breakpoints": "https://docs.langchain.com/oss/python/langgraph/interrupts",
"/how-tos/human_in_the_loop/breakpoints": "https://docs.langchain.com/oss/python/langgraph/interrupts",
"/cloud/how-tos/human_in_the_loop_breakpoint": "https://docs.langchain.com/langsmith/add-human-in-the-loop",
"/how-tos/human_in_the_loop/edit-graph-state": "https://docs.langchain.com/oss/python/langgraph/use-time-travel",
"/examples/index": "https://docs.langchain.com/oss/python/langgraph/case-studies",
"/guides/index": "https://docs.langchain.com/oss/python/langchain/overview",
"/tutorials/index": "https://docs.langchain.com/oss/python/learn",
"/llms-txt-overview": "https://docs.langchain.com/llms.txt",
"/tutorials/rag/langgraph_adaptive_rag": "https://docs.langchain.com/oss/python/langgraph/agentic-rag",
"/tutorials/multi_agent/multi-agent-collaboration": "https://docs.langchain.com/oss/python/langchain/multi-agent",
"/how-tos/create-react-agent-manage-message-history": "https://docs.langchain.com/oss/python/langgraph/add-memory",
"/how-tos/many-tools": "https://docs.langchain.com/oss/python/langchain/tools",
"/tutorials/customer-support/customer-support": "https://docs.langchain.com/oss/python/langgraph/agentic-rag",
"/how-tos/react-agent-structured-output": "https://docs.langchain.com/oss/python/langchain/agents#structured-output",
"/tutorials/code_assistant/langgraph_code_assistant": "https://docs.langchain.com/oss/python/langgraph/agentic-rag",
"/tutorials/multi_agent/hierarchical_agent_teams": "https://docs.langchain.com/oss/python/langchain/supervisor",
"/tutorials/auth/getting_started": "https://docs.langchain.com/langsmith/auth",
"/tutorials/auth/resource_auth": "https://docs.langchain.com/langsmith/resource-auth",
"/tutorials/auth/add_auth_server": "https://docs.langchain.com/langsmith/add-auth-server",
"/how-tos/use-remote-graph": "https://docs.langchain.com/langsmith/use-remote-graph",
"/how-tos/autogen-integration": "https://docs.langchain.com/langsmith/autogen-integration",
"/how-tos/human_in_the_loop/wait-user-input": "https://docs.langchain.com/oss/python/langgraph/interrupts",
"/cloud/how-tos/use_stream_react": "https://docs.langchain.com/langsmith/use-stream-react",
"/cloud/how-tos/generative_ui_react": "https://docs.langchain.com/langsmith/generative-ui-react",
"/concepts/langgraph_platform": "https://docs.langchain.com/langsmith/home",
"/concepts/langgraph_components": "https://docs.langchain.com/langsmith/components",
"/concepts/langgraph_server": "https://docs.langchain.com/langsmith/agent-server",
"/concepts/langgraph_data_plane": "https://docs.langchain.com/langsmith/data-plane",
"/concepts/langgraph_control_plane": "https://docs.langchain.com/langsmith/control-plane",
"/concepts/langgraph_cli": "https://docs.langchain.com/langsmith/cli",
"/concepts/langgraph_studio": "https://docs.langchain.com/langsmith/studio",
"/cloud/how-tos/studio/quick_start": "https://docs.langchain.com/langsmith/quick-start-studio",
"/cloud/how-tos/invoke_studio": "https://docs.langchain.com/langsmith/use-studio",
"/cloud/how-tos/studio/manage_assistants": "https://docs.langchain.com/langsmith/use-studio",
"/cloud/how-tos/threads_studio": "https://docs.langchain.com/langsmith/use-threads",
"/cloud/how-tos/iterate_graph_studio": "https://docs.langchain.com/langsmith/use-studio",
"/cloud/how-tos/studio/run_evals": "https://docs.langchain.com/langsmith/observability",
"/cloud/how-tos/clone_traces_studio": "https://docs.langchain.com/langsmith/observability",
"/cloud/how-tos/datasets_studio": "https://docs.langchain.com/langsmith/use-studio",
"/concepts/sdk": "https://docs.langchain.com/langsmith/sdk",
"/concepts/plans": "https://docs.langchain.com/langsmith/home",
"/concepts/application_structure": "https://docs.langchain.com/langsmith/application-structure",
"/concepts/scalability_and_resilience": "https://docs.langchain.com/langsmith/scalability-and-resilience",
"/concepts/auth": "https://docs.langchain.com/langsmith/auth",
"/how-tos/auth/custom_auth": "https://docs.langchain.com/langsmith/custom-auth",
"/how-tos/auth/openapi_security": "https://docs.langchain.com/langsmith/openapi-security",
"/concepts/assistants": "https://docs.langchain.com/langsmith/assistants",
"/cloud/how-tos/configuration_cloud": "https://docs.langchain.com/langsmith/configuration-cloud",
"/cloud/how-tos/use_threads": "https://docs.langchain.com/langsmith/use-threads",
"/cloud/how-tos/background_run": "https://docs.langchain.com/langsmith/background-run",
"/cloud/how-tos/same-thread": "https://docs.langchain.com/langsmith/same-thread",
"/cloud/how-tos/stateless_runs": "https://docs.langchain.com/langsmith/stateless-runs",
"/cloud/how-tos/configurable_headers": "https://docs.langchain.com/langsmith/configurable-headers",
"/concepts/double_texting": "https://docs.langchain.com/langsmith/double-texting",
"/cloud/how-tos/interrupt_concurrent": "https://docs.langchain.com/langsmith/interrupt-concurrent",
"/cloud/how-tos/rollback_concurrent": "https://docs.langchain.com/langsmith/rollback-concurrent",
"/cloud/how-tos/reject_concurrent": "https://docs.langchain.com/langsmith/reject-concurrent",
"/cloud/how-tos/enqueue_concurrent": "https://docs.langchain.com/langsmith/enqueue-concurrent",
"/cloud/concepts/webhooks": "https://docs.langchain.com/langsmith/use-webhooks",
"/cloud/how-tos/webhooks": "https://docs.langchain.com/langsmith/use-webhooks",
"/cloud/concepts/cron_jobs": "https://docs.langchain.com/langsmith/cron-jobs",
"/cloud/how-tos/cron_jobs": "https://docs.langchain.com/langsmith/cron-jobs",
"/how-tos/http/custom_lifespan": "https://docs.langchain.com/langsmith/custom-lifespan",
"/how-tos/http/custom_middleware": "https://docs.langchain.com/langsmith/custom-middleware",
"/how-tos/http/custom_routes": "https://docs.langchain.com/langsmith/custom-routes",
"/cloud/concepts/data_storage_and_privacy": "https://docs.langchain.com/langsmith/data-storage-and-privacy",
"/cloud/deployment/semantic_search": "https://docs.langchain.com/langsmith/semantic-search",
"/how-tos/ttl/configure_ttl": "https://docs.langchain.com/langsmith/configure-ttl",
"/concepts/deployment_options": "https://docs.langchain.com/langsmith/deployments",
"/cloud/quick_start": "https://docs.langchain.com/langsmith/deployment-quickstart",
"/cloud/deployment/setup": "https://docs.langchain.com/langsmith/setup-app-requirements-txt",
"/cloud/deployment/setup_pyproject": "https://docs.langchain.com/langsmith/setup-pyproject",
"/cloud/deployment/setup_javascript": "https://docs.langchain.com/langsmith/setup-javascript",
"/cloud/deployment/custom_docker": "https://docs.langchain.com/langsmith/custom-docker",
"/cloud/deployment/graph_rebuild": "https://docs.langchain.com/langsmith/graph-rebuild",
"/concepts/langgraph_cloud": "https://docs.langchain.com/langsmith/cloud",
"/concepts/langgraph_self_hosted_data_plane": "https://docs.langchain.com/langsmith/platform-setup",
"/concepts/langgraph_self_hosted_control_plane": "https://docs.langchain.com/langsmith/platform-setup",
"/concepts/langgraph_standalone_container": "https://docs.langchain.com/langsmith/docker",
"/cloud/deployment/cloud": "https://docs.langchain.com/langsmith/cloud",
"/cloud/deployment/self_hosted_data_plane": "https://docs.langchain.com/langsmith/platform-setup",
"/cloud/deployment/self_hosted_control_plane": "https://docs.langchain.com/langsmith/platform-setup",
"/cloud/deployment/standalone_container": "https://docs.langchain.com/langsmith/docker",
"/concepts/server-mcp": "https://docs.langchain.com/langsmith/server-mcp",
"/cloud/how-tos/human_in_the_loop_time_travel": "https://docs.langchain.com/langsmith/human-in-the-loop-time-travel",
"/cloud/how-tos/add-human-in-the-loop": "https://docs.langchain.com/langsmith/add-human-in-the-loop",
"/cloud/deployment/egress": "https://docs.langchain.com/langsmith/env-var",
"/cloud/how-tos/streaming": "https://docs.langchain.com/langsmith/streaming",
"/cloud/reference/api/api_ref": "https://docs.langchain.com/langsmith/server-api-ref",
"/cloud/reference/langgraph_server_changelog": "https://docs.langchain.com/langsmith/agent-server-changelog",
"/cloud/reference/api/api_ref_control_plane": "https://docs.langchain.com/langsmith/api-ref-control-plane",
"/cloud/reference/cli": "https://docs.langchain.com/langsmith/cli",
"/cloud/reference/env_var": "https://docs.langchain.com/langsmith/env-var",
"/troubleshooting/studio": "https://docs.langchain.com/langsmith/troubleshooting-studio",
"/index": "https://docs.langchain.com/oss/python/langgraph/overview",
"/agents/agents": "https://docs.langchain.com/oss/python/langchain/agents",
"/concepts/why-langgraph": "https://docs.langchain.com/oss/python/langgraph/overview",
"/tutorials/get-started/1-build-basic-chatbot": "https://docs.langchain.com/oss/python/langgraph/quickstart",
"/tutorials/get-started/2-add-tools": "https://docs.langchain.com/oss/python/langgraph/quickstart",
"/tutorials/get-started/3-add-memory": "https://docs.langchain.com/oss/python/langgraph/quickstart",
"/tutorials/get-started/4-human-in-the-loop": "https://docs.langchain.com/oss/python/langgraph/quickstart",
"/tutorials/get-started/5-customize-state": "https://docs.langchain.com/oss/python/langgraph/quickstart",
"/tutorials/get-started/6-time-travel": "https://docs.langchain.com/oss/python/langgraph/quickstart",
"/tutorials/langsmith/local-server": "https://docs.langchain.com/oss/python/langgraph/local-server",
"/tutorials/workflows": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
"/tutorials/plan-and-execute/plan-and-execute": "https://docs.langchain.com/oss/python/langchain/middleware/built-in#to-do-list",
"/tutorials/langgraph-platform/local-server/local-server": "https://docs.langchain.com/langsmith/local-server",
"/concepts/agentic_concepts": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
"/agents/overview": "https://docs.langchain.com/oss/python/langchain/agents",
"/agents/run_agents": "https://docs.langchain.com/oss/python/langgraph/quickstart",
"/concepts/low_level": "https://docs.langchain.com/oss/python/langgraph/graph-api",
"/how-tos/graph-api": "https://docs.langchain.com/oss/python/langgraph/graph-api",
"/how-tos/react-agent-from-scratch": "https://docs.langchain.com/oss/python/langchain/quickstart",
"/concepts/functional_api": "https://docs.langchain.com/oss/python/langgraph/functional-api",
"/how-tos/use-functional-api": "https://docs.langchain.com/oss/python/langgraph/functional-api",
"/concepts/pregel": "https://docs.langchain.com/oss/python/langgraph/pregel",
"/concepts/streaming": "https://docs.langchain.com/oss/python/langgraph/streaming",
"/how-tos/streaming": "https://docs.langchain.com/oss/python/langgraph/streaming",
"/concepts/persistence": "https://docs.langchain.com/oss/python/langgraph/persistence",
"/concepts/durable_execution": "https://docs.langchain.com/oss/python/langgraph/durable-execution",
"/concepts/memory": "https://docs.langchain.com/oss/python/langgraph/memory",
"/how-tos/memory/add-memory": "https://docs.langchain.com/oss/python/langgraph/add-memory",
"/agents/context": "https://docs.langchain.com/oss/python/langgraph/add-memory",
"/agents/models": "https://docs.langchain.com/oss/python/langgraph/overview",
"/concepts/tools": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
"/how-tos/tool-calling": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
"/concepts/human_in_the_loop": "https://docs.langchain.com/oss/python/langgraph/interrupts",
"/how-tos/human_in_the_loop/add-human-in-the-loop": "https://docs.langchain.com/oss/python/langgraph/interrupts",
"/concepts/time-travel": "https://docs.langchain.com/oss/python/langgraph/persistence",
"/how-tos/human_in_the_loop/time-travel": "https://docs.langchain.com/oss/python/langgraph/use-time-travel",
"/concepts/subgraphs": "https://docs.langchain.com/oss/python/langgraph/use-subgraphs",
"/how-tos/subgraph": "https://docs.langchain.com/oss/python/langgraph/use-subgraphs",
"/concepts/multi_agent": "https://docs.langchain.com/oss/python/langgraph/graph-api",
"/agents/multi-agent": "https://docs.langchain.com/oss/python/langchain/multi-agent",
"/how-tos/multi_agent": "https://docs.langchain.com/oss/python/langgraph/graph-api",
"/concepts/mcp": "https://docs.langchain.com/oss/python/langgraph/overview",
"/agents/mcp": "https://docs.langchain.com/oss/python/langgraph/overview",
"/concepts/tracing": "https://docs.langchain.com/oss/python/langgraph/observability",
"/how-tos/enable-tracing": "https://docs.langchain.com/oss/python/langgraph/observability",
"/agents/evals": "https://docs.langchain.com/oss/python/langgraph/overview",
"/concepts/template_applications": "https://docs.langchain.com/oss/python/langgraph/overview",
"/tutorials/rag/langgraph_agentic_rag": "https://docs.langchain.com/oss/python/langgraph/agentic-rag",
"/tutorials/multi_agent/agent_supervisor": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
"/tutorials/sql/sql-agent": "https://docs.langchain.com/oss/python/langgraph/sql-agent",
"/agents/ui": "https://docs.langchain.com/oss/python/langgraph/ui",
"/how-tos/run-id-langsmith": "https://docs.langchain.com/oss/python/langgraph/observability",
"/troubleshooting/errors/index": "https://docs.langchain.com/oss/python/langgraph/common-errors",
"/troubleshooting/errors/INVALID_CHAT_HISTORY": "https://docs.langchain.com/oss/python/langgraph/INVALID_CHAT_HISTORY",
"/troubleshooting/errors/INVALID_LICENSE": "https://docs.langchain.com/oss/python/langgraph/common-errors",
"/adopters": "https://docs.langchain.com/oss/python/langgraph/case-studies",
"/concepts/faq": "https://docs.langchain.com/oss/python/langgraph/overview",
"/agents/prebuilt": "https://docs.langchain.com/oss/python/langchain/agents",
"/reference/index": "https://reference.langchain.com/python/langgraph/",
"/reference/graphs": "https://reference.langchain.com/python/langgraph/graphs/",
"/reference/func": "https://reference.langchain.com/python/langgraph/func/",
"/reference/pregel": "https://reference.langchain.com/python/langgraph/pregel/",
"/reference/checkpoints": "https://reference.langchain.com/python/langgraph/checkpoints/",
"/reference/store": "https://reference.langchain.com/python/langgraph/store/",
"/reference/cache": "https://reference.langchain.com/python/langgraph/cache/",
"/reference/types": "https://reference.langchain.com/python/langgraph/types/",
"/reference/runtime": "https://reference.langchain.com/python/langgraph/runtime/",
"/reference/config": "https://reference.langchain.com/python/langgraph/config/",
"/reference/errors": "https://reference.langchain.com/python/langgraph/errors/",
"/reference/constants": "https://reference.langchain.com/python/langgraph/constants/",
"/reference/channels": "https://reference.langchain.com/python/langgraph/channels/",
"/reference/agents": "https://reference.langchain.com/python/langgraph/agents/",
"/reference/supervisor": "https://reference.langchain.com/python/langgraph/supervisor/",
"/reference/swarm": "https://reference.langchain.com/python/langgraph/swarm/",
"/reference/mcp": "https://reference.langchain.com/python/langgraph/mcp/",
"/cloud/reference/sdk/python_sdk_ref": "https://reference.langchain.com/python/langsmith/deployment/sdk/",
"/reference/remote_graph": "https://reference.langchain.com/python/langsmith/deployment/remote_graph/",
"/additional-resources/index": "https://docs.langchain.com/oss/python/langchain/overview",
"/cloud/reference/sdk/js_ts_sdk_ref": "https://reference.langchain.com/javascript/modules/langsmith.html",
"/snippets/chat_model_tabs": "https://docs.langchain.com/oss/python/langchain/overview",
"/troubleshooting/errors/GRAPH_RECURSION_LIMIT": "https://docs.langchain.com/oss/python/langgraph/GRAPH_RECURSION_LIMIT",
"/troubleshooting/errors/INVALID_CONCURRENT_GRAPH_UPDATE": "https://docs.langchain.com/oss/python/langgraph/INVALID_CONCURRENT_GRAPH_UPDATE",
"/troubleshooting/errors/INVALID_GRAPH_NODE_RETURN_VALUE": "https://docs.langchain.com/oss/python/langgraph/INVALID_GRAPH_NODE_RETURN_VALUE",
"/troubleshooting/errors/MULTIPLE_SUBGRAPHS": "https://docs.langchain.com/oss/python/langgraph/MULTIPLE_SUBGRAPHS",
"/tutorials/rag/langgraph_self_rag": "https://docs.langchain.com/oss/python/langgraph/agentic-rag",
"/additional-resources": "https://docs.langchain.com/oss/python/langgraph/overview",
"/examples": "https://docs.langchain.com/oss/python/langgraph/overview",
"/guides": "https://docs.langchain.com/oss/python/langgraph/overview",
"/how-tos/autogen-integration-functional": "https://docs.langchain.com/oss/python/langgraph/overview",
"/how-tos/cross-thread-persistence-functional": "https://docs.langchain.com/oss/python/langgraph/add-memory#add-long-term-memory",
"/how-tos/disable-streaming": "https://docs.langchain.com/oss/python/langgraph/streaming",
"/how-tos/memory/semantic-search": "https://docs.langchain.com/oss/python/langgraph/add-memory",
"/how-tos/multi-agent-multi-turn-convo-functional": "https://docs.langchain.com/oss/python/langgraph/graph-api",
"/how-tos/multi-agent-network-functional": "https://docs.langchain.com/oss/python/langgraph/graph-api",
"/how-tos/persistence-functional": "https://docs.langchain.com/oss/python/langgraph/add-memory",
"/how-tos/react-agent-from-scratch-functional": "https://docs.langchain.com/oss/python/langgraph/workflows-agents",
"/reference": "https://reference.langchain.com/python/langgraph/",
"/troubleshooting/errors": "https://docs.langchain.com/oss/python/langgraph/common-errors",
"/tutorials/chatbot-simulation-evaluation/agent-simulation-evaluation": "https://docs.langchain.com/oss/python/langgraph/overview",
"/tutorials/chatbot-simulation-evaluation/langsmith-agent-simulation-evaluation": "https://docs.langchain.com/oss/python/langgraph/overview",
"/tutorials/chatbots/information-gather-prompting": "https://docs.langchain.com/oss/python/langgraph/overview",
"/tutorials/extraction/retries": "https://docs.langchain.com/oss/python/langgraph/overview",
"/tutorials/langgraph-platform/local-server": "https://docs.langchain.com/langsmith/agent-server",
"/tutorials/lats/lats": "https://docs.langchain.com/oss/python/langgraph/overview",
"/tutorials/llm-compiler/LLMCompiler": "https://docs.langchain.com/oss/python/langgraph/overview",
"/tutorials/rag/langgraph_adaptive_rag_local": "https://docs.langchain.com/oss/python/langgraph/agentic-rag",
"/tutorials/rag/langgraph_crag": "https://docs.langchain.com/oss/python/langgraph/agentic-rag",
"/tutorials/rag/langgraph_crag_local": "https://docs.langchain.com/oss/python/langgraph/agentic-rag",
"/tutorials/rag/langgraph_self_rag_local": "https://docs.langchain.com/oss/python/langgraph/agentic-rag",
"/tutorials/reflection/reflection": "https://docs.langchain.com/oss/python/langgraph/overview",
"/tutorials/reflexion/reflexion": "https://docs.langchain.com/oss/python/langgraph/overview",
"/tutorials/rewoo/rewoo": "https://docs.langchain.com/oss/python/langgraph/overview",
"/tutorials/self-discover/self-discover": "https://docs.langchain.com/oss/python/langgraph/overview",
"/tutorials/tnt-llm/tnt-llm": "https://docs.langchain.com/oss/python/langgraph/overview",
"/tutorials/tot/tot": "https://docs.langchain.com/oss/python/langgraph/overview",
"/tutorials/usaco/usaco": "https://docs.langchain.com/oss/python/langgraph/overview",
"/tutorials/web-navigation/web_voyager": "https://docs.langchain.com/oss/python/langgraph/overview"
}
@@ -62,3 +62,4 @@ target-version = "py310"
name = "testpypi"
url = "https://test.pypi.org/simple/"
publish-url = "https://test.pypi.org/legacy/"
explicit = true
+1 -1
View File
@@ -13,7 +13,7 @@ license = "MIT"
license-files = ['LICENSE']
dependencies = [
"langgraph-checkpoint>=2.1.2,<5.0.0",
"orjson>=3.10.1",
"orjson>=3.11.5",
"psycopg>=3.2.0",
"psycopg-pool>=3.2.0",
]
+1 -1
View File
@@ -346,7 +346,7 @@ test = [
[package.metadata]
requires-dist = [
{ name = "langgraph-checkpoint", editable = "../checkpoint" },
{ name = "orjson", specifier = ">=3.10.1" },
{ name = "orjson", specifier = ">=3.11.5" },
{ name = "psycopg", specifier = ">=3.2.0" },
{ name = "psycopg-pool", specifier = ">=3.2.0" },
]
+11 -2
View File
@@ -3,10 +3,11 @@ from __future__ import annotations
import dataclasses
import types
import weakref
from collections.abc import Generator, Sequence
from typing import Annotated, Any, Optional, Union, get_origin, get_type_hints
from collections.abc import Callable, Generator, Sequence
from typing import Annotated, Any, Optional, Union, cast, get_origin, get_type_hints
from pydantic import BaseModel
from pydantic_core import PydanticUndefined
from typing_extensions import NotRequired, ReadOnly, Required
from langgraph._internal._typing import MISSING
@@ -101,6 +102,14 @@ def get_field_default(name: str, type_: Any, schema: type[Any]) -> Any:
return ...
# Handle NotRequired[<type>] for earlier versions of python
return None
if isinstance(schema, type) and issubclass(schema, BaseModel):
if name in schema.model_fields:
field = schema.model_fields[name]
if field.default_factory is not None:
factory = cast(Callable[[], Any], field.default_factory)
return factory()
if field.default is not PydanticUndefined:
return field.default
if dataclasses.is_dataclass(schema):
field_info = next(
(f for f in dataclasses.fields(schema) if f.name == name), None
+25 -12
View File
@@ -1,4 +1,5 @@
import collections.abc
import copy
from collections.abc import Callable, Sequence
from typing import Any, Generic
@@ -48,11 +49,17 @@ class BinaryOperatorAggregate(Generic[Value], BaseChannel[Value, Value, Value]):
```
"""
__slots__ = ("value", "operator")
__slots__ = ("value", "operator", "default")
def __init__(self, typ: type[Value], operator: Callable[[Value, Value], Value]):
def __init__(
self,
typ: type[Value],
operator: Callable[[Value, Value], Value],
default: Any = MISSING,
):
super().__init__(typ)
self.operator = operator
self.default = default
# special forms from typing or collections.abc are not instantiable
# so we need to replace them with their concrete counterparts
typ = _strip_extras(typ)
@@ -62,17 +69,23 @@ class BinaryOperatorAggregate(Generic[Value], BaseChannel[Value, Value, Value]):
typ = set
if typ in (collections.abc.Mapping, collections.abc.MutableMapping):
typ = dict
try:
self.value = typ()
except Exception:
self.value = MISSING
if default is not MISSING:
self.value = copy.deepcopy(default)
else:
try:
self.value = typ()
except Exception:
self.value = MISSING
def __eq__(self, value: object) -> bool:
return isinstance(value, BinaryOperatorAggregate) and (
value.operator is self.operator
if value.operator.__name__ != "<lambda>"
and self.operator.__name__ != "<lambda>"
else True
(
value.operator is self.operator
if value.operator.__name__ != "<lambda>"
and self.operator.__name__ != "<lambda>"
else True
)
and value.default == self.default
)
@property
@@ -87,13 +100,13 @@ class BinaryOperatorAggregate(Generic[Value], BaseChannel[Value, Value, Value]):
def copy(self) -> Self:
"""Return a copy of the channel."""
empty = self.__class__(self.typ, self.operator)
empty = self.__class__(self.typ, self.operator, self.default)
empty.key = self.key
empty.value = self.value
return empty
def from_checkpoint(self, checkpoint: Value) -> Self:
empty = self.__class__(self.typ, self.operator)
empty = self.__class__(self.typ, self.operator, self.default)
empty.key = self.key
if checkpoint is not MISSING:
empty.value = checkpoint
+29 -7
View File
@@ -1567,7 +1567,7 @@ def _get_channels(
type_hints = get_type_hints(schema, include_extras=True)
all_keys = {
name: _get_channel(name, typ)
name: _get_channel(name, typ, schema=schema)
for name, typ in type_hints.items()
if name != "__slots__"
}
@@ -1580,18 +1580,30 @@ def _get_channels(
@overload
def _get_channel(
name: str, annotation: Any, *, allow_managed: Literal[False]
name: str,
annotation: Any,
*,
allow_managed: Literal[False],
schema: type[Any] | None = None,
) -> BaseChannel: ...
@overload
def _get_channel(
name: str, annotation: Any, *, allow_managed: Literal[True] = True
name: str,
annotation: Any,
*,
allow_managed: Literal[True] = True,
schema: type[Any] | None = None,
) -> BaseChannel | ManagedValueSpec: ...
def _get_channel(
name: str, annotation: Any, *, allow_managed: bool = True
name: str,
annotation: Any,
*,
allow_managed: bool = True,
schema: type[Any] | None = None,
) -> BaseChannel | ManagedValueSpec:
# Strip out Required and NotRequired wrappers
if hasattr(annotation, "__origin__") and annotation.__origin__ in (
@@ -1607,7 +1619,7 @@ def _get_channel(
elif channel := _is_field_channel(annotation):
channel.key = name
return channel
elif channel := _is_field_binop(annotation):
elif channel := _is_field_binop(annotation, name=name, schema=schema):
channel.key = name
return channel
@@ -1630,7 +1642,12 @@ def _is_field_channel(typ: type[Any]) -> BaseChannel | None:
return None
def _is_field_binop(typ: type[Any]) -> BinaryOperatorAggregate | None:
def _is_field_binop(
typ: type[Any],
*,
name: str | None = None,
schema: type[Any] | None = None,
) -> BinaryOperatorAggregate | None:
if hasattr(typ, "__metadata__"):
meta = typ.__metadata__
if len(meta) >= 1 and callable(meta[-1]):
@@ -1643,7 +1660,12 @@ def _is_field_binop(typ: type[Any]) -> BinaryOperatorAggregate | None:
)
== 2
):
return BinaryOperatorAggregate(typ, meta[-1])
default: Any = MISSING
if name is not None and schema is not None:
field_default = get_field_default(name, typ, schema)
if field_default is not ...:
default = field_default
return BinaryOperatorAggregate(typ, meta[-1], default=default)
else:
raise ValueError(
f"Invalid reducer signature. Expected (a, b) -> c. Got {sig}"
File diff suppressed because one or more lines are too long
+71
View File
@@ -90,6 +90,77 @@ def test_binop() -> None:
assert channel.get() == 10
def test_binop_with_default() -> None:
# Test that a default value is used instead of typ()
channel = BinaryOperatorAggregate(int, operator.add, default=10).from_checkpoint(
MISSING
)
assert channel.get() == 10
channel.update([5])
assert channel.get() == 15
# Test checkpoint round-trip preserves default for new channels
checkpoint = channel.checkpoint()
restored = BinaryOperatorAggregate(int, operator.add, default=10).from_checkpoint(
checkpoint
)
assert restored.get() == 15
# Test from_checkpoint with MISSING uses default
fresh = BinaryOperatorAggregate(int, operator.add, default=10).from_checkpoint(
MISSING
)
assert fresh.get() == 10
# Test dict default with or_ reducer
channel = BinaryOperatorAggregate(
dict, operator.or_, default={"a": 1}
).from_checkpoint(MISSING)
assert channel.get() == {"a": 1}
channel.update([{"b": 2}])
assert channel.get() == {"a": 1, "b": 2}
def test_binop_with_default_mutable_safety() -> None:
"""Mutable defaults should not be shared across channel instances."""
default = {"a": 1}
ch1 = BinaryOperatorAggregate(dict, operator.or_, default=default).from_checkpoint(
MISSING
)
ch2 = BinaryOperatorAggregate(dict, operator.or_, default=default).from_checkpoint(
MISSING
)
# Mutate ch1's value via a reducer that mutates in-place
def mutating_reducer(a: dict, b: dict) -> dict:
a.update(b)
return a
ch1.operator = mutating_reducer
ch1.update([{"b": 2}])
assert ch1.get() == {"a": 1, "b": 2}
# ch2 should be unaffected
assert ch2.get() == {"a": 1}
# Original default should be unaffected
assert default == {"a": 1}
def test_binop_with_default_multi_invoke() -> None:
"""Defaults should be fresh across multiple from_checkpoint calls."""
template = BinaryOperatorAggregate(dict, operator.or_, default={"a": 1})
# Simulate two separate runs
run1 = template.from_checkpoint(MISSING)
run1.update([{"b": 2}])
assert run1.get() == {"a": 1, "b": 2}
run2 = template.from_checkpoint(MISSING)
assert run2.get() == {"a": 1} # Should NOT see {"b": 2}
def test_untracked_value() -> None:
channel = UntrackedValue(dict).from_checkpoint(MISSING)
assert channel.ValueType is dict
+141
View File
@@ -9059,3 +9059,144 @@ def test_fork_does_not_apply_pending_writes(
# Should be: 1 (input) + 20 (forked node_a) + 100 (node_b) = 121
assert result == {"value": 121}
def test_reducer_field_with_pydantic_default() -> None:
"""Test that Annotated reducer fields respect Pydantic Field defaults."""
class State(BaseModel):
query: str
files: Annotated[dict[str, str], operator.or_] = Field(
default_factory=lambda: {"default.txt": "content"}
)
observed_files: list[dict] = []
def node(state: State) -> dict:
observed_files.append(state.files)
return {"files": {"new.txt": "new"}}
graph = StateGraph(State)
graph.add_node("node", node)
graph.set_entry_point("node")
graph.set_finish_point("node")
app = graph.compile()
# Invoke without providing files — should use default
result = app.invoke({"query": "test"})
assert observed_files[0] == {"default.txt": "content"}
assert result == {
"query": "test",
"files": {"default.txt": "content", "new.txt": "new"},
}
def test_reducer_field_with_pydantic_default_explicit_value() -> None:
"""Test that an explicit value overrides the default for reducer fields."""
class State(BaseModel):
files: Annotated[dict[str, str], operator.or_] = Field(
default_factory=lambda: {"default.txt": "content"}
)
observed_files: list[dict] = []
def node(state: State) -> dict:
observed_files.append(state.files)
return {}
graph = StateGraph(State)
graph.add_node("node", node)
graph.set_entry_point("node")
graph.set_finish_point("node")
app = graph.compile()
# Invoke WITH explicit files — should use provided value, not default
app.invoke({"files": {"custom.txt": "custom"}})
assert observed_files[0] == {"default.txt": "content", "custom.txt": "custom"}
def test_reducer_field_with_dataclass_default() -> None:
"""Test that Annotated reducer fields respect dataclass defaults."""
@dataclass
class State:
query: str
files: Annotated[dict[str, str], operator.or_] = field(
default_factory=lambda: {"default.txt": "content"}
)
observed_files: list[dict] = []
def node(state: State) -> dict:
observed_files.append(state.files)
return {"files": {"new.txt": "new"}}
graph = StateGraph(State)
graph.add_node("node", node)
graph.set_entry_point("node")
graph.set_finish_point("node")
app = graph.compile()
result = app.invoke({"query": "test"})
assert observed_files[0] == {"default.txt": "content"}
assert result == {
"query": "test",
"files": {"default.txt": "content", "new.txt": "new"},
}
def test_reducer_field_with_pydantic_none_default() -> None:
"""Test that None default is respected (not confused with 'no default')."""
def _reducer(a: str | None, b: str | None) -> str | None:
if b is not None:
return b
return a
class State(BaseModel):
query: str
data: Annotated[str | None, _reducer] = None
observed: list[Any] = []
def node(state: State) -> dict:
observed.append(state.data)
return {"data": "updated"}
graph = StateGraph(State)
graph.add_node("node", node)
graph.set_entry_point("node")
graph.set_finish_point("node")
app = graph.compile()
result = app.invoke({"query": "test"})
assert observed[0] is None # default None was respected
assert result == {"query": "test", "data": "updated"}
def test_reducer_field_with_default_multi_step() -> None:
"""Test that defaults work correctly across multiple graph steps."""
class State(BaseModel):
items: Annotated[list[str], operator.add] = Field(
default_factory=lambda: ["initial"]
)
def step1(state: State) -> dict:
return {"items": ["step1"]}
def step2(state: State) -> dict:
return {"items": ["step2"]}
graph = StateGraph(State)
graph.add_node("step1", step1)
graph.add_node("step2", step2)
graph.set_entry_point("step1")
graph.add_edge("step1", "step2")
graph.set_finish_point("step2")
app = graph.compile()
result = app.invoke({})
# Default ["initial"] + ["step1"] + ["step2"]
assert result == {"items": ["initial", "step1", "step2"]}
+79
View File
@@ -9347,3 +9347,82 @@ async def test_fork_does_not_apply_pending_writes(
# 1 (input) + 20 (forked node_a) + 100 (node_b) = 121
assert result == {"value": 121}
async def test_reducer_field_with_pydantic_default() -> None:
"""Test that Annotated reducer fields respect Pydantic Field defaults."""
class State(BaseModel):
query: str
files: Annotated[dict[str, str], operator.or_] = Field(
default_factory=lambda: {"default.txt": "content"}
)
observed_files: list[dict] = []
def node(state: State) -> dict:
observed_files.append(state.files)
return {"files": {"new.txt": "new"}}
graph = StateGraph(State)
graph.add_node("node", node)
graph.set_entry_point("node")
graph.set_finish_point("node")
app = graph.compile()
result = await app.ainvoke({"query": "test"})
assert observed_files[0] == {"default.txt": "content"}
assert result == {
"query": "test",
"files": {"default.txt": "content", "new.txt": "new"},
}
async def test_reducer_field_with_pydantic_default_explicit_value() -> None:
"""Test that an explicit value overrides the default for reducer fields."""
class State(BaseModel):
files: Annotated[dict[str, str], operator.or_] = Field(
default_factory=lambda: {"default.txt": "content"}
)
observed_files: list[dict] = []
def node(state: State) -> dict:
observed_files.append(state.files)
return {}
graph = StateGraph(State)
graph.add_node("node", node)
graph.set_entry_point("node")
graph.set_finish_point("node")
app = graph.compile()
await app.ainvoke({"files": {"custom.txt": "custom"}})
assert observed_files[0] == {"default.txt": "content", "custom.txt": "custom"}
async def test_reducer_field_with_default_multi_step() -> None:
"""Test that defaults work correctly across multiple graph steps."""
class State(BaseModel):
items: Annotated[list[str], operator.add] = Field(
default_factory=lambda: ["initial"]
)
def step1(state: State) -> dict:
return {"items": ["step1"]}
def step2(state: State) -> dict:
return {"items": ["step2"]}
graph = StateGraph(State)
graph.add_node("step1", step1)
graph.add_node("step2", step2)
graph.set_entry_point("step1")
graph.add_edge("step1", "step2")
graph.set_finish_point("step2")
app = graph.compile()
result = await app.ainvoke({})
assert result == {"items": ["initial", "step1", "step2"]}
+2 -2
View File
@@ -1607,7 +1607,7 @@ dependencies = [
[package.metadata]
requires-dist = [
{ name = "langgraph-checkpoint", editable = "../checkpoint" },
{ name = "orjson", specifier = ">=3.10.1" },
{ name = "orjson", specifier = ">=3.11.5" },
{ name = "psycopg", specifier = ">=3.2.0" },
{ name = "psycopg-pool", specifier = ">=3.2.0" },
]
@@ -1812,7 +1812,7 @@ dependencies = [
[package.metadata]
requires-dist = [
{ name = "httpx", specifier = ">=0.25.2" },
{ name = "orjson", specifier = ">=3.10.1" },
{ name = "orjson", specifier = ">=3.11.5" },
]
[package.metadata.requires-dev]
+2 -2
View File
@@ -411,7 +411,7 @@ dependencies = [
[package.metadata]
requires-dist = [
{ name = "langgraph-checkpoint", editable = "../checkpoint" },
{ name = "orjson", specifier = ">=3.10.1" },
{ name = "orjson", specifier = ">=3.11.5" },
{ name = "psycopg", specifier = ">=3.2.0" },
{ name = "psycopg-pool", specifier = ">=3.2.0" },
]
@@ -585,7 +585,7 @@ dependencies = [
[package.metadata]
requires-dist = [
{ name = "httpx", specifier = ">=0.25.2" },
{ name = "orjson", specifier = ">=3.10.1" },
{ name = "orjson", specifier = ">=3.11.5" },
]
[package.metadata.requires-dev]
+1 -1
View File
@@ -3,6 +3,6 @@ from langgraph_sdk.client import get_client, get_sync_client
from langgraph_sdk.encryption import Encryption
from langgraph_sdk.encryption.types import EncryptionContext
__version__ = "0.3.6"
__version__ = "0.3.7"
__all__ = ["Auth", "Encryption", "EncryptionContext", "get_client", "get_sync_client"]
@@ -290,7 +290,7 @@ class AssistantsClient:
"""
get_params = {"recurse": recurse}
if params:
get_params = {**get_params, **params}
get_params = {**get_params, **dict(params)}
if namespace is not None:
return await self.http.get(
f"/assistants/{assistant_id}/subgraphs/{namespace}",
@@ -425,9 +425,9 @@ class AssistantsClient:
payload: dict[str, Any] = {}
if graph_id:
payload["graph_id"] = graph_id
if config:
if config is not None:
payload["config"] = config
if context:
if context is not None:
payload["context"] = context
if metadata:
payload["metadata"] = metadata
+2 -2
View File
@@ -110,7 +110,7 @@ def get_client(
if url is None:
url = "http://api"
if os.environ.get("__LANGGRAPH_DEFER_LOOPBACK_TRANSPORT") == "true":
transport = get_asgi_transport()(app=None, root_path="/noauth")
transport = get_asgi_transport()(app=None, root_path="/noauth") # type: ignore[invalid-argument-type]
_registered_transports.append(transport)
else:
try:
@@ -122,7 +122,7 @@ def get_client(
"Failed to connect to in-process LangGraph server. Deferring configuration.",
exc_info=True,
)
transport = get_asgi_transport()(app=None, root_path="/noauth")
transport = get_asgi_transport()(app=None, root_path="/noauth") # type: ignore[invalid-argument-type]
_registered_transports.append(transport)
if transport is None:
+8 -7
View File
@@ -2,6 +2,7 @@
from __future__ import annotations
import builtins
import warnings
from collections.abc import AsyncIterator, Callable, Mapping, Sequence
from typing import Any, overload
@@ -507,11 +508,11 @@ class RunsClient:
async def create_batch(
self,
payloads: list[RunCreate],
payloads: builtins.list[RunCreate],
*,
headers: Mapping[str, str] | None = None,
params: QueryParamTypes | None = None,
) -> list[Run]:
) -> builtins.list[Run]:
"""Create a batch of stateless background runs."""
def filter_payload(payload: RunCreate):
@@ -547,7 +548,7 @@ class RunsClient:
headers: Mapping[str, str] | None = None,
params: QueryParamTypes | None = None,
on_run_created: Callable[[RunCreateMetadata], None] | None = None,
) -> list[dict] | dict[str, Any]: ...
) -> builtins.list[dict] | dict[str, Any]: ...
@overload
async def wait(
@@ -572,7 +573,7 @@ class RunsClient:
headers: Mapping[str, str] | None = None,
params: QueryParamTypes | None = None,
on_run_created: Callable[[RunCreateMetadata], None] | None = None,
) -> list[dict] | dict[str, Any]: ...
) -> builtins.list[dict] | dict[str, Any]: ...
async def wait(
self,
@@ -600,7 +601,7 @@ class RunsClient:
params: QueryParamTypes | None = None,
on_run_created: Callable[[RunCreateMetadata], None] | None = None,
durability: Durability | None = None,
) -> list[dict] | dict[str, Any]:
) -> builtins.list[dict] | dict[str, Any]:
"""Create a run, wait until it finishes and return the final state.
Args:
@@ -751,10 +752,10 @@ class RunsClient:
limit: int = 10,
offset: int = 0,
status: RunStatus | None = None,
select: list[RunSelectField] | None = None,
select: builtins.list[RunSelectField] | None = None,
headers: Mapping[str, str] | None = None,
params: QueryParamTypes | None = None,
) -> list[Run]:
) -> builtins.list[Run]:
"""List runs.
Args:
+1 -1
View File
@@ -138,7 +138,7 @@ class StoreClient:
if refresh_ttl is not None:
get_params["refresh_ttl"] = refresh_ttl
if params:
get_params = {**get_params, **params}
get_params = {**get_params, **dict(params)}
return await self.http.get("/store/items", params=get_params, headers=headers)
async def delete_item(
+2 -2
View File
@@ -543,7 +543,7 @@ class ThreadsClient:
elif checkpoint_id:
get_params = {"subgraphs": subgraphs}
if params:
get_params = {**get_params, **params}
get_params = {**get_params, **dict(params)}
return await self.http.get(
f"/threads/{thread_id}/state/{checkpoint_id}",
params=get_params,
@@ -552,7 +552,7 @@ class ThreadsClient:
else:
get_params = {"subgraphs": subgraphs}
if params:
get_params = {**get_params, **params}
get_params = {**get_params, **dict(params)}
return await self.http.get(
f"/threads/{thread_id}/state",
params=get_params,
@@ -294,7 +294,7 @@ class SyncAssistantsClient:
"""
get_params = {"recurse": recurse}
if params:
get_params = {**get_params, **params}
get_params = {**get_params, **dict(params)}
if namespace is not None:
return self.http.get(
f"/assistants/{assistant_id}/subgraphs/{namespace}",
@@ -427,9 +427,9 @@ class SyncAssistantsClient:
payload: dict[str, Any] = {}
if graph_id:
payload["graph_id"] = graph_id
if config:
if config is not None:
payload["config"] = config
if context:
if context is not None:
payload["context"] = context
if metadata:
payload["metadata"] = metadata
+8 -7
View File
@@ -2,6 +2,7 @@
from __future__ import annotations
import builtins
import warnings
from collections.abc import Callable, Iterator, Mapping, Sequence
from typing import Any, overload
@@ -503,11 +504,11 @@ class SyncRunsClient:
def create_batch(
self,
payloads: list[RunCreate],
payloads: builtins.list[RunCreate],
*,
headers: Mapping[str, str] | None = None,
params: QueryParamTypes | None = None,
) -> list[Run]:
) -> builtins.list[Run]:
"""Create a batch of stateless background runs."""
def filter_payload(payload: RunCreate):
@@ -543,7 +544,7 @@ class SyncRunsClient:
headers: Mapping[str, str] | None = None,
params: QueryParamTypes | None = None,
on_run_created: Callable[[RunCreateMetadata], None] | None = None,
) -> list[dict] | dict[str, Any]: ...
) -> builtins.list[dict] | dict[str, Any]: ...
@overload
def wait(
@@ -568,7 +569,7 @@ class SyncRunsClient:
headers: Mapping[str, str] | None = None,
params: QueryParamTypes | None = None,
on_run_created: Callable[[RunCreateMetadata], None] | None = None,
) -> list[dict] | dict[str, Any]: ...
) -> builtins.list[dict] | dict[str, Any]: ...
def wait(
self,
@@ -596,7 +597,7 @@ class SyncRunsClient:
params: QueryParamTypes | None = None,
on_run_created: Callable[[RunCreateMetadata], None] | None = None,
durability: Durability | None = None,
) -> list[dict] | dict[str, Any]:
) -> builtins.list[dict] | dict[str, Any]:
"""Create a run, wait until it finishes and return the final state.
Args:
@@ -740,10 +741,10 @@ class SyncRunsClient:
limit: int = 10,
offset: int = 0,
status: RunStatus | None = None,
select: list[RunSelectField] | None = None,
select: builtins.list[RunSelectField] | None = None,
headers: Mapping[str, str] | None = None,
params: QueryParamTypes | None = None,
) -> list[Run]:
) -> builtins.list[Run]:
"""List runs.
Args:
+2 -2
View File
@@ -530,7 +530,7 @@ class SyncThreadsClient:
elif checkpoint_id:
get_params = {"subgraphs": subgraphs}
if params:
get_params = {**get_params, **params}
get_params = {**get_params, **dict(params)}
return self.http.get(
f"/threads/{thread_id}/state/{checkpoint_id}",
params=get_params,
@@ -539,7 +539,7 @@ class SyncThreadsClient:
else:
get_params = {"subgraphs": subgraphs}
if params:
get_params = {**get_params, **params}
get_params = {**get_params, **dict(params)}
return self.http.get(
f"/threads/{thread_id}/state",
params=get_params,
+3 -1
View File
@@ -146,7 +146,9 @@ AssistantSortBy = Literal[
The field to sort by.
"""
ThreadSortBy = Literal["thread_id", "status", "created_at", "updated_at"]
ThreadSortBy = Literal[
"thread_id", "status", "created_at", "updated_at", "state_updated_at"
]
"""
The field to sort by.
"""
+1 -1
View File
@@ -11,7 +11,7 @@ requires-python = ">=3.10"
readme = "README.md"
license = "MIT"
license-files = ['LICENSE']
dependencies = ["httpx>=0.25.2", "orjson>=3.10.1"]
dependencies = ["httpx>=0.25.2", "orjson>=3.11.5"]
[tool.hatch.version]
path = "langgraph_sdk/__init__.py"
+2 -2
View File
@@ -19,7 +19,7 @@ class AsyncListByteStream(httpx.AsyncByteStream):
self._chunks = list(chunks)
self._exc = exc
async def __aiter__(self): # type: ignore[override]
async def __aiter__(self):
for chunk in self._chunks:
yield chunk
if self._exc is not None:
@@ -34,7 +34,7 @@ class ListByteStream(httpx.ByteStream):
self._chunks = list(chunks)
self._exc = exc
def __iter__(self): # type: ignore[override]
def __iter__(self):
yield from self._chunks
if self._exc is not None:
raise self._exc
+2 -2
View File
@@ -65,7 +65,7 @@ def test_raise_for_status_typed_maps_exceptions_and_sets_status_code(
with pytest.raises(exc_type) as ei:
_raise_for_status_typed(r)
err = cast("APIStatusError", ei.value)
err = ei.value
assert err.status_code == status
# response attribute should be present and match
assert err.response.status_code == status
@@ -113,7 +113,7 @@ def test_error_message_in_str_and_args() -> None:
r = make_response(422, json_body={"message": "Validation failed"})
with pytest.raises(UnprocessableEntityError) as ei:
_raise_for_status_typed(r)
err = cast("UnprocessableEntityError", ei.value)
err = ei.value
assert str(err) == "Validation failed"
assert err.args == ("Validation failed",)
assert err.message == "Validation failed"
+1 -1
View File
@@ -484,7 +484,7 @@ test = [
[package.metadata]
requires-dist = [
{ name = "httpx", specifier = ">=0.25.2" },
{ name = "orjson", specifier = ">=3.10.1" },
{ name = "orjson", specifier = ">=3.11.5" },
]
[package.metadata.requires-dev]