- Bump langgraph from 0.4 to 1.0+, langgraph-supervisor from 0.0.12 to 0.0.30+ - Bump langchain-core, langchain-anthropic, langchain-openai to 1.x - Add langchain>=1.0 dependency for new create_agent location - Migrate create_react_agent -> create_agent (prompt -> system_prompt) - Fix create_supervisor positional arg to named agents= parameter - Replace AsyncMock checkpointer with InMemorySaver in tests (v1 type validation) - Update version references in README, ARCHITECTURE, eng-review-plan
91 lines
3.3 KiB
Python
91 lines
3.3 KiB
Python
"""Tests for app.graph module."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
|
|
import pytest
|
|
from langgraph.checkpoint.memory import InMemorySaver
|
|
|
|
from app.graph import build_agent_nodes, build_graph, classify_intent
|
|
from app.intent import ClassificationResult, IntentTarget
|
|
|
|
if TYPE_CHECKING:
|
|
from app.registry import AgentRegistry
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestBuildAgentNodes:
|
|
def test_creates_correct_number_of_nodes(self, sample_registry: AgentRegistry) -> None:
|
|
mock_llm = MagicMock()
|
|
nodes = build_agent_nodes(sample_registry, mock_llm)
|
|
assert len(nodes) == 3
|
|
|
|
def test_nodes_are_runnable(self, sample_registry: AgentRegistry) -> None:
|
|
mock_llm = MagicMock()
|
|
nodes = build_agent_nodes(sample_registry, mock_llm)
|
|
for node in nodes:
|
|
assert hasattr(node, "invoke") or hasattr(node, "ainvoke")
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestBuildGraph:
|
|
def test_graph_compiles_with_mock_checkpointer(self, sample_registry: AgentRegistry) -> None:
|
|
mock_llm = MagicMock()
|
|
mock_llm.bind_tools = MagicMock(return_value=mock_llm)
|
|
mock_llm.with_structured_output = MagicMock(return_value=mock_llm)
|
|
checkpointer = InMemorySaver()
|
|
|
|
graph = build_graph(sample_registry, mock_llm, checkpointer)
|
|
assert graph is not None
|
|
|
|
def test_graph_has_classifier_attached(self, sample_registry: AgentRegistry) -> None:
|
|
mock_llm = MagicMock()
|
|
mock_llm.bind_tools = MagicMock(return_value=mock_llm)
|
|
mock_llm.with_structured_output = MagicMock(return_value=mock_llm)
|
|
checkpointer = InMemorySaver()
|
|
mock_classifier = MagicMock()
|
|
|
|
graph = build_graph(
|
|
sample_registry, mock_llm, checkpointer, intent_classifier=mock_classifier
|
|
)
|
|
assert graph.intent_classifier is mock_classifier
|
|
assert graph.agent_registry is sample_registry
|
|
|
|
def test_graph_without_classifier(self, sample_registry: AgentRegistry) -> None:
|
|
mock_llm = MagicMock()
|
|
mock_llm.bind_tools = MagicMock(return_value=mock_llm)
|
|
mock_llm.with_structured_output = MagicMock(return_value=mock_llm)
|
|
checkpointer = InMemorySaver()
|
|
|
|
graph = build_graph(sample_registry, mock_llm, checkpointer)
|
|
assert graph.intent_classifier is None
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestClassifyIntent:
|
|
@pytest.mark.asyncio
|
|
async def test_returns_none_without_classifier(self) -> None:
|
|
graph = MagicMock()
|
|
graph.intent_classifier = None
|
|
result = await classify_intent(graph, "hello")
|
|
assert result is None
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_calls_classifier(self) -> None:
|
|
expected = ClassificationResult(
|
|
intents=(IntentTarget(agent_name="order_lookup", confidence=0.9, reasoning="test"),),
|
|
)
|
|
mock_classifier = AsyncMock()
|
|
mock_classifier.classify = AsyncMock(return_value=expected)
|
|
|
|
graph = MagicMock()
|
|
graph.intent_classifier = mock_classifier
|
|
graph.agent_registry = MagicMock()
|
|
graph.agent_registry.list_agents = MagicMock(return_value=())
|
|
|
|
result = await classify_intent(graph, "check order")
|
|
assert result is not None
|
|
assert result.intents[0].agent_name == "order_lookup"
|