renamed + added functionality

This commit is contained in:
nicole pardal 2025-09-23 17:44:18 -07:00 committed by ParthSareen
parent 850a6bc35b
commit 51c9dace2a
2 changed files with 99 additions and 66 deletions

View File

@ -3,19 +3,65 @@ from __future__ import annotations
import os
from typing import Any, Dict, List
from browser_tool_helpers import Browser
from gpt_oss_browser_tool_helper import Browser
from ollama import Client
def main() -> None:
client = Client(headers={'Authorization': os.getenv('OLLAMA_API_KEY')})
api_key = os.getenv('OLLAMA_API_KEY')
if api_key:
client = Client(headers={'Authorization': f'Bearer {api_key}'})
else:
client = Client()
browser = Browser(initial_state=None, client=client)
# Minimal tool schemas
browser_search_schema = {'type': 'function', 'function': {'name': 'browser.search'}}
browser_open_schema = {'type': 'function', 'function': {'name': 'browser.open'}}
browser_find_schema = {'type': 'function', 'function': {'name': 'browser.find'}}
# Tool schemas
browser_search_schema = {
'type': 'function',
'function': {
'name': 'browser.search',
'parameters': {
'type': 'object',
'properties': {
'query': {'type': 'string'},
'topn': {'type': 'integer'},
},
'required': ['query'],
},
},
}
browser_open_schema = {
'type': 'function',
'function': {
'name': 'browser.open',
'parameters': {
'type': 'object',
'properties': {
'id': {'anyOf': [{'type': 'integer'}, {'type': 'string'}]},
'cursor': {'type': 'integer'},
'loc': {'type': 'integer'},
'num_lines': {'type': 'integer'},
},
},
},
}
browser_find_schema = {
'type': 'function',
'function': {
'name': 'browser.find',
'parameters': {
'type': 'object',
'properties': {
'pattern': {'type': 'string'},
'cursor': {'type': 'integer'},
},
'required': ['pattern'],
},
},
}
def browser_search(query: str, topn: int = 10) -> str:
return browser.search(query=query, topn=topn)['pageText']
@ -23,7 +69,7 @@ def main() -> None:
def browser_open(id: int | str = -1, cursor: int = -1, loc: int = -1, num_lines: int = -1) -> str:
return browser.open(id=id, cursor=cursor, loc=loc, num_lines=num_lines)['pageText']
def browser_find(pattern: str, cursor: int = -1) -> str:
def browser_find(pattern: str, cursor: int = -1, **_: Any) -> str:
return browser.find(pattern=pattern, cursor=cursor)['pageText']
available_tools = {
@ -32,7 +78,7 @@ def main() -> None:
'browser.find': browser_find,
}
messages: List[Dict[str, Any]] = [{'role': 'user', 'content': 'What is Ollama?'}]
messages: List[Dict[str, Any]] = [{'role': 'user', 'content': 'When did Ollama announce the new engine?'}]
print('----- Prompt:', messages[0]['content'], '\n')
while True:

View File

