From 0f2e7837a2bee96c85d1d10c2360016c64b4dfea Mon Sep 17 00:00:00 2001 From: ParthSareen Date: Mon, 7 Jul 2025 15:02:26 -0700 Subject: [PATCH 1/4] types/examples: add tool_name to message and examples --- examples/README.md | 1 + examples/multi-tool.py | 88 ++++++++++++++++++++++++++++++++++++++++++ ollama/_types.py | 3 ++ 3 files changed, 92 insertions(+) create mode 100644 examples/multi-tool.py diff --git a/examples/README.md b/examples/README.md index dbe480b5..b14d56f1 100644 --- a/examples/README.md +++ b/examples/README.md @@ -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 diff --git a/examples/multi-tool.py b/examples/multi-tool.py new file mode 100644 index 00000000..c2d6257d --- /dev/null +++ b/examples/multi-tool.py @@ -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') diff --git a/ollama/_types.py b/ollama/_types.py index f86c06d2..b53aea9f 100644 --- a/ollama/_types.py +++ b/ollama/_types.py @@ -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. From 7a09310f85c680909280518e00d28a8045063806 Mon Sep 17 00:00:00 2001 From: ParthSareen Date: Mon, 7 Jul 2025 15:56:36 -0700 Subject: [PATCH 2/4] add ignore to support current API: FBT001 Boolean-typed positional argument in function definition --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index ca1b992a..f666caeb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,6 +60,7 @@ select = [ 'FLY', # flynt 'RUF', # ruff-specific rules ] +ignore = ['FBT001'] [tool.pytest.ini_options] addopts = ['--doctest-modules'] From 0296031bb0b4cc7244eb2200e56cbd09d27161e0 Mon Sep 17 00:00:00 2001 From: ParthSareen Date: Mon, 7 Jul 2025 16:05:31 -0700 Subject: [PATCH 3/4] update existing tool examples --- examples/async-tools.py | 2 +- examples/tools.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/async-tools.py b/examples/async-tools.py index 55782298..16e123de 100644 --- a/examples/async-tools.py +++ b/examples/async-tools.py @@ -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) diff --git a/examples/tools.py b/examples/tools.py index d6f3fcf2..86019fd3 100644 --- a/examples/tools.py +++ b/examples/tools.py @@ -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) From ac9143d16b5fb6d2bc1250de91218805e42059be Mon Sep 17 00:00:00 2001 From: ParthSareen Date: Mon, 7 Jul 2025 16:07:18 -0700 Subject: [PATCH 4/4] add comment to ignore --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f666caeb..6e62fc91 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ select = [ 'FLY', # flynt 'RUF', # ruff-specific rules ] -ignore = ['FBT001'] +ignore = ['FBT001'] # Boolean-typed positional argument in function definition [tool.pytest.ini_options] addopts = ['--doctest-modules']