feat(core-ai): add web search tool and enhance agent persona
Add SearXNG-powered web search tool and update agent persona to "Tatlock", a helpful British butler assistant. Changes: - Add web_search tool for SearXNG metasearch integration - Supports multiple search categories (general, it, science, news, etc.) - Configurable max_results (1-20) - Privacy-focused (no tracking via SearXNG) - Formatted results with titles, URLs, and descriptions - Proper error handling for timeouts and failures - Endpoint: http://searxng:8080/search - Update agent persona to "Tatlock" (British butler) - Formal yet personable tone - Addresses users as "sir" - Fact verification emphasis - Slight snark and puns when appropriate - Clear tool categorization in prompt - Enhance prompt with tool organization - Core tools: web_search, calculate, time/date - Infrastructure tools: core_api__* prefix for system management - Clear usage guidelines for each category Web Search Categories Supported: - general: Web search (Google, Bing, DuckDuckGo) - it: Programming/technical (StackOverflow, GitHub) - science: Academic (arXiv, PubMed, Semantic Scholar) - news: News articles - images/videos: Media search - map: Geographic queries Integration: Requires SearXNG service running on docker-dataplane network. See stacks/searxng.yml for deployment. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -7,13 +7,26 @@ This file contains minimal, clean prompts for the Core AI service.
|
||||
PROMPTS = {
|
||||
"minimal_agent": """You are a helpful assistant. You can answer questions. If you need information, use the available tools.""",
|
||||
|
||||
"pydantic_agent": """You are a helpful assistant with access to tools.
|
||||
"pydantic_agent": """You are Tatlock, a helpful personal assistant with the demeanor of a British butler. Address users as "sir" and maintain a formal yet personable tone. You are not overly apologetic and may be slightly snarky when appropriate. If an opportunity for a pun presents itself, you cannot resist.
|
||||
|
||||
CRITICAL: When you have tools available, you MUST use them to get accurate, real-time information. NEVER guess or make up answers when a tool can provide the actual data.
|
||||
Your core responsibility: Verify facts before presenting them as truth.
|
||||
|
||||
For specific time queries (like "what time is it in Amsterdam?"), use the get_current_time(timezone) tool.
|
||||
You have access to two categories of tools:
|
||||
|
||||
Be concise and thorough in your responses."""
|
||||
**Core Tools** (essential utilities):
|
||||
- web_search: Latest/current/recent information (always verify facts, sir)
|
||||
- calculate: Mathematical operations (precision is paramount)
|
||||
- get_current_time/get_current_date: Time/date queries
|
||||
|
||||
**Infrastructure Tools** (prefixed with "core_api__"):
|
||||
When managing sir's home infrastructure, use these tools:
|
||||
- Services: List, start, stop Docker services
|
||||
- Domains: List configured domains
|
||||
- Proxy: Manage reverse proxy configurations
|
||||
- Monitors: Health monitoring (Uptime Kuma integration)
|
||||
- Ports: Check allocated ports
|
||||
|
||||
Be concise unless details are specifically requested. When using tools, acknowledge them naturally in your dignified manner."""
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
Local utility tools for the AI agent.
|
||||
|
||||
These tools run locally in core-ai and don't require REST calls.
|
||||
They provide basic utilities like time, date, and calculations.
|
||||
They provide basic utilities like time, date, calculations, and web search.
|
||||
"""
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
import pytz
|
||||
import httpx
|
||||
from src.tools.registry import register_tool
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -167,3 +168,92 @@ async def calculate(expression: str) -> str:
|
||||
except Exception as e:
|
||||
logger.error(f"Calculation error: {e}")
|
||||
return f"Error: Could not evaluate expression - {type(e).__name__}"
|
||||
|
||||
|
||||
@register_tool
|
||||
async def web_search(query: str, category: str = "general", max_results: int = 5) -> str:
|
||||
"""
|
||||
Search the web using SearXNG metasearch engine.
|
||||
|
||||
Aggregates results from multiple search engines (Google, Bing, DuckDuckGo, etc.)
|
||||
while maintaining privacy - no tracking or data collection.
|
||||
|
||||
Args:
|
||||
query: Search query string (e.g., "Python programming best practices")
|
||||
category: Search category - options:
|
||||
"general" (default) - Web search
|
||||
"images" - Image search
|
||||
"videos" - Video search
|
||||
"news" - News articles
|
||||
"it" - Programming/technical (StackOverflow, GitHub, docs)
|
||||
"science" - Academic (arXiv, PubMed, Semantic Scholar)
|
||||
"map" - Geographic/location
|
||||
"music" - Music/audio
|
||||
"files" - File repositories
|
||||
max_results: Maximum number of results to return (default: 5, max: 20)
|
||||
|
||||
Returns:
|
||||
Formatted search results with titles, URLs, and descriptions
|
||||
|
||||
Examples:
|
||||
- web_search("kubernetes deployment strategies")
|
||||
- web_search("docker best practices", category="it")
|
||||
- web_search("climate change research", category="science")
|
||||
"""
|
||||
logger.info(f"Web search: query='{query}', category='{category}', max_results={max_results}")
|
||||
|
||||
try:
|
||||
# Limit max_results to prevent overwhelming responses
|
||||
max_results = min(max_results, 20)
|
||||
|
||||
# Call SearXNG JSON API
|
||||
async with httpx.AsyncClient(timeout=10.0) as client:
|
||||
response = await client.get(
|
||||
"http://searxng:8080/search",
|
||||
params={
|
||||
"q": query,
|
||||
"format": "json",
|
||||
"categories": category
|
||||
}
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
data = response.json()
|
||||
results = data.get("results", [])
|
||||
|
||||
if not results:
|
||||
return f"No results found for: {query}"
|
||||
|
||||
# Format results for LLM consumption
|
||||
formatted_results = []
|
||||
for i, result in enumerate(results[:max_results], 1):
|
||||
title = result.get("title", "No title")
|
||||
url = result.get("url", "")
|
||||
content = result.get("content", "No description available")
|
||||
engine = result.get("engine", "unknown")
|
||||
|
||||
formatted_results.append(
|
||||
f"{i}. **{title}**\n"
|
||||
f" URL: {url}\n"
|
||||
f" {content}\n"
|
||||
f" (Source: {engine})"
|
||||
)
|
||||
|
||||
summary = f"Found {len(results)} total results for '{query}' (showing top {len(formatted_results)}):\n\n"
|
||||
summary += "\n\n".join(formatted_results)
|
||||
|
||||
logger.info(f"Web search completed: {len(formatted_results)} results returned")
|
||||
return summary
|
||||
|
||||
except httpx.TimeoutException:
|
||||
error_msg = "Web search timed out. The search engine may be slow or unavailable."
|
||||
logger.error(error_msg)
|
||||
return error_msg
|
||||
except httpx.HTTPStatusError as e:
|
||||
error_msg = f"Web search failed with HTTP {e.response.status_code}"
|
||||
logger.error(error_msg)
|
||||
return error_msg
|
||||
except Exception as e:
|
||||
error_msg = f"Web search error: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
return error_msg
|
||||
|
||||
Reference in New Issue
Block a user