Skip to content

Tool parameter named "title" is stripped from input_schema but kept in required #1679

Description

@miyagawa

A tool function with a parameter named title has that parameter silently dropped from its generated input_schema, while required still lists it. The model therefore can never supply the argument, and the call fails with TypeError: ... missing 1 required positional argument: 'title'.

Reproduction

import llm

def demo_a(title: str, when: str) -> str:
    "param named title"
    return ""

def demo_b(heading: str, when: str) -> str:
    "same thing, renamed"
    return ""

for f in (demo_a, demo_b):
    s = llm.Tool.function(f).input_schema
    print(f.__name__, "-> properties:", list(s["properties"].keys()), "| required:", s["required"])
demo_a -> properties: ['when']             | required: ['title', 'when']
demo_b -> properties: ['heading', 'when']  | required: ['heading', 'when']

Note the inconsistency in demo_a: required names title, but properties has no such key.

Cause

_get_arguments_input_schema() (llm/models.py:260) builds a Pydantic model that correctly includes title as a field. Tool.__post_init__ then passes it through _ensure_dict_schema() (llm/models.py:3640), which calls:

def _remove_titles_recursively(obj):
    """Recursively remove all 'title' fields from a nested dictionary."""
    if isinstance(obj, dict):
        obj.pop("title", None)
        for value in obj.values():
            _remove_titles_recursively(value)
    elif isinstance(obj, list):
        for item in obj:
            _remove_titles_recursively(item)

The intent is to strip Pydantic's auto-generated "title": "When" annotations. But the recursion descends into properties, where title is a property name rather than a schema keyword, so the whole property is deleted. required is a list of plain strings, so the recursion never touches it — hence the two ending up out of sync.

Impact

This is easy to miss, because llm invokes tools as func(**args) with no filtering against the schema. If a model supplies title anyway — mine did for a long time, because the tool description happened to say "You MUST include title" — the call succeeds and nothing looks wrong.

It surfaced for me on gpt-5.6-luna, which strictly follows the declared schema and so won't emit an argument that isn't in properties:

Tool call: create_event({'when': '2026-11-07 20:00', 'location': '...', 'duration': 60, 'calendar': 'Personal', 'timezone': 'America/Los_Angeles'})
  Error: create_event() missing 1 required positional argument: 'title'
  Exception: create_event() missing 1 required positional argument: 'title'

The same tool had been working for months on other models that were willing to go off-schema.

I reproduced it on 0.26 and 0.35. 0.26 is the first release with llm.Tool, so it appears to have been there since tool support landed.

Suggested fix

Only strip title where it is a schema annotation, not where it is a property name — e.g. skip descending by key into properties maps, or drop title only when the containing dict also has type/anyOf.

Environment

  • llm 0.35, Python 3.13, macOS
  • Observed with gpt-5.6-luna; previously masked on other models

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions