Files
langgraph/.github/workflows/ci.yml
T
Nick HollonandGitHub 68fa011fc9 feat(langgraph): add v3 streaming support to RemoteGraph (#7927)
## Summary

- Adds `stream_events(version="v3")` and `astream_events(version="v3")`
to `RemoteGraph`, matching the local `CompiledStateGraph` surface and
unblocking polymorphic v3 streaming over `Graph | RemoteGraph`.
- Implementation is a thin adapter
(`libs/langgraph/langgraph/pregel/_remote_run_stream.py`) that wraps the
v3 SDK's `AsyncThreadStream` / `SyncThreadStream` and duck-types
`GraphRunStream` / `AsyncGraphRunStream`. No coupling to local v3 mux
internals.
- `v1` / `v2` paths unchanged. `astream_events(version='v1'|'v2')` still
raises `NotImplementedError` (separate gap).

### Scope decisions baked into this PR

- Unsupported v3 kwargs hard-reject at dispatch with
`NotImplementedError`: `control`, `transformers`, `interrupt_before`,
`interrupt_after`, and any unknown `**kwargs`. Server / SDK don't plumb
these through v3 yet; easy to lift later.
- Sync `interleave()` raises `NotImplementedError` pointing callers at
`astream_events`. Real sync interleave would need drainer threads;
deferred since most sync RemoteGraph callers just iterate raw events.
- Async `interleave()` is best-effort ordering (client receive order),
documented as a divergence from local v3's monotonic stamp ordering.
- Adapter `interrupted` / `interrupts` properties are **non-blocking**
snapshots of the SDK's current state. This differs from local
`(Async)GraphRunStream.interrupted`, which pump-drives the run to
terminal before returning. Callers needing a wait-for-interrupt pattern
should drain a projection (e.g., `interleave('values')`) until the SDK's
paused sentinel fires. Documented in the adapter docstrings.

### Audit of impact

Existing RemoteGraph callers in this org all use the v2 `.stream()` /
`.astream()` path (deepagents production wrapper, langgraph-api test
graphs, langgraph-supervisor TS type guard). **Zero callers** use
`stream_events` / `astream_events` on RemoteGraph today, so the new v3
methods are net-new surface — no risk of breaking existing consumers.

### Out of scope (follow-ups)

- Bumping `libs/langgraph/pyproject.toml`'s `langgraph-sdk` constraint
from `<0.4.0` to `<0.5.0`. Deferred until 0.4.0 publishes to PyPI; dev
resolution unaffected via the editable workspace dep.
- Real `astream_events(version='v1'|'v2')` implementation.
- Server-side plumbing for `control` / `interrupt_before` /
`interrupt_after` on v3 runs.
- Sync `interleave()` via drainer threads.


## Test plan

- [x] \`make test\` in \`libs/langgraph/\`: 1874 passed, 4 skipped (43
new in \`test_remote_graph_v3.py\`)
- [x] \`make lint\` in \`libs/langgraph/\`: ruff + mypy clean
- [x] \`pytest -m integration
tests/integration/test_remote_graph_v3.py\` in \`libs/sdk-py/\` against
the docker stack: 4/4 passed in 1.35s
- [x] Manual smoke: \`RemoteGraph('tools_agent',
url='http://localhost:2024').astream_events(..., version='v3')\`
end-to-end against the v3 integration api
- [x] Existing RemoteGraph v2 tests untouched (31 passed, 3 skipped with
docker up)
- [x] Will need rebase after \`langgraph-sdk 0.4.0\` lands on PyPI and
the version constraint is bumped in a separate PR
2026-05-29 17:13:43 -04:00

197 lines
5.9 KiB
YAML

---
name: CI
on:
workflow_dispatch:
push:
branches:
- main
pull_request:
permissions:
contents: read
# If another push to the same PR or branch happens while this workflow is still running,
# cancel the earlier run in favor of the next run.
#
# There's no point in testing an outdated version of the code. GitHub only allows
# a limited number of job runners to be active at the same time, so it's better to cancel
# pointless jobs early so that more useful jobs can run sooner.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
changes:
runs-on: ubuntu-latest
outputs:
python: ${{ steps.filter.outputs.python || 'true' }}
deps: ${{ steps.filter.outputs.deps || 'true' }}
sdk_py: ${{ steps.filter.outputs.sdk_py || 'true' }}
steps:
- uses: actions/checkout@v6
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4
if: github.event_name != 'workflow_dispatch'
id: filter
with:
filters: |
python:
- 'libs/langgraph/**'
- 'libs/sdk-py/**'
- 'libs/cli/**'
- 'libs/checkpoint/**'
- 'libs/checkpoint-sqlite/**'
- 'libs/checkpoint-postgres/**'
- 'libs/checkpoint-conformance/**'
- 'libs/prebuilt/**'
deps:
- '**/pyproject.toml'
- '**/uv.lock'
sdk_py:
- 'libs/sdk-py/**'
- 'libs/langgraph/langgraph/pregel/remote.py'
- 'libs/langgraph/langgraph/pregel/_remote_run_stream.py'
lint:
needs: changes
name: cd ${{ matrix.working-directory }}
strategy:
matrix:
working-directory:
[
"libs/langgraph",
"libs/sdk-py",
"libs/cli",
"libs/checkpoint",
"libs/checkpoint-sqlite",
"libs/checkpoint-postgres",
"libs/checkpoint-conformance",
"libs/prebuilt",
]
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.deps == 'true'
uses: ./.github/workflows/_lint.yml
with:
working-directory: ${{ matrix.working-directory }}
secrets: inherit
test:
needs: changes
name: cd ${{ matrix.working-directory }}
strategy:
matrix:
working-directory:
[
"libs/cli",
"libs/checkpoint",
"libs/checkpoint-sqlite",
"libs/checkpoint-postgres",
"libs/checkpoint-conformance",
"libs/prebuilt",
"libs/sdk-py",
]
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.deps == 'true'
uses: ./.github/workflows/_test.yml
with:
working-directory: ${{ matrix.working-directory }}
secrets: inherit
# NOTE: we're testing langgraph separately because it requires a different matrix
test-langgraph:
needs: changes
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.deps == 'true'
name: "cd libs/langgraph"
uses: ./.github/workflows/_test_langgraph.yml
secrets: inherit
check-sdk-methods:
needs: changes
if: needs.changes.outputs.python == 'true'
name: "Check SDK methods matching"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.11"
- name: Run check_sdk_methods script
run: python .github/scripts/check_sdk_methods.py
check-schema:
needs: changes
if: needs.changes.outputs.python == 'true'
name: "Check CLI schema hasn't changed #${{ matrix.python-version }}"
runs-on: ubuntu-latest
strategy:
matrix:
python-version:
- "3.13"
steps:
- uses: actions/checkout@v6
- name: Set up Python ${{ matrix.python-version }}
uses: ./.github/actions/uv_setup
with:
python-version: "3.13"
cache-suffix: "schema-check-cli"
working-directory: libs/cli
- name: Install CLI dependencies
run: |
cd libs/cli
uv sync
- name: Generate schema and check for changes
run: |
cd libs/cli
# Create a temporary copy of the current schema
cp schemas/schema.json schemas/schema.current.json
# Generate new schema
uv run python generate_schema.py
# Compare the new schema with the original
if ! diff -q schemas/schema.json schemas/schema.current.json > /dev/null; then
echo "Error: Langgraph.json configuration schema has changed. Please run 'uv run python generate_schema.py' in the libs/cli directory and commit the changes."
diff schemas/schema.json schemas/schema.current.json
exit 1
fi
echo "Schema check passed - no changes detected"
integration-test:
needs: changes
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.deps == 'true'
name: CLI integration test
uses: ./.github/workflows/_integration_test.yml
secrets: inherit
sdk-py-integration-test:
needs: changes
if: needs.changes.outputs.sdk_py == 'true'
name: "sdk-py integration test"
uses: ./.github/workflows/_sdk_integration_test.yml
secrets: inherit
ci_success:
name: "CI Success"
needs:
[
lint,
test,
test-langgraph,
check-sdk-methods,
check-schema,
integration-test,
sdk-py-integration-test,
]
if: |
always()
runs-on: ubuntu-latest
env:
JOBS_JSON: ${{ toJSON(needs) }}
RESULTS_JSON: ${{ toJSON(needs.*.result) }}
EXIT_CODE: ${{!contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') && '0' || '1'}}
steps:
- name: "CI Success"
run: |
echo $JOBS_JSON
echo $RESULTS_JSON
echo "Exiting with $EXIT_CODE"
exit $EXIT_CODE