Skip to content

Commit d051da5

Browse files
peterjekzhu
andauthored
fix: ollama fails when tools use optional args (#6343)
## Why are these changes needed? `convert_tools` failed if Optional args were used in tools (the `type` field doesn't exist in that case and `anyOf` must be used). This uses the `anyOf` field to pick the first non-null type to use. ## Related issue number Fixes #6323 ## Checks - [ ] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [x] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [x] I've made sure all auto checks have passed. --------- Signed-off-by: Peter Jausovec <peter.jausovec@solo.io> Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
1 parent 89d77c7 commit d051da5

2 files changed

Lines changed: 54 additions & 5 deletions

File tree

python/packages/autogen-ext/src/autogen_ext/models/ollama/_ollama_client.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,8 +315,17 @@ def convert_tools(
315315
if parameters is not None:
316316
ollama_properties = {}
317317
for prop_name, prop_schema in parameters["properties"].items():
318+
# Determine property type, checking "type" first, then "anyOf", defaulting to "string"
319+
prop_type = prop_schema.get("type")
320+
if prop_type is None and "anyOf" in prop_schema:
321+
prop_type = next(
322+
(opt.get("type") for opt in prop_schema["anyOf"] if opt.get("type") != "null"),
323+
None, # Default to None if no non-null type found in anyOf
324+
)
325+
prop_type = prop_type or "string"
326+
318327
ollama_properties[prop_name] = OllamaTool.Function.Parameters.Property(
319-
type=prop_schema["type"],
328+
type=prop_type,
320329
description=prop_schema["description"] if "description" in prop_schema else None,
321330
)
322331
result.append(

python/packages/autogen-ext/tests/models/test_ollama_chat_completion_client.py

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import json
22
import logging
3-
from typing import Any, AsyncGenerator, Dict, List, Mapping
3+
from typing import Any, AsyncGenerator, Dict, List, Mapping, Optional
44

55
import httpx
66
import pytest
@@ -13,11 +13,11 @@
1313
FunctionExecutionResultMessage,
1414
UserMessage,
1515
)
16-
from autogen_core.tools import FunctionTool
16+
from autogen_core.tools import FunctionTool, ToolSchema
1717
from autogen_ext.models.ollama import OllamaChatCompletionClient
18-
from autogen_ext.models.ollama._ollama_client import OLLAMA_VALID_CREATE_KWARGS_KEYS
18+
from autogen_ext.models.ollama._ollama_client import OLLAMA_VALID_CREATE_KWARGS_KEYS, convert_tools
1919
from httpx import Response
20-
from ollama import AsyncClient, ChatResponse, Message
20+
from ollama import AsyncClient, ChatResponse, Message, Tool
2121
from pydantic import BaseModel
2222

2323

@@ -206,6 +206,46 @@ async def _mock_chat(*args: Any, **kwargs: Any) -> ChatResponse:
206206
assert create_result.usage.completion_tokens == 12
207207

208208

209+
@pytest.mark.asyncio
210+
async def test_convert_tools() -> None:
211+
def add(x: int, y: Optional[int]) -> str:
212+
if y is None:
213+
return str(x)
214+
return str(x + y)
215+
216+
add_tool = FunctionTool(add, description="Add two numbers")
217+
218+
tool_schema_noparam: ToolSchema = {
219+
"name": "manual_tool",
220+
"description": "A tool defined manually",
221+
"parameters": {
222+
"type": "object",
223+
"properties": {
224+
"param_with_type": {"type": "integer", "description": "An integer param"},
225+
"param_without_type": {"description": "A param without explicit type"},
226+
},
227+
"required": ["param_with_type"],
228+
},
229+
}
230+
231+
converted_tools = convert_tools([add_tool, tool_schema_noparam])
232+
assert len(converted_tools) == 2
233+
assert isinstance(converted_tools[0].function, Tool.Function)
234+
assert isinstance(converted_tools[0].function.parameters, Tool.Function.Parameters)
235+
assert converted_tools[0].function.parameters.properties is not None
236+
assert converted_tools[0].function.name == add_tool.name
237+
assert converted_tools[0].function.parameters.properties["y"].type == "integer"
238+
239+
# test it defaults to string
240+
assert isinstance(converted_tools[1].function, Tool.Function)
241+
assert isinstance(converted_tools[1].function.parameters, Tool.Function.Parameters)
242+
assert converted_tools[1].function.parameters.properties is not None
243+
assert converted_tools[1].function.name == "manual_tool"
244+
assert converted_tools[1].function.parameters.properties["param_with_type"].type == "integer"
245+
assert converted_tools[1].function.parameters.properties["param_without_type"].type == "string"
246+
assert converted_tools[1].function.parameters.required == ["param_with_type"]
247+
248+
209249
@pytest.mark.asyncio
210250
async def test_create_stream_tools(monkeypatch: pytest.MonkeyPatch) -> None:
211251
def add(x: int, y: int) -> str:

0 commit comments

Comments
 (0)