From 64aa1e6cd8ab23cf3a04df9c9a4fb9488010344d Mon Sep 17 00:00:00 2001 From: Nuno Campos Date: Fri, 11 Apr 2025 09:53:54 -0700 Subject: [PATCH 1/8] Use tuple entry for control branch --- libs/langgraph/langgraph/graph/state.py | 71 ++++-------------------- libs/langgraph/tests/test_large_cases.py | 8 +-- 2 files changed, 16 insertions(+), 63 deletions(-) diff --git a/libs/langgraph/langgraph/graph/state.py b/libs/langgraph/langgraph/graph/state.py index 73d242dfd..7b9aef42c 100644 --- a/libs/langgraph/langgraph/graph/state.py +++ b/libs/langgraph/langgraph/graph/state.py @@ -44,6 +44,7 @@ from langgraph.constants import ( NS_END, NS_SEP, TAG_HIDDEN, + TASKS, ) from langgraph.errors import ( ErrorCode, @@ -78,7 +79,7 @@ from langgraph.store.base import BaseStore from langgraph.types import All, Checkpointer, Command, RetryPolicy from langgraph.utils.fields import get_field_default from langgraph.utils.pydantic import create_model -from langgraph.utils.runnable import RunnableCallable, RunnableLike, coerce_to_runnable +from langgraph.utils.runnable import RunnableLike, coerce_to_runnable logger = logging.getLogger(__name__) @@ -669,10 +670,6 @@ class StateGraph(Graph): for key, node in self.nodes.items(): compiled.attach_node(key, node) - compiled.nodes[START].writers.append(CONTROL_BRANCH_PATH) - for key in self.nodes: - compiled.nodes[key].writers.append(CONTROL_BRANCH_PATH) - for start, end in self.edges: compiled.attach_edge(start, end) @@ -801,6 +798,7 @@ class CompiledStateGraph(CompiledGraph): ChannelWriteTupleEntry( mapper=_get_root if output_keys == ["__root__"] else _get_updates ), + ChannelWriteTupleEntry(mapper=_control_branch), ) # add node and output channel @@ -935,7 +933,7 @@ class CompiledStateGraph(CompiledGraph): if end != END: self.nodes[end].writers.append( ChannelWrite( - [ChannelWriteEntry(channel_name, end)], tags=[TAG_HIDDEN] + (ChannelWriteEntry(channel_name, end),), tags=[TAG_HIDDEN] ) ) @@ -1061,10 +1059,9 @@ def _coerce_state(schema: Type[Any], input: dict[str, Any]) -> dict[str, Any]: return schema(**input) -def _control_branch(value: Any, config: RunnableConfig) -> Any: +def _control_branch(value: Any) -> Sequence[tuple[str, Any]]: if isinstance(value, Send): - ChannelWrite.do_write(config, (value,)) - return value + return ((TASKS, value),) commands: list[Command] = [] if isinstance(value, Command): commands.append(value) @@ -1072,66 +1069,22 @@ def _control_branch(value: Any, config: RunnableConfig) -> Any: for cmd in value: if isinstance(cmd, Command): commands.append(cmd) - rtn: list[Union[ChannelWriteEntry, Send]] = [] + rtn: list[tuple[str, Any]] = [] for command in commands: if command.graph == Command.PARENT: raise ParentCommand(command) if isinstance(command.goto, Send): - rtn.append(command.goto) + rtn.append((TASKS, command.goto)) elif isinstance(command.goto, str): - rtn.append(ChannelWriteEntry(CHANNEL_BRANCH_TO.format(command.goto), None)) + rtn.append((CHANNEL_BRANCH_TO.format(command.goto), None)) else: rtn.extend( - go + (TASKS, go) if isinstance(go, Send) - else ChannelWriteEntry(CHANNEL_BRANCH_TO.format(go), None) + else (CHANNEL_BRANCH_TO.format(go), None) for go in command.goto ) - if rtn: - ChannelWrite.do_write(config, rtn) - return value - - -async def _acontrol_branch(value: Any, config: RunnableConfig) -> Any: - if isinstance(value, Send): - ChannelWrite.do_write(config, (value,)) - return value - commands: list[Command] = [] - if isinstance(value, Command): - commands.append(value) - elif isinstance(value, (list, tuple)): - for cmd in value: - if isinstance(cmd, Command): - commands.append(cmd) - rtn: list[Union[ChannelWriteEntry, Send]] = [] - for command in commands: - if command.graph == Command.PARENT: - raise ParentCommand(command) - if isinstance(command.goto, Send): - rtn.append(command.goto) - elif isinstance(command.goto, str): - rtn.append(ChannelWriteEntry(CHANNEL_BRANCH_TO.format(command.goto), None)) - else: - rtn.extend( - go - if isinstance(go, Send) - else ChannelWriteEntry(CHANNEL_BRANCH_TO.format(go), None) - for go in command.goto - ) - if rtn: - ChannelWrite.do_write(config, rtn) - return value - - -CONTROL_BRANCH_PATH = RunnableCallable( - _control_branch, - _acontrol_branch, - tags=[TAG_HIDDEN], - trace=False, - recurse=False, - set_context=False, - func_accepts_config=True, -) + return rtn def _get_root(input: Any) -> Optional[Sequence[tuple[str, Any]]]: diff --git a/libs/langgraph/tests/test_large_cases.py b/libs/langgraph/tests/test_large_cases.py index f5be91cee..bb8e124ff 100644 --- a/libs/langgraph/tests/test_large_cases.py +++ b/libs/langgraph/tests/test_large_cases.py @@ -4660,7 +4660,7 @@ def test_root_graph( content="result for query", name="search_api", tool_call_id="tool_call123", - id="00000000-0000-4000-8000-000000000040", + id="00000000-0000-4000-8000-000000000033", ) ] }, @@ -4683,7 +4683,7 @@ def test_root_graph( content="result for another", name="search_api", tool_call_id="tool_call456", - id="00000000-0000-4000-8000-000000000049", + id="00000000-0000-4000-8000-000000000041", ) ] }, @@ -5387,7 +5387,7 @@ def test_root_graph( "__root__": [ HumanMessage( content="what is weather in sf", - id="00000000-0000-4000-8000-000000000083", + id="00000000-0000-4000-8000-000000000070", ), AIMessage( content="", @@ -5407,7 +5407,7 @@ def test_root_graph( ), AIMessage(content="answer", id="ai2"), AIMessage( - content="an extra message", id="00000000-0000-4000-8000-000000000107" + content="an extra message", id="00000000-0000-4000-8000-000000000091" ), HumanMessage(content="what is weather in la"), ], From d1ac0a0e13bf26784621a3ced3337556e6ca37ec Mon Sep 17 00:00:00 2001 From: Andrew Nguonly Date: Fri, 11 Apr 2025 12:01:43 -0700 Subject: [PATCH 2/8] docs: Add alpha and beta labels for respective LangGraph Platform deployment options (#4249) ### Summary Examples: ![image](https://github.com/user-attachments/assets/2a36a262-5373-498d-9907-19d5447fbb6a) ![image](https://github.com/user-attachments/assets/70671e08-34b6-40ed-964d-9d195ea8308d) ![image](https://github.com/user-attachments/assets/fcb877a6-475c-47a4-b8af-91cbdc00f89b) --- docs/docs/cloud/deployment/cloud.md | 2 +- docs/docs/cloud/deployment/self_hosted_control_plane.md | 2 +- docs/docs/cloud/deployment/self_hosted_data_plane.md | 2 +- docs/docs/concepts/deployment_options.md | 6 +++--- docs/docs/concepts/langgraph_cloud.md | 2 +- docs/docs/concepts/langgraph_self_hosted_control_plane.md | 2 +- docs/docs/concepts/langgraph_self_hosted_data_plane.md | 2 +- docs/docs/tutorials/deployment.md | 6 +++--- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/docs/cloud/deployment/cloud.md b/docs/docs/cloud/deployment/cloud.md index c53d516a6..638d095ba 100644 --- a/docs/docs/cloud/deployment/cloud.md +++ b/docs/docs/cloud/deployment/cloud.md @@ -1,4 +1,4 @@ -# How to Deploy to Cloud SaaS +# How to Deploy to Cloud SaaS (Beta) Before deploying, review the [conceptual guide for the Cloud SaaS](../../concepts/langgraph_cloud.md) deployment option. diff --git a/docs/docs/cloud/deployment/self_hosted_control_plane.md b/docs/docs/cloud/deployment/self_hosted_control_plane.md index f72de8bd2..c9e8265c1 100644 --- a/docs/docs/cloud/deployment/self_hosted_control_plane.md +++ b/docs/docs/cloud/deployment/self_hosted_control_plane.md @@ -1,4 +1,4 @@ -# How to Deploy Self-Hosted Control Plane +# How to Deploy Self-Hosted Control Plane (Beta) Before deploying, review the [conceptual guide for the Self-Hosted Control Plane](../../concepts/langgraph_self_hosted_control_plane.md) deployment option. diff --git a/docs/docs/cloud/deployment/self_hosted_data_plane.md b/docs/docs/cloud/deployment/self_hosted_data_plane.md index 652842e49..c9512eb10 100644 --- a/docs/docs/cloud/deployment/self_hosted_data_plane.md +++ b/docs/docs/cloud/deployment/self_hosted_data_plane.md @@ -1,4 +1,4 @@ -# How to Deploy Self-Hosted Data Plane +# How to Deploy Self-Hosted Data Plane (Beta) Before deploying, review the [conceptual guide for the Self-Hosted Data Plane](../../concepts/langgraph_self_hosted_data_plane.md) deployment option. diff --git a/docs/docs/concepts/deployment_options.md b/docs/docs/concepts/deployment_options.md index f5af7f3ff..554c0d87c 100644 --- a/docs/docs/concepts/deployment_options.md +++ b/docs/docs/concepts/deployment_options.md @@ -10,11 +10,11 @@ There are 4 main options for deploying with the LangGraph Platform: -1. **[Cloud SaaS](#cloud-saas)**: Available for **Plus** and **Enterprise** plans. +1. **Cloud SaaS(Beta)**: Available for **Plus** and **Enterprise** plans. -1. **[Self-Hosted Data Plane](#self-hosted-data-plane)**: Available for the **Enterprise** plan. +1. **Self-Hosted Data Plane(Beta)**: Available for the **Enterprise** plan. -1. **[Self-Hosted Control Plane](#self-hosted-control-plane)**: Available for the **Enterprise** plan. +1. **Self-Hosted Control Plane(Beta)**: Available for the **Enterprise** plan. 1. **[Standalone Container](#standalone-container)**: Available for all plans. diff --git a/docs/docs/concepts/langgraph_cloud.md b/docs/docs/concepts/langgraph_cloud.md index 557b39e60..1e083dca8 100644 --- a/docs/docs/concepts/langgraph_cloud.md +++ b/docs/docs/concepts/langgraph_cloud.md @@ -1,4 +1,4 @@ -# Cloud SaaS +# Cloud SaaS (Beta) To deploy a [LangGraph Server](../concepts/langgraph_server.md), follow the how-to guide for [how to deploy to Cloud SaaS](../cloud/deployment/cloud.md). diff --git a/docs/docs/concepts/langgraph_self_hosted_control_plane.md b/docs/docs/concepts/langgraph_self_hosted_control_plane.md index 9ab2c2549..718556dc7 100644 --- a/docs/docs/concepts/langgraph_self_hosted_control_plane.md +++ b/docs/docs/concepts/langgraph_self_hosted_control_plane.md @@ -1,4 +1,4 @@ -# Self-Hosted Control Plane +# Self-Hosted Control Plane (Beta) To deploy a [LangGraph Server](../concepts/langgraph_server.md), follow the how-to guide for [how to deploy the Self-Hosted Control Plane](../cloud/deployment/self_hosted_control_plane.md). diff --git a/docs/docs/concepts/langgraph_self_hosted_data_plane.md b/docs/docs/concepts/langgraph_self_hosted_data_plane.md index bc4c65c0c..51f0114e1 100644 --- a/docs/docs/concepts/langgraph_self_hosted_data_plane.md +++ b/docs/docs/concepts/langgraph_self_hosted_data_plane.md @@ -1,4 +1,4 @@ -# Self-Hosted Data Plane +# Self-Hosted Data Plane (Beta) To deploy a [LangGraph Server](../concepts/langgraph_server.md), follow the how-to guide for [how to deploy the Self-Hosted Data Plane](../cloud/deployment/self_hosted_data_plane.md). diff --git a/docs/docs/tutorials/deployment.md b/docs/docs/tutorials/deployment.md index 021d52aad..d2c7567e7 100644 --- a/docs/docs/tutorials/deployment.md +++ b/docs/docs/tutorials/deployment.md @@ -17,9 +17,9 @@ Get started deploying your LangGraph applications locally or on the cloud with ## Deployment Options -- [Cloud SaaS](../concepts/langgraph_cloud.md): Connect to your GitHub repositories and deploy LangGraph Servers to LangChain's cloud. We manage everything. -- [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_data_plane.md): Create deployments from the [Control Plane UI](../concepts/langgraph_control_plane.md#control-plane-ui) and deploy LangGraph Servers to your cloud. We manage the [control plane](../concepts/langgraph_control_plane.md), you manage the deployments. -- [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md#control-plane-ui): Create deployments from a self-hosted [Control Plane UI](../concepts/langgraph_control_plane.md) and deploy LangGraph Servers to your cloud. You manage everything. +- Cloud SaaS(Beta): Connect to your GitHub repositories and deploy LangGraph Servers to LangChain's cloud. We manage everything. +- Self-Hosted Data Plane(Beta): Create deployments from the [Control Plane UI](../concepts/langgraph_control_plane.md#control-plane-ui) and deploy LangGraph Servers to your cloud. We manage the [control plane](../concepts/langgraph_control_plane.md), you manage the deployments. +- Self-Hosted Control Plane(Beta): Create deployments from a self-hosted [Control Plane UI](../concepts/langgraph_control_plane.md#control-plane-ui) and deploy LangGraph Servers to your cloud. You manage everything. - [Standalone Container](../concepts/langgraph_standalone_container.md): Deploy LangGraph Server Docker images however you like. A quick comparison... From 233cca1357e7e38ac2f1987ce150eab944a436a2 Mon Sep 17 00:00:00 2001 From: Eugene Yurtsev Date: Fri, 11 Apr 2025 15:29:11 -0400 Subject: [PATCH 3/8] Update langgraph_platform.md (#4251) Co-authored-by: Catherine --- docs/docs/concepts/langgraph_platform.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/docs/concepts/langgraph_platform.md b/docs/docs/concepts/langgraph_platform.md index bde8917d7..77d3f7c06 100644 --- a/docs/docs/concepts/langgraph_platform.md +++ b/docs/docs/concepts/langgraph_platform.md @@ -5,6 +5,10 @@ search: # LangGraph Platform +Watch this 4-minute overview of LangGraph Platform to see how it helps you build, deploy, and evaluate agentic applications. + + + ## Overview LangGraph Platform is a commercial solution for deploying agentic applications to production, built on the open-source [LangGraph framework](./high_level.md). From a9be75f745282dbb77adce3d77cfd444ab8d3e29 Mon Sep 17 00:00:00 2001 From: Andrew Nguonly Date: Fri, 11 Apr 2025 14:48:20 -0700 Subject: [PATCH 4/8] docs: Add Data Plane features sections for custom Postgres/Redis, tracing, telemetry, and licensing (#4254) --- docs/docs/cloud/reference/env_var.md | 19 ++++----- docs/docs/concepts/index.md | 6 +-- docs/docs/concepts/langgraph_data_plane.md | 45 +++++++++++++++++++++- 3 files changed, 57 insertions(+), 13 deletions(-) diff --git a/docs/docs/cloud/reference/env_var.md b/docs/docs/cloud/reference/env_var.md index e4d4de0f2..4f21ee890 100644 --- a/docs/docs/cloud/reference/env_var.md +++ b/docs/docs/cloud/reference/env_var.md @@ -1,6 +1,6 @@ # Environment Variables -The LangGraph Cloud Server supports specific environment variables for configuring a deployment. +The LangGraph Server supports specific environment variables for configuring a deployment. ## `BG_JOB_ISOLATED_LOOPS` @@ -32,7 +32,7 @@ See Cloud SaaS(Beta): Connect to your GitHub repositories and deploy LangGraph Servers to LangChain's cloud. We manage everything. +- Self-Hosted Data Plane(Beta): Create deployments from the [Control Plane UI](../concepts/langgraph_control_plane.md#control-plane-ui) and deploy LangGraph Servers to your cloud. We manage the [control plane](../concepts/langgraph_control_plane.md), you manage the deployments. +- Self-Hosted Control Plane(Beta): Create deployments from a self-hosted [Control Plane UI](../concepts/langgraph_control_plane.md#control-plane-ui) and deploy LangGraph Servers to your cloud. You manage everything. - [Standalone Container](../concepts/langgraph_standalone_container.md): Deploy LangGraph Server Docker images however you like. diff --git a/docs/docs/concepts/langgraph_data_plane.md b/docs/docs/concepts/langgraph_data_plane.md index ecb154a10..fa2368387 100644 --- a/docs/docs/concepts/langgraph_data_plane.md +++ b/docs/docs/concepts/langgraph_data_plane.md @@ -58,7 +58,7 @@ In the future, the autoscaling implementation may evolve to accommodate other me ### Static IP Addresses !!! info "Only for Cloud SaaS" - Static IP addresses are only available for [Cloud SaaS](../concepts/langgraph_cloud.md). + Static IP addresses are only available for [Cloud SaaS](../concepts/langgraph_cloud.md) deployments. All traffic from deployments created after January 6th 2025 will come through a NAT gateway. This NAT gateway will have several static IP addresses depending on the data region. Refer to the table below for the list of static IP addresses: @@ -72,3 +72,46 @@ All traffic from deployments created after January 6th 2025 will come through a | 34.169.88.30 | 34.91.238.184 | | 34.19.93.202 | 35.204.101.241 | | 34.19.34.50 | 35.204.48.32 | + +### Custom Postgres + +!!! info "Only for Self-Hosted Data Plane and Self-Hosted Control Plane" + Custom Postgres instances are only available for [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md) deployments. + +A custom Postgres instance can be used instead of the [one automatically created by the control plane](./langgraph_control_plane.md#database-provisioning). Specify the [`POSTGRES_URI_CUSTOM`](../cloud/reference/env_var.md#postgres_uri_custom) environment variable to use a custom Postgres instance. + +Multiple deployments can share the same Postgres instance. For example, for `Deployment A`, `POSTGRES_URI_CUSTOM` can be set to `postgres://:@/?host=` and for `Deployment B`, `POSTGRES_URI_CUSTOM` can be set to `postgres://:@/?host=`. `` and `database_name_2` are different databases within the same instance, but `` is shared. **The same database cannot be used for separate deployments**. + +### Custom Redis + +!!! info "Only for Self-Hosted Data Plane and Self-Hosted Control Plane" + Custom Redis instances are only available for [Self-Hosted Data Plane](../concepts/langgraph_self_hosted_data_plane.md) and [Self-Hosted Control Plane](../concepts/langgraph_self_hosted_control_plane.md) deployments. + +A custom Redis instance can be used instead of the one automatically created by the control plane. Specify the [REDIS_URI_CUSTOM](../cloud/reference/env_var.md#redis_uri_custom) environment variable to use a custom Redis instance. + + +Multiple deployments can share the same Redis instance. For example, for `Deployment A`, `REDIS_URI_CUSTOM` can be set to `redis://:/1` and for `Deployment B`, `REDIS_URI_CUSTOM` can be set to `redis://:/2`. `1` and `2` are different database numbers within the same instance, but `` is shared. **The same database number cannot be used for separate deployments**. + +### LangSmith Tracing + +LangGraph Server is automatically configured to send traces to LangSmith. See the table below for details with respect to each deployment option. + +| Cloud SaaS | Self-Hosted Data Plane | Self-Hosted Control Plane | Standalone Container | +|------------|------------------------|---------------------------|----------------------| +| Required

Trace to LangSmith SaaS. | Optional

Disable tracing or trace to LangSmith SaaS. | Optional

Disable tracing or trace to Self-Hosted LangSmith. | Optional

Disable tracing, trace to LangSmith SaaS, or trace to Self-Hosted LangSmith. | + +### Telemetry + +LangGraph Server is automatically configured to report telemetry metadata for billing purposes. See the table below for details with respect to each deployment option. + +| Cloud SaaS | Self-Hosted Data Plane | Self-Hosted Control Plane | Standalone Container | +|------------|------------------------|---------------------------|----------------------| +| Telemetry sent to LangSmith SaaS. | Telemetry sent to LangSmith SaaS. | Self-reported usage (audit) for air-gapped license key.

Telemetry sent to LangSmith SaaS for LangGraph Platform License Key. | Self-reported usage (audit) for air-gapped license key.

Telemetry sent to LangSmith SaaS for LangGraph Platform License Key. | + +### Licensing + +LangGraph Server is automatically configured to perform license key validation. See the table below for details with respect to each deployment option. + +| Cloud SaaS | Self-Hosted Data Plane | Self-Hosted Control Plane | Standalone Container | +|------------|------------------------|---------------------------|----------------------| +| LangSmith API Key validated against LangSmith SaaS. | LangSmith API Key validated against LangSmith SaaS. | Air-gapped license key or LangGraph Platform License Key validated against LangSmith SaaS. | Air-gapped license key or LangGraph Platform License Key validated against LangSmith SaaS. | From 560d6a1f650e179a8586763d1eff1a681366d9fb Mon Sep 17 00:00:00 2001 From: Nuno Campos Date: Fri, 11 Apr 2025 15:01:17 -0700 Subject: [PATCH 5/8] Reduce perf impact of set_context - call it less often - find the run from the run manager at callsite --- libs/langgraph/bench/fanout_to_subgraph.py | 4 + libs/langgraph/langgraph/func/__init__.py | 5 +- libs/langgraph/langgraph/graph/branch.py | 1 - libs/langgraph/langgraph/graph/graph.py | 11 +- libs/langgraph/langgraph/graph/state.py | 12 +- libs/langgraph/langgraph/pregel/__init__.py | 7 +- libs/langgraph/langgraph/pregel/call.py | 4 +- libs/langgraph/langgraph/pregel/read.py | 1 + libs/langgraph/langgraph/pregel/write.py | 6 +- libs/langgraph/langgraph/utils/runnable.py | 385 ++++++++++++-------- libs/langgraph/tests/test_large_cases.py | 8 +- 11 files changed, 263 insertions(+), 181 deletions(-) diff --git a/libs/langgraph/bench/fanout_to_subgraph.py b/libs/langgraph/bench/fanout_to_subgraph.py index 612d0fbf2..fc8c7133b 100644 --- a/libs/langgraph/bench/fanout_to_subgraph.py +++ b/libs/langgraph/bench/fanout_to_subgraph.py @@ -106,6 +106,7 @@ def fanout_to_subgraph_sync() -> StateGraph: if __name__ == "__main__": import asyncio import random + import time import uvloop @@ -123,4 +124,7 @@ if __name__ == "__main__": len([c async for c in graph.astream(input, config=config)]) uvloop.install() + start = time.time() asyncio.run(run()) + end = time.time() + print(f"Time taken: {end - start:.4f} seconds") diff --git a/libs/langgraph/langgraph/func/__init__.py b/libs/langgraph/langgraph/func/__init__.py index 787254687..84802c52f 100644 --- a/libs/langgraph/langgraph/func/__init__.py +++ b/libs/langgraph/langgraph/func/__init__.py @@ -19,7 +19,7 @@ from typing import ( from langgraph.channels.ephemeral_value import EphemeralValue from langgraph.channels.last_value import LastValue from langgraph.checkpoint.base import BaseCheckpointSaver -from langgraph.constants import END, PREVIOUS, START, TAG_HIDDEN +from langgraph.constants import END, PREVIOUS, START from langgraph.pregel import Pregel from langgraph.pregel.call import ( P, @@ -429,8 +429,7 @@ class entrypoint: [ ChannelWriteEntry(END, mapper=_pluck_return_value), ChannelWriteEntry(PREVIOUS, mapper=_pluck_save_value), - ], - tags=[TAG_HIDDEN], + ] ) ], ) diff --git a/libs/langgraph/langgraph/graph/branch.py b/libs/langgraph/langgraph/graph/branch.py index fd2039f20..a1ae358d6 100644 --- a/libs/langgraph/langgraph/graph/branch.py +++ b/libs/langgraph/langgraph/graph/branch.py @@ -138,7 +138,6 @@ class Branch(NamedTuple): reader=reader, name=None, trace=False, - set_context=False, func_accepts_config=True, ) ) diff --git a/libs/langgraph/langgraph/graph/graph.py b/libs/langgraph/langgraph/graph/graph.py index d8bde68ec..fa28243fb 100644 --- a/libs/langgraph/langgraph/graph/graph.py +++ b/libs/langgraph/langgraph/graph/graph.py @@ -366,16 +366,14 @@ class CompiledGraph(Pregel): self.nodes[key] = ( PregelNode(channels=[], triggers=[], metadata=node.metadata) | node.runnable - | ChannelWrite([ChannelWriteEntry(key)], tags=[TAG_HIDDEN]) + | ChannelWrite([ChannelWriteEntry(key)]) ) cast(list[str], self.stream_channels).append(key) def attach_edge(self, start: str, end: str) -> None: if end == END: # publish to end channel - self.nodes[start].writers.append( - ChannelWrite([ChannelWriteEntry(END)], tags=[TAG_HIDDEN]) - ) + self.nodes[start].writers.append(ChannelWrite([ChannelWriteEntry(END)])) else: # subscribe to start channel self.nodes[end].triggers.append(start) @@ -393,10 +391,7 @@ class CompiledGraph(Pregel): ) for p in packets ] - return ChannelWrite( - cast(Sequence[Union[ChannelWriteEntry, Send]], writes), - tags=[TAG_HIDDEN], - ) + return ChannelWrite(cast(Sequence[Union[ChannelWriteEntry, Send]], writes)) # add hidden start node if start == START and start not in self.nodes: diff --git a/libs/langgraph/langgraph/graph/state.py b/libs/langgraph/langgraph/graph/state.py index 7b9aef42c..952a29d1e 100644 --- a/libs/langgraph/langgraph/graph/state.py +++ b/libs/langgraph/langgraph/graph/state.py @@ -807,7 +807,7 @@ class CompiledStateGraph(CompiledGraph): tags=[TAG_HIDDEN], triggers=[START], channels=[START], - writers=[ChannelWrite(write_entries, tags=[TAG_HIDDEN])], + writers=[ChannelWrite(write_entries)], ) elif node is not None: input_schema = node.input if node else self.builder.schema @@ -832,7 +832,7 @@ class CompiledStateGraph(CompiledGraph): # coerce state dict to schema class (eg. pydantic model) mapper=mapper, # publish to state keys - writers=[ChannelWrite(write_entries, tags=[TAG_HIDDEN])], + writers=[ChannelWrite(write_entries)], metadata=node.metadata, retry_policy=node.retry_policy, bound=node.runnable, @@ -858,9 +858,7 @@ class CompiledStateGraph(CompiledGraph): # publish to channel for start in starts: self.nodes[start].writers.append( - ChannelWrite( - (ChannelWriteEntry(channel_name, start),), tags=[TAG_HIDDEN] - ) + ChannelWrite((ChannelWriteEntry(channel_name, start),)) ) def attach_branch( @@ -932,9 +930,7 @@ class CompiledStateGraph(CompiledGraph): for end in ends: if end != END: self.nodes[end].writers.append( - ChannelWrite( - (ChannelWriteEntry(channel_name, end),), tags=[TAG_HIDDEN] - ) + ChannelWrite((ChannelWriteEntry(channel_name, end),)) ) def _migrate_checkpoint(self, checkpoint: Checkpoint) -> None: diff --git a/libs/langgraph/langgraph/pregel/__init__.py b/libs/langgraph/langgraph/pregel/__init__.py index c9821248e..a7d2adcd0 100644 --- a/libs/langgraph/langgraph/pregel/__init__.py +++ b/libs/langgraph/langgraph/pregel/__init__.py @@ -2547,11 +2547,12 @@ class Pregel(PregelProtocol): do_stream = ( next( ( - cast(_StreamingCallbackHandler, h) + True for h in run_manager.handlers if isinstance(h, _StreamingCallbackHandler) + and not isinstance(h, StreamMessagesHandler) ), - None, + False, ) if _StreamingCallbackHandler is not None else False @@ -2621,7 +2622,7 @@ class Pregel(PregelProtocol): ), put_writes=weakref.WeakMethod(loop.put_writes), schedule_task=weakref.WeakMethod(loop.accept_push), - use_astream=do_stream is not None, + use_astream=do_stream, node_finished=config[CONF].get(CONFIG_KEY_NODE_FINISHED), ) # enable subgraph streaming diff --git a/libs/langgraph/langgraph/pregel/call.py b/libs/langgraph/langgraph/pregel/call.py index 61a451335..b3294e68b 100644 --- a/libs/langgraph/langgraph/pregel/call.py +++ b/libs/langgraph/langgraph/pregel/call.py @@ -10,7 +10,7 @@ from typing import Any, Callable, Generator, Generic, Optional, TypeVar, cast from langchain_core.runnables import Runnable from typing_extensions import ParamSpec -from langgraph.constants import CONF, CONFIG_KEY_CALL, RETURN, TAG_HIDDEN +from langgraph.constants import CONF, CONFIG_KEY_CALL, RETURN from langgraph.pregel.write import ChannelWrite, ChannelWriteEntry from langgraph.types import RetryPolicy from langgraph.utils.config import get_config @@ -197,7 +197,7 @@ def get_runnable_for_task(func: Callable[..., Any]) -> RunnableSeq: ) seq = RunnableSeq( run, - ChannelWrite([ChannelWriteEntry(RETURN)], tags=[TAG_HIDDEN]), + ChannelWrite([ChannelWriteEntry(RETURN)]), name=name, trace_inputs=functools.partial( _explode_args_trace_inputs, inspect.signature(func) diff --git a/libs/langgraph/langgraph/pregel/read.py b/libs/langgraph/langgraph/pregel/read.py index a44206e71..5a1a8e460 100644 --- a/libs/langgraph/langgraph/pregel/read.py +++ b/libs/langgraph/langgraph/pregel/read.py @@ -68,6 +68,7 @@ class ChannelRead(RunnableCallable): afunc=self._aread, tags=tags, name=None, + trace=False, func_accepts_config=True, ) self.fresh = fresh diff --git a/libs/langgraph/langgraph/pregel/write.py b/libs/langgraph/langgraph/pregel/write.py index 05fe5a388..234c1f5d7 100644 --- a/libs/langgraph/langgraph/pregel/write.py +++ b/libs/langgraph/langgraph/pregel/write.py @@ -54,14 +54,14 @@ class ChannelWrite(RunnableCallable): self, writes: Sequence[Union[ChannelWriteEntry, ChannelWriteTupleEntry, Send]], *, - tags: Optional[Sequence[str]] = None, + tags: Optional[Sequence[str]] = None, # ignored require_at_least_one_of: Optional[Sequence[str]] = None, # ignored ): super().__init__( func=self._write, afunc=self._awrite, name=None, - tags=tags, + trace=False, func_accepts_config=True, ) self.writes = cast( @@ -152,6 +152,8 @@ class ChannelWrite(RunnableCallable): tuples.append((w.channel, value)) else: raise ValueError(f"Invalid write entry: {w}") + # if we want to persist writes found before hitting a ParentCommand + # can move this to a finally block write: TYPE_SEND = config[CONF][CONFIG_KEY_SEND] write(tuples) diff --git a/libs/langgraph/langgraph/utils/runnable.py b/libs/langgraph/langgraph/utils/runnable.py index 2736cc7d5..c4ba527d6 100644 --- a/libs/langgraph/langgraph/utils/runnable.py +++ b/libs/langgraph/langgraph/utils/runnable.py @@ -36,6 +36,7 @@ from langchain_core.runnables.config import ( var_child_runnable_config, ) from langchain_core.runnables.utils import Input, Output +from langchain_core.tracers.langchain import LangChainTracer from typing_extensions import TypeGuard from langgraph.constants import ( @@ -60,58 +61,34 @@ except ImportError: def _set_config_context( - config: RunnableConfig, -) -> tuple[Token[Optional[RunnableConfig]], Optional[dict[str, Any]]]: + config: RunnableConfig, run: Any = None +) -> Token[Optional[RunnableConfig]]: """Set the child Runnable config + tracing context. Args: config (RunnableConfig): The config to set. """ - from langchain_core.tracers.langchain import LangChainTracer - config_token = var_child_runnable_config.set(config) - current_context = None - if ( - (callbacks := config.get("callbacks")) - and ( - parent_run_id := getattr(callbacks, "parent_run_id", None) - ) # Is callback manager - and ( - tracer := next( - ( - handler - for handler in getattr(callbacks, "handlers", []) - if isinstance(handler, LangChainTracer) - ), - None, - ) - ) - and (run := tracer.run_map.get(str(parent_run_id))) - ): - from langsmith.run_helpers import _set_tracing_context, get_tracing_context + if run is not None: + from langsmith.run_helpers import _set_tracing_context - current_context = get_tracing_context() _set_tracing_context({"parent": run}) - return config_token, current_context + return config_token -@contextmanager -def set_config_context(config: RunnableConfig) -> Generator[Context, None, None]: +def _unset_config_context( + token: Token[Optional[RunnableConfig]], run: Any = None +) -> None: """Set the child Runnable config + tracing context. Args: config (RunnableConfig): The config to set. """ - from langsmith.run_helpers import _set_tracing_context + var_child_runnable_config.reset(token) + if run is not None: + from langsmith.run_helpers import _set_tracing_context - ctx = copy_context() - config_token, _ = ctx.run(_set_config_context, config) - try: - yield ctx - finally: - ctx.run(var_child_runnable_config.reset, config_token) - ctx.run( - _set_tracing_context, + _set_tracing_context( { "parent": None, "project_name": None, @@ -119,10 +96,27 @@ def set_config_context(config: RunnableConfig) -> Generator[Context, None, None] "metadata": None, "enabled": None, "client": None, - }, + } ) +@contextmanager +def set_config_context( + config: RunnableConfig, run: Any = None +) -> Generator[Context, None, None]: + """Set the child Runnable config + tracing context. + + Args: + config (RunnableConfig): The config to set. + """ + ctx = copy_context() + config_token = ctx.run(_set_config_context, config, run) + try: + yield ctx + finally: + ctx.run(_unset_config_context, config_token, run) + + # Before Python 3.11 native StrEnum is not available class StrEnum(str, enum.Enum): """A string enum.""" @@ -254,7 +248,6 @@ class RunnableCallable(Runnable): tags: Optional[Sequence[str]] = None, trace: bool = True, recurse: bool = True, - set_context: bool = True, explode_args: bool = False, func_accepts_config: Optional[bool] = None, **kwargs: Any, @@ -278,7 +271,6 @@ class RunnableCallable(Runnable): self.kwargs = kwargs self.trace = trace self.recurse = recurse - self.set_context = set_context self.explode_args = explode_args # check signature if func is None and afunc is None: @@ -365,19 +357,21 @@ class RunnableCallable(Runnable): ) try: child_config = patch_config(config, callbacks=run_manager.get_child()) - if self.set_context: - with set_config_context(child_config) as context: - ret = context.run(self.func, *args, **kwargs) + # get the run + for h in run_manager.handlers: + if isinstance(h, LangChainTracer): + run = h.run_map.get(str(run_manager.run_id)) + break else: - ret = self.func(*args, **kwargs) + run = None + # run in context + with set_config_context(child_config, run) as context: + ret = context.run(self.func, *args, **kwargs) except BaseException as e: run_manager.on_chain_error(e) raise else: run_manager.on_chain_end(ret) - elif self.set_context: - with set_config_context(config) as context: - ret = context.run(self.func, *args, **kwargs) else: ret = self.func(*args, **kwargs) if self.recurse and isinstance(ret, Runnable): @@ -425,8 +419,14 @@ class RunnableCallable(Runnable): try: child_config = patch_config(config, callbacks=run_manager.get_child()) coro = cast(Coroutine[None, None, Any], self.afunc(*args, **kwargs)) - if ASYNCIO_ACCEPTS_CONTEXT and self.set_context: - with set_config_context(child_config) as context: + if ASYNCIO_ACCEPTS_CONTEXT: + for h in run_manager.handlers: + if isinstance(h, LangChainTracer): + run = h.run_map.get(str(run_manager.run_id)) + break + else: + run = None + with set_config_context(child_config, run) as context: ret = await asyncio.create_task(coro, context=context) else: ret = await coro @@ -435,10 +435,6 @@ class RunnableCallable(Runnable): raise else: await run_manager.on_chain_end(ret) - elif ASYNCIO_ACCEPTS_CONTEXT and self.set_context: - with set_config_context(config) as context: - coro = cast(Coroutine[None, None, Any], self.afunc(*args, **kwargs)) - ret = await asyncio.create_task(coro, context=context) else: ret = await self.afunc(*args, **kwargs) if self.recurse and isinstance(ret, Runnable): @@ -604,7 +600,6 @@ class RunnableSeq(Runnable): name=config.get("run_name") or self.get_name(), run_id=config.pop("run_id", None), ) - # invoke all steps in sequence try: for i, step in enumerate(self.steps): @@ -612,8 +607,19 @@ class RunnableSeq(Runnable): config = patch_config( config, callbacks=run_manager.get_child(f"seq:step:{i + 1}") ) + # 1st step is the actual node, + # others are writers which don't need to be run in context if i == 0: - input = step.invoke(input, config, **kwargs) + # get the run object + for h in run_manager.handlers: + if isinstance(h, LangChainTracer): + run = h.run_map.get(str(run_manager.run_id)) + break + else: + run = None + # run in context + with set_config_context(config, run) as context: + input = context.run(step.invoke, input, config, **kwargs) else: input = step.invoke(input, config) # finish the root run @@ -649,8 +655,24 @@ class RunnableSeq(Runnable): config = patch_config( config, callbacks=run_manager.get_child(f"seq:step:{i + 1}") ) + # 1st step is the actual node, + # others are writers which don't need to be run in context if i == 0: - input = await step.ainvoke(input, config, **kwargs) + if ASYNCIO_ACCEPTS_CONTEXT: + # get the run object + for h in run_manager.handlers: + if isinstance(h, LangChainTracer): + run = h.run_map.get(str(run_manager.run_id)) + break + else: + run = None + # run in context + with set_config_context(config, run) as context: + input = await asyncio.create_task( + step.ainvoke(input, config, **kwargs), context=context + ) + else: + input = await step.ainvoke(input, config, **kwargs) else: input = await step.ainvoke(input, config) # finish the root run @@ -678,53 +700,48 @@ class RunnableSeq(Runnable): name=config.get("run_name") or self.get_name(), run_id=config.pop("run_id", None), ) - - try: - # stream the last steps - # transform the input stream of each step with the next - # steps that don't natively support transforming an input stream will - # buffer input in memory until all available, and then start emitting output - for idx, step in enumerate(self.steps): - config = patch_config( - config, - callbacks=run_manager.get_child(f"seq:step:{idx + 1}"), - ) - if idx == 0: - iterator = step.stream(input, config, **kwargs) - else: - iterator = step.transform(iterator, config) - if _StreamingCallbackHandler is not None and ( - stream_handler := next( - ( - cast(_StreamingCallbackHandler, h) - for h in run_manager.handlers - if isinstance(h, _StreamingCallbackHandler) - ), - None, - ) - ): - # populates streamed_output in astream_log() output if needed - iterator = stream_handler.tap_output_iter(run_manager.run_id, iterator) - output: Any = None - add_supported = False - for chunk in iterator: - yield chunk - # collect final output - if output is None: - output = chunk - elif add_supported: - try: - output = output + chunk - except TypeError: - output = chunk - add_supported = False - else: - output = chunk - except BaseException as e: - run_manager.on_chain_error(e) - raise + # get the run object + for h in run_manager.handlers: + if isinstance(h, LangChainTracer): + run = h.run_map.get(str(run_manager.run_id)) + break else: - run_manager.on_chain_end(output) + run = None + # create first step config + config = patch_config( + config, + callbacks=run_manager.get_child(f"seq:step:{1}"), + ) + # run all in context + with set_config_context(config, run) as context: + try: + # stream the last steps + # transform the input stream of each step with the next + # steps that don't natively support transforming an input stream will + # buffer input in memory until all available, and then start emitting output + for idx, step in enumerate(self.steps): + if idx == 0: + iterator = step.stream(input, config, **kwargs) + else: + config = patch_config( + config, + callbacks=run_manager.get_child(f"seq:step:{idx + 1}"), + ) + iterator = step.transform(iterator, config) + # populates streamed_output in astream_log() output if needed + if _StreamingCallbackHandler is not None: + for h in run_manager.handlers: + if isinstance(h, _StreamingCallbackHandler): + iterator = h.tap_output_iter(run_manager.run_id, iterator) + # consume into final output + output = context.run(_consume_iter, iterator) + # sequence doesn't emit output, yield to mark as generator + yield + except BaseException as e: + run_manager.on_chain_error(e) + raise + else: + run_manager.on_chain_end(output) async def astream( self, @@ -743,53 +760,121 @@ class RunnableSeq(Runnable): name=config.get("run_name") or self.get_name(), run_id=config.pop("run_id", None), ) - - try: - async with AsyncExitStack() as stack: - # stream the last steps - # transform the input stream of each step with the next - # steps that don't natively support transforming an input stream will - # buffer input in memory until all available, and then start emitting output - for idx, step in enumerate(self.steps): - config = patch_config( - config, - callbacks=run_manager.get_child(f"seq:step:{idx + 1}"), - ) - if idx == 0: - aiterator = step.astream(input, config, **kwargs) - else: - aiterator = step.atransform(aiterator, config) - if hasattr(aiterator, "aclose"): - stack.push_async_callback(aiterator.aclose) - if _StreamingCallbackHandler is not None and ( - stream_handler := next( - ( - cast(_StreamingCallbackHandler, h) - for h in run_manager.handlers - if isinstance(h, _StreamingCallbackHandler) - ), - None, - ) - ): - # populates streamed_output in astream_log() output if needed - aiterator = stream_handler.tap_output_aiter( - run_manager.run_id, aiterator - ) - output: Any = None - add_supported = False - async for chunk in aiterator: - yield chunk - # collect final output - if add_supported: - try: - output = output + chunk - except TypeError: - output = chunk - add_supported = False - else: - output = chunk - except BaseException as e: - await run_manager.on_chain_error(e) - raise + # stream the last steps + # transform the input stream of each step with the next + # steps that don't natively support transforming an input stream will + # buffer input in memory until all available, and then start emitting output + if ASYNCIO_ACCEPTS_CONTEXT: + # get the run object + for h in run_manager.handlers: + if isinstance(h, LangChainTracer): + run = h.run_map.get(str(run_manager.run_id)) + break + else: + run = None + # create first step config + config = patch_config( + config, + callbacks=run_manager.get_child(f"seq:step:{1}"), + ) + # run all in context + with set_config_context(config, run) as context: + try: + async with AsyncExitStack() as stack: + for idx, step in enumerate(self.steps): + if idx == 0: + aiterator = step.astream(input, config, **kwargs) + else: + config = patch_config( + config, + callbacks=run_manager.get_child( + f"seq:step:{idx + 1}" + ), + ) + aiterator = step.atransform(aiterator, config) + if hasattr(aiterator, "aclose"): + stack.push_async_callback(aiterator.aclose) + # populates streamed_output in astream_log() output if needed + if _StreamingCallbackHandler is not None: + for h in run_manager.handlers: + if isinstance(h, _StreamingCallbackHandler): + aiterator = h.tap_output_aiter( + run_manager.run_id, aiterator + ) + # consume into final output + output = await asyncio.create_task( + _consume_aiter(aiterator), context=context + ) + # sequence doesn't emit output, yield to mark as generator + yield + except BaseException as e: + await run_manager.on_chain_error(e) + raise + else: + await run_manager.on_chain_end(output) else: - await run_manager.on_chain_end(output) + try: + async with AsyncExitStack() as stack: + for idx, step in enumerate(self.steps): + config = patch_config( + config, + callbacks=run_manager.get_child(f"seq:step:{idx + 1}"), + ) + if idx == 0: + aiterator = step.astream(input, config, **kwargs) + else: + aiterator = step.atransform(aiterator, config) + if hasattr(aiterator, "aclose"): + stack.push_async_callback(aiterator.aclose) + # populates streamed_output in astream_log() output if needed + if _StreamingCallbackHandler is not None: + for h in run_manager.handlers: + if isinstance(h, _StreamingCallbackHandler): + aiterator = h.tap_output_aiter( + run_manager.run_id, aiterator + ) + # consume into final output + output = await _consume_aiter(aiterator) + # sequence doesn't emit output, yield to mark as generator + yield + except BaseException as e: + await run_manager.on_chain_error(e) + raise + else: + await run_manager.on_chain_end(output) + + +def _consume_iter(it: Iterator[Any]) -> Any: + """Consume an iterator.""" + output: Any = None + add_supported = False + for chunk in it: + # collect final output + if output is None: + output = chunk + elif add_supported: + try: + output = output + chunk + except TypeError: + output = chunk + add_supported = False + else: + output = chunk + return output + + +async def _consume_aiter(it: AsyncIterator[Any]) -> Any: + """Consume an async iterator.""" + output: Any = None + add_supported = False + async for chunk in it: + # collect final output + if add_supported: + try: + output = output + chunk + except TypeError: + output = chunk + add_supported = False + else: + output = chunk + return output diff --git a/libs/langgraph/tests/test_large_cases.py b/libs/langgraph/tests/test_large_cases.py index bb8e124ff..7bba55ff5 100644 --- a/libs/langgraph/tests/test_large_cases.py +++ b/libs/langgraph/tests/test_large_cases.py @@ -4660,7 +4660,7 @@ def test_root_graph( content="result for query", name="search_api", tool_call_id="tool_call123", - id="00000000-0000-4000-8000-000000000033", + id="00000000-0000-4000-8000-000000000024", ) ] }, @@ -4683,7 +4683,7 @@ def test_root_graph( content="result for another", name="search_api", tool_call_id="tool_call456", - id="00000000-0000-4000-8000-000000000041", + id="00000000-0000-4000-8000-000000000030", ) ] }, @@ -5387,7 +5387,7 @@ def test_root_graph( "__root__": [ HumanMessage( content="what is weather in sf", - id="00000000-0000-4000-8000-000000000070", + id="00000000-0000-4000-8000-000000000051", ), AIMessage( content="", @@ -5407,7 +5407,7 @@ def test_root_graph( ), AIMessage(content="answer", id="ai2"), AIMessage( - content="an extra message", id="00000000-0000-4000-8000-000000000091" + content="an extra message", id="00000000-0000-4000-8000-000000000066" ), HumanMessage(content="what is weather in la"), ], From dfbf0ddbcb80ab64a395f10b7104efbda3184484 Mon Sep 17 00:00:00 2001 From: Nuno Campos Date: Fri, 11 Apr 2025 16:20:45 -0700 Subject: [PATCH 6/8] Don't run branch reader in bg thread --- libs/langgraph/langgraph/graph/branch.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libs/langgraph/langgraph/graph/branch.py b/libs/langgraph/langgraph/graph/branch.py index a1ae358d6..33a2aca1e 100644 --- a/libs/langgraph/langgraph/graph/branch.py +++ b/libs/langgraph/langgraph/graph/branch.py @@ -1,4 +1,3 @@ -import asyncio from inspect import ( isfunction, ismethod, @@ -178,7 +177,7 @@ class Branch(NamedTuple): ], ) -> Runnable: if reader: - value = await asyncio.to_thread(reader, config) + value = reader(config) # passthrough additional keys from node to branch # only doable when using dict states if ( From 62b2580ad5101cf55da0b6bebcd09913d2512022 Mon Sep 17 00:00:00 2001 From: Nuno Campos Date: Fri, 11 Apr 2025 16:21:09 -0700 Subject: [PATCH 7/8] 0.3.29 --- libs/langgraph/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langgraph/pyproject.toml b/libs/langgraph/pyproject.toml index 75d349bd9..8adf1d9cf 100644 --- a/libs/langgraph/pyproject.toml +++ b/libs/langgraph/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langgraph" -version = "0.3.28" +version = "0.3.29" description = "Building stateful, multi-actor applications with LLMs" authors = [] license = "MIT" From c700dab97c7104c59904f81f476d5b97ef883702 Mon Sep 17 00:00:00 2001 From: Andrew Nguonly Date: Sun, 13 Apr 2025 15:33:44 -0700 Subject: [PATCH 8/8] docs: Add docs for `LANGSMITH_TRACING` env var (#4257) --- docs/docs/cloud/reference/env_var.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/docs/cloud/reference/env_var.md b/docs/docs/cloud/reference/env_var.md index 4f21ee890..e15157cac 100644 --- a/docs/docs/cloud/reference/env_var.md +++ b/docs/docs/cloud/reference/env_var.md @@ -44,6 +44,13 @@ Set this environment variable to have a BYOC deployment send traces to a self-ho `SELF_HOSTED_LANGSMITH_HOSTNAME` is the hostname of the self-hosted LangSmith instance. It must be accessible to the BYOC deployment. `LANGSMITH_API_KEY` is a LangSmith API generated from the self-hosted LangSmith instance. +## `LANGSMITH_TRACING` + +!!! info "Only for Self-Hosted Data Plane, Self-Hosted Control Plane, and Standalone Container" + Disabling LangSmith tracing is only available for [Self-Hosted Data Plane](../../concepts/langgraph_self_hosted_data_plane.md), [Self-Hosted Control Plane](../../concepts/langgraph_self_hosted_control_plane.md), and [Standalone Container](../../concepts/langgraph_standalone_container.md) deployments. + +Set `LANGSMITH_TRACING` to `false` to disable tracing to LangSmith. + ## `LOG_LEVEL` Configure [log level](https://docs.python.org/3/library/logging.html#logging-levels). Defaults to `INFO`.