Introduce FastTools: pre-flight classifier + context enrichment

New fast_tools.py module:
- FastTool base class (matches + run interface)
- RealTimeSearchTool: SearXNG search for weather/news/prices/scores
- FastToolRunner: classifier that checks all tools, runs matching
  ones concurrently and returns combined context

Router accepts FastToolRunner; any_matches() forces medium tier
before LLM classification (replaces _MEDIUM_FORCE_PATTERNS regex).

agent.py: _REALTIME_RE and _searxng_search_async removed; pre-flight
gather now includes fast_tool_runner.run_matching() alongside URL
fetch and memory retrieval.

To add a new fast tool: subclass FastTool, add to the list in agent.py.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Alvis
2026-03-13 05:18:44 +00:00
parent 436299f7e2
commit f5fc2e9bfb
4 changed files with 145 additions and 72 deletions

View File

@@ -1,6 +1,7 @@
import re
from typing import Optional
from langchain_core.messages import SystemMessage, HumanMessage
from fast_tools import FastToolRunner
# ── Regex pre-classifier ──────────────────────────────────────────────────────
# Catches obvious light-tier patterns before calling the LLM.
@@ -23,16 +24,6 @@ _LIGHT_PATTERNS = re.compile(
re.IGNORECASE,
)
# Queries that require live data — never answer from static knowledge
_MEDIUM_FORCE_PATTERNS = re.compile(
r"\b(weather|forecast|temperature|rain(ing)?|snow(ing)?|humidity|wind speed"
r"|today.?s news|breaking news|latest news|news today|current events"
r"|bitcoin price|crypto price|stock price|exchange rate|usd|eur|btc"
r"|right now|currently|at the moment|live score|score now|score today"
r"|open now|hours today|is .+ open)\b",
re.IGNORECASE,
)
# ── LLM classification prompt ─────────────────────────────────────────────────
CLASSIFY_PROMPT = """Classify the message. Output ONLY one word: light, medium, or complex.
@@ -83,8 +74,9 @@ def _parse_tier(text: str) -> str:
class Router:
def __init__(self, model):
def __init__(self, model, fast_tool_runner: FastToolRunner | None = None):
self.model = model
self._fast_tool_runner = fast_tool_runner
async def route(
self,
@@ -100,9 +92,10 @@ class Router:
if force_complex:
return "complex", None
# Step 0a: force medium for real-time / live-data queries
if _MEDIUM_FORCE_PATTERNS.search(message.strip()):
print(f"[router] regex→medium (real-time query)", flush=True)
# Step 0a: force medium if any fast tool matches (live-data queries)
if self._fast_tool_runner and self._fast_tool_runner.any_matches(message.strip()):
names = self._fast_tool_runner.matching_names(message.strip())
print(f"[router] fast_tool_match={names} → medium", flush=True)
return "medium", None
# Step 0b: regex pre-classification for obvious light patterns