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:
4
agent.py
4
agent.py
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user