diff --git a/.github/workflows/_test.yml b/.github/workflows/_test.yml index ea8849b95..565ab5989 100644 --- a/.github/workflows/_test.yml +++ b/.github/workflows/_test.yml @@ -21,11 +21,8 @@ jobs: - "3.10" - "3.11" - "3.12" - core-version: - - ">=0.3.0.dev4,<0.4.0" - - "latest" - name: "test #${{ matrix.python-version }} (langchain-core: ${{ matrix.core-version }})" + name: "test #${{ matrix.python-version }}" steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} + Poetry ${{ env.POETRY_VERSION }} @@ -41,11 +38,8 @@ jobs: working-directory: ${{ inputs.working-directory }} run: | poetry install --with dev - if [ "${{ matrix.core-version }}" != "latest" ]; then - poetry run pip install "langchain-core${{ matrix.core-version }}" - fi - - name: Run core tests + - name: Run tests shell: bash working-directory: ${{ inputs.working-directory }} run: | diff --git a/.github/workflows/_test_langgraph.yml b/.github/workflows/_test_langgraph.yml new file mode 100644 index 000000000..8b063c74e --- /dev/null +++ b/.github/workflows/_test_langgraph.yml @@ -0,0 +1,59 @@ +name: test + +on: + workflow_call: + +env: + POETRY_VERSION: "1.7.1" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: + - "3.9" + - "3.10" + - "3.11" + - "3.12" + core-version: + - ">=0.3.0.dev4,<0.4.0" + - "latest" + + defaults: + run: + working-directory: libs/langgraph + name: "test #${{ matrix.python-version }} (langchain-core: ${{ matrix.core-version }})" + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + Poetry ${{ env.POETRY_VERSION }} + uses: "./.github/actions/poetry_setup" + with: + python-version: ${{ matrix.python-version }} + poetry-version: ${{ env.POETRY_VERSION }} + cache-key: test-langgraph + + - name: Install dependencies + shell: bash + run: | + poetry install --with dev + if [ "${{ matrix.core-version }}" != "latest" ]; then + poetry run pip install "langchain-core${{ matrix.core-version }}" + fi + + - name: Run tests + shell: bash + run: | + make test + + - name: Ensure the tests did not create any additional files + shell: bash + run: | + set -eu + + STATUS="$(git status)" + echo "$STATUS" + + # grep will exit non-zero if the target message isn't found, + # and `set -e` above will cause the step to fail. + echo "$STATUS" | grep 'nothing to commit, working tree clean' diff --git a/.github/workflows/_test_scheduler_kafka.yml b/.github/workflows/_test_scheduler_kafka.yml new file mode 100644 index 000000000..8bfdfb18c --- /dev/null +++ b/.github/workflows/_test_scheduler_kafka.yml @@ -0,0 +1,51 @@ +name: test + +on: + workflow_call: + +env: + POETRY_VERSION: "1.7.1" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: + - "3.11" + - "3.12" + + defaults: + run: + working-directory: libs/scheduler-kafka + name: "test #${{ matrix.python-version }}" + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + Poetry ${{ env.POETRY_VERSION }} + uses: "./.github/actions/poetry_setup" + with: + python-version: ${{ matrix.python-version }} + poetry-version: ${{ env.POETRY_VERSION }} + cache-key: test-scheduler-kafka + + - name: Install dependencies + shell: bash + run: | + poetry install --with dev + + - name: Run tests + shell: bash + run: | + make test + + - name: Ensure the tests did not create any additional files + shell: bash + run: | + set -eu + + STATUS="$(git status)" + echo "$STATUS" + + # grep will exit non-zero if the target message isn't found, + # and `set -e` above will cause the step to fail. + echo "$STATUS" | grep 'nothing to commit, working tree clean' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 48ee947c9..f6870d881 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,18 +44,28 @@ jobs: strategy: matrix: working-directory: [ - "libs/langgraph", "libs/cli", "libs/checkpoint", "libs/checkpoint-sqlite", - "libs/checkpoint-postgres", - # "libs/scheduler-kafka" + "libs/checkpoint-postgres" ] 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: + name: "cd libs/langgraph" + uses: ./.github/workflows/_test_langgraph.yml + secrets: inherit + + # NOTE: we're testing scheduler-kafka separately because it requires a different matrix + test-scheduler-kafka: + name: "cd libs/scheduler-kafka" + uses: ./.github/workflows/_test_scheduler_kafka.yml + secrets: inherit + integration-test: name: CLI integration test uses: ./.github/workflows/_integration_test.yml diff --git a/libs/scheduler-kafka/tests/messages.py b/libs/scheduler-kafka/tests/messages.py new file mode 100644 index 000000000..5f1dc1f0e --- /dev/null +++ b/libs/scheduler-kafka/tests/messages.py @@ -0,0 +1,28 @@ +"""Redefined messages as a work-around for pydantic issue with AnyStr. + +The code below creates version of pydantic models +that will work in unit tests with AnyStr as id field +Please note that the `id` field is assigned AFTER the model is created +to workaround an issue with pydantic ignoring the __eq__ method on +subclassed strings. +""" + +from typing import Any + +from langchain_core.messages import AIMessage, HumanMessage + +from tests.any import AnyStr + + +def _AnyIdAIMessage(**kwargs: Any) -> AIMessage: + """Create ai message with an any id field.""" + message = AIMessage(**kwargs) + message.id = AnyStr() + return message + + +def _AnyIdHumanMessage(**kwargs: Any) -> HumanMessage: + """Create a human message with an any id field.""" + message = HumanMessage(**kwargs) + message.id = AnyStr() + return message diff --git a/libs/scheduler-kafka/tests/test_subgraph.py b/libs/scheduler-kafka/tests/test_subgraph.py index ab3b4d20c..c7ccb7e50 100644 --- a/libs/scheduler-kafka/tests/test_subgraph.py +++ b/libs/scheduler-kafka/tests/test_subgraph.py @@ -5,7 +5,7 @@ from aiokafka import AIOKafkaProducer from langchain_core.language_models.fake_chat_models import ( FakeMessagesListChatModel, ) -from langchain_core.messages import AIMessage, HumanMessage, ToolCall +from langchain_core.messages import AIMessage, ToolCall from langchain_core.tools import tool from langgraph.checkpoint.base import BaseCheckpointSaver @@ -17,6 +17,7 @@ from langgraph.scheduler.kafka import serde from langgraph.scheduler.kafka.types import MessageToOrchestrator, Topics from tests.any import AnyDict, AnyStr from tests.drain import drain_topics +from tests.messages import _AnyIdAIMessage, _AnyIdHumanMessage pytestmark = pytest.mark.anyio @@ -135,7 +136,7 @@ async def test_subgraph_w_interrupt( state = await graph.aget_state(config) assert state.next == ("weather_graph",) assert state.values == { - "messages": [HumanMessage(id=AnyStr(), content="what's the weather in sf")], + "messages": [_AnyIdHumanMessage(content="what's the weather in sf")], "route": "weather", } @@ -421,8 +422,8 @@ async def test_subgraph_w_interrupt( assert state.next == () assert state.values == { "messages": [ - HumanMessage(id=AnyStr(), content="what's the weather in sf"), - AIMessage(content="I'ts sunny in San Francisco!", id=AnyStr()), + _AnyIdHumanMessage(content="what's the weather in sf"), + _AnyIdAIMessage(content="I'ts sunny in San Francisco!"), ], "route": "weather", }