Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
3051548
LangGraph: Add Phase 1 prototype structure
mfateev Dec 25, 2025
56394b0
LangGraph: Implement Pregel loop submit injection prototype
mfateev Dec 25, 2025
8d1409f
LangGraph: Use internal constant import to avoid deprecation warning
mfateev Dec 25, 2025
1ac7158
Fix write capture test for PregelExecutableTask type
mfateev Dec 25, 2025
c5c9896
Add task interface prototype and tests
mfateev Dec 25, 2025
4e078c7
Add serialization prototype using Temporal data converters
mfateev Dec 25, 2025
5521520
LangGraph: Add Phase 1 validation prototypes before cleanup
mfateev Dec 25, 2025
b12a24e
LangGraph: Remove prototype files after Phase 2 implementation
mfateev Dec 25, 2025
7a29282
LangGraph: Implement Phase 3 and 4 - Activity write capture and per-n…
mfateev Dec 25, 2025
6d72b9c
LangGraph: Fix conditional edge routing and use AsyncPregelLoop
mfateev Dec 25, 2025
5feff61
LangGraph: Remove prototype test files
mfateev Dec 25, 2025
0f555e1
LangGraph: Execute tasks in parallel within each tick
mfateev Dec 25, 2025
5ed4b86
LangGraph: Implement native interrupt API with comprehensive tests
mfateev Dec 25, 2025
13ef23c
LangGraph: Fix multi-interrupt handling and add e2e tests
mfateev Dec 25, 2025
60ed486
LangGraph: Add checkpoint and should_continue APIs for continue-as-new
mfateev Dec 25, 2025
4e4301f
LangGraph: Add Store support for cross-node persistence
mfateev Dec 26, 2025
83e90e5
LangGraph: Add e2e tests for Store functionality
mfateev Dec 26, 2025
1f49320
LangGraph: Add Send API support and validation tests
mfateev Dec 26, 2025
2d5738e
LangGraph: Add design docs for interrupt and store APIs
mfateev Dec 26, 2025
3f7c044
LangGraph: Remove design docs (preserved in git history)
mfateev Dec 26, 2025
535851a
LangGraph: Add user-facing README documentation
mfateev Dec 26, 2025
5b0231e
LangGraph: Add temporal_node_metadata() helper for typed activity opt…
mfateev Dec 26, 2025
3d54aba
LangGraph: Use temporal_node_metadata() for compile() defaults
mfateev Dec 26, 2025
788e5cc
LangGraph: Rename API for consistency
mfateev Dec 26, 2025
904c566
LangGraph: Add plugin-level activity options
mfateev Dec 26, 2025
cf45004
LangGraph: Add temporal_tool() and temporal_model() for durable agent…
mfateev Dec 26, 2025
b518249
LangGraph: Add create_agent support and temporal_node_metadata() helper
mfateev Dec 26, 2025
bda9006
LangGraph: Reorganize tests and fix sandbox graph building
mfateev Dec 26, 2025
be1d36e
LangGraph: Add experimental warnings and improve documentation
mfateev Dec 27, 2025
afbd452
LangGraph: Document internal API usage with detailed rationale
mfateev Dec 27, 2025
87f6b9b
LangGraph: Add logging infrastructure
mfateev Dec 27, 2025
6bf1456
LangGraph: Remove warning suppression by using internal imports directly
mfateev Dec 27, 2025
ec44b14
LangGraph: Add domain-specific exceptions with ApplicationError
mfateev Dec 27, 2025
ea808c4
LangGraph: Tidy docstrings to be precise and concise
mfateev Dec 27, 2025
7e98835
LangGraph: Remove enable_workflow_execution compile parameter
mfateev Dec 27, 2025
720667e
LangGraph: Rename activities and add meaningful summaries for UI
mfateev Dec 27, 2025
ed1da9a
LangGraph: Run __start__ node inline in workflow
mfateev Dec 27, 2025
7a3121a
LangGraph: Use ClientConfig for example connection setup
mfateev Dec 27, 2025
02eb922
LangGraph: Add sandbox passthrough for pydantic_core and langchain_core
mfateev Dec 27, 2025
dc1d2e3
LangGraph: Align with SDK style conventions
mfateev Dec 27, 2025
386d9b1
LangGraph: Remove example.py from module
mfateev Dec 27, 2025
a86d651
LangGraph: Implement bind_tools for temporal_model
mfateev Dec 27, 2025
406acc9
LangGraph: Document bind_tools support in README
mfateev Dec 27, 2025
bf072c4
LangGraph: Add summary to temporal_tool activity
mfateev Dec 27, 2025
8952549
LangGraph: Show tool names in activity summary for tools node
mfateev Dec 27, 2025
1f010d9
LangGraph: Simplify activity names and add metadata description support
mfateev Dec 27, 2025
54ee95f
LangGraph: Add create_durable_agent and create_durable_react_agent fu…
mfateev Dec 27, 2025
01216c6
LangGraph: Document create_durable_agent and create_durable_react_agent
mfateev Dec 27, 2025
f69c100
LangGraph: Rename node_activity_options to activity_options
mfateev Dec 27, 2025
ee7dbd0
LangGraph: Fix temporal_model deepcopy issue with HTTP clients
mfateev Dec 27, 2025
68d7370
LangGraph: Remove temporal_model and temporal_tool wrappers
mfateev Dec 27, 2025
212a80a
LangGraph: Update README to reflect simplified architecture
mfateev Dec 27, 2025
b53c66a
LangGraph: Document create_agent as preferred over deprecated create_…
mfateev Dec 27, 2025
c97eede
LangGraph: Add langchain test dependency and use create_react_agent i…
mfateev Dec 27, 2025
6921021
LangGraph: Fix state reading for create_agent routing edges
mfateev Dec 28, 2025
1e87448
LangGraph: Improve activity summaries for model nodes
mfateev Dec 28, 2025
dc2dd26
LangGraph: Execute subgraph inner nodes as separate activities
mfateev Dec 28, 2025
c1edae9
LangGraph: Fix subgraph routing and add unit tests
mfateev Dec 28, 2025
62b245c
LangGraph: Fix node execution to always use ainvoke
mfateev Dec 28, 2025
1f1c209
LangGraph: Handle ParentCommand for supervisor multi-agent routing
mfateev Dec 29, 2025
8c0f3ae
LangGraph: Update README to reference create_agent
mfateev Dec 29, 2025
517c01d
LangGraph: Classify node errors as retryable or non-retryable
mfateev Dec 29, 2025
97e4312
LangGraph: Execute Send packets in parallel using asyncio.gather
mfateev Dec 29, 2025
82f0725
LangGraph: Extract query from input state for activity summaries
mfateev Dec 29, 2025
2193a41
Add .mypy_cache to .gitignore
mfateev Dec 29, 2025
63dcfdf
LangGraph: Refactor ainvoke and _execute_subgraph into smaller methods
mfateev Dec 29, 2025
fb46f18
LangGraph: Group instance variables into state dataclasses
mfateev Dec 29, 2025
5576183
LangGraph: Extract magic strings to constants module
mfateev Dec 29, 2025
dbb1821
LangGraph: Extract nested functions from _execute_node_impl
mfateev Dec 29, 2025
4cc4ff8
Update CODE_REVIEW.md to mark completed refactoring items
mfateev Dec 29, 2025
470d916
LangGraph: Fix linting issues (import order and formatting)
mfateev Dec 30, 2025
d80affd
Fix lint errors: add type annotations and docstrings
mfateev Dec 30, 2025
12cc97d
LangGraph: Set ContextVar for get_config()/get_store() in activities
mfateev Dec 30, 2025
56fbe77
LangGraph: Support run_in_workflow nodes calling Temporal operations
mfateev Dec 30, 2025
1d2e2ad
LangGraph: Run user function sandboxed in run_in_workflow nodes
mfateev Dec 30, 2025
95b2e32
LangGraph: Add E2E test for sandbox enforcement on run_in_workflow nodes
mfateev Dec 30, 2025
4a389e3
LangGraph: Improve README documentation
mfateev Dec 30, 2025
b15f465
Update README sample links to langgraph_plugin directory
mfateev Dec 31, 2025
d2fb7ac
LangGraph: Add graph visualization methods
mfateev Dec 31, 2025
7e2cf0c
Add Graph Visualization Queries section to README
mfateev Dec 31, 2025
4c4d5ef
LangGraph: Fix parallel branch execution in BSP model
mfateev Dec 31, 2025
9c3ee17
Add unit tests for LangGraph Functional API implementation
mfateev Jan 2, 2026
697faca
LangGraph: Add Functional API implementation and fix lint issues
mfateev Jan 2, 2026
8b92932
Fix asyncio compatibility for LangGraph Functional API
mfateev Jan 2, 2026
ccfc578
LangGraph Functional API: Bypass Pregel runner for proper task routing
mfateev Jan 2, 2026
02c4ca7
LangGraph: Support activity_options() helper in Functional API
mfateev Jan 3, 2026
eeeaf00
LangGraph: Unify plugin for Graph API and Functional API
mfateev Jan 3, 2026
30975ab
Remove backward compatibility code from LangGraph integration
mfateev Jan 4, 2026
32f59cb
Update README installation instructions for development branch
mfateev Jan 4, 2026
e370b43
Add CLAUDE.md with design decisions and implementation guide
mfateev Jan 4, 2026
9fde135
Remove duplicate design doc (langgraph-plugin-design.md)
mfateev Jan 4, 2026
91fa485
Update design documentation to match implementation
mfateev Jan 4, 2026
2648bc0
Add task result caching for Functional API continue-as-new
mfateev Jan 4, 2026
60f0305
Document task result caching for Functional API in CLAUDE.md
mfateev Jan 4, 2026
61e8a1e
Add Functional API documentation to README
mfateev Jan 5, 2026
cb926d1
Update README sample applications with both API styles
mfateev Jan 5, 2026
bce3522
Add should_continue callback to Functional API for continue-as-new
mfateev Jan 8, 2026
3f6898e
Document should_continue callback for checkpointing in CLAUDE.md
mfateev Jan 8, 2026
64b1bc4
Fix _coerce_state_values to handle non-dict inputs
mfateev Jan 9, 2026
2f058ff
Improve type safety and SDK consistency in langgraph plugin
mfateev Jan 11, 2026
b28280b
Address low priority code review items
mfateev Jan 11, 2026
6905df1
Update CLAUDE.md to reflect code review fixes
mfateev Jan 11, 2026
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
Prev Previous commit
Next Next commit
LangGraph: Align with SDK style conventions
- Convert Pydantic models to dataclasses in _models.py (item #3)
- Standardize type annotations: replace Optional[X] with X | None (item #9)
- Verify docstring style follows SDK conventions (item #4)
  • Loading branch information
mfateev committed Dec 27, 2025
commit dc1d2e3e5825d4114d95893ef68c6a295b5e17dc
123 changes: 55 additions & 68 deletions temporalio/contrib/langgraph/STYLE_REVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ This document captures discrepancies between the LangGraph integration (`tempora
|---|----------|----------|-------------|
| 1 | ~~Experimental warnings~~ | ~~Medium~~ | ~~Missing `.. warning::` notices for experimental API~~ **FIXED** |
| 2 | ~~Internal API usage~~ | ~~High~~ | ~~Uses `langgraph._internal.*` private modules~~ **DOCUMENTED** |
| 3 | Data structures | Low | Uses Pydantic instead of dataclasses |
| 4 | Docstrings | Low | Different style from SDK conventions |
| 3 | ~~Data structures~~ | ~~Low~~ | ~~Uses Pydantic instead of dataclasses~~ **FIXED** |
| 4 | ~~Docstrings~~ | ~~Low~~ | ~~Different style from SDK conventions~~ **FIXED** |
| 5 | ~~Logging~~ | ~~Medium~~ | ~~No module-level logger defined~~ **FIXED** |
| 6 | ~~Warnings suppression~~ | ~~Medium~~ | ~~Suppresses deprecation warnings~~ **FIXED** |
| 7 | File organization | Low | Example file in production code |
| 8 | Test naming | Low | Uses `e2e_` prefix not standard in SDK |
| 9 | Type annotations | Low | Mixed `Optional[X]` and `X | None` |
| 9 | ~~Type annotations~~ | ~~Low~~ | ~~Mixed `Optional[X]` and `X | None`~~ **FIXED** |
| 10 | ~~Exceptions~~ | ~~Medium~~ | ~~Uses generic exceptions instead of domain-specific~~ **FIXED** |
| 11 | Design docs | Low | Design document in production directory |

Expand Down Expand Up @@ -80,84 +80,73 @@ from langgraph._internal._scratchpad import PregelScratchpad

---

### 3. Pydantic Models vs Dataclasses
### 3. Pydantic Models vs Dataclasses **FIXED**

**Severity**: Low
**Location**: `_models.py`

**Issue**: The SDK predominantly uses `@dataclass` (often `@dataclass(frozen=True)`) for data structures, while the LangGraph integration uses Pydantic `BaseModel`:
**Issue**: The SDK predominantly uses `@dataclass` (often `@dataclass(frozen=True)`) for data structures, while the LangGraph integration was using Pydantic `BaseModel`.

**Resolution**: Converted all models in `_models.py` from Pydantic `BaseModel` to Python `@dataclass`:
- Replaced `BaseModel` inheritance with `@dataclass` decorator
- Replaced `model_config = ConfigDict(arbitrary_types_allowed=True)` (no longer needed for dataclasses)
- Replaced Pydantic's `BeforeValidator` for `LangGraphState` with `__post_init__` method in `NodeActivityInput`
- Updated to SDK-style inline docstrings after field definitions
- Converted `Optional[X]` to `X | None` for consistency

The models now follow SDK conventions while maintaining full functionality:
```python
# SDK pattern (common.py, activity.py, etc.):
@dataclass(frozen=True)
class RetryPolicy:
initial_interval: timedelta = timedelta(seconds=1)
"""Backoff interval for the first retry. Default 1s."""

# LangGraph pattern (_models.py):
class StoreItem(BaseModel):
"""Single item in the store."""
@dataclass
class StoreItem:
"""A key-value pair within a namespace."""

namespace: tuple[str, ...]
"""Hierarchical namespace tuple."""

key: str
"""The key within the namespace."""

value: dict[str, Any]
"""The stored value."""
```

**Context**: This may be intentional due to LangChain's Pydantic dependency and serialization requirements, but creates inconsistency with the rest of the SDK.

**Recommendation**: Document why Pydantic is used (likely for LangChain compatibility) in the module docstring.
Note: `_coerce_to_message()` still uses Pydantic's `TypeAdapter` internally for LangChain message deserialization, which is acceptable since LangChain already depends on Pydantic.

---

### 4. Docstring Style Inconsistencies
### 4. Docstring Style Inconsistencies **FIXED**

**Severity**: Low
**Location**: Various files

#### 4a. Module Docstrings

**SDK Pattern**: Short, single-sentence module docstrings:
```python
"""Activity worker."""
"""Common Temporal exceptions."""
"""Client for accessing Temporal."""
```
**Issue**: Original concern was about module docstrings and attribute documentation style.

**LangGraph Pattern**: Longer, more detailed module docstrings with usage examples:
```python
"""Temporal integration for LangGraph.
**Resolution**: The module now follows SDK conventions:

This module provides seamless integration between LangGraph and Temporal,
enabling durable execution of LangGraph agents...
#### 4a. Module Docstrings
All module docstrings use short, single-sentence style:
- `_activities.py`: "Temporal activities for LangGraph node execution."
- `_models.py`: "Dataclass models for LangGraph-Temporal integration."
- `_plugin.py`: "LangGraph plugin for Temporal integration."
- etc.

Quick Start:
>>> from temporalio.client import Client
...
"""
```
The `__init__.py` includes an experimental warning which is appropriate for a public API.

#### 4b. Attribute Documentation

**SDK Pattern**: Uses inline docstrings after attributes in dataclasses:
All dataclasses in `_models.py` use SDK-style inline docstrings after attributes:
```python
@dataclass
class RetryPolicy:
initial_interval: timedelta = timedelta(seconds=1)
"""Backoff interval for the first retry. Default 1s."""
```
class StoreItem:
"""A key-value pair within a namespace."""

**LangGraph Pattern**: Uses `Attributes:` section in class docstring:
```python
class StoreItem(BaseModel):
"""Single item in the store.

Attributes:
namespace: Hierarchical namespace tuple...
key: The key within the namespace.
value: The stored value...
"""
namespace: tuple[str, ...]
"""Hierarchical namespace tuple."""

key: str
"""The key within the namespace."""
```

**Recommendation**: Consider aligning with SDK's inline docstring pattern where possible.
This pattern was established when converting from Pydantic to dataclasses (item #3).

---

Expand Down Expand Up @@ -235,25 +224,22 @@ tests/contrib/langgraph/

---

### 9. Type Annotations Style
### 9. Type Annotations Style **FIXED**

**Severity**: Low
**Location**: Various files

**Issue**: Mixed use of `Optional[X]` and `X | None`:

```python
# Mixed in _runner.py:
checkpoint: Optional[dict[str, Any]] = None
resume_value: Optional[Any] = None

# vs newer style:
config: dict[str, Any] | None = None
```
**Issue**: Mixed use of `Optional[X]` and `X | None`.

**SDK Trend**: Newer SDK code tends to prefer `X | None` syntax consistently.
**Resolution**: Standardized all type annotations to use `X | None` syntax throughout the module:
- `_temporal_tool.py` - Converted all `Optional` usages
- `_runner.py` - Converted all `Optional` usages
- `_model_registry.py` - Removed unused `Optional` import
- `_temporal_model.py` - Converted all `Optional` usages
- `__init__.py` - Converted all `Optional` usages in public APIs
- `_store.py` - Converted all `Optional` usages

**Recommendation**: Standardize on `X | None` syntax throughout.
All files now consistently use the `X | None` syntax preferred by newer SDK code.

---

Expand Down Expand Up @@ -334,8 +320,9 @@ These should be documented as optional dependencies in `pyproject.toml`.
- [x] Consider domain-specific exceptions (item #10) **FIXED** - Created `_exceptions.py` with `ApplicationError` factory functions and configuration exceptions

### Low Priority
- [x] Convert Pydantic models to dataclasses (item #3) **FIXED** - Converted all models in `_models.py` to dataclasses
- [ ] Move example file (item #7)
- [ ] Standardize type annotation style (item #9)
- [x] Standardize type annotation style (item #9) **FIXED** - Converted all `Optional[X]` to `X | None` syntax
- [ ] Move design document (item #11)
- [ ] Align docstring style (item #4)
- [x] Align docstring style (item #4) **FIXED** - Module and attribute docstrings follow SDK conventions
- [ ] Review test organization (item #8)
34 changes: 17 additions & 17 deletions temporalio/contrib/langgraph/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from __future__ import annotations

from datetime import timedelta
from typing import Any, Optional
from typing import Any

import temporalio.common
import temporalio.workflow
Expand Down Expand Up @@ -42,16 +42,16 @@

def node_activity_options(
*,
schedule_to_close_timeout: Optional[timedelta] = None,
schedule_to_start_timeout: Optional[timedelta] = None,
start_to_close_timeout: Optional[timedelta] = None,
heartbeat_timeout: Optional[timedelta] = None,
task_queue: Optional[str] = None,
retry_policy: Optional[temporalio.common.RetryPolicy] = None,
cancellation_type: Optional[temporalio.workflow.ActivityCancellationType] = None,
versioning_intent: Optional[temporalio.workflow.VersioningIntent] = None,
summary: Optional[str] = None,
priority: Optional[temporalio.common.Priority] = None,
schedule_to_close_timeout: timedelta | None = None,
schedule_to_start_timeout: timedelta | None = None,
start_to_close_timeout: timedelta | None = None,
heartbeat_timeout: timedelta | None = None,
task_queue: str | None = None,
retry_policy: temporalio.common.RetryPolicy | None = None,
cancellation_type: temporalio.workflow.ActivityCancellationType | None = None,
versioning_intent: temporalio.workflow.VersioningIntent | None = None,
summary: str | None = None,
priority: temporalio.common.Priority | None = None,
) -> dict[str, Any]:
"""Create activity options for LangGraph nodes.

Expand Down Expand Up @@ -84,7 +84,7 @@ def node_activity_options(

def temporal_node_metadata(
*,
activity_options: Optional[dict[str, Any]] = None,
activity_options: dict[str, Any] | None = None,
run_in_workflow: bool = False,
) -> dict[str, Any]:
"""Create node metadata combining activity options and execution flags.
Expand Down Expand Up @@ -112,9 +112,9 @@ def temporal_node_metadata(
def compile(
graph_id: str,
*,
default_activity_options: Optional[dict[str, Any]] = None,
per_node_activity_options: Optional[dict[str, dict[str, Any]]] = None,
checkpoint: Optional[dict] = None,
default_activity_options: dict[str, Any] | None = None,
per_node_activity_options: dict[str, dict[str, Any]] | None = None,
checkpoint: dict | None = None,
) -> TemporalLangGraphRunner:
"""Compile a registered graph for Temporal execution.

Expand Down Expand Up @@ -150,13 +150,13 @@ def _merge_activity_options(
return {"temporal": {**base_temporal, **override_temporal}}

# Merge options: compile options override plugin options
merged_default_options: Optional[dict[str, Any]] = None
merged_default_options: dict[str, Any] | None = None
if plugin_default_options or default_activity_options:
merged_default_options = _merge_activity_options(
plugin_default_options or {}, default_activity_options or {}
)

merged_per_node_options: Optional[dict[str, dict[str, Any]]] = None
merged_per_node_options: dict[str, dict[str, Any]] | None = None
if plugin_per_node_options or per_node_activity_options:
merged_per_node_options = {}
# Start with plugin options
Expand Down
8 changes: 4 additions & 4 deletions temporalio/contrib/langgraph/_model_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from __future__ import annotations

import threading
from typing import TYPE_CHECKING, Callable, Optional
from typing import TYPE_CHECKING, Callable

from temporalio.contrib.langgraph._exceptions import model_not_found_error

Expand All @@ -16,7 +16,7 @@
_registry_lock = threading.Lock()


def register_model(model: "BaseChatModel", name: Optional[str] = None) -> None:
def register_model(model: "BaseChatModel", name: str | None = None) -> None:
"""Register a model instance in the global registry."""
if name is None:
name = getattr(model, "model_name", None) or getattr(model, "model", None)
Expand Down Expand Up @@ -61,9 +61,9 @@ def get_model(name: str) -> "BaseChatModel":
raise model_not_found_error(name, available)


def _try_auto_create_model(name: str) -> Optional["BaseChatModel"]:
def _try_auto_create_model(name: str) -> "BaseChatModel | None":
"""Try to auto-create a model based on common naming patterns."""
model: Optional["BaseChatModel"] = None
model: "BaseChatModel | None" = None
try:
# OpenAI models
if name.startswith("gpt-") or name.startswith("o1"):
Expand Down
Loading