Fix research pipeline runtime failures
This commit is contained in:
@@ -6,14 +6,17 @@ import asyncio
|
||||
from uuid import uuid4
|
||||
|
||||
import httpx
|
||||
from unittest.mock import AsyncMock
|
||||
from unittest.mock import AsyncMock, Mock
|
||||
|
||||
from nsct.config import AppSettings, AudioConfig, DatabaseConfig, LLMConfig, VisionConfig
|
||||
from nsct.orchestration.orchestrator import ResearchOrchestrator
|
||||
from nsct.orchestration.budget import BudgetExhaustedError, BudgetTracker, HardBudgetConfig
|
||||
from nsct.orchestration.state import ResearchRunState
|
||||
from nsct.providers.abstract import NormalizedResult
|
||||
from nsct.providers.llm import _LLMProviderImpl
|
||||
from nsct.providers.metrics import ProviderMetrics
|
||||
from nsct.providers.searxng import SearXNGProvider
|
||||
from nsct.crawler.normalize import NormalizedDocument
|
||||
|
||||
|
||||
def _config(*, searxng_base_url: str | None = "http://searxng:8080") -> AppSettings:
|
||||
@@ -71,6 +74,81 @@ def test_orchestrator_registers_searxng_provider() -> None:
|
||||
assert isinstance(provider, SearXNGProvider)
|
||||
|
||||
|
||||
def test_searching_accepts_normalized_result_with_empty_snippet() -> None:
|
||||
"""A Pydantic result must not be treated as a mapping during conversion."""
|
||||
async def run() -> None:
|
||||
orchestrator = ResearchOrchestrator(_config(), uuid4(), "test")
|
||||
result = NormalizedResult.from_raw(
|
||||
provider_name="searxng",
|
||||
title="Example",
|
||||
url="https://example.org/article",
|
||||
snippet="",
|
||||
)
|
||||
multi_search = Mock()
|
||||
multi_search.enabled_providers.return_value = ["searxng"]
|
||||
multi_search.search = AsyncMock(return_value=[result])
|
||||
orchestrator._multi_search = multi_search
|
||||
|
||||
response = await orchestrator._step_searching()
|
||||
|
||||
assert response["success"] is True
|
||||
assert response["url_count"] == 1
|
||||
assert orchestrator._search_results == [{
|
||||
"url": "https://example.org/article",
|
||||
"title": "Example",
|
||||
"snippet": "",
|
||||
"provider": "searxng",
|
||||
}]
|
||||
|
||||
asyncio.run(run())
|
||||
|
||||
|
||||
def test_fetching_uses_actual_bytes_and_respects_remaining_source_budget() -> None:
|
||||
async def run() -> None:
|
||||
budget = HardBudgetConfig(max_sources=2, max_total_download_bytes=1_000)
|
||||
orchestrator = ResearchOrchestrator(_config(), uuid4(), "test", budget_config=budget)
|
||||
orchestrator._search_results = [
|
||||
{"url": "https://example.org/one"},
|
||||
{"url": "https://example.org/two"},
|
||||
{"url": "https://example.org/three"},
|
||||
]
|
||||
doc_one = NormalizedDocument.from_text(
|
||||
"https://example.org/one", "one", metadata={"download_bytes": 120}
|
||||
)
|
||||
doc_two = NormalizedDocument.from_text(
|
||||
"https://example.org/two", "two", metadata={"download_bytes": 180}
|
||||
)
|
||||
crawler = Mock()
|
||||
crawler.fetch_and_extract_many = AsyncMock(return_value=[doc_one, doc_two])
|
||||
orchestrator._crawler = crawler
|
||||
|
||||
response = await orchestrator._step_fetching()
|
||||
|
||||
assert response["success"] is True
|
||||
crawler.fetch_and_extract_many.assert_awaited_once_with(
|
||||
["https://example.org/one", "https://example.org/two"], max_size=500
|
||||
)
|
||||
usage = orchestrator.budget_tracker.get_usage()
|
||||
assert usage["max_sources"]["usage"] == 2
|
||||
assert usage["max_total_download_bytes"]["usage"] == 300
|
||||
|
||||
asyncio.run(run())
|
||||
|
||||
|
||||
def test_hard_budget_allows_exactly_the_configured_limit() -> None:
|
||||
tracker = BudgetTracker(HardBudgetConfig(max_sources=2))
|
||||
tracker.increment_sources(2)
|
||||
tracker.check_budget()
|
||||
|
||||
tracker.increment_sources()
|
||||
try:
|
||||
tracker.check_budget()
|
||||
except BudgetExhaustedError as exc:
|
||||
assert exc.exhausted_limits == ["max_sources"]
|
||||
else:
|
||||
raise AssertionError("Source usage above the limit must exhaust the budget")
|
||||
|
||||
|
||||
def test_pipeline_executes_extracting_between_fetching_and_analyzing() -> None:
|
||||
async def run() -> None:
|
||||
orchestrator = ResearchOrchestrator(_config(), uuid4(), "test")
|
||||
|
||||
Reference in New Issue
Block a user