File size: 2,476 Bytes
897d5bd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
echo/tools/research.py
----------------------
The world-grounding tool. Given a location + era, it returns real-world detail
(cost of living, cultural moment, climate, notable events) that the Curator
weaves in so an alternate life feels *anchored* rather than hallucinated. This
grounding is the moment a judge thinks "wait, how does it know that?"

ResearchTool is an interface with:
* MockResearch — deterministic offline facts (for GPU-free testing),
* WebResearch  — wraps a real search call (web_search / an API) at deploy time.
"""

from __future__ import annotations

from abc import ABC, abstractmethod


class ResearchTool(ABC):
    @abstractmethod
    def ground(self, location: str, year: int, theme: str = "") -> str:
        """Return a short bundle of grounding facts for location/year."""
        ...


class MockResearch(ResearchTool):
    """Offline, deterministic grounding. Enough to exercise the pipeline."""

    _NOTES = {
        "Lisbon": "steep tiled streets, cheap pastéis, a tech-expat wave, the 28 tram",
        "Tokyo": "rail punctual to the second, conbini at every corner, quiet density",
        "Berlin": "late winters, techno after-hours, a city still half rebuilt",
        "São Paulo": "endless gray sprawl, helicopters over traffic, rain that floods",
        "Reykjavik": "winter dark by 3pm, geothermal pools, wool against the wind",
        "Montreal": "bilingual signs, brutal Februaries, bagels better than New York",
        "Nairobi": "matatus in bright livery, high-altitude light, a startup hum",
        "Hanoi": "scooters like a river, broth at dawn, humidity that never lifts",
    }

    def ground(self, location: str, year: int, theme: str = "") -> str:
        note = self._NOTES.get(location, "a place with its own particular light")
        return f"{location} (~{year}): {note}."


class WebResearch(ResearchTool):
    """
    Deploy-time grounding via a real search function injected at construction.
    `search_fn(query) -> str` keeps this decoupled from any specific API and
    lets the Gradio app pass in web_search or a cached corpus.
    """

    def __init__(self, search_fn):
        self.search_fn = search_fn

    def ground(self, location: str, year: int, theme: str = "") -> str:
        q = f"{location} {year} daily life cost of living culture {theme}".strip()
        try:
            return str(self.search_fn(q))[:600]
        except Exception:
            return f"{location} (~{year})."