Skip to content

Commit 73ad8e8

Browse files
committed
examples: add gpt-oss browser example
1 parent 53ff3cd commit 73ad8e8

File tree

2 files changed

+185
-0
lines changed

2 files changed

+185
-0
lines changed

examples/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ See [ollama/docs/api.md](https://github.com/ollama/ollama/blob/main/docs/api.md)
3030
#### gpt-oss
3131
- [gpt-oss-tools.py](gpt-oss-tools.py) - Using tools with gpt-oss
3232
- [gpt-oss-tools-stream.py](gpt-oss-tools-stream.py) - Using tools with gpt-oss, with streaming enabled
33+
- [gpt-oss-tools-browser.py](gpt-oss-tools-browser.py) - Using browser tools with gpt-oss
3334

3435

3536
### Multimodal with Images - Chat with a multimodal (image chat) model

examples/gpt-oss-tools-browser.py

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# /// script
2+
# requires-python = ">=3.11"
3+
# dependencies = [
4+
# "gpt-oss",
5+
# "ollama",
6+
# ]
7+
# ///
8+
9+
import asyncio
10+
import json
11+
12+
from gpt_oss.tools.simple_browser import SimpleBrowserTool, ExaBackend
13+
from openai_harmony import Author, Role, TextContent
14+
from openai_harmony import Message as HarmonyMessage
15+
16+
from ollama import chat, Client
17+
18+
_backend = ExaBackend(source='web')
19+
_browser_tool = SimpleBrowserTool(backend=_backend)
20+
21+
22+
def heading(text):
23+
print(text)
24+
print('=' * (len(text) +3))
25+
26+
27+
async def _browser_search_async(query: str, topn: int = 10, source: str = None) -> str:
28+
# map Ollama message to Harmony format
29+
harmony_message = HarmonyMessage(
30+
author=Author(role=Role.USER),
31+
content=[TextContent(text=json.dumps({'query': query, 'topn': topn}))],
32+
recipient='browser.search',
33+
)
34+
35+
result_text: str = ''
36+
async for response in _browser_tool._process(harmony_message):
37+
if response.content:
38+
for content in response.content:
39+
if isinstance(content, TextContent):
40+
result_text += content.text
41+
return result_text or f'No results for query: {query}'
42+
43+
44+
async def _browser_open_async(id: int | str = -1, cursor: int = -1, loc: int = -1, num_lines: int = -1, view_source: bool = False, source: str = None) -> str:
45+
payload = {
46+
'id': id,
47+
'cursor': cursor,
48+
'loc': loc,
49+
'num_lines': num_lines,
50+
'view_source': view_source,
51+
'source': source
52+
}
53+
54+
harmony_message = HarmonyMessage(
55+
author=Author(role=Role.USER),
56+
content=[TextContent(text=json.dumps(payload))],
57+
recipient='browser.open',
58+
)
59+
60+
result_text: str = ''
61+
async for response in _browser_tool._process(harmony_message):
62+
if response.content:
63+
for content in response.content:
64+
if isinstance(content, TextContent):
65+
result_text += content.text
66+
return result_text or f'Could not open: {id}'
67+
68+
69+
async def _browser_find_async(pattern: str, cursor: int = None, page: int = None) -> str:
70+
payload = {'pattern': pattern}
71+
72+
if cursor is not None:
73+
payload['cursor'] = cursor
74+
if page is not None:
75+
payload['page'] = page
76+
77+
harmony_message = HarmonyMessage(
78+
author=Author(role=Role.USER),
79+
content=[TextContent(text=json.dumps(payload))],
80+
recipient='browser.find',
81+
)
82+
83+
result_text: str = ''
84+
async for response in _browser_tool._process(harmony_message):
85+
if response.content:
86+
for content in response.content:
87+
if isinstance(content, TextContent):
88+
result_text += content.text
89+
return result_text or f'Pattern not found: {pattern}'
90+
91+
92+
def browser_search(query: str, topn: int = 10, source: str = None) -> str:
93+
return asyncio.run(_browser_search_async(query=query, topn=topn, source=source))
94+
95+
96+
def browser_open(id: int | str = -1, cursor: int = -1, loc: int = -1, num_lines: int = -1, view_source: bool = False, source: str = None) -> str:
97+
return asyncio.run(_browser_open_async(id=id, cursor=cursor, loc=loc, num_lines=num_lines, view_source=view_source, source=source))
98+
99+
100+
def browser_find(pattern: str= None, cursor: int = None, page: int = None) -> str:
101+
return asyncio.run(_browser_find_async(pattern=pattern, cursor=cursor, page=page))
102+
103+
104+
model = 'gpt-oss:20b'
105+
106+
# Schema definitions for each browser tool
107+
browser_search_schema = {
108+
'type': 'function',
109+
'function': {
110+
'name': 'browser.search',
111+
},
112+
}
113+
114+
browser_open_schema = {
115+
'type': 'function',
116+
'function': {
117+
'name': 'browser.open',
118+
},
119+
}
120+
121+
browser_find_schema = {
122+
'type': 'function',
123+
'function': {
124+
'name': 'browser.find',
125+
},
126+
}
127+
128+
available_tools = {
129+
'browser.search': browser_search,
130+
'browser.open': browser_open,
131+
'browser.find': browser_find,
132+
}
133+
134+
135+
prompt = 'What is Ollama?'
136+
print('You: ', prompt, '\n')
137+
messages = [{'role': 'user', 'content': prompt}]
138+
139+
client = Client()
140+
while True:
141+
response = client.chat(
142+
model=model,
143+
messages=messages,
144+
tools=[browser_search_schema, browser_open_schema, browser_find_schema],
145+
options={'num_ctx': 8192} # 8192 is the recommended lower limit for the context window
146+
)
147+
148+
if hasattr(response.message, 'thinking') and response.message.thinking:
149+
heading('Thinking')
150+
print(response.message.thinking.strip() + '\n')
151+
152+
if hasattr(response.message, 'content') and response.message.content:
153+
heading('Assistant')
154+
print(response.message.content.strip() + '\n')
155+
156+
# add message to chat history
157+
messages.append(response.message)
158+
159+
if response.message.tool_calls:
160+
for tool_call in response.message.tool_calls:
161+
tool_name = tool_call.function.name
162+
args = tool_call.function.arguments or {}
163+
function_to_call = available_tools.get(tool_name)
164+
if not function_to_call:
165+
print(f'Unknown tool: {tool_name}')
166+
continue
167+
168+
try:
169+
result = function_to_call(**args)
170+
heading(f'Tool: {tool_name}')
171+
if args:
172+
print(f'Arguments: {args}')
173+
print(result[:200])
174+
if len(result) > 200:
175+
print('... [truncated]')
176+
print()
177+
messages.append({'role': 'tool', 'content': result, 'tool_name': tool_name})
178+
except Exception as e:
179+
err = f'Error from {tool_name}: {e}'
180+
print(err)
181+
messages.append({'role': 'tool', 'content': err, 'tool_name': tool_name})
182+
else:
183+
# break on no more tool calls
184+
break

0 commit comments

Comments
 (0)