From 9171248ae6ceb0bb9c348962a22392cab3ae8ff6 Mon Sep 17 00:00:00 2001 From: Andrew Ridgway Date: Wed, 29 Apr 2026 09:38:29 +1000 Subject: [PATCH] udpate to use bearer token correctly --- requirements.txt | 1 + .../tools/ollama_web_search_tool.py | 86 +++++++++++++------ 2 files changed, 60 insertions(+), 27 deletions(-) diff --git a/requirements.txt b/requirements.txt index a713d68..d200f73 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ ollama +httpx trilium-py gitpython PyGithub diff --git a/src/ai_generators/tools/ollama_web_search_tool.py b/src/ai_generators/tools/ollama_web_search_tool.py index c3da608..55c27f9 100644 --- a/src/ai_generators/tools/ollama_web_search_tool.py +++ b/src/ai_generators/tools/ollama_web_search_tool.py @@ -4,17 +4,25 @@ Custom CrewAI tool that wraps Ollama's native web search API. This tool allows CrewAI agents to perform web searches using an Ollama subscription instead of third-party services like Serper or EXA. +Uses direct HTTP requests via httpx with explicit Authorization: Bearer +header to ensure the OLLAMA_API_KEY is properly passed to the Ollama cloud +API endpoint (https://ollama.com/api/web_search). + Requires: -- Ollama Python library: pip install ollama +- httpx library: pip install httpx (already a transitive dependency of crewai) - OLLAMA_API_KEY environment variable set with your Ollama API key + +Reference: https://docs.ollama.com/capabilities/web-search """ import os -import ollama +import httpx from crewai.tools import BaseTool from pydantic import BaseModel, Field +OLLAMA_WEB_SEARCH_URL = "https://ollama.com/api/web_search" + class OllamaWebSearchInput(BaseModel): """Input schema for OllamaWebSearchTool.""" @@ -42,6 +50,10 @@ class OllamaWebSearchTool(BaseTool): The tool requires an Ollama subscription and the OLLAMA_API_KEY environment variable to be set. + Authentication is handled by sending the OLLAMA_API_KEY as a Bearer token + in the Authorization header, as documented at: + https://docs.ollama.com/capabilities/web-search + Example usage: from ai_generators.tools.ollama_web_search_tool import OllamaWebSearchTool @@ -65,6 +77,9 @@ class OllamaWebSearchTool(BaseTool): """ Execute a web search and return formatted results. + Makes a POST request to https://ollama.com/api/web_search with the + OLLAMA_API_KEY as a Bearer token in the Authorization header. + Args: query: The search query string max_results: Maximum number of results to return (1-10) @@ -72,16 +87,40 @@ class OllamaWebSearchTool(BaseTool): Returns: Formatted string with search results, each containing title, URL, and content """ + api_key = os.environ.get("OLLAMA_API_KEY") + if not api_key: + return ( + "Error: OLLAMA_API_KEY environment variable is not set. " + "Please set your Ollama API key." + ) + try: - # Ensure API key is set - if not os.environ.get("OLLAMA_API_KEY"): - return "Error: OLLAMA_API_KEY environment variable is not set. Please set your Ollama API key." + response = httpx.post( + OLLAMA_WEB_SEARCH_URL, + json={"query": query, "max_results": max_results}, + headers={ + "Authorization": f"Bearer {api_key}", + "Content-Type": "application/json", + }, + timeout=30.0, + ) - # Perform the web search - response = ollama.web_search(query=query, max_results=max_results) + # Raise for HTTP errors so we can catch them with specific messages + if response.status_code == 401: + return ( + "Authentication error: OLLAMA_API_KEY was rejected. " + "Your key may be invalid or expired. Please verify it at " + "https://ollama.com/settings/keys" + ) + if response.status_code == 429: + return ( + "Rate limit exceeded: Too many search requests. " + "Please wait a moment and try again." + ) + response.raise_for_status() - # Extract and format results - results = response.get("results", []) + data = response.json() + results = data.get("results", []) if not results: return f"No search results found for query: '{query}'" @@ -98,27 +137,20 @@ class OllamaWebSearchTool(BaseTool): return "\n".join(formatted_results) - except Exception as exc: - return f"Error performing web search: {exc}" - - def _handle_exception(self, exc: Exception) -> str: - """Handle exceptions gracefully and return a user-friendly error message.""" - error_message = str(exc) - - # Check for common error types - if "authentication" in error_message.lower() or "401" in error_message: + except httpx.TimeoutException: return ( - "Authentication error: Your OLLAMA_API_KEY may be invalid or expired. " - "Please check your API key and ensure it's set correctly in the environment." + "Timeout error: The web search request timed out. " + "Please try again with a simpler query." ) - elif "rate limit" in error_message.lower() or "429" in error_message: - return "Rate limit exceeded: Too many search requests. Please wait a moment and try again." - elif ( - "network" in error_message.lower() or "connection" in error_message.lower() - ): + except httpx.ConnectError: return ( "Network error: Unable to connect to Ollama's web search service. " "Please check your internet connection and try again." ) - else: - return f"Search failed: {error_message}" + except httpx.HTTPStatusError as exc: + return ( + f"HTTP error {exc.response.status_code} from Ollama web search API: " + f"{exc.response.text}" + ) + except Exception as exc: + return f"Error performing web search: {exc}" -- 2.39.5