From 9e394287d455abf0952dcc88551ca11a783b2699 Mon Sep 17 00:00:00 2001 From: Shayan Ali <217162076+shayanxcyber@users.noreply.github.com> Date: Wed, 14 Oct 2026 15:49:15 +1100 Subject: [PATCH 1/2] Respect default arguments in function tool schemas --- ollama/_utils.py | 9 ++++++--- tests/test_utils.py | 26 ++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/ollama/_utils.py b/ollama/_utils.py index 15f1cc0c..93e756c7 100644 --- a/ollama/_utils.py +++ b/ollama/_utils.py @@ -54,15 +54,17 @@ def _parse_docstring(doc_string: Union[str, None]) -> dict[str, str]: def convert_function_to_tool(func: Callable) -> Tool: + signature = inspect.signature(func) doc_string_hash = str(hash(inspect.getdoc(func))) parsed_docstring = _parse_docstring(inspect.getdoc(func)) schema = type( func.__name__, (pydantic.BaseModel,), { - '__annotations__': {k: v.annotation if v.annotation != inspect._empty else str for k, v in inspect.signature(func).parameters.items()}, - '__signature__': inspect.signature(func), + '__annotations__': {k: v.annotation if v.annotation != inspect._empty else str for k, v in signature.parameters.items()}, + '__signature__': signature, '__doc__': parsed_docstring[doc_string_hash], + **{k: v.default for k, v in signature.parameters.items() if v.default != inspect.Parameter.empty}, }, ).model_json_schema() @@ -70,7 +72,8 @@ def convert_function_to_tool(func: Callable) -> Tool: # If type is missing, the default is string types = {t.get('type', 'string') for t in v.get('anyOf')} if 'anyOf' in v else {v.get('type', 'string')} if 'null' in types: - schema['required'].remove(k) + if k in schema.get('required', []): + schema['required'].remove(k) types.discard('null') schema['properties'][k] = { diff --git a/tests/test_utils.py b/tests/test_utils.py index cb9e0d4f..e287bff2 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -5,6 +5,32 @@ from ollama._utils import convert_function_to_tool +def test_function_defaults_are_not_required(): + def search(query: str, limit: int = 5): + return query, limit + + parameters = convert_function_to_tool(search).function.parameters + assert parameters.required == ['query'] + assert parameters.properties['limit'].type == 'integer' + + +def test_nullable_default_with_no_required_arguments(): + def search(query: Union[str, None] = None): + return query + + parameters = convert_function_to_tool(search).function.parameters + assert not parameters.required + assert parameters.properties['query'].type == 'string' + + +def test_keyword_only_defaults_are_not_required(): + def search(query: str, *, limit: int = 5): + return query, limit + + parameters = convert_function_to_tool(search).function.parameters + assert parameters.required == ['query'] + + def test_function_to_tool_conversion(): def add_numbers(x: int, y: Union[int, None] = None) -> int: """Add two numbers together. From fe22c47c66fcd340aa6a93b4c40a2bd260a13a93 Mon Sep 17 00:00:00 2001 From: Shayan Ali <217162076+shayanxcyber@users.noreply.github.com> Date: Sun, 18 Oct 2026 23:39:22 +1100 Subject: [PATCH 2/2] Avoid comparing function defaults with the missing-value sentinel --- ollama/_utils.py | 2 +- tests/test_utils.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/ollama/_utils.py b/ollama/_utils.py index 93e756c7..b1cffbc3 100644 --- a/ollama/_utils.py +++ b/ollama/_utils.py @@ -64,7 +64,7 @@ def convert_function_to_tool(func: Callable) -> Tool: '__annotations__': {k: v.annotation if v.annotation != inspect._empty else str for k, v in signature.parameters.items()}, '__signature__': signature, '__doc__': parsed_docstring[doc_string_hash], - **{k: v.default for k, v in signature.parameters.items() if v.default != inspect.Parameter.empty}, + **{k: v.default for k, v in signature.parameters.items() if v.default is not inspect.Parameter.empty}, }, ).model_json_schema() diff --git a/tests/test_utils.py b/tests/test_utils.py index e287bff2..1a24c214 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,5 +1,6 @@ import json import sys +from enum import IntEnum from typing import Dict, List, Mapping, Sequence, Set, Tuple, Union from ollama._utils import convert_function_to_tool @@ -14,6 +15,21 @@ def search(query: str, limit: int = 5): assert parameters.properties['limit'].type == 'integer' +def test_function_default_with_custom_comparison(): + class DefaultLimit(IntEnum): + FIVE = 5 + + def __ne__(self, other): + raise RuntimeError('Default value comparison is not supported') + + def search(query: str, limit: int = DefaultLimit.FIVE): + return query, limit + + parameters = convert_function_to_tool(search).function.parameters + assert parameters.required == ['query'] + assert parameters.properties['limit'].type == 'integer' + + def test_nullable_default_with_no_required_arguments(): def search(query: Union[str, None] = None): return query