feat: complete phase 2 -- multi-agent routing, interrupt TTL, escalation, templates
- Intent classification with LLM structured output (single/multi/ambiguous) - Discount agent with apply_discount and generate_coupon tools - Interrupt manager with 30-min TTL auto-expiration and retry prompts - Webhook escalation module with exponential backoff retry (max 3) - Three vertical industry templates (e-commerce, SaaS, fintech) - Template loading in AgentRegistry - Enhanced supervisor prompt with dynamic agent descriptions - 153 tests passing, 90.18% coverage
This commit is contained in:
@@ -7,7 +7,8 @@ from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from app.graph import SUPERVISOR_PROMPT, build_agent_nodes, build_graph
|
||||
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
|
||||
@@ -38,7 +39,51 @@ class TestBuildGraph:
|
||||
graph = build_graph(sample_registry, mock_llm, mock_checkpointer)
|
||||
assert graph is not None
|
||||
|
||||
def test_supervisor_prompt_contains_routing_info(self) -> None:
|
||||
assert "order_lookup" in SUPERVISOR_PROMPT
|
||||
assert "order_actions" in SUPERVISOR_PROMPT
|
||||
assert "fallback" in SUPERVISOR_PROMPT
|
||||
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)
|
||||
mock_checkpointer = AsyncMock()
|
||||
mock_classifier = MagicMock()
|
||||
|
||||
graph = build_graph(
|
||||
sample_registry, mock_llm, mock_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)
|
||||
mock_checkpointer = AsyncMock()
|
||||
|
||||
graph = build_graph(sample_registry, mock_llm, mock_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"
|
||||
|
||||
Reference in New Issue
Block a user