Rename RealTimeSearchTool → WeatherTool, fetch Balashikha weather via SearXNG

WeatherTool queries SearXNG with a fixed 'weather Balashikha Moscow now'
query instead of passing the user message as-is. SearXNG has external
internet access and returns snippets with actual current conditions.
Direct wttr.in fetch not possible — deepagents container has no external
internet routing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Alvis
2026-03-13 05:40:10 +00:00
parent f5fc2e9bfb
commit af181ba7ec
2 changed files with 27 additions and 21 deletions

View File

@@ -24,7 +24,7 @@ from langchain_core.tools import Tool
from vram_manager import VRAMManager
from router import Router
from agent_factory import build_medium_agent, build_complex_agent
from fast_tools import FastToolRunner, RealTimeSearchTool
from fast_tools import FastToolRunner, WeatherTool
import channels
# Bifrost gateway — all LLM inference goes through here
@@ -121,7 +121,7 @@ _memory_search_tool = None
# Fast tools run before the LLM — classifier + context enricher
_fast_tool_runner = FastToolRunner([
RealTimeSearchTool(searxng_url=SEARXNG_URL),
WeatherTool(searxng_url=SEARXNG_URL),
])
# GPU mutex: one LLM inference at a time

View File

@@ -35,55 +35,61 @@ class FastTool(ABC):
async def run(self, message: str) -> str: ...
class RealTimeSearchTool(FastTool):
class WeatherTool(FastTool):
"""
Injects live SearXNG search snippets for queries that require real-time data:
weather, news, prices, scores, business hours.
Fetches current weather for the user's location (Balashikha, Moscow region)
by querying SearXNG, which has external internet access.
Matched queries are also forced to medium tier by the Router so the richer
model handles the injected context.
Triggered by any weather-related query. The Router also forces medium tier
when this tool matches so the richer model handles the injected data.
"""
_PATTERN = re.compile(
r"\b(weather|forecast|temperature|rain(ing)?|snow(ing)?|humidity|wind\s*speed"
r"|today.?s news|breaking news|latest news|news today|current events"
r"|bitcoin price|crypto price|stock price|exchange rate"
r"|right now|currently|at the moment|live score|score now|score today"
r"|open now|hours today|is .+ open)\b",
r"|холодно|тепло|погода|прогноз погоды"
r"|how (hot|cold|warm) is it|what.?s the (weather|temp)|dress for the weather)\b",
re.IGNORECASE,
)
# Fixed query — always fetch home location weather
_SEARCH_QUERY = "weather Balashikha Moscow now"
def __init__(self, searxng_url: str):
self._searxng_url = searxng_url
@property
def name(self) -> str:
return "real_time_search"
return "weather"
def matches(self, message: str) -> bool:
return bool(self._PATTERN.search(message))
async def run(self, message: str) -> str:
"""Search SearXNG and return top snippets as a context block."""
"""Query SearXNG for Balashikha weather and return current conditions snippet."""
try:
async with httpx.AsyncClient(timeout=15) as client:
r = await client.get(
f"{self._searxng_url}/search",
params={"q": message, "format": "json"},
params={"q": self._SEARCH_QUERY, "format": "json"},
)
r.raise_for_status()
items = r.json().get("results", [])[:4]
items = r.json().get("results", [])[:5]
except Exception as e:
return f"[real_time_search error: {e}]"
return f"[weather error: {e}]"
if not items:
return ""
lines = [f"Live web search results for: {message}\n"]
for i, item in enumerate(items, 1):
# Prefer results whose snippets contain actual current conditions
lines = ["Current weather data for Balashikha, Moscow region:\n"]
for item in items:
snippet = item.get("content", "")
title = item.get("title", "")
url = item.get("url", "")
snippet = item.get("content", "")[:400]
lines.append(f"[{i}] {title}\nURL: {url}\n{snippet}\n")
return "\n".join(lines)
if snippet:
lines.append(f"[{title}]\n{snippet}\nSource: {url}\n")
return "\n".join(lines) if len(lines) > 1 else ""
class FastToolRunner: