Skip to content

Fix silent item dropping in ToolsMapper for non-dict/non-Tool items #218

@Kamilbenkirane

Description

@Kamilbenkirane

Priority: Critical

Related: #199

Philosophy

Celeste is a primitives layer. Dict tools are passed through to the provider as-is by design — celeste does not validate tool schemas, names, or structure beyond what it needs for its own dispatch logic. The provider API is the source of truth for what constitutes a valid tool definition. This is the same principle as extra_body: celeste forwards, the provider validates.

This means {"random": "garbage"} reaching the provider and getting rejected there is correct behavior. Celeste should not gate-keep provider-specific tool formats it doesn't know about.

Actual bug

The real issue is that non-dict, non-Tool items (e.g., 42, "string") are silently dropped from the tools list. The for-loop's if/elif chain has no else clause — items that don't match any branch are skipped without error. The user thinks they passed a tool, but it vanishes.

for item in validated_value:
    if isinstance(item, Tool):
        ...
    elif isinstance(item, dict) and "name" in item:
        ...
    elif isinstance(item, dict):
        ...
    # ← non-dict items silently ignored here

Fix

Add an else clause raising TypeError for items that are neither Tool instances nor dicts:

else:
    msg = f"Expected Tool instance or dict, got {type(item).__name__}"
    raise TypeError(msg)

Apply to all 4 ToolsMapper.map() implementations:

  • src/celeste/protocols/chatcompletions/parameters.py
  • src/celeste/protocols/openresponses/parameters.py
  • src/celeste/providers/anthropic/messages/parameters.py
  • src/celeste/providers/google/generate_content/parameters.py

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions