Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ This document explains how to convert an existing LangGraph `StateGraph` into a

---

## Deprecation Notice (LangGraph 0.3.x)
- `codon-instrumentation-langgraph` `0.1.0a5` is the last release that supports LangGraph 0.3.x.
- Starting with `0.2.0a0`, the adapter targets LangChain/LangGraph v1.x only.
- A `DeprecationWarning` is emitted when LangGraph < 1.0 is detected (set `CODON_LANGGRAPH_DEPRECATION_SILENCE=1` to suppress).

---

## Why Wrap a LangGraph Graph?
- **Zero instrumentation boilerplate:** every LangGraph node is auto-wrapped with `track_node`, producing OpenTelemetry spans without manual decorators.
- **Stable identifiers:** nodes become `NodeSpec`s with deterministic SHA-256 IDs, and the overall graph gets a logic ID for caching, retries, and provenance.
Expand Down
12 changes: 12 additions & 0 deletions instrumentation-packages/codon-instrumentation-langgraph/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

If you're already using LangGraph, the Codon SDK provides seamless integration through the `LangGraphWorkloadAdapter`. This allows you to wrap your existing StateGraphs with minimal code changes while gaining comprehensive telemetry and observability.

## Deprecation Notice (LangGraph 0.3.x)

Support for LangGraph 0.3.x is deprecated and will be removed after the `0.1.0a5` release of `codon-instrumentation-langgraph`. If you need to stay on LangGraph 0.3.x, pin this package at `<=0.1.0a5`. Starting with `0.2.0a0`, the adapter will support only LangChain/LangGraph v1.x.

### python-warnings

When running with LangGraph 0.3.x you will see a `DeprecationWarning` explaining the cutoff. To silence the warning, set:

```
CODON_LANGGRAPH_DEPRECATION_SILENCE=1
```

## Understanding State Graph vs Compiled Graph

LangGraph has two distinct graph representations:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
import inspect
import json
import os
import re
import time
import warnings
from importlib import metadata as importlib_metadata
from abc import ABC, abstractmethod
from contextvars import ContextVar
from functools import wraps
Expand Down Expand Up @@ -85,6 +88,41 @@ def current_invocation() -> Optional[NodeTelemetryPayload]:
return _ACTIVE_INVOCATION.get()


def _is_truthy(value: Optional[str]) -> bool:
return str(value or "").strip().lower() in {"1", "true", "yes", "y", "on"}


def _parse_major(version: str) -> Optional[int]:
match = re.match(r"(\d+)", version)
if not match:
return None
try:
return int(match.group(1))
except ValueError:
return None


def _maybe_warn_deprecated_langgraph() -> None:
if _is_truthy(os.getenv("CODON_LANGGRAPH_DEPRECATION_SILENCE")):
return
try:
version = importlib_metadata.version("langgraph")
except importlib_metadata.PackageNotFoundError:
return
except Exception:
return
major = _parse_major(version)
if major is not None and major < 1:
warnings.warn(
"LangGraph <1.0 support is deprecated and will be removed after "
"codon-instrumentation-langgraph 0.1.0a5. Upgrade to LangGraph "
"v1.x or pin codon-instrumentation-langgraph<=0.1.0a5. Set "
"CODON_LANGGRAPH_DEPRECATION_SILENCE=1 to suppress this warning.",
DeprecationWarning,
stacklevel=2,
)


def _safe_repr(value: Any, *, max_length: int = 2048) -> str:
try:
rendered = repr(value)
Expand Down Expand Up @@ -483,3 +521,5 @@ def wrapper(*args, **kwargs):
NodeOverride,
)
from .callbacks import LangGraphTelemetryCallback # noqa: E402 # isort: skip

_maybe_warn_deprecated_langgraph()
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "codon-instrumentation-langgraph"
version = "0.1.0a4"
version = "0.1.0a5"
license = {text = "Apache-2.0"}
authors = [
{ name="Codon, Inc.", email="martin@codonops.ai" },
Expand Down
1 change: 1 addition & 0 deletions sdk/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ This scaffold explains the agent-facing contracts provided by `codon_sdk`. Flesh
- **Instrumentation mixins:** Framework packages ship their own mixins (see `docs/guides/workload-mixin-guidelines.md`) to expose `from_*` constructors while keeping the core SDK agnostic.
- **Reference implementation:** Each instrumentation package should define mixins inside its own namespace (e.g., `codon.instrumentation.langgraph.LangGraphWorkloadMixin`).
- **Adapters:** `LangGraphWorkloadAdapter.from_langgraph(...)` demonstrates how to wrap existing LangGraph graphs with `CodonWorkload` automatically; use it as a model for future adapters.
- **LangGraph compatibility:** `codon-instrumentation-langgraph` `0.1.0a5` is the final release that supports LangGraph 0.3.x. Starting in `0.2.0a0`, the adapter targets LangChain/LangGraph v1.x only and will emit a `DeprecationWarning` on older versions.

## CodonWorkload (Opinionated Implementation)
- **Module:** `codon_sdk.agents.codon_workload`
Expand Down
44 changes: 44 additions & 0 deletions sdk/test/instrumentation/langgraph/test_deprecation_warning.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import importlib
import warnings

from importlib import metadata as importlib_metadata


def _reload_langgraph(monkeypatch, version_value, *, silence=False):
monkeypatch.setattr(importlib_metadata, "version", lambda name: version_value)
if silence:
monkeypatch.setenv("CODON_LANGGRAPH_DEPRECATION_SILENCE", "1")
else:
monkeypatch.delenv("CODON_LANGGRAPH_DEPRECATION_SILENCE", raising=False)
module = importlib.import_module("codon.instrumentation.langgraph")
return importlib.reload(module)


def _has_deprecation_warning(captured):
for warning in captured:
if issubclass(warning.category, DeprecationWarning) and "LangGraph <1.0" in str(
warning.message
):
return True
return False


def test_deprecation_warning_emitted(monkeypatch):
with warnings.catch_warnings(record=True) as captured:
warnings.simplefilter("always", DeprecationWarning)
_reload_langgraph(monkeypatch, "0.3.2")
assert _has_deprecation_warning(captured)


def test_deprecation_warning_suppressed(monkeypatch):
with warnings.catch_warnings(record=True) as captured:
warnings.simplefilter("always", DeprecationWarning)
_reload_langgraph(monkeypatch, "0.3.2", silence=True)
assert not _has_deprecation_warning(captured)


def test_no_warning_for_v1(monkeypatch):
with warnings.catch_warnings(record=True) as captured:
warnings.simplefilter("always", DeprecationWarning)
_reload_langgraph(monkeypatch, "1.0.0")
assert not _has_deprecation_warning(captured)
Loading