@ -3,7 +3,7 @@ from __future__ import annotations
import re
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any, Dict, List, Optional, Protocol, Tuple
from typing import Any, Dict, List, Optional, Protocol, Tuple, Union
from urllib.parse import urlparse
from ollama import Client
@ -30,7 +30,7 @@ class BrowserStateData:
class WebSearchResult:
title: str
url: str
content: Dict[str, str] # {"fullText": str}
content: Dict[str, str]
class SearchClient(Protocol):
@ -94,7 +94,6 @@ class Browser:
self.state = BrowserState(initial_state)
self._client: Optional[Client] = client
# parity with TS: one setter that accepts both
def set_client(self, client: Client) -> None:
self._client = client
@ -160,10 +159,9 @@ class Browser:
links: Dict[int, str] = {}
link_id = 0
# collapse [text]\n(url) -> [text](url)
multiline_pattern = re.compile(r'\[([^\]]+)\]\s*\n\s*\(([^)]+)\)')
text = multiline_pattern.sub(lambda m: f'[{m.group(1)}]({m.group(2)})', text)
text = re.sub(r'\s+', ' ', text) # mild cleanup from the above
text = re.sub(r'\s+', ' ', text)
link_pattern = re.compile(r'\[([^\]]+)\]\(([^)]+)\)')
@ -185,7 +183,6 @@ class Browser:
txt = self._join_lines_with_numbers(lines[loc:])
data = self.state.get_data()
if len(txt) > data.view_tokens:
# approximate char-per-token heuristic (keep identical to TS flow)
max_chars_per_token = 128
upper_bound = min((data.view_tokens + 1) * max_chars_per_token, len(txt))
segment = txt[:upper_bound]
@ -242,10 +239,10 @@ class Browser:
)
tb = []
tb.append('') # L0 blank
tb.append('URL: ') # L1 "URL: "
tb.append('# Search Results') # L2
tb.append('') # L3 blank
tb.append('')
tb.append('URL: ')
tb.append('# Search Results')
tb.append('')
link_idx = 0
for query_results in results.get('results', {}).values():
@ -276,7 +273,6 @@ class Browser:
fetched_at=datetime.utcnow(),
)
# preview block (when no full text)
link_fmt = f'{link_idx}{result.title}\n'
preview = link_fmt + f'URL: {result.url}\n'
full_text = result.content.get('fullText', '') if result.content else ''
@ -296,7 +292,7 @@ class Browser:
page.lines = self._wrap_lines(page.text, 80)
return page
def _build_page_from_crawl(self, requested_url: str, crawl_response: Dict[str, Any]) -> Page:
def _build_page_from_fetch(self, requested_url: str, fetch_response: Dict[str, Any]) -> Page:
page = Page(
url=requested_url,
title=requested_url,
@ -306,7 +302,7 @@ class Browser:
fetched_at=datetime.utcnow(),
)
for url, url_results in crawl_response.get('results', {}).items():
for url, url_results in fetch_response.get('results', {}).items():
if url_results:
r0 = url_results[0]
if r0.get('content'):
@ -372,22 +368,20 @@ class Browser:
if not self._client:
raise RuntimeError('Client not provided')
resp = self._client.web_search([query], max_results=topn)
resp = self._client.web_search(query, max_results=topn)
# Normalize to dict shape used by page builders
normalized: Dict[str, Any] = {'results': {}}
for q, items in resp.results.items():
rows: List[Dict[str, str]] = []
for item in items:
content = item.content or ''
rows.append(
{
'title': item.title,
'url': item.url,
'content': content,
}
)
normalized['results'][q] = rows
rows: List[Dict[str, str]] = []
for item in resp.results:
content = item.content or ''
rows.append(
{
'title': item.title,
'url': item.url,
'content': content,
}
)
normalized['results'][query] = rows
search_page = self._build_search_results_page_collection(query, normalized)
self._save_page(search_page)
@ -430,7 +424,6 @@ class Browser:
if state.page_stack:
page = self._page_from_stack(state.page_stack[-1])
# Open by URL (string id)
if isinstance(id, str):
url = id
if url in state.url_to_page:
@ -439,35 +432,30 @@ class Browser:
page_text = self._display_page(state.url_to_page[url], cursor, loc, num_lines)
return {'state': self.get_state(), 'pageText': cap_tool_content(page_text)}
crawl_response = self._client.web_crawl([url])
# Normalize to dict shape used by page builders
normalized: Dict[str, Any] = {'results': {}}
for u, items in crawl_response.results.items():
rows: List[Dict[str, str]] = []
for item in items:
content = item.content or ''
rows.append(
fetch_response = self._client.web_fetch(url)
normalized: Dict[str, Any] = {
'results': {
url: [
{
'title': item.title,
'url': item.url,
'content': content,
'title': fetch_response.title or url,
'url': url,
'content': fetch_response.content or '',
}
)
normalized['results'][u] = rows
new_page = self._build_page_from_crawl(url, normalized)
]
}
}
new_page = self._build_page_from_fetch(url, normalized)
self._save_page(new_page)
cursor = len(self.get_state().page_stack) - 1
page_text = self._display_page(new_page, cursor, loc, num_lines)
return {'state': self.get_state(), 'pageText': cap_tool_content(page_text)}
# Open by link id (int) from current page
if isinstance(id, int):
if not page:
raise RuntimeError('No current page to resolve link from')
link_url = page.links.get(id)
if not link_url:
# build an error page like TS
err = Page(
url=f'invalid_link_{id}',
title=f'No link with id {id} on `{page.title}`',
@ -497,28 +485,25 @@ class Browser:
new_page = state.url_to_page.get(link_url)
if not new_page:
crawl_response = self._client.web_crawl([link_url])
normalized: Dict[str, Any] = {'results': {}}
for u, items in crawl_response.results.items():
rows: List[Dict[str, str]] = []
for item in items:
content = item.content or ''
rows.append(
fetch_response = self._client.web_fetch(link_url)
normalized: Dict[str, Any] = {
'results': {
link_url: [
{
'title': item.title,
'url': item.url,
'content': content,
'title': fetch_response.title or link_url,
'url': link_url,
'content': fetch_response.content or '',
}
)
normalized['results'][u] = rows
new_page = self._build_page_from_crawl(link_url, normalized)
]
}
}
new_page = self._build_page_from_fetch(link_url, normalized)
self._save_page(new_page)
cursor = len(self.get_state().page_stack) - 1
page_text = self._display_page(new_page, cursor, loc, num_lines)
return {'state': self.get_state(), 'pageText': cap_tool_content(page_text)}
# No id: just re-display the current page and advance stack
if not page:
raise RuntimeError('No current page to display')
@ -547,3 +532,5 @@ class Browser:
page_text = self._display_page(find_page, new_cursor, 0, -1)
return {'state': self.get_state(), 'pageText': cap_tool_content(page_text)}