Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ See [ollama/docs/api.md](https://github.com/ollama/ollama/blob/main/docs/api.md)
### Tools/Function Calling - Call a function with a model
- [tools.py](tools.py) - Simple example of Tools/Function Calling
- [async-tools.py](async-tools.py)
- [multi-tool.py](multi-tool.py) - Using multiple tools, with thinking enabled


### Multimodal with Images - Chat with a multimodal (image chat) model
Expand Down
2 changes: 1 addition & 1 deletion examples/async-tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ async def main():
if response.message.tool_calls:
# Add the function response to messages for the model to use
messages.append(response.message)
messages.append({'role': 'tool', 'content': str(output), 'name': tool.function.name})
messages.append({'role': 'tool', 'content': str(output), 'tool_name': tool.function.name})

# Get final response from model with function outputs
final_response = await client.chat('llama3.1', messages=messages)
Expand Down
88 changes: 88 additions & 0 deletions examples/multi-tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import random
from typing import Iterator

from ollama import ChatResponse, Client


def get_temperature(city: str) -> int:
"""
Get the temperature for a city in Celsius

Args:
city (str): The name of the city

Returns:
int: The current temperature in Celsius
"""
# This is a mock implementation - would need to use a real weather API
import random

if city not in ['London', 'Paris', 'New York', 'Tokyo', 'Sydney']:
return 'Unknown city'

return str(random.randint(0, 35)) + ' degrees Celsius'


def get_conditions(city: str) -> str:
"""
Get the weather conditions for a city
"""
if city not in ['London', 'Paris', 'New York', 'Tokyo', 'Sydney']:
return 'Unknown city'
# This is a mock implementation - would need to use a real weather API
conditions = ['sunny', 'cloudy', 'rainy', 'snowy']
return random.choice(conditions)


available_functions = {
'get_temperature': get_temperature,
'get_conditions': get_conditions,
}


cities = ['London', 'Paris', 'New York', 'Tokyo', 'Sydney']
city = random.choice(cities)
city2 = random.choice(cities)
messages = [{'role': 'user', 'content': f'What is the temperature in {city}? and what are the weather conditions in {city2}?'}]
print('----- Prompt:', messages[0]['content'], '\n')

model = 'qwen3'
client = Client()
response: Iterator[ChatResponse] = client.chat(model, stream=True, messages=messages, tools=[get_temperature, get_conditions], think=True)

for chunk in response:
if chunk.message.thinking:
print(chunk.message.thinking, end='', flush=True)
if chunk.message.content:
print(chunk.message.content, end='', flush=True)
if chunk.message.tool_calls:
for tool in chunk.message.tool_calls:
if function_to_call := available_functions.get(tool.function.name):
print('\nCalling function:', tool.function.name, 'with arguments:', tool.function.arguments)
output = function_to_call(**tool.function.arguments)
print('> Function output:', output, '\n')

# Add the assistant message and tool call result to the messages
messages.append(chunk.message)
messages.append({'role': 'tool', 'content': str(output), 'tool_name': tool.function.name})
else:
print('Function', tool.function.name, 'not found')

print('----- Sending result back to model \n')
if any(msg.get('role') == 'tool' for msg in messages):
res = client.chat(model, stream=True, tools=[get_temperature, get_conditions], messages=messages, think=True)
done_thinking = False
for chunk in res:
if chunk.message.thinking:
print(chunk.message.thinking, end='', flush=True)
if chunk.message.content:
if not done_thinking:
print('\n----- Final result:')
done_thinking = True
print(chunk.message.content, end='', flush=True)
if chunk.message.tool_calls:
# Model should be explaining the tool calls and the results in this output
print('Model returned tool calls:')
print(chunk.message.tool_calls)
else:
print('No tool calls returned')
2 changes: 1 addition & 1 deletion examples/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def subtract_two_numbers(a: int, b: int) -> int:
if response.message.tool_calls:
# Add the function response to messages for the model to use
messages.append(response.message)
messages.append({'role': 'tool', 'content': str(output), 'name': tool.function.name})
messages.append({'role': 'tool', 'content': str(output), 'tool_name': tool.function.name})

# Get final response from model with function outputs
final_response = chat('llama3.1', messages=messages)
Expand Down
3 changes: 3 additions & 0 deletions ollama/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,9 @@ class Message(SubscriptableBaseModel):
Valid image formats depend on the model. See the model card for more information.
"""

tool_name: Optional[str] = None
'Name of the executed tool.'

class ToolCall(SubscriptableBaseModel):
"""
Model tool calls.
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ select = [
'FLY', # flynt
'RUF', # ruff-specific rules
]
ignore = ['FBT001'] # Boolean-typed positional argument in function definition

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @mxyng


[tool.pytest.ini_options]
addopts = ['--doctest-modules']