From e69184decf93236e580ee04fb57684f04e16a85c Mon Sep 17 00:00:00 2001 From: nicole pardal Date: Mon, 22 Sep 2025 15:16:58 -0700 Subject: [PATCH 01/12] implemented shape update --- examples/web-search-crawl.py | 53 ++++++++++++----------------------- ollama/__init__.py | 6 ++-- ollama/_client.py | 54 +++++++++++++++++------------------- ollama/_types.py | 25 +++++------------ 4 files changed, 53 insertions(+), 85 deletions(-) diff --git a/examples/web-search-crawl.py b/examples/web-search-crawl.py index 6222d9dc..8a490b4d 100644 --- a/examples/web-search-crawl.py +++ b/examples/web-search-crawl.py @@ -7,58 +7,39 @@ # /// from typing import Union -from rich import print +from ollama import Client, WebFetchResponse, WebSearchResponse -from ollama import WebCrawlResponse, WebSearchResponse, chat, web_crawl, web_search - -def format_tool_results(results: Union[WebSearchResponse, WebCrawlResponse]): +def format_tool_results(results: Union[WebSearchResponse, WebFetchResponse]): if isinstance(results, WebSearchResponse): - if not results.success: - error_msg = ', '.join(results.errors) if results.errors else 'Unknown error' - return f'Web search failed: {error_msg}' - output = [] - for query, search_results in results.results.items(): - output.append(f'Search results for "{query}":') - for i, result in enumerate(search_results, 1): - output.append(f'{i}. {result.title}') - output.append(f' URL: {result.url}') - output.append(f' Content: {result.content}') - output.append('') + for i, result in enumerate(results.results, 1): + output.append(f'{i}. {result.content}') + output.append('') return '\n'.join(output).rstrip() - elif isinstance(results, WebCrawlResponse): - if not results.success: - error_msg = ', '.join(results.errors) if results.errors else 'Unknown error' - return f'Web crawl failed: {error_msg}' - - output = [] - for url, crawl_results in results.results.items(): - output.append(f'Crawl results for "{url}":') - for i, result in enumerate(crawl_results, 1): - output.append(f'{i}. {result.title}') - output.append(f' URL: {result.url}') - output.append(f' Content: {result.content}') - if result.links: - output.append(f' Links: {", ".join(result.links)}') - output.append('') + elif isinstance(results, WebFetchResponse): + output = [ + f'Title: {results.title}', + f'Content: {results.content}', + ] + if results.links: + output.append(f'Links: {", ".join(results.links)}') + output.append('') return '\n'.join(output).rstrip() -# Set OLLAMA_API_KEY in the environment variable or use the headers parameter to set the authorization header -# client = Client(headers={'Authorization': 'Bearer '}) - -available_tools = {'web_search': web_search, 'web_crawl': web_crawl} +client = Client(headers={'Authorization': (os.getenv('OLLAMA_API_KEY'))}) +available_tools = {'web_search': client.web_search, 'web_fetch': client.web_fetch} query = "ollama's new engine" print('Query: ', query) messages = [{'role': 'user', 'content': query}] while True: - response = chat(model='qwen3', messages=messages, tools=[web_search, web_crawl], think=True) + response = client.chat(model='qwen3', messages=messages, tools=[client.web_search, client.web_fetch], think=True) if response.message.thinking: print('Thinking: ') print(response.message.thinking + '\n\n') @@ -72,7 +53,7 @@ def format_tool_results(results: Union[WebSearchResponse, WebCrawlResponse]): for tool_call in response.message.tool_calls: function_to_call = available_tools.get(tool_call.function.name) if function_to_call: - result: WebSearchResponse | WebCrawlResponse = function_to_call(**tool_call.function.arguments) + result: Union[WebSearchResponse, WebFetchResponse] = function_to_call(**tool_call.function.arguments) print('Result from tool call name: ', tool_call.function.name, 'with arguments: ', tool_call.function.arguments) print('Result: ', format_tool_results(result)[:200]) diff --git a/ollama/__init__.py b/ollama/__init__.py index c7d68395..92bba280 100644 --- a/ollama/__init__.py +++ b/ollama/__init__.py @@ -15,7 +15,7 @@ ShowResponse, StatusResponse, Tool, - WebCrawlResponse, + WebFetchResponse, WebSearchResponse, ) @@ -37,7 +37,7 @@ 'ShowResponse', 'StatusResponse', 'Tool', - 'WebCrawlResponse', + 'WebFetchResponse', 'WebSearchResponse', ] @@ -56,4 +56,4 @@ show = _client.show ps = _client.ps web_search = _client.web_search -web_crawl = _client.web_crawl +web_fetch = _client.web_fetch diff --git a/ollama/_client.py b/ollama/_client.py index d6a26c69..799da8ab 100644 --- a/ollama/_client.py +++ b/ollama/_client.py @@ -66,8 +66,8 @@ ShowResponse, StatusResponse, Tool, - WebCrawlRequest, - WebCrawlResponse, + WebFetchRequest, + WebFetchResponse, WebSearchRequest, WebSearchResponse, ) @@ -633,12 +633,12 @@ def ps(self) -> ProcessResponse: '/api/ps', ) - def web_search(self, queries: Sequence[str], max_results: int = 3) -> WebSearchResponse: + def web_search(self, query: str, max_results: int = 3) -> WebSearchResponse: """ Performs a web search Args: - queries: The queries to search for + query: The query to search for max_results: The maximum number of results to return. Returns: @@ -652,34 +652,32 @@ def web_search(self, queries: Sequence[str], max_results: int = 3) -> WebSearchR return self._request( WebSearchResponse, 'POST', - 'https://ollama.com/api/web_search', + 'http://localhost:8080/api/web_search', json=WebSearchRequest( - queries=queries, + query=query, max_results=max_results, ).model_dump(exclude_none=True), ) - def web_crawl(self, urls: Sequence[str]) -> WebCrawlResponse: + def web_fetch(self, url: str) -> WebFetchResponse: """ - Gets the content of web pages for the provided URLs. + Fetches the content of a web page for the provided URL. Args: - urls: The URLs to crawl + url: The URL to fetch Returns: - WebCrawlResponse with the crawl results - Raises: - ValueError: If OLLAMA_API_KEY environment variable is not set + WebFetchResponse with the fetched result """ if not self._client.headers.get('authorization', '').startswith('Bearer '): raise ValueError('Authorization header with Bearer token is required for web fetch') return self._request( - WebCrawlResponse, + WebFetchResponse, 'POST', - 'https://ollama.com/api/web_crawl', - json=WebCrawlRequest( - urls=urls, + 'http://localhost:8080/api/web_fetch', + json=WebFetchRequest( + url=url, ).model_dump(exclude_none=True), ) @@ -752,12 +750,12 @@ async def inner(): return cls(**(await self._request_raw(*args, **kwargs)).json()) - async def websearch(self, queries: Sequence[str], max_results: int = 3) -> WebSearchResponse: + async def websearch(self, query: str, max_results: int = 3) -> WebSearchResponse: """ Performs a web search Args: - queries: The queries to search for + query: The query to search for max_results: The maximum number of results to return. Returns: @@ -766,29 +764,29 @@ async def websearch(self, queries: Sequence[str], max_results: int = 3) -> WebSe return await self._request( WebSearchResponse, 'POST', - 'https://ollama.com/api/web_search', + '/api/web_search', json=WebSearchRequest( - queries=queries, + query=query, max_results=max_results, ).model_dump(exclude_none=True), ) - async def webcrawl(self, urls: Sequence[str]) -> WebCrawlResponse: + async def webfetch(self, url: str) -> WebFetchResponse: """ - Gets the content of web pages for the provided URLs. + Fetches the content of a web page for the provided URL. Args: - urls: The URLs to crawl + url: The URL to fetch Returns: - WebCrawlResponse with the crawl results + WebFetchResponse with the fetched result """ return await self._request( - WebCrawlResponse, + WebFetchResponse, 'POST', - 'https://ollama.com/api/web_crawl', - json=WebCrawlRequest( - urls=urls, + '/api/web_fetch', + json=WebFetchRequest( + url=url, ).model_dump(exclude_none=True), ) diff --git a/ollama/_types.py b/ollama/_types.py index 896ee8a8..89cee704 100644 --- a/ollama/_types.py +++ b/ollama/_types.py @@ -542,37 +542,26 @@ class Model(SubscriptableBaseModel): class WebSearchRequest(SubscriptableBaseModel): - queries: Sequence[str] + query: str max_results: Optional[int] = None class WebSearchResult(SubscriptableBaseModel): - title: str - url: str content: str -class WebCrawlResult(SubscriptableBaseModel): - title: str +class WebFetchRequest(SubscriptableBaseModel): url: str - content: str - links: Optional[Sequence[str]] = None class WebSearchResponse(SubscriptableBaseModel): - results: Mapping[str, Sequence[WebSearchResult]] - success: bool - errors: Optional[Sequence[str]] = None + results: Sequence[WebSearchResult] -class WebCrawlRequest(SubscriptableBaseModel): - urls: Sequence[str] - - -class WebCrawlResponse(SubscriptableBaseModel): - results: Mapping[str, Sequence[WebCrawlResult]] - success: bool - errors: Optional[Sequence[str]] = None +class WebFetchResponse(SubscriptableBaseModel): + title: str + content: str + links: Optional[Sequence[str]] = None class RequestError(Exception): From d4e9af57afbd7bda3365e99d4d98f1e8ab34b15f Mon Sep 17 00:00:00 2001 From: nicole pardal Date: Mon, 22 Sep 2025 15:22:15 -0700 Subject: [PATCH 02/12] removed localhost hardcoding --- ollama/_client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ollama/_client.py b/ollama/_client.py index 799da8ab..d00f028a 100644 --- a/ollama/_client.py +++ b/ollama/_client.py @@ -652,7 +652,7 @@ def web_search(self, query: str, max_results: int = 3) -> WebSearchResponse: return self._request( WebSearchResponse, 'POST', - 'http://localhost:8080/api/web_search', + 'https://ollama.com/api/web_search', json=WebSearchRequest( query=query, max_results=max_results, @@ -675,7 +675,7 @@ def web_fetch(self, url: str) -> WebFetchResponse: return self._request( WebFetchResponse, 'POST', - 'http://localhost:8080/api/web_fetch', + 'https://ollama.com/api/web_fetch', json=WebFetchRequest( url=url, ).model_dump(exclude_none=True), From 8039bfd8b9745ee05bce6b260dbdff50a96f84d3 Mon Sep 17 00:00:00 2001 From: nicole pardal Date: Mon, 22 Sep 2025 15:24:26 -0700 Subject: [PATCH 03/12] updated url --- ollama/_client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ollama/_client.py b/ollama/_client.py index d00f028a..49619ed2 100644 --- a/ollama/_client.py +++ b/ollama/_client.py @@ -764,7 +764,7 @@ async def websearch(self, query: str, max_results: int = 3) -> WebSearchResponse return await self._request( WebSearchResponse, 'POST', - '/api/web_search', + 'https://ollama.com/api/web_search', json=WebSearchRequest( query=query, max_results=max_results, @@ -784,7 +784,7 @@ async def webfetch(self, url: str) -> WebFetchResponse: return await self._request( WebFetchResponse, 'POST', - '/api/web_fetch', + 'https://ollama.com/api/web_fetch', json=WebFetchRequest( url=url, ).model_dump(exclude_none=True), From 80ef0c15aeb9f550c7221ecf7a7226a41d347199 Mon Sep 17 00:00:00 2001 From: nicole pardal Date: Mon, 22 Sep 2025 17:38:08 -0700 Subject: [PATCH 04/12] replied to comments --- examples/web-search-crawl.py | 64 +++++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 12 deletions(-) diff --git a/examples/web-search-crawl.py b/examples/web-search-crawl.py index 8a490b4d..a139bdc3 100644 --- a/examples/web-search-crawl.py +++ b/examples/web-search-crawl.py @@ -2,28 +2,59 @@ # requires-python = ">=3.11" # dependencies = [ # "rich", -# "ollama", +# "ollama @ file:///Users/nicole/Desktop/ollama-python", # ] # /// -from typing import Union +import os +from typing import Optional, Union + +from rich import print from ollama import Client, WebFetchResponse, WebSearchResponse -def format_tool_results(results: Union[WebSearchResponse, WebFetchResponse]): +def format_tool_results( + results: Union[WebSearchResponse, WebFetchResponse], + *, + query: Optional[str] = None, + url: Optional[str] = None, +): if isinstance(results, WebSearchResponse): output = [] - for i, result in enumerate(results.results, 1): - output.append(f'{i}. {result.content}') - output.append('') + if isinstance(results.results, dict): + for q, search_results in results.results.items(): + output.append(f'Search results for "{q}":') + for i, result in enumerate(search_results, 1): + title = getattr(result, 'title', None) + url_value = getattr(result, 'url', None) + output.append(f'{i}. {title}' if title else f'{i}. {getattr(result, "content", "")}') + if url_value: + output.append(f' URL: {url_value}') + output.append(f' Content: {getattr(result, "content", "")}') + output.append('') + else: + if query: + output.append(f'Search results for "{query}":') + for i, result in enumerate(results.results, 1): + title = getattr(result, 'title', None) + url_value = getattr(result, 'url', None) + output.append(f'{i}. {title}' if title else f'{i}. {getattr(result, "content", "")}') + if url_value: + output.append(f' URL: {url_value}') + output.append(f' Content: {getattr(result, "content", "")}') + output.append('') return '\n'.join(output).rstrip() elif isinstance(results, WebFetchResponse): - output = [ + output = [] + if url: + output.append(f'Fetch results for "{url}":') + output.extend([ f'Title: {results.title}', + f'URL: {url}' if url else '', f'Content: {results.content}', - ] + ]) if results.links: output.append(f'Links: {", ".join(results.links)}') output.append('') @@ -53,12 +84,21 @@ def format_tool_results(results: Union[WebSearchResponse, WebFetchResponse]): for tool_call in response.message.tool_calls: function_to_call = available_tools.get(tool_call.function.name) if function_to_call: - result: Union[WebSearchResponse, WebFetchResponse] = function_to_call(**tool_call.function.arguments) - print('Result from tool call name: ', tool_call.function.name, 'with arguments: ', tool_call.function.arguments) - print('Result: ', format_tool_results(result)[:200]) + args = tool_call.function.arguments + result: Union[WebSearchResponse, WebFetchResponse] = function_to_call(**args) + print('Result from tool call name: ', tool_call.function.name, 'with arguments: ', args) + + if tool_call.function.name == 'web_search': + formatted = format_tool_results(result, query=args.get('query')) + elif tool_call.function.name == 'web_fetch': + formatted = format_tool_results(result, url=args.get('url')) + else: + formatted = format_tool_results(result) + + print('Result: ', formatted[:200]) # caps the result at ~2000 tokens - messages.append({'role': 'tool', 'content': format_tool_results(result)[: 2000 * 4], 'tool_name': tool_call.function.name}) + messages.append({'role': 'tool', 'content': formatted[: 2000 * 4], 'tool_name': tool_call.function.name}) else: print(f'Tool {tool_call.function.name} not found') messages.append({'role': 'tool', 'content': f'Tool {tool_call.function.name} not found', 'tool_name': tool_call.function.name}) From b3a5767a2ff3ad70a3a3b6738562b64b93cc497b Mon Sep 17 00:00:00 2001 From: nicole pardal Date: Mon, 22 Sep 2025 17:38:33 -0700 Subject: [PATCH 05/12] remove --- examples/web-search-crawl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/web-search-crawl.py b/examples/web-search-crawl.py index a139bdc3..93610490 100644 --- a/examples/web-search-crawl.py +++ b/examples/web-search-crawl.py @@ -2,7 +2,7 @@ # requires-python = ">=3.11" # dependencies = [ # "rich", -# "ollama @ file:///Users/nicole/Desktop/ollama-python", +# "ollama", # ] # /// import os From 844c5baf0d77051b85c75e965194b60f54c99b5d Mon Sep 17 00:00:00 2001 From: nicole pardal Date: Mon, 22 Sep 2025 17:57:34 -0700 Subject: [PATCH 06/12] lint fix --- examples/web-search-crawl.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/web-search-crawl.py b/examples/web-search-crawl.py index 93610490..340eca34 100644 --- a/examples/web-search-crawl.py +++ b/examples/web-search-crawl.py @@ -7,7 +7,6 @@ # /// import os from typing import Optional, Union - from rich import print from ollama import Client, WebFetchResponse, WebSearchResponse From 3f2f4dbd5cc9de628392745334c1eb8d0b988c76 Mon Sep 17 00:00:00 2001 From: nicole pardal Date: Mon, 22 Sep 2025 17:59:33 -0700 Subject: [PATCH 07/12] format fix --- examples/web-search-crawl.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/examples/web-search-crawl.py b/examples/web-search-crawl.py index 340eca34..415d40dc 100644 --- a/examples/web-search-crawl.py +++ b/examples/web-search-crawl.py @@ -49,11 +49,13 @@ def format_tool_results( output = [] if url: output.append(f'Fetch results for "{url}":') - output.extend([ - f'Title: {results.title}', - f'URL: {url}' if url else '', - f'Content: {results.content}', - ]) + output.extend( + [ + f'Title: {results.title}', + f'URL: {url}' if url else '', + f'Content: {results.content}', + ] + ) if results.links: output.append(f'Links: {", ".join(results.links)}') output.append('') From 5a3b22ad557d7622a8e44910f96761da9efff5a9 Mon Sep 17 00:00:00 2001 From: nicole pardal Date: Mon, 22 Sep 2025 18:01:45 -0700 Subject: [PATCH 08/12] added space for format --- examples/web-search-crawl.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/web-search-crawl.py b/examples/web-search-crawl.py index 415d40dc..310a5a5a 100644 --- a/examples/web-search-crawl.py +++ b/examples/web-search-crawl.py @@ -7,6 +7,7 @@ # /// import os from typing import Optional, Union + from rich import print from ollama import Client, WebFetchResponse, WebSearchResponse From 81c71ba44ee73155e237dd747dc6c190b8fd05db Mon Sep 17 00:00:00 2001 From: nicole pardal Date: Tue, 23 Sep 2025 12:00:03 -0700 Subject: [PATCH 09/12] removed redundancy, added formatting --- examples/web-search-crawl.py | 71 ++++++++++++++---------------------- 1 file changed, 28 insertions(+), 43 deletions(-) diff --git a/examples/web-search-crawl.py b/examples/web-search-crawl.py index 310a5a5a..e6c63a0f 100644 --- a/examples/web-search-crawl.py +++ b/examples/web-search-crawl.py @@ -15,55 +15,35 @@ def format_tool_results( results: Union[WebSearchResponse, WebFetchResponse], - *, - query: Optional[str] = None, - url: Optional[str] = None, + user_search: str, ): if isinstance(results, WebSearchResponse): output = [] - if isinstance(results.results, dict): - for q, search_results in results.results.items(): - output.append(f'Search results for "{q}":') - for i, result in enumerate(search_results, 1): - title = getattr(result, 'title', None) - url_value = getattr(result, 'url', None) - output.append(f'{i}. {title}' if title else f'{i}. {getattr(result, "content", "")}') - if url_value: - output.append(f' URL: {url_value}') - output.append(f' Content: {getattr(result, "content", "")}') - output.append('') - else: - if query: - output.append(f'Search results for "{query}":') - for i, result in enumerate(results.results, 1): - title = getattr(result, 'title', None) - url_value = getattr(result, 'url', None) - output.append(f'{i}. {title}' if title else f'{i}. {getattr(result, "content", "")}') - if url_value: - output.append(f' URL: {url_value}') - output.append(f' Content: {getattr(result, "content", "")}') - output.append('') - + output.append(f'Search results for "{user_search}":') + print() + for i, result in enumerate(results.results, 1): + title = getattr(result, 'title', None) + url_value = getattr(result, 'url', None) + output.append(f'{i}. {title}' if title else f'{i}. {getattr(result, "content", "")}') + if url_value: + output.append(f' URL: {url_value}') + output.append(f' Content: {getattr(result, "content", "")}') + output.append('') return '\n'.join(output).rstrip() elif isinstance(results, WebFetchResponse): output = [] - if url: - output.append(f'Fetch results for "{url}":') - output.extend( - [ - f'Title: {results.title}', - f'URL: {url}' if url else '', - f'Content: {results.content}', - ] - ) + output.append(f'Fetch results for "{user_search}":') + output.extend([ + f'Title: {results.title}', + f'URL: {user_search}' if user_search else '', + f'Content: {results.content}', + ]) if results.links: output.append(f'Links: {", ".join(results.links)}') output.append('') - return '\n'.join(output).rstrip() - client = Client(headers={'Authorization': (os.getenv('OLLAMA_API_KEY'))}) available_tools = {'web_search': client.web_search, 'web_fetch': client.web_fetch} @@ -88,19 +68,24 @@ def format_tool_results( if function_to_call: args = tool_call.function.arguments result: Union[WebSearchResponse, WebFetchResponse] = function_to_call(**args) - print('Result from tool call name: ', tool_call.function.name, 'with arguments: ', args) + print('Result from tool call name:', tool_call.function.name, 'with arguments:') + print(args) + print() + user_search = args.get('query', '') or args.get('url', '') if tool_call.function.name == 'web_search': - formatted = format_tool_results(result, query=args.get('query')) + formatted_tool_results = format_tool_results(result, user_search=user_search) elif tool_call.function.name == 'web_fetch': - formatted = format_tool_results(result, url=args.get('url')) + formatted_tool_results = format_tool_results(result, user_search=user_search) else: - formatted = format_tool_results(result) + formatted_tool_results = format_tool_results(result) - print('Result: ', formatted[:200]) + print('Result:') + print(formatted_tool_results[:200]) + print() # caps the result at ~2000 tokens - messages.append({'role': 'tool', 'content': formatted[: 2000 * 4], 'tool_name': tool_call.function.name}) + messages.append({'role': 'tool', 'content': formatted_tool_results[: 2000 * 4], 'tool_name': tool_call.function.name}) else: print(f'Tool {tool_call.function.name} not found') messages.append({'role': 'tool', 'content': f'Tool {tool_call.function.name} not found', 'tool_name': tool_call.function.name}) From d3ddff329f8184b2092131c76a1f5b187f5cfa5b Mon Sep 17 00:00:00 2001 From: nicole pardal Date: Tue, 23 Sep 2025 12:06:11 -0700 Subject: [PATCH 10/12] formatting issues --- examples/web-search-crawl.py | 6 +++--- ollama/_client.py | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/web-search-crawl.py b/examples/web-search-crawl.py index e6c63a0f..973fc46d 100644 --- a/examples/web-search-crawl.py +++ b/examples/web-search-crawl.py @@ -6,7 +6,7 @@ # ] # /// import os -from typing import Optional, Union +from typing import Union from rich import print @@ -20,7 +20,6 @@ def format_tool_results( if isinstance(results, WebSearchResponse): output = [] output.append(f'Search results for "{user_search}":') - print() for i, result in enumerate(results.results, 1): title = getattr(result, 'title', None) url_value = getattr(result, 'url', None) @@ -44,7 +43,8 @@ def format_tool_results( output.append('') return '\n'.join(output).rstrip() -client = Client(headers={'Authorization': (os.getenv('OLLAMA_API_KEY'))}) +api_key = os.getenv('OLLAMA_API_KEY') +client = Client(headers={'Authorization': f"Bearer {s.getenv('OLLAMA_API_KEY')}"} if api_key else None) available_tools = {'web_search': client.web_search, 'web_fetch': client.web_fetch} query = "ollama's new engine" diff --git a/ollama/_client.py b/ollama/_client.py index 49619ed2..3066841f 100644 --- a/ollama/_client.py +++ b/ollama/_client.py @@ -652,7 +652,7 @@ def web_search(self, query: str, max_results: int = 3) -> WebSearchResponse: return self._request( WebSearchResponse, 'POST', - 'https://ollama.com/api/web_search', + 'http://localhost:8080/api/web_search', json=WebSearchRequest( query=query, max_results=max_results, @@ -675,7 +675,7 @@ def web_fetch(self, url: str) -> WebFetchResponse: return self._request( WebFetchResponse, 'POST', - 'https://ollama.com/api/web_fetch', + 'http://localhost:8080/api/web_fetch', json=WebFetchRequest( url=url, ).model_dump(exclude_none=True), @@ -764,7 +764,7 @@ async def websearch(self, query: str, max_results: int = 3) -> WebSearchResponse return await self._request( WebSearchResponse, 'POST', - 'https://ollama.com/api/web_search', + 'http://localhost:8080/api/web_search', json=WebSearchRequest( query=query, max_results=max_results, @@ -784,7 +784,7 @@ async def webfetch(self, url: str) -> WebFetchResponse: return await self._request( WebFetchResponse, 'POST', - 'https://ollama.com/api/web_fetch', + 'http://localhost:8080/api/web_fetch', json=WebFetchRequest( url=url, ).model_dump(exclude_none=True), From 9d051dccaefed026ff31c2ee28e048ad8e056967 Mon Sep 17 00:00:00 2001 From: nicole pardal Date: Tue, 23 Sep 2025 12:22:19 -0700 Subject: [PATCH 11/12] updated tests --- ollama/_client.py | 4 ++-- tests/test_client.py | 32 ++++++++++++++++---------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/ollama/_client.py b/ollama/_client.py index 3066841f..8380a3fa 100644 --- a/ollama/_client.py +++ b/ollama/_client.py @@ -652,7 +652,7 @@ def web_search(self, query: str, max_results: int = 3) -> WebSearchResponse: return self._request( WebSearchResponse, 'POST', - 'http://localhost:8080/api/web_search', + 'https://ollama.com/api/web_search', json=WebSearchRequest( query=query, max_results=max_results, @@ -675,7 +675,7 @@ def web_fetch(self, url: str) -> WebFetchResponse: return self._request( WebFetchResponse, 'POST', - 'http://localhost:8080/api/web_fetch', + 'https://ollama.com/api/web_fetch', json=WebFetchRequest( url=url, ).model_dump(exclude_none=True), diff --git a/tests/test_client.py b/tests/test_client.py index 17d5750b..449d6ab2 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1203,29 +1203,29 @@ def test_client_web_search_requires_bearer_auth_header(monkeypatch: pytest.Monke client = Client() with pytest.raises(ValueError, match='Authorization header with Bearer token is required for web search'): - client.web_search(['test query']) + client.web_search('test query') -def test_client_web_crawl_requires_bearer_auth_header(monkeypatch: pytest.MonkeyPatch): +def test_client_web_fetch_requires_bearer_auth_header(monkeypatch: pytest.MonkeyPatch): monkeypatch.delenv('OLLAMA_API_KEY', raising=False) client = Client() with pytest.raises(ValueError, match='Authorization header with Bearer token is required for web fetch'): - client.web_crawl(['https://example.com']) + client.web_fetch('https://example.com') def _mock_request_web_search(self, cls, method, url, json=None, **kwargs): assert method == 'POST' assert url == 'https://ollama.com/api/web_search' - assert json is not None and 'queries' in json and 'max_results' in json + assert json is not None and 'query' in json and 'max_results' in json return httpxResponse(status_code=200, content='{"results": {}, "success": true}') -def _mock_request_web_crawl(self, cls, method, url, json=None, **kwargs): +def _mock_request_web_fetch(self, cls, method, url, json=None, **kwargs): assert method == 'POST' - assert url == 'https://ollama.com/api/web_crawl' - assert json is not None and 'urls' in json + assert url == 'https://ollama.com/api/web_fetch' + assert json is not None and 'url' in json return httpxResponse(status_code=200, content='{"results": {}, "success": true}') @@ -1234,15 +1234,15 @@ def test_client_web_search_with_env_api_key(monkeypatch: pytest.MonkeyPatch): monkeypatch.setattr(Client, '_request', _mock_request_web_search) client = Client() - client.web_search(['what is ollama?'], max_results=2) + client.web_search('what is ollama?', max_results=2) -def test_client_web_crawl_with_env_api_key(monkeypatch: pytest.MonkeyPatch): +def test_client_web_fetch_with_env_api_key(monkeypatch: pytest.MonkeyPatch): monkeypatch.setenv('OLLAMA_API_KEY', 'test-key') - monkeypatch.setattr(Client, '_request', _mock_request_web_crawl) + monkeypatch.setattr(Client, '_request', _mock_request_web_fetch) client = Client() - client.web_crawl(['https://example.com']) + client.web_fetch('https://example.com') def test_client_web_search_with_explicit_bearer_header(monkeypatch: pytest.MonkeyPatch): @@ -1250,15 +1250,15 @@ def test_client_web_search_with_explicit_bearer_header(monkeypatch: pytest.Monke monkeypatch.setattr(Client, '_request', _mock_request_web_search) client = Client(headers={'Authorization': 'Bearer custom-token'}) - client.web_search(['what is ollama?'], max_results=1) + client.web_search('what is ollama?', max_results=1) -def test_client_web_crawl_with_explicit_bearer_header(monkeypatch: pytest.MonkeyPatch): +def test_client_web_fetch_with_explicit_bearer_header(monkeypatch: pytest.MonkeyPatch): monkeypatch.delenv('OLLAMA_API_KEY', raising=False) - monkeypatch.setattr(Client, '_request', _mock_request_web_crawl) + monkeypatch.setattr(Client, '_request', _mock_request_web_fetch) client = Client(headers={'Authorization': 'Bearer custom-token'}) - client.web_crawl(['https://example.com']) + client.web_fetch('https://example.com') def test_client_bearer_header_from_env(monkeypatch: pytest.MonkeyPatch): @@ -1274,4 +1274,4 @@ def test_client_explicit_bearer_header_overrides_env(monkeypatch: pytest.MonkeyP client = Client(headers={'Authorization': 'Bearer explicit-token'}) assert client._client.headers['authorization'] == 'Bearer explicit-token' - client.web_search(['override check']) + client.web_search('override check') From 79eda0c2f5e08cb8d9fbb9d928d67e2723cedd20 Mon Sep 17 00:00:00 2001 From: ParthSareen Date: Tue, 23 Sep 2025 13:22:45 -0700 Subject: [PATCH 12/12] fix types, client, example --- examples/web-search-crawl.py | 49 +++++++++++++++--------------------- ollama/_client.py | 12 ++++----- ollama/_types.py | 8 +++--- 3 files changed, 31 insertions(+), 38 deletions(-) diff --git a/examples/web-search-crawl.py b/examples/web-search-crawl.py index 973fc46d..fde367a0 100644 --- a/examples/web-search-crawl.py +++ b/examples/web-search-crawl.py @@ -5,54 +5,51 @@ # "ollama", # ] # /// -import os from typing import Union from rich import print -from ollama import Client, WebFetchResponse, WebSearchResponse +from ollama import WebFetchResponse, WebSearchResponse, chat, web_fetch, web_search def format_tool_results( results: Union[WebSearchResponse, WebFetchResponse], user_search: str, ): + output = [] if isinstance(results, WebSearchResponse): - output = [] output.append(f'Search results for "{user_search}":') - for i, result in enumerate(results.results, 1): - title = getattr(result, 'title', None) - url_value = getattr(result, 'url', None) - output.append(f'{i}. {title}' if title else f'{i}. {getattr(result, "content", "")}') - if url_value: - output.append(f' URL: {url_value}') - output.append(f' Content: {getattr(result, "content", "")}') + for result in results.results: + output.append(f'{result.title}' if result.title else f'{result.content}') + output.append(f' URL: {result.url}') + output.append(f' Content: {result.content}') output.append('') return '\n'.join(output).rstrip() elif isinstance(results, WebFetchResponse): - output = [] output.append(f'Fetch results for "{user_search}":') - output.extend([ - f'Title: {results.title}', - f'URL: {user_search}' if user_search else '', - f'Content: {results.content}', - ]) + output.extend( + [ + f'Title: {results.title}', + f'URL: {user_search}' if user_search else '', + f'Content: {results.content}', + ] + ) if results.links: output.append(f'Links: {", ".join(results.links)}') output.append('') return '\n'.join(output).rstrip() -api_key = os.getenv('OLLAMA_API_KEY') -client = Client(headers={'Authorization': f"Bearer {s.getenv('OLLAMA_API_KEY')}"} if api_key else None) -available_tools = {'web_search': client.web_search, 'web_fetch': client.web_fetch} -query = "ollama's new engine" +# client = Client(headers={'Authorization': f"Bearer {os.getenv('OLLAMA_API_KEY')}"} if api_key else None) +available_tools = {'web_search': web_search, 'web_fetch': web_fetch} + +query = "what is ollama's new engine" print('Query: ', query) messages = [{'role': 'user', 'content': query}] while True: - response = client.chat(model='qwen3', messages=messages, tools=[client.web_search, client.web_fetch], think=True) + response = chat(model='qwen3', messages=messages, tools=[web_search, web_fetch], think=True) if response.message.thinking: print('Thinking: ') print(response.message.thinking + '\n\n') @@ -73,15 +70,9 @@ def format_tool_results( print() user_search = args.get('query', '') or args.get('url', '') - if tool_call.function.name == 'web_search': - formatted_tool_results = format_tool_results(result, user_search=user_search) - elif tool_call.function.name == 'web_fetch': - formatted_tool_results = format_tool_results(result, user_search=user_search) - else: - formatted_tool_results = format_tool_results(result) + formatted_tool_results = format_tool_results(result, user_search=user_search) - print('Result:') - print(formatted_tool_results[:200]) + print(formatted_tool_results[:300]) print() # caps the result at ~2000 tokens diff --git a/ollama/_client.py b/ollama/_client.py index 8380a3fa..dcd81268 100644 --- a/ollama/_client.py +++ b/ollama/_client.py @@ -639,7 +639,7 @@ def web_search(self, query: str, max_results: int = 3) -> WebSearchResponse: Args: query: The query to search for - max_results: The maximum number of results to return. + max_results: The maximum number of results to return (default: 3) Returns: WebSearchResponse with the search results @@ -750,13 +750,13 @@ async def inner(): return cls(**(await self._request_raw(*args, **kwargs)).json()) - async def websearch(self, query: str, max_results: int = 3) -> WebSearchResponse: + async def web_search(self, query: str, max_results: int = 3) -> WebSearchResponse: """ Performs a web search Args: query: The query to search for - max_results: The maximum number of results to return. + max_results: The maximum number of results to return (default: 3) Returns: WebSearchResponse with the search results @@ -764,14 +764,14 @@ async def websearch(self, query: str, max_results: int = 3) -> WebSearchResponse return await self._request( WebSearchResponse, 'POST', - 'http://localhost:8080/api/web_search', + 'https://ollama.com/api/web_search', json=WebSearchRequest( query=query, max_results=max_results, ).model_dump(exclude_none=True), ) - async def webfetch(self, url: str) -> WebFetchResponse: + async def web_fetch(self, url: str) -> WebFetchResponse: """ Fetches the content of a web page for the provided URL. @@ -784,7 +784,7 @@ async def webfetch(self, url: str) -> WebFetchResponse: return await self._request( WebFetchResponse, 'POST', - 'http://localhost:8080/api/web_fetch', + 'https://ollama.com/api/web_fetch', json=WebFetchRequest( url=url, ).model_dump(exclude_none=True), diff --git a/ollama/_types.py b/ollama/_types.py index 89cee704..638fb9e7 100644 --- a/ollama/_types.py +++ b/ollama/_types.py @@ -547,7 +547,9 @@ class WebSearchRequest(SubscriptableBaseModel): class WebSearchResult(SubscriptableBaseModel): - content: str + content: Optional[str] = None + title: Optional[str] = None + url: Optional[str] = None class WebFetchRequest(SubscriptableBaseModel): @@ -559,8 +561,8 @@ class WebSearchResponse(SubscriptableBaseModel): class WebFetchResponse(SubscriptableBaseModel): - title: str - content: str + title: Optional[str] = None + content: Optional[str] = None links: Optional[Sequence[str]] = None