e30e75b5d4
Changesets / Create Version PR (push) Has been cancelled
Deploy Shadcn Registry / Deploy Production (push) Has been cancelled
Template Metrics / LOC + Bundle Size (push) Has been cancelled
Code Quality / Oxlint + Oxfmt (push) Has been cancelled
Code Quality / Template Sync (push) Has been cancelled
Code Quality / Build Changed Packages (push) Has been cancelled
Code Quality / Test Changed Packages (push) Has been cancelled
Deploy Expo Example / Deploy Production (push) Has been cancelled
Deploy Ink Example / Deploy Production (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.12) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Has been cancelled
123 lines
4.7 KiB
Markdown
123 lines
4.7 KiB
Markdown
# LangGraph Integration for assistant-stream
|
|
|
|
This document describes the LangGraph integration for the assistant-stream package.
|
|
|
|
## Installation
|
|
|
|
To use the LangGraph integration, install the assistant-stream package with the langgraph extra:
|
|
|
|
```bash
|
|
pip install assistant-stream[langgraph]
|
|
```
|
|
|
|
This will install the required dependencies including `langchain-core`.
|
|
|
|
## Usage
|
|
|
|
The integration exposes `append_langgraph_event`, which folds the events produced by LangGraph's native streaming into the state managed by a `RunController`. It is meant to consume `graph.astream(..., stream_mode=["messages", "updates"])` output directly: the event type is the LangGraph stream mode name, and the payload is the raw chunk LangGraph yields for that mode.
|
|
|
|
### Function Signature
|
|
|
|
```python
|
|
def append_langgraph_event(
|
|
state: Any,
|
|
_namespace: Any,
|
|
type: str,
|
|
payload: Any,
|
|
) -> None
|
|
```
|
|
|
|
### Parameters
|
|
|
|
- **state**: The state to mutate, normally `controller.state`. It is read and written with dictionary-style access.
|
|
- **_namespace**: The LangGraph namespace for the event. It is accepted for forward compatibility and is currently unused.
|
|
- **type**: The LangGraph stream mode that produced the event, either `"messages"` or `"updates"`.
|
|
- **payload**: The raw chunk LangGraph yields for that stream mode, described below.
|
|
|
|
### Event Types
|
|
|
|
#### Message events (`type="messages"`)
|
|
|
|
The payload is the `(message, metadata)` tuple that LangGraph yields in `"messages"` mode, where `message` is a single `BaseMessage` (often an `AIMessageChunk`) and `metadata` is currently unused.
|
|
|
|
The function will:
|
|
|
|
- Create a `messages` list in the state if it does not exist.
|
|
- Convert the message to a plain dict with `model_dump()`.
|
|
- Merge into an existing message when the `id` (or `tool_call_id`) matches: for an `AIMessageChunk` the message is merged with `add_ai_message_chunks`, then patched into state with granular `set` / `append-text` operations where possible. This lets streaming text and tool-call argument chunks update only the field that changed instead of sending the whole message again. If the shape cannot be represented safely as object-stream operations, the helper falls back to replacing the message.
|
|
- Append the message when no existing id matches.
|
|
|
|
```python
|
|
from langchain_core.messages import AIMessageChunk
|
|
|
|
# one chunk yielded by stream_mode="messages"
|
|
chunk = (AIMessageChunk(content="Hello", id="msg1"), {})
|
|
append_langgraph_event(controller.state, namespace, "messages", chunk)
|
|
```
|
|
|
|
#### Updates events (`type="updates"`)
|
|
|
|
The payload is the `{node_name: {channel: value}}` dict that LangGraph yields in `"updates"` mode.
|
|
|
|
The function will:
|
|
|
|
- Write each channel value directly onto the state (`state[channel] = value`), so the node name is not retained.
|
|
- Skip the `messages` channel, since messages are handled by message events.
|
|
- Skip a node whose value is not a dict.
|
|
|
|
```python
|
|
updates = {"weather_agent": {"status": "completed", "temperature": 72}}
|
|
append_langgraph_event(controller.state, namespace, "updates", updates)
|
|
# state now contains {"status": "completed", "temperature": 72}
|
|
```
|
|
|
|
### Notes
|
|
|
|
- The state holds plain JSON values (lists, dicts, str, int, bool, None); LangChain messages are converted with `model_dump()` before they are stored.
|
|
- Event types other than `"messages"` and `"updates"` are ignored.
|
|
|
|
## Example Integration
|
|
|
|
The events come straight from `graph.astream`, so a run callback forwards each one to `append_langgraph_event`:
|
|
|
|
```python
|
|
from assistant_stream import RunController, create_run
|
|
from assistant_stream.modules.langgraph import append_langgraph_event
|
|
from assistant_stream.serialization import DataStreamResponse
|
|
|
|
|
|
async def run_callback(controller: RunController):
|
|
async for namespace, event_type, chunk in graph.astream(
|
|
{"messages": input_messages},
|
|
stream_mode=["messages", "updates"],
|
|
subgraphs=True,
|
|
):
|
|
append_langgraph_event(controller.state, namespace, event_type, chunk)
|
|
|
|
|
|
stream = create_run(run_callback, state={})
|
|
response = DataStreamResponse(stream)
|
|
```
|
|
|
|
As the assistant message streams in, its chunks merge by id, so `controller.state["messages"]` ends with a single assistant message:
|
|
|
|
```python
|
|
# controller.state
|
|
# {
|
|
# "messages": [
|
|
# {"type": "human", "content": "What is the weather?", "id": "user1"},
|
|
# {"type": "ai", "content": "The weather is sunny today.", "id": "ai1"},
|
|
# ],
|
|
# }
|
|
```
|
|
|
|
See `python/assistant-transport-backend-langgraph` for a complete server built on this pattern, including subgraph state via `get_tool_call_subgraph_state`.
|
|
|
|
## Testing
|
|
|
|
The integration is covered by unit tests that exercise `append_langgraph_event` against a real state proxy:
|
|
|
|
```bash
|
|
uv run pytest tests/test_langgraph.py
|
|
```
|