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 vram_manager import VRAMManager
from router import Router from router import Router
from agent_factory import build_medium_agent, build_complex_agent from agent_factory import build_medium_agent, build_complex_agent
from fast_tools import FastToolRunner, RealTimeSearchTool from fast_tools import FastToolRunner, WeatherTool
import channels import channels
# Bifrost gateway — all LLM inference goes through here # 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 tools run before the LLM — classifier + context enricher
_fast_tool_runner = FastToolRunner([ _fast_tool_runner = FastToolRunner([
RealTimeSearchTool(searxng_url=SEARXNG_URL), WeatherTool(searxng_url=SEARXNG_URL),
]) ])
# GPU mutex: one LLM inference at a time # GPU mutex: one LLM inference at a time

View File

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