OverviewFilesCommitsIssuesDocsReleasesPeers

ollama_semantic_function_calling commits / 7e0dec7

adding in some web func
7e0dec70a91e5bf185601c757414d4c2532c5d79
by fleeky.ashatron <fleeky@7rnx.net> · 2025-04-10T13:28:50.000Z

Changed files (10) +911 -48

ollamatools-project/examples/secure_web_tools.py +100 -0
……@@ -0,0 +1,100 @@
1"""
2Example demonstrating the use of web tools with permission system in OllamaTools.
3
4This script shows how to securely use the web tools with the permission system.
5"""
6
7from ollamatools.tools.web_tools import fetch_url, get_weather, search_web
8from ollamatools.security import PermissionManager
9import json
10
11def main():
12 """Run the secure web tools example."""
13 print("OllamaTools Secure Web Tools Example")
14 print("===================================")
15
16 # Create a permission manager with prompting enabled
17 permission_manager = PermissionManager(prompt_enabled=True)
18 print("\nCreated permission manager with prompting enabled.")
19 print("You will be prompted for permission before each web request.")
20
21 # Example 1: Basic fetch_url with permission
22 print("\n1. Secure URL Fetch")
23 print("-----------------")
24 try:
25 print("Attempting to fetch https://httpbin.org/get...")
26 result = fetch_url(
27 "https://httpbin.org/get",
28 permission_manager=permission_manager
29 )
30 print(f"Request successful! Status: {result['status']}")
31 print(f"Response content type: {result['headers'].get('Content-Type', 'Unknown')}")
32 print(f"Response URL: {result['url']}")
33 except PermissionError as e:
34 print(f"Permission denied: {e}")
35 except Exception as e:
36 print(f"Error: {e}")
37
38 # Example 2: fetch_url to a different domain
39 print("\n2. Fetch from a Different Domain")
40 print("------------------------------")
41 try:
42 print("Attempting to fetch https://example.com...")
43 result = fetch_url(
44 "https://example.com",
45 permission_manager=permission_manager
46 )
47 print(f"Request successful! Status: {result['status']}")
48 print(f"Response content type: {result['headers'].get('Content-Type', 'Unknown')}")
49 print(f"Response content length: {len(str(result['content']))} characters")
50 except PermissionError as e:
51 print(f"Permission denied: {e}")
52 except Exception as e:
53 print(f"Error: {e}")
54
55 # Example 3: Search web with permission
56 print("\n3. Secure Web Search")
57 print("-----------------")
58 try:
59 print("Attempting to search for 'Python programming'...")
60 results = search_web(
61 "Python programming",
62 num_results=3,
63 permission_manager=permission_manager
64 )
65 print(f"Search successful! Found {len(results)} results")
66 for i, result in enumerate(results[:2]): # Show first 2 results
67 print(f"\nResult {i+1}:")
68 print(f" Title: {result['title']}")
69 print(f" URL: {result['url']}")
70 except PermissionError as e:
71 print(f"Permission denied: {e}")
72 except Exception as e:
73 print(f"Error: {e}")
74
75 # Example 4: POST request with permission
76 print("\n4. Secure POST Request")
77 print("--------------------")
78 try:
79 print("Attempting to make a POST request to https://httpbin.org/post...")
80 data = {"name": "OllamaTools", "purpose": "security_demo"}
81 result = fetch_url(
82 "https://httpbin.org/post",
83 method="POST",
84 data=data,
85 permission_manager=permission_manager
86 )
87 print(f"Request successful! Status: {result['status']}")
88 print(f"Server received our data: {json.dumps(result['content'].get('json', {}))}")
89 except PermissionError as e:
90 print(f"Permission denied: {e}")
91 except Exception as e:
92 print(f"Error: {e}")
93
94 print("\nExample completed. The permission system allows users to control which")
95 print("web requests are allowed, providing an additional layer of security.")
96 print("This can be especially important when functions are automatically triggered")
97 print("by an AI model.")
98
99if __name__ == "__main__":
100 main()
ollamatools-project/examples/web_requests.py +75 -0
……@@ -0,0 +1,75 @@
1"""
2Example demonstrating the use of web tools in OllamaTools.
3
4This script shows how to make web requests using the fetch_url function.
5"""
6
7from ollamatools.tools.web_tools import fetch_url
8from ollamatools.security import PermissionManager
9import json
10
11def main():
12 """Run the example."""
13 print("OllamaTools Web Request Example")
14 print("===============================")
15
16 # Create a permission manager
17 permission_manager = PermissionManager(prompt_enabled=True)
18
19 print("\n1. Simple GET request")
20 try:
21 result = fetch_url(
22 "https://httpbin.org/get",
23 permission_manager=permission_manager
24 )
25 print(f"Status: {result['status']}")
26 print(f"Response content type: {result['headers'].get('Content-Type', 'Unknown')}")
27 print(f"Response snippet: {json.dumps(result['content'], indent=2)[:200]}...")
28 except Exception as e:
29 print(f"Error: {e}")
30
31 print("\n2. POST request with data")
32 try:
33 post_data = {"name": "OllamaTools", "version": "0.1.0"}
34 result = fetch_url(
35 "https://httpbin.org/post",
36 method="POST",
37 data=post_data,
38 permission_manager=permission_manager
39 )
40 print(f"Status: {result['status']}")
41 print(f"Response content type: {result['headers'].get('Content-Type', 'Unknown')}")
42 print(f"Response snippet: {json.dumps(result['content'], indent=2)[:200]}...")
43 except Exception as e:
44 print(f"Error: {e}")
45
46 print("\n3. GET request with parameters")
47 try:
48 params = {"query": "ollama", "limit": "5"}
49 result = fetch_url(
50 "https://httpbin.org/get",
51 params=params,
52 permission_manager=permission_manager
53 )
54 print(f"Status: {result['status']}")
55 print(f"Response content type: {result['headers'].get('Content-Type', 'Unknown')}")
56 print(f"Response snippet: {json.dumps(result['content'], indent=2)[:200]}...")
57 except Exception as e:
58 print(f"Error: {e}")
59
60 print("\n4. Request with custom headers")
61 try:
62 headers = {"X-API-Key": "demo-key", "User-Agent": "OllamaTools/0.1.0"}
63 result = fetch_url(
64 "https://httpbin.org/headers",
65 headers=headers,
66 permission_manager=permission_manager
67 )
68 print(f"Status: {result['status']}")
69 print(f"Response content type: {result['headers'].get('Content-Type', 'Unknown')}")
70 print(f"Response snippet: {json.dumps(result['content'], indent=2)[:200]}...")
71 except Exception as e:
72 print(f"Error: {e}")
73
74if __name__ == "__main__":
75 main()
ollamatools-project/examples/web_tools_examples.py +105 -0
……@@ -0,0 +1,105 @@
1"""
2Examples demonstrating the use of advanced web tools in OllamaTools.
3
4This script shows how to use the get_weather and search_web functions.
5"""
6
7from ollamatools.tools.web_tools import fetch_url, get_weather, search_web
8from ollamatools.security import PermissionManager
9import json
10
11def main():
12 """Run the example."""
13 print("OllamaTools Advanced Web Tools Example")
14 print("=====================================")
15
16 # Create a permission manager
17 permission_manager = PermissionManager(prompt_enabled=True)
18
19 # Example 1: Get Weather
20 print("\n1. Weather Information")
21 print("---------------------")
22
23 try:
24 # Replace 'YOUR_API_KEY' with an actual OpenWeatherMap API key
25 api_key = "YOUR_API_KEY"
26
27 # Skip this example if no API key is provided
28 if api_key == "YOUR_API_KEY":
29 print("Skipping weather example - Please add your OpenWeatherMap API key to use this example.")
30 else:
31 print("Getting weather for London...")
32 weather_info = get_weather(
33 "London",
34 days=3,
35 units="metric",
36 api_key=api_key,
37 permission_manager=permission_manager
38 )
39
40 # Display current weather
41 print(f"Current weather in {weather_info['location']['name']}, {weather_info['location']['country']}:")
42 print(f" Temperature: {weather_info['current']['temperature']}°C")
43 print(f" Description: {weather_info['current']['description']}")
44 print(f" Humidity: {weather_info['current']['humidity']}%")
45
46 # Display forecast summary
47 print("\nForecast:")
48 for i, item in enumerate(weather_info['forecast'][:3]): # Show first 3 forecast items
49 print(f" Item {i+1}: {item['temperature']}°C, {item['description']}")
50
51 except Exception as e:
52 print(f"Error getting weather: {e}")
53
54 # Example 2: Web Search
55 print("\n2. Web Search")
56 print("------------")
57
58 try:
59 # Replace 'YOUR_API_KEY' with an actual Search API key if you have one
60 # This example will use the fallback search method if no API key is provided
61 api_key = "YOUR_API_KEY"
62
63 print("Searching for 'Python programming'...")
64 search_results = search_web(
65 "Python programming",
66 num_results=3,
67 api_key=api_key if api_key != "YOUR_API_KEY" else None,
68 permission_manager=permission_manager
69 )
70
71 # Display search results
72 print(f"Found {len(search_results)} results:")
73 for i, result in enumerate(search_results):
74 print(f"\nResult {i+1}:")
75 print(f" Title: {result['title']}")
76 print(f" URL: {result['url']}")
77 print(f" Snippet: {result['snippet'][:100]}...")
78
79 except Exception as e:
80 print(f"Error searching web: {e}")
81
82 # Example 3: Combined Usage
83 print("\n3. Combined Usage Example")
84 print("-----------------------")
85
86 try:
87 print("This example would fetch weather data for a city from a search result.")
88 print("(Skipped in this demo to avoid API key requirements)")
89
90 # In a real implementation, you might do something like:
91 # 1. Search for "best cities to visit in Europe"
92 # 2. Extract a city from the results
93 # 3. Get weather for that city
94
95 print("\nPseudo-code:")
96 print(" results = search_web('best cities to visit in Europe')")
97 print(" city = extract_city_from_results(results)")
98 print(" weather = get_weather(city)")
99 print(" print(f'Weather in {city}: {weather['current']['temperature']}°C')")
100
101 except Exception as e:
102 print(f"Error in combined example: {e}")
103
104if __name__ == "__main__":
105 main()
ollamatools-project/ollamatools/tools/__init__.py +20 -15
……@@ -1,23 +1,28 @@
11"""
2OllamaTools function libraries.
2Tools package for OllamaTools.
33
4This package contains various function libraries that can be used
5with the OllamaAgent for semantic function detection and execution.
4This package provides various tools for the OllamaTools library, including:
5- System tools: Execute commands and file operations
6- Web tools: Make web requests and process responses
7- Documentation tools: Generate and process documentation
68"""
79
810# Import and expose system tools
9from ollamatools.tools.system_tools import (
10 execute_command,
11 read_file,
12 write_file,
13 find_files
14)
11from .system_tools import execute_command, read_file, write_file, find_files
1512
16# Define which functions should be exposed at the package level
13# Import and expose web tools
14from .web_tools import fetch_url, get_weather, search_web
15
16# Export all tools
1717__all__ = [
18 "execute_command",
19 "read_file",
20 "write_file",
21 "find_files"
18 # System tools
19 'execute_command',
20 'read_file',
21 'write_file',
22 'find_files',
23
24 # Web tools
25 'fetch_url',
26 'get_weather',
27 'search_web',
2228]
23
ollamatools-project/ollamatools/tools/web_tools.py +352 -11
……@@ -1,11 +1,19 @@
11"""
2Web tools for OllamaTools.
2Web tools module for OllamaTools.
33
4This module contains functions for making web requests and processing responses.
4This module provides functions for making web requests and processing responses,
5including weather information fetching and web search functionality.
56"""
67
7from typing import Optional, Dict, Any
8import requests
9from typing import Dict, Any, Optional, Union, List
10import logging
11import json
12from urllib.parse import urlparse, quote
13from bs4 import BeautifulSoup
814
15# Get logger
16logger = logging.getLogger(__name__)
917
1018def fetch_url(
1119 url: str,
……@@ -13,25 +21,358 @@ def fetch_url(
1321 params: Optional[Dict[str, str]] = None,
1422 headers: Optional[Dict[str, str]] = None,
1523 data: Optional[Dict[str, Any]] = None,
16 timeout: int = 30
24 timeout: int = 30,
25 permission_manager = None
1726) -> Dict[str, Any]:
1827 """
1928 Fetch a URL with specified parameters.
2029
2130 Args:
2231 url: URL to fetch
23 method: HTTP method
32 method: HTTP method (GET, POST, PUT, DELETE, etc.)
2433 params: URL parameters
2534 headers: HTTP headers
2635 data: Request data
2736 timeout: Request timeout in seconds
37 permission_manager: Optional permission manager to request permission
2838
2939 Returns:
3040 dict: Response with status, headers, and content
41
42 Raises:
43 ValueError: If URL or method is invalid
44 requests.RequestException: If the request fails
45 PermissionError: If permission is denied
46 """
47 # Validate URL
48 if not url:
49 raise ValueError("URL cannot be empty")
50
51 # Basic URL validation
52 parsed_url = urlparse(url)
53 if not parsed_url.scheme or not parsed_url.netloc:
54 raise ValueError(f"Invalid URL: {url}")
55
56 # Validate method
57 valid_methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']
58 method = method.upper()
59 if method not in valid_methods:
60 raise ValueError(f"Invalid HTTP method: {method}. Must be one of {valid_methods}")
61
62 # Request permission if permission manager is provided
63 if permission_manager:
64 args = {
65 "url": url,
66 "method": method,
67 "params": params,
68 "headers": headers,
69 "data": data
70 }
71
72 if not permission_manager.request_permission("fetch_url", args):
73 logger.warning(f"Permission denied for web request to {url}")
74 raise PermissionError(f"Permission denied for web request to {url}")
75
76 logger.info(f"Making {method} request to {url}")
77
78 try:
79 response = requests.request(
80 method=method,
81 url=url,
82 params=params,
83 headers=headers,
84 json=data if method in ['POST', 'PUT', 'PATCH'] else None,
85 timeout=timeout
86 )
87
88 # Try to parse response as JSON, fall back to text
89 content_type = response.headers.get('Content-Type', '')
90 if 'application/json' in content_type:
91 try:
92 content = response.json()
93 except ValueError:
94 content = response.text
95 else:
96 content = response.text
97
98 result = {
99 "status": response.status_code,
100 "headers": dict(response.headers),
101 "content": content,
102 "url": response.url
103 }
104
105 logger.debug(f"Request to {url} completed with status {response.status_code}")
106 return result
107
108 except requests.RequestException as e:
109 logger.error(f"Request to {url} failed: {str(e)}")
110 raise
111
112
113def get_weather(
114 location: str,
115 days: int = 3,
116 units: str = 'metric',
117 api_key: Optional[str] = None,
118 permission_manager = None
119) -> Dict[str, Any]:
120 """
121 Get weather information for a location.
122
123 Args:
124 location: Location name or coordinates
125 days: Number of forecast days (1-7)
126 units: Temperature units ('metric' or 'imperial')
127 api_key: OpenWeatherMap API key (if None, uses config)
128 permission_manager: Optional permission manager to request permission
129
130 Returns:
131 dict: Weather information including current conditions and forecast
132
133 Raises:
134 ValueError: If parameters are invalid
135 requests.RequestException: If the API request fails
136 PermissionError: If permission is denied
137 """
138 # Validate parameters
139 if not location:
140 raise ValueError("Location cannot be empty")
141
142 if days < 1 or days > 7:
143 raise ValueError("Days must be between 1 and 7")
144
145 valid_units = ['metric', 'imperial']
146 if units not in valid_units:
147 raise ValueError(f"Units must be one of {valid_units}")
148
149 # Request permission if permission manager is provided
150 if permission_manager:
151 args = {
152 "location": location,
153 "days": days,
154 "units": units
155 }
156
157 if not permission_manager.request_permission("get_weather", args):
158 logger.warning(f"Permission denied for weather request for {location}")
159 raise PermissionError(f"Permission denied for weather request for {location}")
160
161 # Check if API key is provided
162 if api_key is None:
163 # In a real implementation, this would get the API key from the config
164 # For now, we'll use a placeholder message
165 logger.error("No API key provided for weather API")
166 raise ValueError("Weather API key is required. Please provide an API key or set it in the configuration.")
167
168 logger.info(f"Fetching weather for {location}")
169
170 try:
171 # Base URL for OpenWeatherMap API
172 base_url = "https://api.openweathermap.org/data/2.5"
173
174 # Get current weather
175 current_url = f"{base_url}/weather"
176 params = {
177 "q": location,
178 "appid": api_key,
179 "units": units
180 }
181
182 current_response = requests.get(current_url, params=params, timeout=10)
183 current_response.raise_for_status()
184 current_data = current_response.json()
185
186 # Get forecast
187 forecast_url = f"{base_url}/forecast"
188 params = {
189 "q": location,
190 "appid": api_key,
191 "units": units,
192 "cnt": days * 8 # 8 forecasts per day (every 3 hours)
193 }
194
195 forecast_response = requests.get(forecast_url, params=params, timeout=10)
196 forecast_response.raise_for_status()
197 forecast_data = forecast_response.json()
198
199 # Format the response
200 weather_info = {
201 "location": {
202 "name": current_data.get("name", ""),
203 "country": current_data.get("sys", {}).get("country", ""),
204 "coordinates": current_data.get("coord", {})
205 },
206 "current": {
207 "temperature": current_data.get("main", {}).get("temp"),
208 "feels_like": current_data.get("main", {}).get("feels_like"),
209 "humidity": current_data.get("main", {}).get("humidity"),
210 "pressure": current_data.get("main", {}).get("pressure"),
211 "wind_speed": current_data.get("wind", {}).get("speed"),
212 "wind_direction": current_data.get("wind", {}).get("deg"),
213 "description": current_data.get("weather", [{}])[0].get("description", ""),
214 "icon": current_data.get("weather", [{}])[0].get("icon", "")
215 },
216 "forecast": []
217 }
218
219 # Process forecast data
220 for item in forecast_data.get("list", [])[:days * 8]:
221 forecast_item = {
222 "timestamp": item.get("dt"),
223 "temperature": item.get("main", {}).get("temp"),
224 "feels_like": item.get("main", {}).get("feels_like"),
225 "humidity": item.get("main", {}).get("humidity"),
226 "pressure": item.get("main", {}).get("pressure"),
227 "wind_speed": item.get("wind", {}).get("speed"),
228 "wind_direction": item.get("wind", {}).get("deg"),
229 "description": item.get("weather", [{}])[0].get("description", ""),
230 "icon": item.get("weather", [{}])[0].get("icon", "")
231 }
232 weather_info["forecast"].append(forecast_item)
233
234 return weather_info
235
236 except requests.RequestException as e:
237 logger.error(f"Weather API request failed: {str(e)}")
238 raise
239
240
241def search_web(
242 query: str,
243 num_results: int = 5,
244 api_key: Optional[str] = None,
245 permission_manager = None
246) -> List[Dict[str, str]]:
247 """
248 Search the web for a query.
249
250 Args:
251 query: Search query
252 num_results: Number of results to return (1-10)
253 api_key: Search API key (if None, uses config)
254 permission_manager: Optional permission manager to request permission
255
256 Returns:
257 List[dict]: Search results with title, URL, and snippet
258
259 Raises:
260 ValueError: If parameters are invalid
261 requests.RequestException: If the API request fails
262 PermissionError: If permission is denied
263 """
264 # Validate parameters
265 if not query:
266 raise ValueError("Query cannot be empty")
267
268 if num_results < 1 or num_results > 10:
269 raise ValueError("Number of results must be between 1 and 10")
270
271 # Request permission if permission manager is provided
272 if permission_manager:
273 args = {
274 "query": query,
275 "num_results": num_results
276 }
277
278 if not permission_manager.request_permission("search_web", args):
279 logger.warning(f"Permission denied for web search: {query}")
280 raise PermissionError(f"Permission denied for web search: {query}")
281
282 # Check if API key is provided
283 if api_key is None:
284 # In a real implementation, this would get the API key from the config
285 # For now, we'll use a fallback to a simple search method that doesn't require API key
286 logger.warning("No API key provided for search API, using fallback method")
287 return _fallback_search(query, num_results)
288
289 logger.info(f"Searching the web for: {query}")
290
291 try:
292 # Example using the Serpapi (Google Search API)
293 # In a real implementation, you would use a proper search API
294 url = "https://serpapi.com/search"
295 params = {
296 "q": query,
297 "api_key": api_key,
298 "num": num_results
299 }
300
301 response = requests.get(url, params=params, timeout=15)
302 response.raise_for_status()
303
304 data = response.json()
305 results = []
306
307 # Process search results
308 for item in data.get("organic_results", [])[:num_results]:
309 result = {
310 "title": item.get("title", ""),
311 "url": item.get("link", ""),
312 "snippet": item.get("snippet", "")
313 }
314 results.append(result)
315
316 return results
317
318 except requests.RequestException as e:
319 logger.error(f"Search API request failed: {str(e)}")
320
321 # Fall back to the simple search method if API request fails
322 logger.info("Falling back to simple search method")
323 return _fallback_search(query, num_results)
324
325
326def _fallback_search(query: str, num_results: int = 5) -> List[Dict[str, str]]:
327 """
328 Fallback search method using DuckDuckGo HTML scraping when API is not available.
329
330 Note: This is a fallback method and not recommended for production use.
331 It may break if the search engine changes its HTML structure.
332
333 Args:
334 query: Search query
335 num_results: Number of results to return
336
337 Returns:
338 List[dict]: Search results with title, URL, and snippet
31339 """
32 # Placeholder implementation
33 return {
34 "status": 200,
35 "headers": {},
36 "content": "URL fetch placeholder"
37 }
340 results = []
341 encoded_query = quote(query)
342 url = f"https://html.duckduckgo.com/html/?q={encoded_query}"
343
344 try:
345 headers = {
346 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
347 }
348 response = requests.get(url, headers=headers, timeout=15)
349 response.raise_for_status()
350
351 soup = BeautifulSoup(response.text, 'html.parser')
352
353 # Find search results
354 result_elements = soup.select('.result')
355
356 for element in result_elements[:num_results]:
357 title_element = element.select_one('.result__title')
358 link_element = element.select_one('.result__url')
359 snippet_element = element.select_one('.result__snippet')
360
361 if title_element and link_element:
362 title = title_element.get_text(strip=True)
363 # The actual URL is in the href attribute of the <a> tag
364 link = title_element.find('a').get('href', '')
365 snippet = snippet_element.get_text(strip=True) if snippet_element else ""
366
367 results.append({
368 "title": title,
369 "url": link,
370 "snippet": snippet
371 })
372
373 return results
374
375 except (requests.RequestException, Exception) as e:
376 logger.error(f"Fallback search failed: {str(e)}")
377 # Return empty results if even the fallback fails
378 return []
ollamatools-project/requirements.txt +7 -1
……@@ -1,7 +1,13 @@
1ollama-python>=0.4.0
1# Core dependencies
22requests>=2.28.0
33beautifulsoup4>=4.11.0
44pydantic>=1.10.0
55markdown>=3.4.0
66typing-extensions>=4.4.0
7
8# Test dependencies
79pytest>=7.0.0
10
11# Ollama client dependency - uncomment and use the correct one based on your system
12# ollama>=0.4.0
13# ollama-python>=0.1.2
ollamatools-project/setup.py +8 -16
……@@ -1,5 +1,5 @@
11"""
2Setup script for OllamaTools.
2Setup script for the OllamaTools package.
33"""
44
55from setuptools import setup, find_packages
……@@ -9,36 +9,28 @@ setup(
99 version="0.1.0",
1010 packages=find_packages(),
1111 install_requires=[
12 "ollama-python>=0.4.0",
1312 "requests>=2.28.0",
1413 "beautifulsoup4>=4.11.0",
1514 "pydantic>=1.10.0",
1615 "markdown>=3.4.0",
1716 "typing-extensions>=4.4.0",
17 # Ollama client will be added later once we determine which package to use
1818 ],
1919 extras_require={
2020 "dev": [
2121 "pytest>=7.0.0",
22 "pytest-cov>=2.12.1",
23 "black>=22.3.0",
24 "isort>=5.10.1",
25 "flake8>=4.0.1",
2622 ],
2723 },
28 python_requires=">=3.8",
29 author="Your Name",
30 author_email="your.email@example.com",
24 author="OllamaTools Team",
25 author_email="example@example.com",
3126 description="Function Library for Ollama LLMs",
3227 long_description=open("README.md").read(),
3328 long_description_content_type="text/markdown",
34 url="https://github.com/yourusername/ollamatools",
29 url="https://github.com/example/ollamatools",
3530 classifiers=[
36 "Development Status :: 3 - Alpha",
37 "Intended Audience :: Developers",
38 "License :: OSI Approved :: MIT License",
3931 "Programming Language :: Python :: 3",
40 "Programming Language :: Python :: 3.8",
41 "Programming Language :: Python :: 3.9",
42 "Programming Language :: Python :: 3.10",
32 "License :: OSI Approved :: MIT License",
33 "Operating System :: OS Independent",
4334 ],
35 python_requires=">=3.8",
4436)
ollamatools-project/simple_test_web_tools.py +115 -0
……@@ -0,0 +1,115 @@
1"""
2Simple script to manually test the web tools functionality.
3"""
4
5import sys
6import os
7from unittest.mock import MagicMock
8
9# Ensure the project directory is in the Python path
10project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
11if project_root not in sys.path:
12 sys.path.insert(0, project_root)
13
14# Import the web tools
15from ollamatools.tools.web_tools import fetch_url, get_weather, search_web
16
17def test_fetch_url():
18 """Test the fetch_url function."""
19 print("\nTesting fetch_url...")
20
21 # Test a simple GET request
22 try:
23 result = fetch_url("https://httpbin.org/get")
24 print(f"Status: {result['status']}")
25 print(f"Content type: {result['headers'].get('Content-Type')}")
26 print("Test passed!")
27 except Exception as e:
28 print(f"Test failed: {e}")
29
30def test_search_web():
31 """Test the search_web function."""
32 print("\nTesting search_web...")
33
34 # Test web search with fallback method (no API key)
35 try:
36 results = search_web("Python programming language")
37 print(f"Found {len(results)} results")
38 if results:
39 print(f"First result title: {results[0]['title']}")
40 print("Test passed!")
41 except Exception as e:
42 print(f"Test failed: {e}")
43
44def test_get_weather_mock():
45 """Test the get_weather function with mocked permission manager."""
46 print("\nTesting get_weather permission handling...")
47
48 # Create a mock permission manager
49 mock_permission_manager = MagicMock()
50
51 # Test 1: Permission granted
52 mock_permission_manager.request_permission.return_value = True
53
54 try:
55 # This should still fail because we don't have a valid API key,
56 # but it should get past the permission check
57 try:
58 get_weather(
59 "London",
60 api_key="fake_api_key",
61 permission_manager=mock_permission_manager
62 )
63 except ValueError as e:
64 if "API key" in str(e):
65 print("Permission check passed, failed with API key error as expected")
66 else:
67 raise
68
69 # Test that permission was requested
70 if mock_permission_manager.request_permission.called:
71 print("Permission request called correctly")
72 else:
73 print("Permission request was not called")
74 except Exception as e:
75 print(f"Test failed: {e}")
76
77 # Test 2: Permission denied
78 mock_permission_manager.request_permission.return_value = False
79 mock_permission_manager.request_permission.reset_mock()
80
81 try:
82 try:
83 get_weather(
84 "London",
85 api_key="fake_api_key",
86 permission_manager=mock_permission_manager
87 )
88 print("Test failed: Expected PermissionError but no exception was raised")
89 except PermissionError:
90 print("Permission denied test passed: PermissionError was raised")
91 except Exception as e:
92 print(f"Test failed: Expected PermissionError but got {type(e).__name__}: {e}")
93
94 # Test that permission was requested
95 if mock_permission_manager.request_permission.called:
96 print("Permission request called correctly")
97 else:
98 print("Permission request was not called")
99 except Exception as e:
100 print(f"Test failed: {e}")
101
102
103def main():
104 """Run all tests."""
105 print("Manual testing of web tools")
106 print("==========================")
107
108 test_fetch_url()
109 test_search_web()
110 test_get_weather_mock()
111
112 print("\nAll tests completed.")
113
114if __name__ == "__main__":
115 main()
ollamatools-project/tests/__init__.py +2 -5
……@@ -1,7 +1,4 @@
11"""
2Utility functions for OllamaTools.
3
4This package contains various utility functions used by the OllamaTools library.
2Test package for OllamaTools.
53"""
6
7from ollamatools.utils.logger import get_logger, enable_debug_logging, disable_logging, get_ollamatools_logger
4# Intentionally left empty
ollamatools-project/tests/test_web_tools.py +127 -0
……@@ -0,0 +1,127 @@
1"""
2Tests for web tools module.
3"""
4
5import pytest
6from unittest.mock import MagicMock, patch
7import requests
8from ollamatools.tools.web_tools import fetch_url
9
10# Test successful GET request
11@patch('requests.request')
12def test_successful_get_request(mock_request):
13 # Setup mock response
14 mock_response = MagicMock()
15 mock_response.status_code = 200
16 mock_response.headers = {'Content-Type': 'application/json'}
17 mock_response.json.return_value = {'key': 'value'}
18 mock_response.url = 'https://example.com'
19 mock_request.return_value = mock_response
20
21 # Call function
22 result = fetch_url('https://example.com')
23
24 # Check result
25 assert result['status'] == 200
26 assert result['content'] == {'key': 'value'}
27 assert 'headers' in result
28 assert result['url'] == 'https://example.com'
29
30 # Check mock was called correctly
31 mock_request.assert_called_once_with(
32 method='GET',
33 url='https://example.com',
34 params=None,
35 headers=None,
36 json=None,
37 timeout=30
38 )
39
40# Test successful POST request with data
41@patch('requests.request')
42def test_post_request_with_data(mock_request):
43 # Setup mock response
44 mock_response = MagicMock()
45 mock_response.status_code = 201
46 mock_response.headers = {'Content-Type': 'application/json'}
47 mock_response.json.return_value = {'id': '123'}
48 mock_response.url = 'https://example.com/api'
49 mock_request.return_value = mock_response
50
51 # Test data
52 data = {'name': 'test', 'value': 42}
53
54 # Call function
55 result = fetch_url(
56 'https://example.com/api',
57 method='POST',
58 data=data
59 )
60
61 # Check result
62 assert result['status'] == 201
63 assert result['content'] == {'id': '123'}
64
65 # Check mock was called correctly
66 mock_request.assert_called_once_with(
67 method='POST',
68 url='https://example.com/api',
69 params=None,
70 headers=None,
71 json=data,
72 timeout=30
73 )
74
75# Test request with invalid URL
76def test_invalid_url():
77 with pytest.raises(ValueError):
78 fetch_url('')
79
80 with pytest.raises(ValueError):
81 fetch_url('not-a-url')
82
83# Test request with invalid method
84def test_invalid_method():
85 with pytest.raises(ValueError):
86 fetch_url('https://example.com', method='INVALID')
87
88# Test request timeout
89@patch('requests.request')
90def test_request_timeout(mock_request):
91 mock_request.side_effect = requests.exceptions.Timeout("Request timed out")
92
93 with pytest.raises(requests.exceptions.Timeout):
94 fetch_url('https://example.com', timeout=5)
95
96# Test permission denied
97def test_permission_denied():
98 # Setup mock permission manager
99 mock_permission_manager = MagicMock()
100 mock_permission_manager.request_permission.return_value = False
101
102 with pytest.raises(PermissionError):
103 fetch_url(
104 'https://example.com',
105 permission_manager=mock_permission_manager
106 )
107
108 # Check permission was requested
109 mock_permission_manager.request_permission.assert_called_once()
110
111# Test handling non-JSON content
112@patch('requests.request')
113def test_non_json_content(mock_request):
114 # Setup mock response
115 mock_response = MagicMock()
116 mock_response.status_code = 200
117 mock_response.headers = {'Content-Type': 'text/html'}
118 mock_response.text = '<html><body>Hello World</body></html>'
119 mock_response.url = 'https://example.com'
120 mock_request.return_value = mock_response
121
122 # Call function
123 result = fetch_url('https://example.com')
124
125 # Check result
126 assert result['status'] == 200
127 assert result['content'] == '<html><body>Hello World</body></html>'