Compare commits

...
Author SHA1 Message Date
infra 98f105fd5f docs: fix langgraph dataplane docs to remove license key 2025-07-13 19:13:07 -04:00
langchain-infraandGitHub 28c529feb2 docs: add mount prefix environment variable (#5061) 2025-06-11 11:21:53 -04:00
infra 91ebc8d3ed docs: add mount prefix environment variable 2025-06-11 11:19:40 -04:00
Eugene YurtsevandGitHub 6e08f4c12e v0: port GTM to v0 (#5056)
This was lost when the v0 branch was cut out and docs started being deployed from v0
2025-06-11 10:21:29 -04:00
Nuno CamposandGitHub f2dc0653f1 docs: list CipherProtocol in API (#5048) 2025-06-10 14:28:39 -07:00
Nuno CamposandNuno Campos 67177a5610 docs: list CipherProtocol in API 2025-06-10 14:25:26 -07:00
Asamu DavidandGitHub b1b238c7ea add docs for image_distro cli option (#4981) 2025-06-06 16:32:21 +01:00
David Asamu 598796ef86 add docs for image_distro cli option 2025-06-06 16:26:25 +01:00
Sydney RunkleandGitHub 3c7981201e docs: remove usage of StateGraph(dict) (#4967)
docs: remove references to `StateGraph(dict)` (#4964)

remove StateGraph(dict)
2025-06-04 21:30:39 -04:00
Sydney RunkleandGitHub 109c0dfb93 docs: deploy from v0 branch for now (#4960) (#4961)
only deploy docs on v0
2025-06-04 13:30:34 -04:00
Nuno Campos d7c364c5bb Port step_timeout/GraphBubbleUp fix to v0
See fix and tests in original PR https://github.com/langchain-ai/langgraph/pull/4950
2025-06-03 17:23:45 -07:00
Nuno Campos 746142fb07 One more 2025-06-02 16:16:11 -07:00
Nuno Campos b9c9c32c31 Allow releases from v0 2025-06-02 16:12:54 -07:00
Nuno Campos c89fe4c45d 0.4.8 2025-06-02 16:11:09 -07:00
Nuno CamposandGitHub b0e28851a6 v0: Fix Command(graph=PARENT) when used together w checkpointer=True (#4920) 2025-06-02 16:10:00 -07:00
Nuno Campos 48fb91deda Fix Command(graph=PARENT) when used together w checkpointer=True 2025-06-02 16:02:35 -07:00
19 changed files with 1808 additions and 1614 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ env:
jobs:
build:
if: github.ref == 'refs/heads/main'
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/v0'
runs-on: ubuntu-latest
outputs:
+7 -5
View File
@@ -4,9 +4,11 @@ on:
push:
branches:
- main
- v0
pull_request:
branches:
- main
- v0
workflow_dispatch:
permissions:
@@ -82,9 +84,9 @@ jobs:
run: make llms-text
- name: Build site
run: |
# If this is main branch, then we want to download stats. we do this
# If this is v0 branch, then we want to download stats. we do this
# with the env variable DOWNLOAD_STATS=true
if [ "${{ github.ref }}" == "refs/heads/main" ]; then
if [ "${{ github.ref }}" == "refs/heads/v0" ]; then
DOWNLOAD_STATS=true make build-docs
else
make build-docs
@@ -144,8 +146,8 @@ jobs:
fi
- name: Configure GitHub Pages
if: github.ref == 'refs/heads/main'
uses: actions/configure-pages@v4
if: github.ref == 'refs/heads/v0'
uses: actions/configure-pages@v5
- name: Upload Pages Artifact
# if: github.ref == 'refs/heads/main'
@@ -154,6 +156,6 @@ jobs:
path: ./docs/site/
- name: Deploy to GitHub Pages
if: github.ref == 'refs/heads/main'
if: github.ref == 'refs/heads/v0'
id: deployment
uses: actions/deploy-pages@v4
+1 -1
View File
@@ -13,7 +13,7 @@ env:
jobs:
build:
if: github.ref == 'refs/heads/main'
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/v0'
runs-on: ubuntu-latest
outputs:
+56 -4
View File
@@ -1,9 +1,16 @@
"""mkdocs hooks for adding custom logic to documentation pipeline.
Lifecycle events: https://www.mkdocs.org/dev-guide/plugins/#events
"""
import logging
import os
import posixpath
import re
from typing import Any, Dict
from bs4 import BeautifulSoup
from mkdocs.config.defaults import MkDocsConfig
from mkdocs.structure.files import Files, File
from mkdocs.structure.pages import Page
@@ -101,8 +108,7 @@ REDIRECT_MAP = {
"how-tos/deploy-self-hosted.md": "cloud/deployment/self_hosted_data_plane.md",
"concepts/self_hosted.md": "concepts/langgraph_self_hosted_data_plane.md",
# assistant redirects
"cloud/how-tos/assistant_versioning.md": "cloud/how-tos/configuration_cloud.md"
"cloud/how-tos/assistant_versioning.md": "cloud/how-tos/configuration_cloud.md",
}
@@ -292,7 +298,7 @@ Redirecting...
"""
def write_html(site_dir, old_path, new_path):
def _write_html(site_dir, old_path, new_path):
"""Write an HTML file in the site_dir with a meta redirect to the new page"""
# Determine all relevant paths
old_path_abs = os.path.join(site_dir, old_path)
@@ -308,6 +314,52 @@ def write_html(site_dir, old_path, new_path):
f.write(content)
def _inject_gtm(html: str) -> str:
"""Inject Google Tag Manager code into the HTML.
Code to inject Google Tag Manager noscript tag immediately after <body>.
This is done via hooks rather than via a template because the MkDocs material
theme does not seem to allow placing the code immediately after the <body> tag
without modifying the template files directly.
Args:
html: The HTML content to modify.
Returns:
The modified HTML content with GTM code injected.
"""
# Code was copied from Google Tag Manager setup instructions.
gtm_code = """
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-T35S4S46"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
"""
soup = BeautifulSoup(html, "html.parser")
body = soup.body
if body:
# Insert the GTM code as raw HTML at the top of <body>
body.insert(0, BeautifulSoup(gtm_code, "html.parser"))
return str(soup)
else:
return html # fallback if no <body> found
def on_post_page(output: str, page: Page, config: MkDocsConfig) -> str:
"""Inject Google Tag Manager noscript tag immediately after <body>.
Args:
output: The HTML output of the page.
page: The page instance.
config: The MkDocs configuration object.
Returns:
modified HTML output with GTM code injected.
"""
return _inject_gtm(output)
# Create HTML files for redirects after site dir has been built
def on_post_build(config):
use_directory_urls = config.get("use_directory_urls")
@@ -324,4 +376,4 @@ def on_post_build(config):
+ hash
+ suffix
)
write_html(config["site_dir"], old_html_path, new_html_path)
_write_html(config["site_dir"], old_html_path, new_html_path)
@@ -31,7 +31,6 @@ Before deploying, review the [conceptual guide for the Self-Hosted Data Plane](.
1. Configure your `langgraph-dataplane-values.yaml` file.
config:
langgraphPlatformLicenseKey: "" # Your LangGraph Platform license key
langsmithApiKey: "" # API Key of your Workspace
langsmithWorkspaceId: "" # Workspace ID
hostBackendUrl: "https://api.host.langchain.com" # Only override this if on EU
+15
View File
@@ -43,6 +43,7 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
| <span style="white-space: nowrap;">`graphs`</span> | **Required**. Mapping from graph ID to path where the compiled graph or a function that makes a graph is defined. Example: <ul><li>`./your_package/your_file.py:variable`, where `variable` is an instance of `langgraph.graph.state.CompiledStateGraph`</li><li>`./your_package/your_file.py:make_graph`, where `make_graph` is a function that takes a config dictionary (`langchain_core.runnables.RunnableConfig`) and returns an instance of `langgraph.graph.state.StateGraph` or `langgraph.graph.state.CompiledStateGraph`. See [how to rebuild a graph at runtime](../../cloud/deployment/graph_rebuild.md) for more details.</li></ul> |
| <span style="white-space: nowrap;">`auth`</span> | _(Added in v0.0.11)_ Auth configuration containing the path to your authentication handler. Example: `./your_package/auth.py:auth`, where `auth` is an instance of `langgraph_sdk.Auth`. See [authentication guide](../../concepts/auth.md) for details. |
| <span style="white-space: nowrap;">`base_image`</span> | Optional. Base image to use for the LangGraph API server. Defaults to `langchain/langgraph-api` or `langchain/langgraphjs-api`. Use this to pin your builds to a particular version of the langgraph API, such as `"langchain/langgraph-server:0.2"`. See https://hub.docker.com/r/langchain/langgraph-server/tags for more details. (added in `langgraph-cli==0.2.8`) |
| <span style="white-space: nowrap;">`image_distro`</span> | Optional. Linux distribution for the base image. Must be either `"debian"` or `"wolfi"`. If omitted, defaults to `"debian"`. Available in `langgraph-cli>=0.2.11`.|
| <span style="white-space: nowrap;">`env`</span> | Path to `.env` file or a mapping from environment variable to its value. |
| <span style="white-space: nowrap;">`store`</span> | Configuration for adding semantic search and/or time-to-live (TTL) to the BaseStore. Contains the following fields: <ul><li>`index` (optional): Configuration for semantic search indexing with fields `embed`, `dims`, and optional `fields`.</li><li>`ttl` (optional): Configuration for item expiration. An object with optional fields: `refresh_on_read` (boolean, defaults to `true`), `default_ttl` (float, lifespan in **minutes**, defaults to no expiration), and `sweep_interval_minutes` (integer, how often to check for expired items, defaults to no sweeping).</li></ul> |
| <span style="white-space: nowrap;">`ui`</span> | Optional. Named definitions of UI components emitted by the agent, each pointing to a JS/TS file. (added in `langgraph-cli==0.1.84`) |
@@ -79,6 +80,20 @@ The LangGraph CLI requires a JSON configuration file that follows this [schema](
}
```
#### Using Wolfi Base Images
You can specify the Linux distribution for your base image using the `image_distro` field. Valid options are `debian` or `wolfi`. Wolfi is the recommended option as it provides smaller and more secure images. This is available in `langgraph-cli>=0.2.11`.
```json
{
"dependencies": ["."],
"graphs": {
"chat": "./chat/graph.py:graph"
},
"image_distro": "wolfi"
}
```
#### Adding semantic search to the store
All deployments come with a DB-backed BaseStore. Adding an "index" configuration to your `langgraph.json` will enable [semantic search](../deployment/semantic_search.md) within the BaseStore of your deployment.
+9
View File
@@ -123,3 +123,12 @@ Defaults to `''`.
Set `REDIS_CLUSTER` to `True` to enable Redis Cluster mode. When enabled, the system will connect to Redis using cluster mode. This is useful when connecting to a Redis Cluster deployment.
Defaults to `False`.
## `MOUNT_PREFIX`
!!! info "Only Allowed in Self-Hosted Deployments"
The `MOUNT_PREFIX` environment variable is only allowed in Self-Hosted Deployment models, LangGraph Platform SaaS will not allow this environment variable.
Set `MOUNT_PREFIX` to serve the LangGraph Server under a specific path prefix. This is useful for deployments where the server is behind a reverse proxy or load balancer that requires a specific path prefix.
For example, if the server is to be served under `https://example.com/langgraph`, set `MOUNT_PREFIX` to `/langgraph`.
+9 -3
View File
@@ -197,19 +197,25 @@ In LangGraph, nodes are typically python functions (sync or async) where the **f
Similar to `NetworkX`, you add these nodes to a graph using the [add_node][langgraph.graph.StateGraph.add_node] method:
```python
from typing_extensions import TypedDict
from langchain_core.runnables import RunnableConfig
from langgraph.graph import StateGraph
builder = StateGraph(dict)
class State(TypedDict):
input: str
results: str
builder = StateGraph(State)
def my_node(state: dict, config: RunnableConfig):
def my_node(state: State, config: RunnableConfig):
print("In node: ", config["configurable"]["user_id"])
return {"results": f"Hello, {state['input']}!"}
# The second argument is optional
def my_other_node(state: dict):
def my_other_node(state: State):
return state
+26 -1
View File
@@ -470,9 +470,34 @@ If the checkpointer is used with asynchronous graph execution (i.e. executing th
### Serializer
When checkpointers save the graph state, they need to serialize the channel values in the state. This is done using serializer objects.
When checkpointers save the graph state, they need to serialize the channel values in the state. This is done using serializer objects.
`langgraph_checkpoint` defines [protocol][langgraph.checkpoint.serde.base.SerializerProtocol] for implementing serializers provides a default implementation ([JsonPlusSerializer][langgraph.checkpoint.serde.jsonplus.JsonPlusSerializer]) that handles a wide variety of types, including LangChain and LangGraph primitives, datetimes, enums and more.
#### Encryption
Checkpointers can optionally encrypt all persisted state. To enable this, pass an instance of [`EncryptedSerializer`][langgraph.checkpoint.serde.encrypted.EncryptedSerializer] to the `serde` argument of any `BaseCheckpointSaver` implementation. The easiest way to create an encrypted serializer is via [`from_pycryptodome_aes`][langgraph.checkpoint.serde.encrypted.EncryptedSerializer.from_pycryptodome_aes], which reads the AES key from the `LANGGRAPH_AES_KEY` environment variable (or accepts a `key` argument):
```python
import sqlite3
from langgraph.checkpoint.serde.encrypted import EncryptedSerializer
from langgraph.checkpoint.sqlite import SqliteSaver
serde = EncryptedSerializer.from_pycryptodome_aes() # reads LANGGRAPH_AES_KEY
checkpointer = SqliteSaver(sqlite3.connect("checkpoint.db"), serde=serde)
```
```python
from langgraph.checkpoint.serde.encrypted import EncryptedSerializer
from langgraph.checkpoint.postgres import PostgresSaver
serde = EncryptedSerializer.from_pycryptodome_aes()
checkpointer = PostgresSaver.from_conn_string("postgresql://...", serde=serde)
checkpointer.setup()
```
When running on LangGraph Platform, encryption is automatically enabled whenever `LANGGRAPH_AES_KEY` is present, so you only need to provide the environment variable. Other encryption schemes can be used by implementing [`CipherProtocol`][langgraph.checkpoint.serde.base.CipherProtocol] and supplying it to `EncryptedSerializer`.
## Capabilities
### Human-in-the-loop
+7 -1
View File
@@ -12,12 +12,18 @@
options:
members:
- SerializerProtocol
- CipherProtocol
::: langgraph.checkpoint.serde.jsonplus
options:
members:
- JsonPlusSerializer
::: langgraph.checkpoint.serde.encrypted
options:
members:
- EncryptedSerializer
::: langgraph.checkpoint.memory
::: langgraph.checkpoint.sqlite
@@ -32,4 +38,4 @@
::: langgraph.checkpoint.postgres.aio
options:
members:
- AsyncPostgresSaver
- AsyncPostgresSaver
-19
View File
@@ -381,25 +381,6 @@ extra:
link: https://github.com/langchain-ai/langgraph
- icon: fontawesome/brands/twitter
link: https://twitter.com/LangChainAI
analytics:
provider: google
property: G-G8X6ELZYE0
feedback:
title: Was this page helpful?
ratings:
- icon: material/emoticon-happy-outline
name: This page was helpful
data: 1
note: >-
Thanks for your feedback!
- icon: material/emoticon-sad-outline
name: This page could be improved
data: 0
note: >-
Thanks for your feedback! Please help us improve this page by adding to the discussion below.
shared_analytics:
provider: google
property: G-47WX3HKKY2
validation:
# https://www.mkdocs.org/user-guide/configuration/
# We are still raising for omitted files because they determine the breadcrumbs for pages.
+11 -2
View File
@@ -1,5 +1,16 @@
{% extends "base.html" %}
{% block analytics %}
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-T35S4S46');</script>
<!-- End Google Tag Manager -->
{% endblock %}
{% block extrahead %}
<meta name="algolia-site-verification" content="165B7E7C89E49946" />
<style>
@@ -185,7 +196,6 @@
</style>
{% endblock %}
{% block content %}
<div class="notebook-links">
{% if page.nb_url %}
@@ -209,7 +219,6 @@
{% endif %}
{% endblock %}
{% block announce %}
<strong>We are growing and hiring for multiple roles for LangChain, LangGraph and LangSmith. <a href="https://www.langchain.com/careers" target="_blank" rel="noopener noreferrer"> Join our team!</a></strong>
{% endblock %}
+9 -3
View File
@@ -319,12 +319,18 @@ class StateGraph(Graph):
Example:
```python
from typing_extensions import TypedDict
from langchain_core.runnables import RunnableConfig
from langgraph.graph import START, StateGraph
def my_node(state, config):
class State(TypedDict):
x: int
def my_node(state: State, config: RunnableConfig) -> State:
return {"x": state["x"] + 1}
builder = StateGraph(dict)
builder = StateGraph(State)
builder.add_node(my_node) # node name will be 'my_node'
builder.add_edge(START, "my_node")
graph = builder.compile()
@@ -334,7 +340,7 @@ class StateGraph(Graph):
Example: Customize the name:
```python
builder = StateGraph(dict)
builder = StateGraph(State)
builder.add_node("my_fair_node", my_node)
builder.add_edge(START, "my_fair_node")
graph = builder.compile()
+2 -2
View File
@@ -41,7 +41,7 @@ def run_with_retry(
except ParentCommand as exc:
ns: str = config[CONF][CONFIG_KEY_CHECKPOINT_NS]
cmd = exc.args[0]
if cmd.graph == ns:
if cmd.graph in (ns, task.name):
# this command is for the current graph, handle it
for w in task.writers:
w.invoke(cmd, config)
@@ -137,7 +137,7 @@ async def arun_with_retry(
except ParentCommand as exc:
ns: str = config[CONF][CONFIG_KEY_CHECKPOINT_NS]
cmd = exc.args[0]
if cmd.graph == ns:
if cmd.graph in (ns, task.name):
# this command is for the current graph, handle it
for w in task.writers:
w.invoke(cmd, config)
+1 -1
View File
@@ -431,7 +431,7 @@ class PregelRunner:
writes.extend(resumes)
self.put_writes()(task.id, writes) # type: ignore[misc]
elif isinstance(exception, GraphBubbleUp):
raise exception
pass
else:
# save error to checkpointer
task.writes.append((ERROR, exception))
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "langgraph"
version = "0.4.7"
version = "0.4.8"
description = "Building stateful, multi-actor applications with LLMs"
authors = []
requires-python = ">=3.9"
+45 -2
View File
@@ -5514,8 +5514,11 @@ def test_runnable_passthrough_node_graph() -> None:
assert graph.get_graph(xray=True).to_json() == graph.get_graph(xray=False).to_json()
@pytest.mark.parametrize("subgraph_persist", [True, False])
@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_SYNC)
def test_parent_command(request: pytest.FixtureRequest, checkpointer_name: str) -> None:
def test_parent_command(
request: pytest.FixtureRequest, checkpointer_name: str, subgraph_persist: bool
) -> None:
from langchain_core.messages import BaseMessage
from langchain_core.tools import tool
@@ -5527,7 +5530,7 @@ def test_parent_command(request: pytest.FixtureRequest, checkpointer_name: str)
subgraph_builder = StateGraph(MessagesState)
subgraph_builder.add_node("tool", get_user_name)
subgraph_builder.add_edge(START, "tool")
subgraph = subgraph_builder.compile()
subgraph = subgraph_builder.compile(checkpointer=subgraph_persist)
class CustomParentState(TypedDict):
messages: Annotated[list[BaseMessage], add_messages]
@@ -8802,3 +8805,43 @@ def test_imp_exception(
{"my_task": 2},
{"my_workflow": "done"},
]
@pytest.mark.parametrize("subgraph_persist", [True, False])
def test_parent_command_goto(
sync_checkpointer: BaseCheckpointSaver, subgraph_persist: bool
) -> None:
class State(TypedDict):
dialog_state: Annotated[list[str], operator.add]
def node_a_child(state):
return {"dialog_state": ["a_child_state"]}
def node_b_child(state):
return Command(
graph=Command.PARENT,
goto="node_b_parent",
update={"dialog_state": ["b_child_state"]},
)
sub_builder = StateGraph(State)
sub_builder.add_node(node_a_child)
sub_builder.add_node(node_b_child)
sub_builder.add_edge(START, "node_a_child")
sub_builder.add_edge("node_a_child", "node_b_child")
sub_graph = sub_builder.compile(checkpointer=subgraph_persist)
def node_b_parent(state):
return {"dialog_state": ["node_b_parent"]}
main_builder = StateGraph(State)
main_builder.add_node(node_b_parent)
main_builder.add_edge(START, "subgraph_node")
main_builder.add_node("subgraph_node", sub_graph, destinations=("node_b_parent",))
main_graph = main_builder.compile(sync_checkpointer, name="parent")
config = {"configurable": {"thread_id": 1}}
assert main_graph.invoke(input={"dialog_state": ["init_state"]}, config=config) == {
"dialog_state": ["init_state", "b_child_state", "node_b_parent"]
}
+43 -2
View File
@@ -6772,8 +6772,9 @@ async def test_debug_nested_subgraphs(async_checkpointer: BaseCheckpointSaver):
assert stream_task.get("state") == history_task.state
@pytest.mark.parametrize("subgraph_persist", [True, False])
@pytest.mark.parametrize("checkpointer_name", ALL_CHECKPOINTERS_ASYNC)
async def test_parent_command(checkpointer_name: str) -> None:
async def test_parent_command(checkpointer_name: str, subgraph_persist: bool) -> None:
from langchain_core.messages import BaseMessage
from langchain_core.tools import tool
@@ -6785,7 +6786,7 @@ async def test_parent_command(checkpointer_name: str) -> None:
subgraph_builder = StateGraph(MessagesState)
subgraph_builder.add_node("tool", get_user_name)
subgraph_builder.add_edge(START, "tool")
subgraph = subgraph_builder.compile()
subgraph = subgraph_builder.compile(checkpointer=subgraph_persist)
class CustomParentState(TypedDict):
messages: Annotated[list[BaseMessage], add_messages]
@@ -9446,3 +9447,43 @@ async def test_imp_exception(
"parent_ids": [],
},
]
@pytest.mark.parametrize("subgraph_persist", [True, False])
async def test_parent_command_goto(
async_checkpointer: BaseCheckpointSaver, subgraph_persist: bool
) -> None:
class State(TypedDict):
dialog_state: Annotated[list[str], operator.add]
async def node_a_child(state):
return {"dialog_state": ["a_child_state"]}
async def node_b_child(state):
return Command(
graph=Command.PARENT,
goto="node_b_parent",
update={"dialog_state": ["b_child_state"]},
)
sub_builder = StateGraph(State)
sub_builder.add_node(node_a_child)
sub_builder.add_node(node_b_child)
sub_builder.add_edge(START, "node_a_child")
sub_builder.add_edge("node_a_child", "node_b_child")
sub_graph = sub_builder.compile(checkpointer=subgraph_persist)
async def node_b_parent(state):
return {"dialog_state": ["node_b_parent"]}
main_builder = StateGraph(State)
main_builder.add_node(node_b_parent)
main_builder.add_edge(START, "subgraph_node")
main_builder.add_node("subgraph_node", sub_graph, destinations=("node_b_parent",))
main_graph = main_builder.compile(async_checkpointer, name="parent")
config = {"configurable": {"thread_id": 1}}
assert await main_graph.ainvoke(
input={"dialog_state": ["init_state"]}, config=config
) == {"dialog_state": ["init_state", "b_child_state", "node_b_parent"]}
+1565 -1565
View File
File diff suppressed because it is too large Load Diff