#1679·llm

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

Author: miyagawaCreated Sep 11, 2026Updated Sep 11, 2026

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

python
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:

python
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