Add comprehensive two-tier architecture where Steward analyzes requests and Tatlock executes with scoped tools. Includes full infrastructure for request preprocessing, tool tracking, benchmarking, and streaming. **Added:** - Steward agent for request analysis and capability recommendation - Household Registry for centralized capability management - Request preprocessing pipeline (Steward → Tatlock flow) - Tool usage tracking and benchmarking system - Streaming transparency (Steward reasoning visible in streams) - Structured logging with operation timing - Redis benchmark storage with 30-day expiry - Benchmark analysis CLI tools **Infrastructure:** - src/agents/steward/ - Steward agent implementation - src/agents/tatlock_core/ - Tatlock capability domain - src/core/preprocessing.py - Request preprocessing pipeline - src/core/tool_tracking.py - Tool call tracking - src/core/benchmarks.py - Benchmark recording system - src/core/household_registry.py - Capability registry - src/core/startup.py - Application startup coordination - src/core/logging_config.py - Structured logging setup **Integration:** - Responses API uses Steward for Tatlock requests - Chat Completions wraps Responses API for OpenAI compatibility - Streaming coordinator supports Steward + Tatlock flow - Tool scoping per request based on Steward recommendations **Testing:** - Integration tests for Steward-Tatlock flow - Benchmark and registry unit tests - Steward streaming tests See PHASE2_PLAN.md and PHASE2_COMPLETE.md for detailed documentation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
202 lines
7.8 KiB
Python
202 lines
7.8 KiB
Python
"""
|
|
Tests for Steward service layer.
|
|
|
|
Tests request analysis, logging, and benchmarking integration.
|
|
"""
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from src.agents.steward.schemas import ConversationContext, StewardRecommendation
|
|
from src.agents.steward.service import analyze_request, format_steward_note
|
|
from src.core.startup import initialize_application
|
|
|
|
|
|
@pytest.fixture(scope="module", autouse=True)
|
|
def setup_household_registry():
|
|
"""Initialize household registry before running tests."""
|
|
initialize_application()
|
|
|
|
|
|
class TestAnalyzeRequest:
|
|
"""Test the analyze_request service function."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_analyze_simple_greeting(self):
|
|
"""Test analyzing a simple greeting."""
|
|
# Mock the Steward agent's analyze method (plain text approach)
|
|
mock_agent = MagicMock()
|
|
mock_agent.analyze = AsyncMock(return_value="Simple greeting requires no tools. This is a simple request.")
|
|
|
|
with patch("src.agents.steward.service.get_steward_agent", return_value=mock_agent):
|
|
with patch("src.agents.steward.service.get_benchmark_store") as mock_store:
|
|
mock_store.return_value.record = AsyncMock()
|
|
|
|
result = await analyze_request(
|
|
"Hello!",
|
|
conversation_history=[],
|
|
)
|
|
|
|
assert result.recommended_capabilities == []
|
|
assert result.estimated_complexity == "simple"
|
|
assert mock_agent.analyze.called
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_analyze_math_request(self):
|
|
"""Test analyzing a mathematical request."""
|
|
mock_agent = MagicMock()
|
|
mock_agent.analyze = AsyncMock(
|
|
return_value="Mathematical calculation requires tatlock_core for solving this simple problem."
|
|
)
|
|
|
|
with patch("src.agents.steward.service.get_steward_agent", return_value=mock_agent):
|
|
with patch("src.agents.steward.service.get_benchmark_store") as mock_store:
|
|
mock_store.return_value.record = AsyncMock()
|
|
|
|
result = await analyze_request(
|
|
"What's sqrt(144)?",
|
|
conversation_history=[],
|
|
)
|
|
|
|
assert "tatlock_core" in result.recommended_capabilities
|
|
assert result.estimated_complexity == "simple"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_analyze_with_conversation_history(self):
|
|
"""Test analyzing with previous conversation context."""
|
|
mock_agent = MagicMock()
|
|
mock_agent.analyze = AsyncMock(
|
|
return_value="Follow-up to previous calculation in turn 0. Requires tatlock_core. Complexity: moderate."
|
|
)
|
|
|
|
conversation_history = [
|
|
{"role": "user", "content": "What's 2 + 2?"},
|
|
{"role": "assistant", "content": "4"},
|
|
]
|
|
|
|
with patch("src.agents.steward.service.get_steward_agent", return_value=mock_agent):
|
|
with patch("src.agents.steward.service.get_benchmark_store") as mock_store:
|
|
mock_store.return_value.record = AsyncMock()
|
|
|
|
result = await analyze_request(
|
|
"And what's that times 5?",
|
|
conversation_history=conversation_history,
|
|
)
|
|
|
|
assert result.conversation_context.has_previous_context is True
|
|
assert 0 in result.conversation_context.relevant_turns
|
|
|
|
# Verify conversation history was passed
|
|
call_kwargs = mock_agent.analyze.call_args.kwargs
|
|
assert "conversation_history" in call_kwargs
|
|
assert len(call_kwargs["conversation_history"]) == 2
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_analyze_with_missing_capabilities(self):
|
|
"""Test analyzing request that needs unavailable capabilities."""
|
|
mock_agent = MagicMock()
|
|
mock_agent.analyze = AsyncMock(
|
|
return_value="Image generation not available. Would be needed for this request. Complexity: simple."
|
|
)
|
|
|
|
with patch("src.agents.steward.service.get_steward_agent", return_value=mock_agent):
|
|
with patch("src.agents.steward.service.get_benchmark_store") as mock_store:
|
|
mock_store.return_value.record = AsyncMock()
|
|
|
|
result = await analyze_request(
|
|
"Generate an image of a sunset",
|
|
conversation_history=[],
|
|
)
|
|
|
|
assert result.missing_capabilities is not None
|
|
assert "not available" in result.missing_capabilities
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_analyze_with_conversation_id(self):
|
|
"""Test that analysis includes conversation ID in context."""
|
|
mock_agent = MagicMock()
|
|
mock_agent.analyze = AsyncMock(
|
|
return_value="This simple request requires tatlock_core to solve."
|
|
)
|
|
|
|
with patch("src.agents.steward.service.get_steward_agent", return_value=mock_agent):
|
|
with patch("src.agents.steward.service.get_benchmark_store") as mock_store:
|
|
mock_store.return_value.record = AsyncMock()
|
|
|
|
result = await analyze_request(
|
|
"Test request",
|
|
conversation_history=[],
|
|
conversation_id="test_conv_123",
|
|
)
|
|
|
|
# Verify analysis completed successfully
|
|
assert result.recommended_capabilities == ["tatlock_core"]
|
|
assert result.estimated_complexity == "simple"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_analyze_handles_errors(self):
|
|
"""Test error handling in analyze_request."""
|
|
mock_agent = MagicMock()
|
|
mock_agent.analyze = AsyncMock(side_effect=Exception("Test error"))
|
|
|
|
with patch("src.agents.steward.service.get_steward_agent", return_value=mock_agent):
|
|
with pytest.raises(Exception, match="Test error"):
|
|
await analyze_request("Test", conversation_history=[])
|
|
|
|
|
|
class TestFormatStewardNote:
|
|
"""Test the format_steward_note function."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_format_simple_note(self):
|
|
"""Test formatting a simple recommendation."""
|
|
rec = StewardRecommendation(
|
|
recommended_capabilities=["tatlock_core"],
|
|
reasoning="Math needed",
|
|
estimated_complexity="simple",
|
|
conversation_context=ConversationContext(has_previous_context=False),
|
|
)
|
|
|
|
note = await format_steward_note(rec)
|
|
|
|
assert "📋 Steward's Analysis" in note
|
|
assert "SIMPLE" in note
|
|
assert "tatlock_core" in note
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_format_note_with_context(self):
|
|
"""Test formatting note with conversation context."""
|
|
context = ConversationContext(
|
|
has_previous_context=True,
|
|
relevant_turns=[0, 1],
|
|
context_summary="Previous discussion about calculations"
|
|
)
|
|
|
|
rec = StewardRecommendation(
|
|
recommended_capabilities=["tatlock_core"],
|
|
reasoning="Follow-up calculation",
|
|
estimated_complexity="moderate",
|
|
conversation_context=context,
|
|
)
|
|
|
|
note = await format_steward_note(rec)
|
|
|
|
assert "Context:" in note
|
|
assert "Previous discussion" in note
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_format_note_with_missing_capabilities(self):
|
|
"""Test formatting note with missing capabilities warning."""
|
|
rec = StewardRecommendation(
|
|
recommended_capabilities=[],
|
|
reasoning="Not available",
|
|
estimated_complexity="simple",
|
|
conversation_context=ConversationContext(has_previous_context=False),
|
|
missing_capabilities="Advanced research tools needed",
|
|
)
|
|
|
|
note = await format_steward_note(rec)
|
|
|
|
assert "⚠️ Missing:" in note
|
|
assert "Advanced research" in note
|