Skip to content

Commit ae33308

Browse files
committed
renamed + added functionality
1 parent 1c6afe4 commit ae33308

File tree

2 files changed

+99
-66
lines changed

2 files changed

+99
-66
lines changed

examples/browser_tool.py renamed to examples/gpt-oss-browser-tool.py

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,73 @@
33
import os
44
from typing import Any, Dict, List
55

6-
from browser_tool_helpers import Browser
6+
from gpt_oss_browser_tool_helper import Browser
77

88
from ollama import Client
99

1010

1111
def main() -> None:
12-
client = Client(headers={'Authorization': os.getenv('OLLAMA_API_KEY')})
12+
api_key = os.getenv('OLLAMA_API_KEY')
13+
if api_key:
14+
client = Client(headers={'Authorization': f'Bearer {api_key}'})
15+
else:
16+
client = Client()
1317
browser = Browser(initial_state=None, client=client)
1418

15-
# Minimal tool schemas
16-
browser_search_schema = {'type': 'function', 'function': {'name': 'browser.search'}}
17-
browser_open_schema = {'type': 'function', 'function': {'name': 'browser.open'}}
18-
browser_find_schema = {'type': 'function', 'function': {'name': 'browser.find'}}
19+
# Tool schemas
20+
browser_search_schema = {
21+
'type': 'function',
22+
'function': {
23+
'name': 'browser.search',
24+
'parameters': {
25+
'type': 'object',
26+
'properties': {
27+
'query': {'type': 'string'},
28+
'topn': {'type': 'integer'},
29+
},
30+
'required': ['query'],
31+
},
32+
},
33+
}
34+
35+
browser_open_schema = {
36+
'type': 'function',
37+
'function': {
38+
'name': 'browser.open',
39+
'parameters': {
40+
'type': 'object',
41+
'properties': {
42+
'id': {'anyOf': [{'type': 'integer'}, {'type': 'string'}]},
43+
'cursor': {'type': 'integer'},
44+
'loc': {'type': 'integer'},
45+
'num_lines': {'type': 'integer'},
46+
},
47+
},
48+
},
49+
}
50+
51+
browser_find_schema = {
52+
'type': 'function',
53+
'function': {
54+
'name': 'browser.find',
55+
'parameters': {
56+
'type': 'object',
57+
'properties': {
58+
'pattern': {'type': 'string'},
59+
'cursor': {'type': 'integer'},
60+
},
61+
'required': ['pattern'],
62+
},
63+
},
64+
}
1965

2066
def browser_search(query: str, topn: int = 10) -> str:
2167
return browser.search(query=query, topn=topn)['pageText']
2268

2369
def browser_open(id: int | str = -1, cursor: int = -1, loc: int = -1, num_lines: int = -1) -> str:
2470
return browser.open(id=id, cursor=cursor, loc=loc, num_lines=num_lines)['pageText']
2571

26-
def browser_find(pattern: str, cursor: int = -1) -> str:
72+
def browser_find(pattern: str, cursor: int = -1, **_: Any) -> str:
2773
return browser.find(pattern=pattern, cursor=cursor)['pageText']
2874

2975
available_tools = {
@@ -32,7 +78,7 @@ def browser_find(pattern: str, cursor: int = -1) -> str:
3278
'browser.find': browser_find,
3379
}
3480

35-
messages: List[Dict[str, Any]] = [{'role': 'user', 'content': 'What is Ollama?'}]
81+
messages: List[Dict[str, Any]] = [{'role': 'user', 'content': 'When did Ollama announce the new engine?'}]
3682
print('----- Prompt:', messages[0]['content'], '\n')
3783

3884
while True:

examples/browser_tool_helpers.py renamed to examples/gpt_oss_browser_tool_helper.py

Lines changed: 45 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import re
44
from dataclasses import dataclass, field
55
from datetime import datetime
6-
from typing import Any, Dict, List, Optional, Protocol, Tuple
6+
from typing import Any, Dict, List, Optional, Protocol, Tuple, Union
77
from urllib.parse import urlparse
88

99
from ollama import Client
@@ -30,7 +30,7 @@ class BrowserStateData:
3030
class WebSearchResult:
3131
title: str
3232
url: str
33-
content: Dict[str, str] # {"fullText": str}
33+
content: Dict[str, str]
3434

