From 5ddfce18144cc257992185151267cc0f545dfb80 Mon Sep 17 00:00:00 2001 From: William FH <13333726+hinthornw@users.noreply.github.com> Date: Thu, 26 Feb 2026 14:45:02 -0800 Subject: [PATCH] chore: support workflow dispatch on ci (#6952) --- .github/workflows/_integration_test.yml | 23 ++++++------- .github/workflows/_lint.yml | 15 +++++---- .github/workflows/_test_langgraph.yml | 7 ++++ .github/workflows/ci.yml | 6 ++-- .../langgraph/checkpoint/serde/_msgpack.py | 18 +++++++++++ libs/checkpoint/tests/test_jsonplus.py | 32 +++++++++++++++++++ 6 files changed, 81 insertions(+), 20 deletions(-) diff --git a/.github/workflows/_integration_test.yml b/.github/workflows/_integration_test.yml index 1f0bf5f2e..1b3b533c9 100644 --- a/.github/workflows/_integration_test.yml +++ b/.github/workflows/_integration_test.yml @@ -40,11 +40,12 @@ jobs: - uses: actions/checkout@v6 - name: Get changed files id: changed-files + if: github.event_name != 'workflow_dispatch' uses: Ana06/get-changed-files@v2.3.0 with: filter: "libs/cli/**" - name: Set up Python ${{ matrix.python-version }} - if: steps.changed-files.outputs.all + if: (steps.changed-files.outputs.all || github.event_name == 'workflow_dispatch') uses: astral-sh/setup-uv@v7 with: python-version: ${{ matrix.python-version }} @@ -52,15 +53,15 @@ jobs: cache-suffix: "cli-integration-test" ignore-nothing-to-cache: true - name: Install cli globally - if: steps.changed-files.outputs.all + if: (steps.changed-files.outputs.all || github.event_name == 'workflow_dispatch') run: pip install -e . - name: Build service ${{ matrix.example.name }} - if: steps.changed-files.outputs.all + if: (steps.changed-files.outputs.all || github.event_name == 'workflow_dispatch') working-directory: ${{ matrix.example.workdir }} run: | langgraph build -t ${{ matrix.example.tag }} - name: Test service ${{ matrix.example.name }} - if: ${{ steps.changed-files.outputs.all && env.HAS_LANGSMITH_API_KEY == 'true' }} + if: ${{ (steps.changed-files.outputs.all || github.event_name == 'workflow_dispatch') &&env.HAS_LANGSMITH_API_KEY == 'true' }} working-directory: ${{ matrix.example.workdir }} env: LANGSMITH_API_KEY: ${{ secrets.LANGSMITH_API_KEY }} @@ -74,24 +75,24 @@ jobs: timeout 60 python "$REPO_ROOT/.github/scripts/run_langgraph_cli_test.py" -t ${{ matrix.example.tag }} - name: Build JS service - if: ${{ steps.changed-files.outputs.all && matrix.example.name == 'A' }} + if: ${{ (steps.changed-files.outputs.all || github.event_name == 'workflow_dispatch') &&matrix.example.name == 'A' }} working-directory: libs/cli/js-examples run: | langgraph build -t langgraph-test-e - name: Build JS monorepo service - if: ${{ steps.changed-files.outputs.all && matrix.example.name == 'A' }} + if: ${{ (steps.changed-files.outputs.all || github.event_name == 'workflow_dispatch') &&matrix.example.name == 'A' }} working-directory: libs/cli/js-monorepo-example run: | langgraph build -t langgraph-test-f -c apps/agent/langgraph.json --build-command "yarn run turbo build" --install-command "yarn install" - name: Build Python monorepo service - if: ${{ steps.changed-files.outputs.all && matrix.example.name == 'A' }} + if: ${{ (steps.changed-files.outputs.all || github.event_name == 'workflow_dispatch') &&matrix.example.name == 'A' }} working-directory: libs/cli/python-monorepo-example run: | langgraph build -t langgraph-test-g -c apps/agent/langgraph.json - name: Test Python monorepo service - if: ${{ steps.changed-files.outputs.all && matrix.example.name == 'A' && env.HAS_LANGSMITH_API_KEY == 'true' }} + if: ${{ (steps.changed-files.outputs.all || github.event_name == 'workflow_dispatch') &&matrix.example.name == 'A' && env.HAS_LANGSMITH_API_KEY == 'true' }} working-directory: libs/cli/python-monorepo-example env: LANGSMITH_API_KEY: ${{ secrets.LANGSMITH_API_KEY }} @@ -101,12 +102,12 @@ jobs: timeout 60 python ../../../.github/scripts/run_langgraph_cli_test.py -t langgraph-test-g -c apps/agent/langgraph.json - name: Build prerelease reqs service - if: ${{ steps.changed-files.outputs.all && matrix.example.name == 'A' }} + if: ${{ (steps.changed-files.outputs.all || github.event_name == 'workflow_dispatch') &&matrix.example.name == 'A' }} working-directory: libs/cli/examples/graph_prerelease_reqs run: | langgraph build -t langgraph-test-h - name: Test prerelease reqs service - if: ${{ steps.changed-files.outputs.all && matrix.example.name == 'A' && env.HAS_LANGSMITH_API_KEY == 'true' }} + if: ${{ (steps.changed-files.outputs.all || github.event_name == 'workflow_dispatch') &&matrix.example.name == 'A' && env.HAS_LANGSMITH_API_KEY == 'true' }} working-directory: libs/cli/examples/graph_prerelease_reqs env: LANGSMITH_API_KEY: ${{ secrets.LANGSMITH_API_KEY }} @@ -132,7 +133,7 @@ jobs: fi - name: Build and test prerelease reqs fail service - if: ${{ steps.changed-files.outputs.all && matrix.example.name == 'A' }} + if: ${{ (steps.changed-files.outputs.all || github.event_name == 'workflow_dispatch') &&matrix.example.name == 'A' }} working-directory: libs/cli/examples/graph_prerelease_reqs_fail run: | langgraph build -t langgraph-test-i || [ $? -eq 1 ] diff --git a/.github/workflows/_lint.yml b/.github/workflows/_lint.yml index 260347379..b47172bde 100644 --- a/.github/workflows/_lint.yml +++ b/.github/workflows/_lint.yml @@ -34,11 +34,12 @@ jobs: - uses: actions/checkout@v6 - name: Get changed files id: changed-files + if: github.event_name != 'workflow_dispatch' uses: Ana06/get-changed-files@v2.3.0 with: filter: "${{ inputs.working-directory }}/**" - name: Set up Python ${{ matrix.python-version }} - if: steps.changed-files.outputs.all + if: steps.changed-files.outputs.all || github.event_name == 'workflow_dispatch' uses: astral-sh/setup-uv@v7 with: python-version: ${{ matrix.python-version }} @@ -46,12 +47,12 @@ jobs: cache-suffix: lint-${{ inputs.working-directory }} - name: Install dependencies - if: steps.changed-files.outputs.all + if: steps.changed-files.outputs.all || github.event_name == 'workflow_dispatch' working-directory: ${{ inputs.working-directory }} run: uv sync --frozen --group lint - name: Get .mypy_cache to speed up mypy - if: steps.changed-files.outputs.all + if: steps.changed-files.outputs.all || github.event_name == 'workflow_dispatch' uses: actions/cache@v5 env: SEGMENT_DOWNLOAD_TIMEOUT_MIN: "2" @@ -61,7 +62,7 @@ jobs: key: mypy-lint-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python-version }}-${{ inputs.working-directory }}-${{ hashFiles(format('{0}/uv.lock', inputs.working-directory)) }} - name: Analysing package code with our lint - if: steps.changed-files.outputs.all + if: steps.changed-files.outputs.all || github.event_name == 'workflow_dispatch' working-directory: ${{ inputs.working-directory }} run: | if make lint_package > /dev/null 2>&1; then @@ -72,12 +73,12 @@ jobs: fi - name: Install test dependencies - if: steps.changed-files.outputs.all + if: steps.changed-files.outputs.all || github.event_name == 'workflow_dispatch' working-directory: ${{ inputs.working-directory }} run: uv sync --group lint - name: Get .mypy_cache_test to speed up mypy - if: steps.changed-files.outputs.all + if: steps.changed-files.outputs.all || github.event_name == 'workflow_dispatch' uses: actions/cache@v5 env: SEGMENT_DOWNLOAD_TIMEOUT_MIN: "2" @@ -87,7 +88,7 @@ jobs: key: mypy-test-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python-version }}-${{ inputs.working-directory }}-${{ hashFiles(format('{0}/uv.lock', inputs.working-directory)) }} - name: Analysing tests with our lint - if: steps.changed-files.outputs.all + if: steps.changed-files.outputs.all || github.event_name == 'workflow_dispatch' working-directory: ${{ inputs.working-directory }} run: | if make lint_tests > /dev/null 2>&1; then diff --git a/.github/workflows/_test_langgraph.yml b/.github/workflows/_test_langgraph.yml index 3aee8b3f9..6763a9def 100644 --- a/.github/workflows/_test_langgraph.yml +++ b/.github/workflows/_test_langgraph.yml @@ -45,6 +45,13 @@ jobs: shell: bash run: make test_parallel + - name: Run strict msgpack pregel tests + if: ${{ matrix.python-version == '3.13' }} + shell: bash + env: + LANGGRAPH_STRICT_MSGPACK: "true" + run: make test TEST="tests/test_pregel.py tests/test_pregel_async.py" + - name: Ensure the tests did not create any additional files shell: bash run: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f3207c4fe..31c8231e2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,7 @@ on: branches: - main pull_request: + permissions: contents: read @@ -25,11 +26,12 @@ jobs: changes: runs-on: ubuntu-latest outputs: - python: ${{ steps.filter.outputs.python }} - deps: ${{ steps.filter.outputs.deps }} + python: ${{ steps.filter.outputs.python || 'true' }} + deps: ${{ steps.filter.outputs.deps || 'true' }} steps: - uses: actions/checkout@v6 - uses: dorny/paths-filter@v3 + if: github.event_name != 'workflow_dispatch' id: filter with: filters: | diff --git a/libs/checkpoint/langgraph/checkpoint/serde/_msgpack.py b/libs/checkpoint/langgraph/checkpoint/serde/_msgpack.py index 9866806d4..0b09a9a4e 100644 --- a/libs/checkpoint/langgraph/checkpoint/serde/_msgpack.py +++ b/libs/checkpoint/langgraph/checkpoint/serde/_msgpack.py @@ -46,6 +46,24 @@ SAFE_MSGPACK_TYPES: frozenset[tuple[str, ...]] = frozenset( ("zoneinfo", "ZoneInfo"), # regex ("re", "compile"), + # langchain-core messages (safe container types used by graph state) + ("langchain_core.messages.base", "BaseMessage"), + ("langchain_core.messages.base", "BaseMessageChunk"), + ("langchain_core.messages.human", "HumanMessage"), + ("langchain_core.messages.human", "HumanMessageChunk"), + ("langchain_core.messages.ai", "AIMessage"), + ("langchain_core.messages.ai", "AIMessageChunk"), + ("langchain_core.messages.system", "SystemMessage"), + ("langchain_core.messages.system", "SystemMessageChunk"), + ("langchain_core.messages.chat", "ChatMessage"), + ("langchain_core.messages.chat", "ChatMessageChunk"), + ("langchain_core.messages.tool", "ToolMessage"), + ("langchain_core.messages.tool", "ToolMessageChunk"), + ("langchain_core.messages.function", "FunctionMessage"), + ("langchain_core.messages.function", "FunctionMessageChunk"), + ("langchain_core.messages.modifier", "RemoveMessage"), + # langchain-core document model + ("langchain_core.documents.base", "Document"), # langgraph ("langgraph.types", "Send"), ("langgraph.types", "Interrupt"), diff --git a/libs/checkpoint/tests/test_jsonplus.py b/libs/checkpoint/tests/test_jsonplus.py index 151b1224e..d153d3c7c 100644 --- a/libs/checkpoint/tests/test_jsonplus.py +++ b/libs/checkpoint/tests/test_jsonplus.py @@ -17,6 +17,8 @@ import numpy as np import ormsgpack import pandas as pd import pytest +from langchain_core.documents.base import Document +from langchain_core.messages import HumanMessage from pydantic import BaseModel, SecretStr from pydantic.v1 import BaseModel as BaseModelV1 from pydantic.v1 import SecretStr as SecretStrV1 @@ -684,6 +686,36 @@ def test_msgpack_strict_allows_safe_types( assert result == safe +def test_msgpack_strict_allows_core_langchain_messages( + caplog: pytest.LogCaptureFixture, +) -> None: + serde = JsonPlusSerializer(allowed_msgpack_modules=None) + msg = HumanMessage(content="hello") + + caplog.clear() + result = serde.loads_typed(serde.dumps_typed(msg)) + + assert "blocked" not in caplog.text.lower() + assert "unregistered" not in caplog.text.lower() + assert isinstance(result, HumanMessage) + assert result == msg + + +def test_msgpack_strict_allows_langchain_document( + caplog: pytest.LogCaptureFixture, +) -> None: + serde = JsonPlusSerializer(allowed_msgpack_modules=None) + doc = Document(page_content="hello", metadata={"k": "v"}) + + caplog.clear() + result = serde.loads_typed(serde.dumps_typed(doc)) + + assert "blocked" not in caplog.text.lower() + assert "unregistered" not in caplog.text.lower() + assert isinstance(result, Document) + assert result == doc + + def test_msgpack_regex_safe_type(caplog: pytest.LogCaptureFixture) -> None: """re.compile patterns should deserialize without warnings as a safe type."""