Skip to content

utils: resolve string annotations when converting functions to tools - #746

Open
breken-ai wants to merge 1 commit into
ollama:mainfrom
breken-ai:fix/tool-string-annotations
Open

breken-ai wants to merge 1 commit into
ollama:mainfrom
breken-ai:fix/tool-string-annotations

Conversation

@breken-ai

Copy link
Copy Markdown

Passing a function as a tool fails when its module uses from __future__ import annotations, or when its annotations are quoted:

from __future__ import annotations
from typing import Literal, Optional
import ollama

def get_weather(city: str, unit: Literal['celsius', 'fahrenheit'] = 'celsius', days: Optional[int] = None) -> str:
    """Get the weather for a city."""
    ...

ollama.chat(model='llama3.2', messages=[...], tools=[get_weather])
pydantic.errors.PydanticUserError: `get_weather` is not fully defined; you should define `Literal`, then call `get_weather.model_rebuild()`.

The call fails inside _copy_tools, before any request is sent. With the future import, every annotation is a string ('Literal[...]', 'Optional[int]'). convert_function_to_tool copies those strings into a pydantic model that it creates inside ollama._utils. Pydantic resolves them there, where names like Optional, Literal or List do not exist. Builtins such as str and int still resolve, so only functions that use typing names are affected. Most tool functions do.

This change evaluates string annotations with get_type_hints against the function's own module, following __wrapped__ for decorated functions. If that fails, it falls back to the raw annotation, which is the current behavior. Annotations that are not strings are passed through unchanged.

Verification

  • New test_function_with_string_annotations uses quoted annotations, which is the same code path as the future import. It fails on main with the PydanticUserError above and passes with this change.
  • End to end, Client.chat(tools=[get_weather]) from a module with the future import, using an httpx.MockTransport: on main it raises before any request is sent. With the change, the request carries city: string, unit: string and days: integer, the same schema as without the future import.
  • pytest ollama tests passes (103 tests), and ruff format --check and ruff check are clean.

Built by breken, your AI support engineer - breken.ai - this one's on us.

Under `from __future__ import annotations`, or with quoted annotations,
every parameter annotation is a string. convert_function_to_tool passed
those strings to a pydantic model built inside ollama._utils, which
cannot resolve names like Optional or Literal from the caller's module,
so chat(tools=[func]) raised PydanticUserError before sending anything.

Evaluate the annotations with get_type_hints in the function's own
module and fall back to the raw annotation if that fails.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant