File size: 3,372 Bytes
619c352
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
"""Unit tests for app/fallback.py ordering and app/hyde.py behavior (mocked LLM/network)."""

import app.fallback as fb
import app.hyde as hyde


# ─── fallback_context ordering ────────────────────────────────────────────────

def test_fallback_prefers_openalex(monkeypatch):
    monkeypatch.setattr(fb, "openalex_fallback",
                        lambda q, n: ("papers ctx", [{"source": "openalex-live"}]))
    monkeypatch.setattr(fb, "duckduckgo_fallback",
                        lambda q, n: ("web ctx", [{"source": "web"}]))
    context, refs, note = fb.fallback_context("q")
    assert context == "papers ctx"
    assert note == "openalex-live"


def test_fallback_uses_web_when_openalex_empty(monkeypatch):
    monkeypatch.setattr(fb, "openalex_fallback", lambda q, n: ("", []))
    monkeypatch.setattr(fb, "duckduckgo_fallback",
                        lambda q, n: ("web ctx", [{"source": "web"}]))
    context, refs, note = fb.fallback_context("q")
    assert context == "web ctx"
    assert note == "web"


def test_fallback_returns_none_note_when_all_empty(monkeypatch):
    monkeypatch.setattr(fb, "openalex_fallback", lambda q, n: ("", []))
    monkeypatch.setattr(fb, "duckduckgo_fallback", lambda q, n: ("", []))
    context, refs, note = fb.fallback_context("q")
    assert context == ""
    assert refs == []
    assert note == "none"


def test_openalex_fallback_survives_api_error(monkeypatch):
    def boom(*a, **k):
        raise RuntimeError("network down")
    monkeypatch.setattr("app.openalex_service.search_openalex", boom)
    context, refs = fb.openalex_fallback("q", 3)
    assert context == ""
    assert refs == []


# ─── HyDE ─────────────────────────────────────────────────────────────────────

def test_hyde_generates_n_hypotheticals(monkeypatch):
    calls = []

    def fake_llm(model, messages, api_key, max_tokens, temperature):
        calls.append(messages)
        return "A hypothetical abstract paragraph."

    monkeypatch.setattr(hyde, "call_llm", fake_llm)
    out = hyde.generate_hypotheticals("What is LoRA?", api_key="k", model="m", n=2)
    assert len(out) == 2
    assert len(calls) == 2


def test_hyde_system_prompt_enforces_same_language(monkeypatch):
    captured = {}

    def fake_llm(model, messages, api_key, max_tokens, temperature):
        captured["system"] = messages[0]["content"]
        return "ok"

    monkeypatch.setattr(hyde, "call_llm", fake_llm)
    hyde.generate_hypotheticals("Apa itu LoRA?", api_key="k", model="m", n=1)
    assert "SAME LANGUAGE" in captured["system"]


def test_hyde_fails_soft_on_llm_error(monkeypatch):
    def boom(*a, **k):
        raise RuntimeError("rate limited")
    monkeypatch.setattr(hyde, "call_llm", boom)
    out = hyde.generate_hypotheticals("q", api_key="k", model="m", n=2)
    assert out == []


def test_hyde_skips_empty_generations(monkeypatch):
    responses = iter(["", "  ", "real one"])

    def fake_llm(*a, **k):
        return next(responses)

    monkeypatch.setattr(hyde, "call_llm", fake_llm)
    out = hyde.generate_hypotheticals("q", api_key="k", model="m", n=3)
    assert out == ["real one"]