fix(agents): make librarian failures structured and user-safe
- run_librarian / run_librarian_stream raise AgentError instead of
returning/yielding error text as normal output; detail stays in logs
- delegate_to_* wrappers now put a curated butler-toned sentence in
DelegationResult.output on failure and never expose str(e), so
streaming's error branch is reachable and honest
- _execute_single_delegation propagates success; direct delegation only
records delegate_to_* as called when the expert actually succeeded
- librarian tools return user-safe messages instead of
'Error searching: {e}' strings that leaked internal URLs into
synthesis; coordination stream errors are curated as well
- ruff cleanups (TYPE_CHECKING forward refs, B904, unused locals) in
the touched files to keep them lint-clean
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,18 +4,19 @@ Tests for delegation infrastructure.
|
||||
Tests the DelegationTask dataclass and delegation wrapper functions
|
||||
that implement the agent-as-tool pattern.
|
||||
"""
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
from unittest.mock import AsyncMock, patch, MagicMock
|
||||
|
||||
from src.agents.delegation import (
|
||||
ActionType,
|
||||
DelegationTask,
|
||||
DelegationResult,
|
||||
HOUSEHOLD_THINK_MESSAGES,
|
||||
STREAMING_DELEGATION_WRAPPERS,
|
||||
ActionType,
|
||||
DelegationResult,
|
||||
DelegationTask,
|
||||
_detect_action_type,
|
||||
delegate_to_librarian,
|
||||
get_think_message,
|
||||
_detect_action_type,
|
||||
)
|
||||
|
||||
|
||||
@@ -170,11 +171,11 @@ class TestDelegateToLibrarian:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delegate_to_librarian_handles_error(self):
|
||||
"""Test delegation handles Librarian errors gracefully."""
|
||||
"""Test delegation maps Librarian errors to a user-safe result."""
|
||||
with patch(
|
||||
"src.agents.librarian.agent.run_librarian",
|
||||
new_callable=AsyncMock,
|
||||
side_effect=Exception("Connection refused"),
|
||||
side_effect=Exception("Connection refused to http://internal:8089"),
|
||||
):
|
||||
result = await delegate_to_librarian(
|
||||
task="Search for information",
|
||||
@@ -182,8 +183,15 @@ class TestDelegateToLibrarian:
|
||||
|
||||
assert isinstance(result, DelegationResult)
|
||||
assert result.success is False
|
||||
assert result.output == ""
|
||||
assert result.error == "Connection refused"
|
||||
# Output carries a curated butler-toned sentence
|
||||
assert result.output == get_think_message(
|
||||
"librarian", "Search for information", "error"
|
||||
)
|
||||
# Exception detail stays in logs only - never in the result
|
||||
assert "Connection refused" not in result.output
|
||||
assert result.error is not None
|
||||
assert "Connection refused" not in result.error
|
||||
assert "internal" not in result.error
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delegate_to_librarian_preserves_task(self):
|
||||
|
||||
Reference in New Issue
Block a user