From 282645503662b593b3ec906c6365fbdf02f48b51 Mon Sep 17 00:00:00 2001 From: Christian Gick Date: Sat, 28 Feb 2026 13:30:36 +0200 Subject: [PATCH] feat(MAT-64): Add web search tool to text bot The text bot had no websearch capability while the voice agent did. Added Brave Search integration as a web_search tool so the bot can answer questions about current events and look up information. Co-Authored-By: Claude Opus 4.6 --- bot.py | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/bot.py b/bot.py index 7c64a65..0c8837b 100644 --- a/bot.py +++ b/bot.py @@ -76,6 +76,7 @@ CONFLUENCE_USER = os.environ.get("CONFLUENCE_USER", "") CONFLUENCE_TOKEN = os.environ.get("CONFLUENCE_TOKEN", "") PORTAL_URL = os.environ.get("PORTAL_URL", "") BOT_API_KEY = os.environ.get("BOT_API_KEY", "") +BRAVE_API_KEY = os.environ.get("BRAVE_API_KEY", "") MAX_TOOL_ITERATIONS = 5 SYSTEM_PROMPT = """You are a helpful AI assistant in a Matrix chat room. @@ -94,6 +95,7 @@ IMPORTANT RULES — FOLLOW THESE STRICTLY: - You can see and analyze images that users send. Describe what you see when asked about an image. - You can read and analyze PDF documents that users send. Summarize content and answer questions about them. - You can generate images when asked — use the generate_image tool for any image creation, drawing, or illustration requests. +- You can search the web using the web_search tool. Use it when users ask about current events, facts, or anything that needs up-to-date information. - You can search Confluence and Jira using tools. When users ask about documentation, wiki pages, tickets, or tasks, use the appropriate tool. Use confluence_recent_pages FIRST to show recently edited pages before searching. - When creating Jira issues, always confirm the project key and summary with the user before creating. - If a user's Atlassian account is not connected, tell them to connect it at matrixhost.eu/settings and provide the link. @@ -268,7 +270,23 @@ ATLASSIAN_TOOLS = [ }, ] -ALL_TOOLS = IMAGE_GEN_TOOLS + ATLASSIAN_TOOLS +WEB_SEARCH_TOOLS = [{ + "type": "function", + "function": { + "name": "web_search", + "description": "Search the web for current information. Use when the user asks about recent events, facts, or anything that needs up-to-date information from the internet.", + "parameters": { + "type": "object", + "properties": { + "query": {"type": "string", "description": "Search query"}, + "count": {"type": "integer", "description": "Number of results (default 5)", "default": 5}, + }, + "required": ["query"], + }, + }, +}] + +ALL_TOOLS = IMAGE_GEN_TOOLS + WEB_SEARCH_TOOLS + ATLASSIAN_TOOLS ATLASSIAN_NOT_CONNECTED_MSG = ( "Your Atlassian account is not connected. " @@ -1923,6 +1941,30 @@ class Bot: finally: self._pending_connects.pop(sender, None) + async def _brave_search(self, query: str, count: int = 5) -> str: + """Call Brave Search API and return formatted results.""" + if not BRAVE_API_KEY: + return "Web search unavailable (no API key configured)." + try: + async with httpx.AsyncClient(timeout=10.0) as client: + resp = await client.get( + "https://api.search.brave.com/res/v1/web/search", + headers={"Accept": "application/json", "X-Subscription-Token": BRAVE_API_KEY}, + params={"q": query, "count": count, "text_decorations": False}, + ) + resp.raise_for_status() + data = resp.json() + results = data.get("web", {}).get("results", []) + if not results: + return "No results found." + lines = [] + for r in results[:count]: + lines.append(f"- {r.get('title', '')}: {r.get('description', '')} ({r.get('url', '')})") + return "\n".join(lines) + except Exception as exc: + logger.warning("Brave search error: %s", exc) + return f"Search failed: {exc}" + async def _execute_tool(self, tool_name: str, args: dict, sender: str, room_id: str) -> str: """Execute a tool call and return the result as a string.""" # Image generation — no Atlassian token needed @@ -1930,6 +1972,10 @@ class Bot: await self._generate_and_send_image(room_id, args.get("prompt", "")) return "Image generated and sent to the room." + # Web search — no auth needed + if tool_name == "web_search": + return await self._brave_search(args.get("query", ""), args.get("count", 5)) + # Atlassian tools — need per-user token token = await self.atlassian.get_token(sender) if sender else None if not token: