the-echo / echo /tools /research.py
frankyy03's picture
Deploy The Echo (MockLLM path): Gradio app + echo package
897d5bd verified
"""
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})."