3535

3636
class SearchClient(Protocol):
@@ -94,7 +94,6 @@ def __init__(
9494
self.state = BrowserState(initial_state)
9595
self._client: Optional[Client] = client
9696

97-
# parity with TS: one setter that accepts both
9897
def set_client(self, client: Client) -> None:
9998
self._client = client
10099

@@ -160,10 +159,9 @@ def _process_markdown_links(self, text: str) -> Tuple[str, Dict[int, str]]:
160159
links: Dict[int, str] = {}
161160
link_id = 0
162161

163-
# collapse [text]\n(url) -> [text](url)
164162
multiline_pattern = re.compile(r'\[([^\]]+)\]\s*\n\s*\(([^)]+)\)')
165163
text = multiline_pattern.sub(lambda m: f'[{m.group(1)}]({m.group(2)})', text)
166-
text = re.sub(r'\s+', ' ', text) # mild cleanup from the above
164+
text = re.sub(r'\s+', ' ', text)
167165

168166
link_pattern = re.compile(r'\[([^\]]+)\]\(([^)]+)\)')
169167

@@ -185,7 +183,6 @@ def _get_end_loc(self, loc: int, num_lines: int, total_lines: int, lines: List[s
185183
txt = self._join_lines_with_numbers(lines[loc:])
186184
data = self.state.get_data()
187185
if len(txt) > data.view_tokens:
188-
# approximate char-per-token heuristic (keep identical to TS flow)
189186
max_chars_per_token = 128
190187
upper_bound = min((data.view_tokens + 1) * max_chars_per_token, len(txt))
191188
segment = txt[:upper_bound]
@@ -242,10 +239,10 @@ def _build_search_results_page_collection(self, query: str, results: Dict[str, A
242239
)
243240

244241
tb = []
245-
tb.append('') # L0 blank
246-
tb.append('URL: ') # L1 "URL: "
247-
tb.append('# Search Results') # L2
248-
tb.append('') # L3 blank
242+
tb.append('')
243+
tb.append('URL: ')
244+
tb.append('# Search Results')
245+
tb.append('')
249246

250247
link_idx = 0
251248
for query_results in results.get('results', {}).values():
@@ -276,7 +273,6 @@ def _build_search_result_page(self, result: WebSearchResult, link_idx: int) -> P
276273
fetched_at=datetime.utcnow(),
277274
)
278275

279-
# preview block (when no full text)
280276
link_fmt = f'【{link_idx}{result.title}\n'
281277
preview = link_fmt + f'URL: {result.url}\n'
282278
full_text = result.content.get('fullText', '') if result.content else ''
@@ -296,7 +292,7 @@ def _build_search_result_page(self, result: WebSearchResult, link_idx: int) -> P
296292
page.lines = self._wrap_lines(page.text, 80)
297293
return page
298294

299-
def _build_page_from_crawl(self, requested_url: str, crawl_response: Dict[str, Any]) -> Page:
295+
def _build_page_from_fetch(self, requested_url: str, fetch_response: Dict[str, Any]) -> Page:
300296
page = Page(
301297
url=requested_url,
302298
title=requested_url,
@@ -306,7 +302,7 @@ def _build_page_from_crawl(self, requested_url: str, crawl_response: Dict[str, A
306302
fetched_at=datetime.utcnow(),
307303
)
308304

309-
for url, url_results in crawl_response.get('results', {}).items():
305+
for url, url_results in fetch_response.get('results', {}).items():
310306
if url_results:
311307
r0 = url_results[0]
312308
if r0.get('content'):
@@ -372,22 +368,20 @@ def search(self, *, query: str, topn: int = 5) -> Dict[str, Any]:
372368
if not self._client:
373369
raise RuntimeError('Client not provided')
374370

375-
resp = self._client.web_search([query], max_results=topn)
371+
resp = self._client.web_search(query, max_results=topn)
376372

377-
# Normalize to dict shape used by page builders
378373
normalized: Dict[str, Any] = {'results': {}}
379-
for q, items in resp.results.items():
380-
rows: List[Dict[str, str]] = []
381-
for item in items:
382-
content = item.content or ''
383-
rows.append(
384-
{
385-
'title': item.title,
386-
'url': item.url,
387-
'content': content,
388-
}
389-
)
390-
normalized['results'][q] = rows
374+
rows: List[Dict[str, str]] = []
375+
for item in resp.results:
376+
content = item.content or ''
377+
rows.append(
378+
{
379+
'title': item.title,
380+
'url': item.url,
381+
'content': content,
382+
}
383+
)
384+
normalized['results'][query] = rows
391385

392386
search_page = self._build_search_results_page_collection(query, normalized)
393387
self._save_page(search_page)
@@ -430,7 +424,6 @@ def open(
430424
if state.page_stack:
431425
page = self._page_from_stack(state.page_stack[-1])
432426

433-
# Open by URL (string id)
434427
if isinstance(id, str):
435428
url = id
436429
if url in state.url_to_page:
@@ -439,35 +432,30 @@ def open(
439432
page_text = self._display_page(state.url_to_page[url], cursor, loc, num_lines)
440433
return {'state': self.get_state(), 'pageText': cap_tool_content(page_text)}
441434

442-
crawl_response = self._client.web_crawl([url])
443-
# Normalize to dict shape used by page builders
444-
normalized: Dict[str, Any] = {'results': {}}
445-
for u, items in crawl_response.results.items():
446-
rows: List[Dict[str, str]] = []
447-
for item in items:
448-
content = item.content or ''
449-
rows.append(
435+
fetch_response = self._client.web_fetch(url)
436+
normalized: Dict[str, Any] = {
437+
'results': {
438+
url: [
450439
{
451-
'title': item.title,
452-
'url': item.url,
453-
'content': content,
440+
'title': fetch_response.title or url,
441+
'url': url,
442+
'content': fetch_response.content or '',
454443
}
455-
)
456-
normalized['results'][u] = rows
457-
new_page = self._build_page_from_crawl(url, normalized)
444+
]
445+
}
446+
}
447+
new_page = self._build_page_from_fetch(url, normalized)
458448
self._save_page(new_page)
459449
cursor = len(self.get_state().page_stack) - 1
460450
page_text = self._display_page(new_page, cursor, loc, num_lines)
461451
return {'state': self.get_state(), 'pageText': cap_tool_content(page_text)}
462452

463-
# Open by link id (int) from current page
464453
if isinstance(id, int):
465454
if not page:
466455
raise RuntimeError('No current page to resolve link from')
467456

468457
link_url = page.links.get(id)
469458
if not link_url:
470-
# build an error page like TS
471459
err = Page(
472460
url=f'invalid_link_{id}',
473461
title=f'No link with id {id} on `{page.title}`',
@@ -497,28 +485,25 @@ def open(
497485

498486
new_page = state.url_to_page.get(link_url)
499487
if not new_page:
500-
crawl_response = self._client.web_crawl([link_url])
501-
normalized: Dict[str, Any] = {'results': {}}
502-
for u, items in crawl_response.results.items():
503-
rows: List[Dict[str, str]] = []
504-
for item in items:
505-
content = item.content or ''
506-
rows.append(
488+
fetch_response = self._client.web_fetch(link_url)
489+
normalized: Dict[str, Any] = {
490+
'results': {
491+
link_url: [
507492
{
508-
'title': item.title,
509-
'url': item.url,
510-
'content': content,
493+
'title': fetch_response.title or link_url,
494+
'url': link_url,
495+
'content': fetch_response.content or '',
511496
}
512-
)
513-
normalized['results'][u] = rows
514-
new_page = self._build_page_from_crawl(link_url, normalized)
497+
]
498+
}
499+
}
500+
new_page = self._build_page_from_fetch(link_url, normalized)
515501

516502
self._save_page(new_page)
517503
cursor = len(self.get_state().page_stack) - 1
518504
page_text = self._display_page(new_page, cursor, loc, num_lines)
519505
return {'state': self.get_state(), 'pageText': cap_tool_content(page_text)}
520506

521-
# No id: just re-display the current page and advance stack
522507
if not page:
523508
raise RuntimeError('No current page to display')
524509

@@ -547,3 +532,5 @@ def find(self, *, pattern: str, cursor: int = -1) -> Dict[str, Any]:
547532

548533
page_text = self._display_page(find_page, new_cursor, 0, -1)
549534
return {'state': self.get_state(), 'pageText': cap_tool_content(page_text)}
535+
536+

0 commit comments

Comments
 (0)