Mechanical only, and separated from the judgment calls that follow so the reviewable changes are not buried in a 98-file whitespace diff. 227 automatic fixes: 60 blank lines carrying whitespace, 60 unsorted import blocks, 34 Optional[X] to X | None, 28 unused imports, 16 deprecated typing imports, 12 datetime.timezone.utc to datetime.UTC, and assorted smaller modernisations. Then `ruff format` over src and tests: 98 files reformatted, 35 already conforming. No file among the unused-import findings defines __all__ or is an __init__.py, so nothing here removes a re-export. `make test`: 658 passed, unchanged from HEAD. Two things observed while verifying, neither addressed here: `pytest tests/` cannot collect — tests/e2e/test_orchestration_e2e.py uses an `e2e` marker that is not registered, and the config is strict about markers. This fails identically at HEAD, so it predates this change; `make test` passes because it ignores tests/e2e, tests/integration and tests/contracts. test_tatlock_tool_call_logging_calculator is flaky. It failed once in a full run with these changes and passed on the next, passes in isolation with them, and fails in isolation at HEAD. It is order- or timing-dependent, not a regression from this commit — established by running the full suite both ways rather than by reasoning about which change could have caused it. Co-Authored-By: Claude <noreply@anthropic.com>
178 lines
6.4 KiB
Python
178 lines
6.4 KiB
Python
"""
|
|
Tests for real-time think message streaming and context plumbing in
|
|
the direct delegation paths.
|
|
|
|
_stream_direct_delegation must be an async generator that yields the
|
|
"start" think message BEFORE the expert runs (so 'Allow me to consult
|
|
the archives, sir.' streams while research is in flight), and both
|
|
direct delegation paths must pass trimmed conversation history as
|
|
expert context.
|
|
"""
|
|
|
|
from types import SimpleNamespace
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
import pytest
|
|
|
|
from src.agents.delegation import DelegationResult
|
|
from src.responses.streaming import (
|
|
ReasoningSummaryDelta,
|
|
ReasoningSummaryDone,
|
|
StreamingCoordinator,
|
|
)
|
|
|
|
HISTORY = [
|
|
{"role": "user", "content": "Tell me about my homelab wiki"},
|
|
{"role": "assistant", "content": "It documents your services, sir."},
|
|
]
|
|
|
|
|
|
def _librarian_result(output: str = "Findings.") -> DelegationResult:
|
|
return DelegationResult(
|
|
expert_name="librarian",
|
|
task="task",
|
|
success=True,
|
|
output=output,
|
|
)
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestStreamDirectDelegation:
|
|
"""Real-time streaming behavior of _stream_direct_delegation."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_start_think_streams_before_research_runs(self):
|
|
coordinator = StreamingCoordinator()
|
|
tracker = AsyncMock()
|
|
results: dict = {}
|
|
|
|
with patch(
|
|
"src.agents.delegation.delegate_to_librarian",
|
|
new_callable=AsyncMock,
|
|
return_value=_librarian_result(),
|
|
) as mock_delegate:
|
|
gen = coordinator._stream_direct_delegation(
|
|
user_message="Search for Docker info",
|
|
recommendation=SimpleNamespace(recommended_capabilities=["librarian"]),
|
|
tracker=tracker,
|
|
conversation_id="conv_1",
|
|
conversation_history=HISTORY,
|
|
results=results,
|
|
)
|
|
|
|
# First event: the start think message, BEFORE any research
|
|
first = await gen.__anext__()
|
|
assert isinstance(first, ReasoningSummaryDelta)
|
|
assert first.delta.strip() != ""
|
|
assert (
|
|
mock_delegate.await_count == 0
|
|
), "start think message must stream before the expert runs"
|
|
|
|
second = await gen.__anext__()
|
|
assert isinstance(second, ReasoningSummaryDone)
|
|
assert mock_delegate.await_count == 0
|
|
|
|
# Third event: completion message - research has now run
|
|
third = await gen.__anext__()
|
|
assert isinstance(third, ReasoningSummaryDelta)
|
|
assert mock_delegate.await_count == 1
|
|
|
|
remaining = [event async for event in gen]
|
|
assert any(isinstance(e, ReasoningSummaryDone) for e in remaining)
|
|
|
|
# Results dict is populated for Phase 2 synthesis
|
|
assert results["expert_results"] == {"librarian": "Findings."}
|
|
assert results["tools_called"] == ["delegate_to_librarian"]
|
|
assert len(results["think_messages"]) == 2
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_conversation_history_passed_as_context(self):
|
|
coordinator = StreamingCoordinator()
|
|
tracker = AsyncMock()
|
|
|
|
with patch(
|
|
"src.agents.delegation.delegate_to_librarian",
|
|
new_callable=AsyncMock,
|
|
return_value=_librarian_result(),
|
|
) as mock_delegate:
|
|
events = [
|
|
event
|
|
async for event in coordinator._stream_direct_delegation(
|
|
user_message="And what services does it list?",
|
|
recommendation=SimpleNamespace(recommended_capabilities=["librarian"]),
|
|
tracker=tracker,
|
|
conversation_id="conv_1",
|
|
conversation_history=HISTORY,
|
|
results={},
|
|
)
|
|
]
|
|
|
|
assert events, "generator must yield think events"
|
|
context = mock_delegate.await_args.kwargs["context"]
|
|
assert "Tell me about my homelab wiki" in context
|
|
assert "It documents your services, sir." in context
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_failed_delegation_streams_error_think(self):
|
|
coordinator = StreamingCoordinator()
|
|
tracker = AsyncMock()
|
|
results: dict = {}
|
|
|
|
failed = DelegationResult(
|
|
expert_name="librarian",
|
|
task="task",
|
|
success=False,
|
|
output="I'm afraid the archives proved difficult to access.",
|
|
error="The Librarian was unable to complete the task.",
|
|
)
|
|
|
|
with patch(
|
|
"src.agents.delegation.delegate_to_librarian",
|
|
new_callable=AsyncMock,
|
|
return_value=failed,
|
|
):
|
|
events = [
|
|
event
|
|
async for event in coordinator._stream_direct_delegation(
|
|
user_message="Search for Docker info",
|
|
recommendation=SimpleNamespace(recommended_capabilities=["librarian"]),
|
|
tracker=tracker,
|
|
conversation_id="conv_1",
|
|
results=results,
|
|
)
|
|
]
|
|
|
|
assert results["tools_called"] == []
|
|
# Expert result carries the curated user-safe sentence
|
|
assert "archives" in results["expert_results"]["librarian"]
|
|
deltas = [e.delta for e in events if isinstance(e, ReasoningSummaryDelta)]
|
|
assert len(deltas) == 2 # start + error think messages
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestServiceDelegationContext:
|
|
"""The non-streaming direct delegation path passes trimmed history."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_direct_delegation_with_results_passes_context(self):
|
|
from src.responses.service import _direct_delegation_with_results
|
|
|
|
tracker = AsyncMock()
|
|
|
|
with patch(
|
|
"src.agents.delegation.delegate_to_librarian",
|
|
new_callable=AsyncMock,
|
|
return_value=_librarian_result(),
|
|
) as mock_delegate:
|
|
results = await _direct_delegation_with_results(
|
|
user_message="And what services does it list?",
|
|
recommendation=SimpleNamespace(recommended_capabilities=["librarian"]),
|
|
tracker=tracker,
|
|
conversation_id="conv_1",
|
|
conversation_history=HISTORY,
|
|
)
|
|
|
|
context = mock_delegate.await_args.kwargs["context"]
|
|
assert "Tell me about my homelab wiki" in context
|
|
assert results["expert_results"]["librarian"] == "Findings."
|