|
# retoor <retoor@molodetz.nl>
|
|
|
|
import json
|
|
|
|
from tests.conftest import run_async
|
|
|
|
from devplacepy.services.jobs.deepsearch import orchestrate as orchestrate_module
|
|
from devplacepy.services.jobs.deepsearch.crawl import CrawledPage
|
|
from devplacepy.services.jobs.deepsearch.orchestrate import Orchestration, orchestrate, source_diversity
|
|
|
|
|
|
def _page(url, text="content " * 30):
|
|
return CrawledPage(url=url, title="T", text=text, source="httpx", status=200)
|
|
|
|
|
|
def test_source_diversity_empty_is_zero():
|
|
assert source_diversity([]) == 0.0
|
|
|
|
|
|
def test_source_diversity_increases_with_distinct_domains():
|
|
one = source_diversity([_page("https://a.example/1"), _page("https://a.example/2")])
|
|
many = source_diversity([_page("https://a.example/1"), _page("https://b.example/2")])
|
|
assert 0.0 < one <= 1.0
|
|
assert many > one
|
|
|
|
|
|
def test_source_diversity_capped_at_one():
|
|
pages = [_page(f"https://d{i}.example") for i in range(8)]
|
|
assert source_diversity(pages) <= 1.0
|
|
|
|
|
|
def test_orchestrate_with_no_pages_is_heuristic():
|
|
result = run_async(orchestrate("q", [], "k", lambda frame: None))
|
|
assert isinstance(result, Orchestration)
|
|
assert result.source_diversity == 0.0
|
|
assert result.synthesis == "heuristic"
|
|
assert not result.findings
|
|
|
|
|
|
def test_orchestrate_grounded_run_emits_agent_frames(monkeypatch):
|
|
frames = []
|
|
|
|
report_payload = "## Answer\nA grounded answer [1]."
|
|
findings_payload = json.dumps(
|
|
{
|
|
"findings": [
|
|
{"title": "Finding", "detail": "Detail", "confidence": 0.7, "citations": [1]}
|
|
]
|
|
}
|
|
)
|
|
link_payload = json.dumps({"confidence": 0.8})
|
|
replies = iter([report_payload, findings_payload, link_payload])
|
|
|
|
async def fake_request_completion(messages, api_key, **kwargs):
|
|
return ({"choices": [{"message": {"content": next(replies)}}]}, {}, 5)
|
|
|
|
monkeypatch.setattr(orchestrate_module, "request_completion", fake_request_completion)
|
|
|
|
pages = [_page("https://a.example"), _page("https://b.example")]
|
|
result = run_async(orchestrate("question", pages, "k", frames.append))
|
|
|
|
assert result.summary == "## Answer\nA grounded answer [1]."
|
|
assert result.findings and result.findings[0]["citations"] == [1]
|
|
assert result.synthesis == "agents"
|
|
assert 0.0 < result.confidence <= 1.0
|
|
assert result.source_diversity > 0.0
|
|
assert result.score > 0
|
|
agents = {f["agent"] for f in frames if f.get("type") == "agent"}
|
|
assert agents == {"summarizer", "extractor", "linker"}
|
|
|
|
|
|
def test_orchestrate_falls_back_to_heuristic_on_failure(monkeypatch):
|
|
async def boom(messages, api_key, **kwargs):
|
|
raise RuntimeError("upstream down")
|
|
|
|
monkeypatch.setattr(orchestrate_module, "request_completion", boom)
|
|
frames = []
|
|
pages = [_page("https://a.example"), _page("https://b.example")]
|
|
result = run_async(orchestrate("question", pages, "k", frames.append))
|
|
assert result.findings
|
|
assert result.synthesis == "heuristic"
|
|
assert any(f.get("status") == "failed" for f in frames if f.get("type") == "agent")
|
|
assert result.source_diversity > 0.0
|
|
|
|
|
|
def test_orchestrate_survives_linker_failure(monkeypatch):
|
|
replies = iter(
|
|
[
|
|
"A full report [1].",
|
|
json.dumps(
|
|
{
|
|
"findings": [
|
|
{"title": "F", "detail": "D", "confidence": 0.5, "citations": [1]}
|
|
]
|
|
}
|
|
),
|
|
]
|
|
)
|
|
|
|
async def flaky(messages, api_key, **kwargs):
|
|
try:
|
|
content = next(replies)
|
|
except StopIteration:
|
|
raise RuntimeError("upstream down")
|
|
return ({"choices": [{"message": {"content": content}}]}, {}, 5)
|
|
|
|
monkeypatch.setattr(orchestrate_module, "request_completion", flaky)
|
|
pages = [_page("https://a.example"), _page("https://b.example")]
|
|
result = run_async(orchestrate("question", pages, "k", lambda frame: None))
|
|
assert result.synthesis == "agents"
|
|
assert result.summary == "A full report [1]."
|
|
assert result.findings
|
|
assert result.confidence >= 0.35
|
|
|
|
|
|
def test_numbered_source_digest_keeps_every_source_number_under_cap():
|
|
from devplacepy.services.jobs.deepsearch.orchestrate import _numbered_source_digest
|
|
import re
|
|
|
|
pages = [_page(f"https://s{i}.example", text="word " * 500) for i in range(1, 13)]
|
|
digest = _numbered_source_digest(pages, per_source=600, cap=4000)
|
|
present = {int(n) for n in re.findall(r"\[(\d+)\]", digest)}
|
|
assert present == set(range(1, 13))
|
|
|
|
|
|
def test_linker_receives_full_source_list(monkeypatch):
|
|
seen = {}
|
|
replies = iter(
|
|
[
|
|
"Report body [1].",
|
|
json.dumps(
|
|
{"findings": [{"title": "F", "detail": "D", "confidence": 1.0, "citations": [2]}]}
|
|
),
|
|
json.dumps({"confidence": 0.9}),
|
|
]
|
|
)
|
|
|
|
async def capture(messages, api_key, **kwargs):
|
|
content = messages[-1]["content"]
|
|
if content.startswith("FINDINGS:") and "SOURCES:" in content:
|
|
seen["linker"] = content
|
|
return ({"choices": [{"message": {"content": next(replies)}}]}, {}, 5)
|
|
|
|
monkeypatch.setattr(orchestrate_module, "request_completion", capture)
|
|
pages = [_page(f"https://s{i}.example") for i in range(1, 13)]
|
|
result = run_async(orchestrate("q", pages, "k", lambda f: None))
|
|
assert result.confidence >= 0.35
|
|
for n in range(1, 13):
|
|
assert f"[{n}]" in seen["linker"]
|
|
|
|
|
|
def test_parse_json_tolerates_fences_and_trailing_garbage():
|
|
from devplacepy.services.jobs.deepsearch.orchestrate import _parse_json
|
|
|
|
assert _parse_json('```json\n{"gaps": ["g"]}\n```') == {"gaps": ["g"]}
|
|
assert _parse_json('prose {"confidence": 0.7} more prose') == {"confidence": 0.7}
|
|
assert _parse_json('{"gaps": ["a"]} {"broken": ') == {"gaps": ["a"]}
|
|
assert _parse_json("no json here") == {}
|