mirror of
https://github.com/ollama/ollama-python.git
synced 2026-06-11 18:54:47 +00:00
Compare commits
7 Commits
v0.5.3
...
parth/test
| Author | SHA1 | Date | |
|---|---|---|---|
| 4390741023 | |||
| 9f41447f20 | |||
| da79e987f0 | |||
| c8392d6524 | |||
| 07ab287cdf | |||
| b0f6b99ca6 | |||
| c87604c66f |
@@ -13,7 +13,7 @@ jobs:
|
||||
id-token: write
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
enable-cache: true
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
|
||||
+5
-3
@@ -28,8 +28,10 @@ See [ollama/docs/api.md](https://github.com/ollama/ollama/blob/main/docs/api.md)
|
||||
- [multi-tool.py](multi-tool.py) - Using multiple tools, with thinking enabled
|
||||
|
||||
#### gpt-oss
|
||||
- [gpt-oss-tools.py](gpt-oss-tools.py) - Using tools with gpt-oss
|
||||
- [gpt-oss-tools-stream.py](gpt-oss-tools-stream.py) - Using tools with gpt-oss, with streaming enabled
|
||||
- [gpt-oss-tools.py](gpt-oss-tools.py)
|
||||
- [gpt-oss-tools-stream.py](gpt-oss-tools-stream.py)
|
||||
- [gpt-oss-tools-browser.py](gpt-oss-tools-browser.py) - Using browser research tools with gpt-oss
|
||||
- [gpt-oss-tools-browser-stream.py](gpt-oss-tools-browser-stream.py) - Using browser research tools with gpt-oss, with streaming enabled
|
||||
|
||||
|
||||
### Multimodal with Images - Chat with a multimodal (image chat) model
|
||||
@@ -75,4 +77,4 @@ Requirement: `pip install tqdm`
|
||||
- [thinking-generate.py](thinking-generate.py)
|
||||
|
||||
### Thinking (levels) - Choose the thinking level
|
||||
- [thinking-levels.py](thinking-generate.py)
|
||||
- [thinking-levels.py](thinking-levels.py)
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = [
|
||||
# "gpt-oss",
|
||||
# "ollama",
|
||||
# "rich",
|
||||
# ]
|
||||
# ///
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from typing import Iterator, Optional
|
||||
|
||||
from gpt_oss.tools.simple_browser import ExaBackend, SimpleBrowserTool
|
||||
from openai_harmony import Author, Role, TextContent
|
||||
from openai_harmony import Message as HarmonyMessage
|
||||
from rich import print
|
||||
|
||||
from ollama import Client
|
||||
from ollama._types import ChatResponse
|
||||
|
||||
_backend = ExaBackend(source='web')
|
||||
_browser_tool = SimpleBrowserTool(backend=_backend)
|
||||
|
||||
|
||||
def heading(text):
|
||||
print(text)
|
||||
print('=' * (len(text) + 3))
|
||||
|
||||
|
||||
async def _browser_search_async(query: str, topn: int = 10, source: str | None = None) -> str:
|
||||
# map Ollama message to Harmony format
|
||||
harmony_message = HarmonyMessage(
|
||||
author=Author(role=Role.USER),
|
||||
content=[TextContent(text=json.dumps({'query': query, 'topn': topn}))],
|
||||
recipient='browser.search',
|
||||
)
|
||||
|
||||
result_text: str = ''
|
||||
async for response in _browser_tool._process(harmony_message):
|
||||
if response.content:
|
||||
for content in response.content:
|
||||
if isinstance(content, TextContent):
|
||||
result_text += content.text
|
||||
return result_text or f'No results for query: {query}'
|
||||
|
||||
|
||||
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 = None) -> str:
|
||||
payload = {'id': id, 'cursor': cursor, 'loc': loc, 'num_lines': num_lines, 'view_source': view_source, 'source': source}
|
||||
|
||||
harmony_message = HarmonyMessage(
|
||||
author=Author(role=Role.USER),
|
||||
content=[TextContent(text=json.dumps(payload))],
|
||||
recipient='browser.open',
|
||||
)
|
||||
|
||||
result_text: str = ''
|
||||
async for response in _browser_tool._process(harmony_message):
|
||||
if response.content:
|
||||
for content in response.content:
|
||||
if isinstance(content, TextContent):
|
||||
result_text += content.text
|
||||
return result_text or f'Could not open: {id}'
|
||||
|
||||
|
||||
async def _browser_find_async(pattern: str, cursor: int = -1) -> str:
|
||||
payload = {'pattern': pattern, 'cursor': cursor}
|
||||
|
||||
harmony_message = HarmonyMessage(
|
||||
author=Author(role=Role.USER),
|
||||
content=[TextContent(text=json.dumps(payload))],
|
||||
recipient='browser.find',
|
||||
)
|
||||
|
||||
result_text: str = ''
|
||||
async for response in _browser_tool._process(harmony_message):
|
||||
if response.content:
|
||||
for content in response.content:
|
||||
if isinstance(content, TextContent):
|
||||
result_text += content.text
|
||||
return result_text or f'Pattern not found: {pattern}'
|
||||
|
||||
|
||||
def browser_search(query: str, topn: int = 10, source: Optional[str] = None) -> str:
|
||||
return asyncio.run(_browser_search_async(query=query, topn=topn, source=source))
|
||||
|
||||
|
||||
def browser_open(id: int | str = -1, cursor: int = -1, loc: int = -1, num_lines: int = -1, *, view_source: bool = False, source: Optional[str] = None) -> str:
|
||||
return asyncio.run(_browser_open_async(id=id, cursor=cursor, loc=loc, num_lines=num_lines, view_source=view_source, source=source))
|
||||
|
||||
|
||||
def browser_find(pattern: str, cursor: int = -1) -> str:
|
||||
return asyncio.run(_browser_find_async(pattern=pattern, cursor=cursor))
|
||||
|
||||
|
||||
# Schema definitions for each browser tool
|
||||
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',
|
||||
},
|
||||
}
|
||||
|
||||
available_tools = {
|
||||
'browser.search': browser_search,
|
||||
'browser.open': browser_open,
|
||||
'browser.find': browser_find,
|
||||
}
|
||||
|
||||
|
||||
model = 'gpt-oss:20b'
|
||||
print('Model: ', model, '\n')
|
||||
|
||||
prompt = 'What is Ollama?'
|
||||
print('You: ', prompt, '\n')
|
||||
messages = [{'role': 'user', 'content': prompt}]
|
||||
|
||||
client = Client()
|
||||
|
||||
# gpt-oss can call tools while "thinking"
|
||||
# a loop is needed to call the tools and get the results
|
||||
final = True
|
||||
while True:
|
||||
response_stream: Iterator[ChatResponse] = client.chat(
|
||||
model=model,
|
||||
messages=messages,
|
||||
tools=[browser_search_schema, browser_open_schema, browser_find_schema],
|
||||
options={'num_ctx': 8192}, # 8192 is the recommended lower limit for the context window
|
||||
stream=True,
|
||||
)
|
||||
|
||||
tool_calls = []
|
||||
thinking = ''
|
||||
content = ''
|
||||
|
||||
for chunk in response_stream:
|
||||
if chunk.message.tool_calls:
|
||||
tool_calls.extend(chunk.message.tool_calls)
|
||||
|
||||
if chunk.message.content:
|
||||
if not (chunk.message.thinking or chunk.message.thinking == '') and final:
|
||||
heading('\n\nFinal result: ')
|
||||
final = False
|
||||
print(chunk.message.content, end='', flush=True)
|
||||
|
||||
if chunk.message.thinking:
|
||||
thinking += chunk.message.thinking
|
||||
print(chunk.message.thinking, end='', flush=True)
|
||||
|
||||
if thinking != '':
|
||||
messages.append({'role': 'assistant', 'content': thinking, 'tool_calls': tool_calls})
|
||||
|
||||
print()
|
||||
|
||||
if tool_calls:
|
||||
for tool_call in tool_calls:
|
||||
tool_name = tool_call.function.name
|
||||
args = tool_call.function.arguments or {}
|
||||
function_to_call = available_tools.get(tool_name)
|
||||
|
||||
if function_to_call:
|
||||
heading(f'\nCalling tool: {tool_name}')
|
||||
if args:
|
||||
print(f'Arguments: {args}')
|
||||
|
||||
try:
|
||||
result = function_to_call(**args)
|
||||
print(f'Tool result: {result[:200]}')
|
||||
if len(result) > 200:
|
||||
heading('... [truncated]')
|
||||
print()
|
||||
|
||||
result_message = {'role': 'tool', 'content': result, 'tool_name': tool_name}
|
||||
messages.append(result_message)
|
||||
|
||||
except Exception as e:
|
||||
err = f'Error from {tool_name}: {e}'
|
||||
print(err)
|
||||
messages.append({'role': 'tool', 'content': err, 'tool_name': tool_name})
|
||||
else:
|
||||
print(f'Tool {tool_name} not found')
|
||||
else:
|
||||
# no more tool calls, we can stop the loop
|
||||
break
|
||||
@@ -0,0 +1,175 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = [
|
||||
# "gpt-oss",
|
||||
# "ollama",
|
||||
# "rich",
|
||||
# ]
|
||||
# ///
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from typing import Optional
|
||||
|
||||
from gpt_oss.tools.simple_browser import ExaBackend, SimpleBrowserTool
|
||||
from openai_harmony import Author, Role, TextContent
|
||||
from openai_harmony import Message as HarmonyMessage
|
||||
|
||||
from ollama import Client
|
||||
|
||||
_backend = ExaBackend(source='web')
|
||||
_browser_tool = SimpleBrowserTool(backend=_backend)
|
||||
|
||||
|
||||
def heading(text):
|
||||
print(text)
|
||||
print('=' * (len(text) + 3))
|
||||
|
||||
|
||||
async def _browser_search_async(query: str, topn: int = 10, source: str | None = None) -> str:
|
||||
# map Ollama message to Harmony format
|
||||
harmony_message = HarmonyMessage(
|
||||
author=Author(role=Role.USER),
|
||||
content=[TextContent(text=json.dumps({'query': query, 'topn': topn}))],
|
||||
recipient='browser.search',
|
||||
)
|
||||
|
||||
result_text: str = ''
|
||||
async for response in _browser_tool._process(harmony_message):
|
||||
if response.content:
|
||||
for content in response.content:
|
||||
if isinstance(content, TextContent):
|
||||
result_text += content.text
|
||||
return result_text or f'No results for query: {query}'
|
||||
|
||||
|
||||
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 = None) -> str:
|
||||
payload = {'id': id, 'cursor': cursor, 'loc': loc, 'num_lines': num_lines, 'view_source': view_source, 'source': source}
|
||||
|
||||
harmony_message = HarmonyMessage(
|
||||
author=Author(role=Role.USER),
|
||||
content=[TextContent(text=json.dumps(payload))],
|
||||
recipient='browser.open',
|
||||
)
|
||||
|
||||
result_text: str = ''
|
||||
async for response in _browser_tool._process(harmony_message):
|
||||
if response.content:
|
||||
for content in response.content:
|
||||
if isinstance(content, TextContent):
|
||||
result_text += content.text
|
||||
return result_text or f'Could not open: {id}'
|
||||
|
||||
|
||||
async def _browser_find_async(pattern: str, cursor: int = -1) -> str:
|
||||
payload = {'pattern': pattern, 'cursor': cursor}
|
||||
|
||||
harmony_message = HarmonyMessage(
|
||||
author=Author(role=Role.USER),
|
||||
content=[TextContent(text=json.dumps(payload))],
|
||||
recipient='browser.find',
|
||||
)
|
||||
|
||||
result_text: str = ''
|
||||
async for response in _browser_tool._process(harmony_message):
|
||||
if response.content:
|
||||
for content in response.content:
|
||||
if isinstance(content, TextContent):
|
||||
result_text += content.text
|
||||
return result_text or f'Pattern not found: {pattern}'
|
||||
|
||||
|
||||
def browser_search(query: str, topn: int = 10, source: Optional[str] = None) -> str:
|
||||
return asyncio.run(_browser_search_async(query=query, topn=topn, source=source))
|
||||
|
||||
|
||||
def browser_open(id: int | str = -1, cursor: int = -1, loc: int = -1, num_lines: int = -1, *, view_source: bool = False, source: Optional[str] = None) -> str:
|
||||
return asyncio.run(_browser_open_async(id=id, cursor=cursor, loc=loc, num_lines=num_lines, view_source=view_source, source=source))
|
||||
|
||||
|
||||
def browser_find(pattern: str, cursor: int = -1) -> str:
|
||||
return asyncio.run(_browser_find_async(pattern=pattern, cursor=cursor))
|
||||
|
||||
|
||||
# Schema definitions for each browser tool
|
||||
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',
|
||||
},
|
||||
}
|
||||
|
||||
available_tools = {
|
||||
'browser.search': browser_search,
|
||||
'browser.open': browser_open,
|
||||
'browser.find': browser_find,
|
||||
}
|
||||
|
||||
|
||||
model = 'gpt-oss:20b'
|
||||
print('Model: ', model, '\n')
|
||||
|
||||
prompt = 'What is Ollama?'
|
||||
print('You: ', prompt, '\n')
|
||||
messages = [{'role': 'user', 'content': prompt}]
|
||||
|
||||
client = Client()
|
||||
while True:
|
||||
response = client.chat(
|
||||
model=model,
|
||||
messages=messages,
|
||||
tools=[browser_search_schema, browser_open_schema, browser_find_schema],
|
||||
options={'num_ctx': 8192}, # 8192 is the recommended lower limit for the context window
|
||||
)
|
||||
|
||||
if hasattr(response.message, 'thinking') and response.message.thinking:
|
||||
heading('Thinking')
|
||||
print(response.message.thinking.strip() + '\n')
|
||||
|
||||
if hasattr(response.message, 'content') and response.message.content:
|
||||
heading('Assistant')
|
||||
print(response.message.content.strip() + '\n')
|
||||
|
||||
# add message to chat history
|
||||
messages.append(response.message)
|
||||
|
||||
if response.message.tool_calls:
|
||||
for tool_call in response.message.tool_calls:
|
||||
tool_name = tool_call.function.name
|
||||
args = tool_call.function.arguments or {}
|
||||
function_to_call = available_tools.get(tool_name)
|
||||
if not function_to_call:
|
||||
print(f'Unknown tool: {tool_name}')
|
||||
continue
|
||||
|
||||
try:
|
||||
result = function_to_call(**args)
|
||||
heading(f'Tool: {tool_name}')
|
||||
if args:
|
||||
print(f'Arguments: {args}')
|
||||
print(result[:200])
|
||||
if len(result) > 200:
|
||||
print('... [truncated]')
|
||||
print()
|
||||
messages.append({'role': 'tool', 'content': result, 'tool_name': tool_name})
|
||||
except Exception as e:
|
||||
err = f'Error from {tool_name}: {e}'
|
||||
print(err)
|
||||
messages.append({'role': 'tool', 'content': err, 'tool_name': tool_name})
|
||||
else:
|
||||
# break on no more tool calls
|
||||
break
|
||||
@@ -1,7 +1,17 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = [
|
||||
# "gpt-oss",
|
||||
# "ollama",
|
||||
# "rich",
|
||||
# ]
|
||||
# ///
|
||||
import random
|
||||
from typing import Iterator
|
||||
|
||||
from ollama import chat
|
||||
from rich import print
|
||||
|
||||
from ollama import Client
|
||||
from ollama._types import ChatResponse
|
||||
|
||||
|
||||
@@ -40,37 +50,55 @@ available_tools = {'get_weather': get_weather, 'get_weather_conditions': get_wea
|
||||
|
||||
messages = [{'role': 'user', 'content': 'What is the weather like in London? What are the conditions in Toronto?'}]
|
||||
|
||||
client = Client(
|
||||
# Ollama Turbo
|
||||
# host="https://ollama.com", headers={'Authorization': (os.getenv('OLLAMA_API_KEY'))}
|
||||
)
|
||||
|
||||
model = 'gpt-oss:20b'
|
||||
# gpt-oss can call tools while "thinking"
|
||||
# a loop is needed to call the tools and get the results
|
||||
final = True
|
||||
while True:
|
||||
response_stream: Iterator[ChatResponse] = chat(model=model, messages=messages, tools=[get_weather, get_weather_conditions], stream=True)
|
||||
response_stream: Iterator[ChatResponse] = client.chat(model=model, messages=messages, tools=[get_weather, get_weather_conditions], stream=True)
|
||||
tool_calls = []
|
||||
thinking = ''
|
||||
content = ''
|
||||
|
||||
for chunk in response_stream:
|
||||
if chunk.message.tool_calls:
|
||||
tool_calls.extend(chunk.message.tool_calls)
|
||||
|
||||
if chunk.message.content:
|
||||
if not (chunk.message.thinking or chunk.message.thinking == '') and final:
|
||||
print('\nFinal result: ')
|
||||
print('\n\n' + '=' * 10)
|
||||
print('Final result: ')
|
||||
final = False
|
||||
print(chunk.message.content, end='', flush=True)
|
||||
|
||||
if chunk.message.thinking:
|
||||
# accumulate thinking
|
||||
thinking += chunk.message.thinking
|
||||
print(chunk.message.thinking, end='', flush=True)
|
||||
|
||||
if thinking != '' or content != '' or len(tool_calls) > 0:
|
||||
messages.append({'role': 'assistant', 'thinking': thinking, 'content': content, 'tool_calls': tool_calls})
|
||||
|
||||
print()
|
||||
|
||||
if chunk.message.tool_calls:
|
||||
for tool_call in chunk.message.tool_calls:
|
||||
if tool_calls:
|
||||
for tool_call in tool_calls:
|
||||
function_to_call = available_tools.get(tool_call.function.name)
|
||||
if function_to_call:
|
||||
print('\nCalling tool: ', tool_call.function.name, 'with arguments: ', tool_call.function.arguments)
|
||||
print('\nCalling tool:', tool_call.function.name, 'with arguments: ', tool_call.function.arguments)
|
||||
result = function_to_call(**tool_call.function.arguments)
|
||||
print('Tool result: ', result + '\n')
|
||||
|
||||
messages.append(chunk.message)
|
||||
messages.append({'role': 'tool', 'content': result, 'tool_name': tool_call.function.name})
|
||||
result_message = {'role': 'tool', 'content': result, 'tool_name': tool_call.function.name}
|
||||
messages.append(result_message)
|
||||
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})
|
||||
|
||||
else:
|
||||
# no more tool calls, we can stop the loop
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = [
|
||||
# "gpt-oss",
|
||||
# "ollama",
|
||||
# "rich",
|
||||
# ]
|
||||
# ///
|
||||
import random
|
||||
|
||||
from ollama import chat
|
||||
from rich import print
|
||||
|
||||
from ollama import Client
|
||||
from ollama._types import ChatResponse
|
||||
|
||||
|
||||
@@ -40,11 +50,15 @@ available_tools = {'get_weather': get_weather, 'get_weather_conditions': get_wea
|
||||
messages = [{'role': 'user', 'content': 'What is the weather like in London? What are the conditions in Toronto?'}]
|
||||
|
||||
|
||||
client = Client(
|
||||
# Ollama Turbo
|
||||
# host="https://ollama.com", headers={'Authorization': (os.getenv('OLLAMA_API_KEY'))}
|
||||
)
|
||||
model = 'gpt-oss:20b'
|
||||
# gpt-oss can call tools while "thinking"
|
||||
# a loop is needed to call the tools and get the results
|
||||
while True:
|
||||
response: ChatResponse = chat(model=model, messages=messages, tools=[get_weather, get_weather_conditions])
|
||||
response: ChatResponse = client.chat(model=model, messages=messages, tools=[get_weather, get_weather_conditions])
|
||||
|
||||
if response.message.content:
|
||||
print('Content: ')
|
||||
@@ -53,18 +67,18 @@ while True:
|
||||
print('Thinking: ')
|
||||
print(response.message.thinking + '\n')
|
||||
|
||||
messages.append(response.message)
|
||||
|
||||
if response.message.tool_calls:
|
||||
for tool_call in response.message.tool_calls:
|
||||
function_to_call = available_tools.get(tool_call.function.name)
|
||||
if function_to_call:
|
||||
result = function_to_call(**tool_call.function.arguments)
|
||||
print('Result from tool call name: ', tool_call.function.name, 'with arguments: ', tool_call.function.arguments, 'result: ', result + '\n')
|
||||
|
||||
messages.append(response.message)
|
||||
messages.append({'role': 'tool', 'content': result, '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})
|
||||
else:
|
||||
# no more tool calls, we can stop the loop
|
||||
break
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
from ollama._client import AsyncClient, Client
|
||||
|
||||
from ollama._browser import (
|
||||
Browser
|
||||
)
|
||||
from ollama._types import (
|
||||
ChatResponse,
|
||||
EmbeddingsResponse,
|
||||
@@ -15,6 +19,8 @@ from ollama._types import (
|
||||
ShowResponse,
|
||||
StatusResponse,
|
||||
Tool,
|
||||
WebSearchResponse,
|
||||
WebCrawlResponse,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
@@ -35,6 +41,9 @@ __all__ = [
|
||||
'ShowResponse',
|
||||
'StatusResponse',
|
||||
'Tool',
|
||||
'WebSearchResponse',
|
||||
'WebCrawlResponse',
|
||||
'Browser',
|
||||
]
|
||||
|
||||
_client = Client()
|
||||
@@ -51,3 +60,5 @@ list = _client.list
|
||||
copy = _client.copy
|
||||
show = _client.show
|
||||
ps = _client.ps
|
||||
websearch = _client.websearch
|
||||
webcrawl = _client.webcrawl
|
||||
@@ -66,6 +66,10 @@ from ollama._types import (
|
||||
ShowResponse,
|
||||
StatusResponse,
|
||||
Tool,
|
||||
WebCrawlRequest,
|
||||
WebCrawlResponse,
|
||||
WebSearchRequest,
|
||||
WebSearchResponse,
|
||||
)
|
||||
|
||||
T = TypeVar('T')
|
||||
@@ -102,6 +106,8 @@ class BaseClient:
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'User-Agent': f'ollama-python/{__version__} ({platform.machine()} {platform.system().lower()}) Python/{platform.python_version()}',
|
||||
# TODO: this is to make the client feel good
|
||||
'Authorization': f'Bearer {(headers or {}).get("Authorization") or os.getenv("OLLAMA_API_KEY")}' if (headers or {}).get("Authorization") or os.getenv("OLLAMA_API_KEY") else None,
|
||||
}.items()
|
||||
},
|
||||
**kwargs,
|
||||
@@ -622,6 +628,45 @@ class Client(BaseClient):
|
||||
'/api/ps',
|
||||
)
|
||||
|
||||
def websearch(self, query: str, max_results: int = 3) -> WebSearchResponse:
|
||||
"""
|
||||
Perform a web search using ollama.com.
|
||||
|
||||
Args:
|
||||
query: The query to search for max_results: The maximum number of results to return.
|
||||
|
||||
Returns:
|
||||
WebSearchResponse with the search results
|
||||
"""
|
||||
return self._request(
|
||||
WebSearchResponse,
|
||||
'POST',
|
||||
'https://ollama.com/api/web_search',
|
||||
json=WebSearchRequest(
|
||||
queries=[query],
|
||||
max_results=max_results,
|
||||
).model_dump(exclude_none=True),
|
||||
)
|
||||
|
||||
def webcrawl(self, urls: Sequence[str]) -> WebCrawlResponse:
|
||||
"""
|
||||
Gets the content of web pages for the provided URLs.
|
||||
|
||||
Args:
|
||||
urls: The URLs to crawl
|
||||
|
||||
Returns:
|
||||
WebCrawlResponse with the crawl results
|
||||
"""
|
||||
return self._request(
|
||||
WebCrawlResponse,
|
||||
'POST',
|
||||
'https://ollama.com/api/web_crawl',
|
||||
json=WebCrawlRequest(
|
||||
urls=urls,
|
||||
).model_dump(exclude_none=True),
|
||||
)
|
||||
|
||||
|
||||
class AsyncClient(BaseClient):
|
||||
def __init__(self, host: Optional[str] = None, **kwargs) -> None:
|
||||
|
||||
@@ -538,6 +538,103 @@ class ProcessResponse(SubscriptableBaseModel):
|
||||
models: Sequence[Model]
|
||||
|
||||
|
||||
class WebSearchRequest(SubscriptableBaseModel):
|
||||
queries: Sequence[str]
|
||||
max_results: Optional[int] = None
|
||||
|
||||
class SearchResult(SubscriptableBaseModel):
|
||||
title: str
|
||||
url: str
|
||||
content: str
|
||||
metadata: Optional['SearchResultMetadata'] = None
|
||||
|
||||
|
||||
class CrawlResult(SubscriptableBaseModel):
|
||||
title: str
|
||||
url: str
|
||||
content: str
|
||||
links: Optional[Sequence[str]] = None
|
||||
metadata: Optional['CrawlResultMetadata'] = None
|
||||
|
||||
|
||||
class SearchResultContent(SubscriptableBaseModel):
|
||||
snippet: str
|
||||
full_text: str
|
||||
|
||||
|
||||
class SearchResultMetadata(SubscriptableBaseModel):
|
||||
published_date: Optional[str] = None
|
||||
author: Optional[str] = None
|
||||
|
||||
|
||||
class WebSearchResponse(SubscriptableBaseModel):
|
||||
results: Mapping[str, Sequence[SearchResult]]
|
||||
success: bool
|
||||
errors: Optional[Sequence[str]] = None
|
||||
|
||||
def __str__(self) -> str:
|
||||
if not self.success:
|
||||
error_msg = ', '.join(self.errors) if self.errors else 'Unknown error'
|
||||
return f'Web search failed: {error_msg}'
|
||||
|
||||
output = []
|
||||
for query, search_results in self.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}')
|
||||
if result.metadata and result.metadata.published_date:
|
||||
output.append(f' Published: {result.metadata.published_date}')
|
||||
if result.metadata and result.metadata.author:
|
||||
output.append(f' Author: {result.metadata.author}')
|
||||
output.append('')
|
||||
|
||||
return '\n'.join(output).rstrip()
|
||||
|
||||
class WebCrawlRequest(SubscriptableBaseModel):
|
||||
urls: Sequence[str]
|
||||
|
||||
|
||||
class CrawlResultContent(SubscriptableBaseModel):
|
||||
# provides the first 200 characters of the full text
|
||||
snippet: str
|
||||
full_text: str
|
||||
|
||||
|
||||
class CrawlResultMetadata(SubscriptableBaseModel):
|
||||
published_date: Optional[str] = None
|
||||
author: Optional[str] = None
|
||||
|
||||
|
||||
class WebCrawlResponse(SubscriptableBaseModel):
|
||||
results: Mapping[str, Sequence[CrawlResult]]
|
||||
success: bool
|
||||
errors: Optional[Sequence[str]] = None
|
||||
|
||||
def __str__(self) -> str:
|
||||
if not self.success:
|
||||
error_msg = ', '.join(self.errors) if self.errors else 'Unknown error'
|
||||
return f'Web crawl failed: {error_msg}'
|
||||
|
||||
output = []
|
||||
for url, crawl_results in self.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)}')
|
||||
if result.metadata and result.metadata.published_date:
|
||||
output.append(f' Published: {result.metadata.published_date}')
|
||||
if result.metadata and result.metadata.author:
|
||||
output.append(f' Author: {result.metadata.author}')
|
||||
output.append('')
|
||||
|
||||
return '\n'.join(output).rstrip()
|
||||
|
||||
|
||||
class RequestError(Exception):
|
||||
"""
|
||||
Common class for request errors.
|
||||
|
||||
Reference in New Issue
Block a user