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:
2026-07-14 10:12:30 +02:00
co-authored by Claude Fable 5
parent 59f5b54ac9
commit f853db8ccc
10 changed files with 277 additions and 140 deletions
+19 -16
View File
@@ -5,21 +5,20 @@ Tests the tool functions that wrap the Library-Desk API,
including the new web search and content extraction tools.
"""
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
from unittest.mock import AsyncMock, patch
import pytest
from src.agents.librarian.client import (
BatchExtractionResponse,
ContentExtractionResult,
WebSearchResponse,
WebSearchResult,
)
from src.agents.librarian.tools import (
search_web,
read_url,
read_urls_batch,
hybrid_search,
search_wiki,
)
from src.agents.librarian.client import (
WebSearchResult,
WebSearchResponse,
ContentExtractionResult,
BatchExtractionResponse,
search_web,
)
@@ -104,8 +103,10 @@ class TestSearchWeb:
@pytest.mark.asyncio
async def test_search_web_error_handling(self, mock_client):
"""Test web search error handling."""
mock_client.search_web.side_effect = Exception("Connection failed")
"""Test web search errors return a user-safe message without internals."""
mock_client.search_web.side_effect = Exception(
"Connection failed to http://internal-host:8089"
)
with patch(
"src.agents.librarian.tools.LibraryDeskClient"
@@ -115,8 +116,10 @@ class TestSearchWeb:
result = await search_web("test query")
assert "Error" in result
assert "Connection failed" in result
assert "unable to search the web" in result
# Exception detail (internal URLs etc.) must not leak
assert "Connection failed" not in result
assert "internal-host" not in result
@pytest.mark.asyncio
async def test_search_web_with_news_type(self, mock_client):
@@ -227,7 +230,7 @@ class TestReadUrl:
mock_client_class.return_value.__aenter__.return_value = mock_client
mock_client_class.return_value.__aexit__.return_value = None
result = await read_url("https://example.com/long", max_length=2000)
await read_url("https://example.com/long", max_length=2000)
mock_client.extract_content.assert_called_with(
url="https://example.com/long",