fix(librarian): quiet healthy-search coverage notes, tag clearing, strict result pairing
Phase A review minors: - Coverage note: source_status (when present) is now used exclusively; the source_counts-absence fallback only considers the optional legs the request explicitly enabled (web/documents/volatile). library-desk computes source_counts from the final top-N fused results only, so absence of the always-on vector/graph legs is normal ranking behavior - the old heuristic warned on virtually every healthy search - update_wiki_page: the empty-list tags sentinel (leave unchanged) made clearing all tags impossible; pass exactly ["__CLEAR__"] to send an empty tag list, documented in the docstring for the local model - Text-delegation parallel fallback: zip(..., strict=True) with an explicit count-mismatch guard so results can never be silently attributed to the wrong agent Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QbFZyDvYksazX6nYQYZ67L
This commit is contained in:
@@ -264,15 +264,27 @@ class TestCoverageNote:
|
||||
)
|
||||
assert note == ""
|
||||
|
||||
def test_note_when_leg_missing_from_counts(self):
|
||||
def test_note_when_enabled_leg_missing_from_counts(self):
|
||||
response = self._response(source_counts={"graph": 3, "web": 2})
|
||||
note = _coverage_note(
|
||||
response, include_web=True, include_documents=True, include_volatile=True
|
||||
)
|
||||
assert "Coverage note" in note
|
||||
assert "vector" in note
|
||||
assert "documents" in note
|
||||
assert "volatile" in note
|
||||
# Always-on wiki legs are never inferred from count absence
|
||||
assert "vector" not in note
|
||||
assert "graph" not in note
|
||||
|
||||
def test_wiki_leg_absence_is_not_degradation(self):
|
||||
"""vector/graph missing from top-N counts is healthy ranking, not outage."""
|
||||
response = self._response(
|
||||
source_counts={"web": 2, "documents": 1, "volatile": 1}
|
||||
)
|
||||
note = _coverage_note(
|
||||
response, include_web=True, include_documents=True, include_volatile=True
|
||||
)
|
||||
assert note == ""
|
||||
|
||||
def test_disabled_legs_are_not_reported_missing(self):
|
||||
response = self._response(source_counts={"vector": 2, "graph": 1})
|
||||
|
||||
@@ -14,11 +14,14 @@ from src.agents.librarian.client import (
|
||||
ContentExtractionResult,
|
||||
WebSearchResponse,
|
||||
WebSearchResult,
|
||||
WikiPage,
|
||||
)
|
||||
from src.agents.librarian.tools import (
|
||||
CLEAR_TAGS_SENTINEL,
|
||||
read_url,
|
||||
read_urls_batch,
|
||||
search_web,
|
||||
update_wiki_page,
|
||||
)
|
||||
|
||||
|
||||
@@ -427,3 +430,59 @@ class TestWebSearchModels:
|
||||
assert response.total_urls == 2
|
||||
assert response.successful == 1
|
||||
assert response.failed == 1
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Wiki Update Tests (tag sentinel behavior)
|
||||
# ============================================================================
|
||||
|
||||
@pytest.mark.unit
|
||||
class TestUpdateWikiPageTagSentinels:
|
||||
"""Empty list leaves tags unchanged; the clear sentinel empties them."""
|
||||
|
||||
def _page(self, tags: list[str]) -> WikiPage:
|
||||
return WikiPage(id=42, path="test/page", title="Test Page", tags=tags)
|
||||
|
||||
def _patched_client(self, mock_client):
|
||||
patcher = patch("src.agents.librarian.tools.LibraryDeskClient")
|
||||
mock_client_class = patcher.start()
|
||||
mock_client_class.return_value.__aenter__.return_value = mock_client
|
||||
mock_client_class.return_value.__aexit__.return_value = None
|
||||
return patcher
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_empty_tags_means_leave_unchanged(self, mock_client):
|
||||
mock_client.update_wiki_page.return_value = self._page(["existing"])
|
||||
patcher = self._patched_client(mock_client)
|
||||
try:
|
||||
await update_wiki_page(42, content="new content")
|
||||
finally:
|
||||
patcher.stop()
|
||||
|
||||
_, kwargs = mock_client.update_wiki_page.call_args
|
||||
assert kwargs["tags"] is None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_clear_sentinel_sends_empty_tag_list(self, mock_client):
|
||||
mock_client.update_wiki_page.return_value = self._page([])
|
||||
patcher = self._patched_client(mock_client)
|
||||
try:
|
||||
result = await update_wiki_page(42, tags=[CLEAR_TAGS_SENTINEL])
|
||||
finally:
|
||||
patcher.stop()
|
||||
|
||||
_, kwargs = mock_client.update_wiki_page.call_args
|
||||
assert kwargs["tags"] == []
|
||||
assert "tags (cleared)" in result
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_real_tags_are_passed_through(self, mock_client):
|
||||
mock_client.update_wiki_page.return_value = self._page(["a", "b"])
|
||||
patcher = self._patched_client(mock_client)
|
||||
try:
|
||||
await update_wiki_page(42, tags=["a", "b"])
|
||||
finally:
|
||||
patcher.stop()
|
||||
|
||||
_, kwargs = mock_client.update_wiki_page.call_args
|
||||
assert kwargs["tags"] == ["a", "b"]
|
||||
|
||||
Reference in New Issue
Block a user