JuliusBrussee/caveman · error · TypeError

Expected native client function tool definitions

Error message

Expected native client function tool definitions

What it means

Every entry in `tools` must be a native client function tool definition: a plain dict with type="function", and (for openai-chat) a nested plain `function` dict carrying a string `name`. Malformed entries raise this TypeError so the adapter can safely mirror and execute them.

Solutions

  1. Use the exact dicts returned by OpenAI's own tool-schema helpers: {"type":"function","function":{"name":...,"description":...,"parameters":{...}}} for openai-chat
  2. For openai-responses use flat {"type":"function","name":...,"parameters":{...}} dicts
  3. Convert Pydantic models via their OpenAI schema helpers before passing
  4. Check each dict has a string `name`

Example fix

// before
tools = [{"name": "get_weather", "input_schema": {...}}]  # Anthropic style
// after
tools = [{"type": "function", "function": {"name": "get_weather", "parameters": {...}}}]  # openai-chat
Defensive patterns

Strategy: validation

Validate before calling

for t in tools:
    assert isinstance(t, dict) and t.get("type") == "function"
    fn = t.get("function", t)
    assert isinstance(fn, dict) and isinstance(fn.get("name"), str)

Type guard

def is_function_tool(t):
    return isinstance(t, dict) and t.get("type") == "function" and isinstance((t.get("function") or t).get("name"), str)

Try / catch

try:
    loop = with_caveman_openai_tools(..., tools=tools, functions=functions)
except TypeError as e:
    if "tool definitions" in str(e):
        tools = normalize_to_openai_function_tools(tools)
        loop = with_caveman_openai_tools(..., tools=tools, functions=functions)
    else:
        raise

Prevention

When it happens

Trigger: Passing tool dicts missing `type: "function"`, missing `name`, with a non-string name, or non-dict items (e.g. Pydantic models, JSON strings, or Anthropic-style {"name":..., "input_schema":...} definitions).

Common situations: Reusing tool schemas built for another SDK; hand-writing tools and forgetting the type field; passing an already-serialized tools list; passing OpenAI Response API 'custom' tools.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/7c431d99923afb77. Report an issue: GitHub.

Appendix: source

Thrown at packages/middleware/python/caveman_middleware/openai.py:72


def with_caveman_openai_tools(client, *, runtime, scope, protocol, tools, functions, transport=None):
    """Bind real application dispatch without introducing a second scheduler.

    ``protocol`` is ``openai-chat`` or ``openai-responses``. ``tools`` contains
    the corresponding native definitions; ``functions`` maps each native tool
    name to a callable accepting the decoded arguments dictionary.
    """
    if protocol not in ("openai-chat", "openai-responses"):
        raise ValueError("Expected openai-chat or openai-responses protocol")
    definitions = copy.deepcopy(list(tools))
    if not plain(functions) or any(type(name) is not str or not callable(fn) for name, fn in functions.items()):
        raise TypeError("functions must map native tool names to callables")
    names = []
    for definition in definitions:
        tool = definition.get("function") if plain(definition) and protocol == "openai-chat" else definition
        if not plain(definition) or definition.get("type") != "function" or not plain(tool) or type(tool.get("name")) is not str:
            raise TypeError("Expected native client function tool definitions")
        names.append(tool["name"])
    if len(set(names)) != len(names) or "caveman_retrieve" in names or "caveman_retrieve" in functions:
        raise ValueError("Duplicate or reserved caveman_retrieve tool name")
    if set(names) != set(functions):
        raise ValueError("Every native function definition needs exactly one executor")
    if runtime.mode != "compress" or not in_range(__version__, "3.10", "4"):
        return CavemanOpenAIToolLoop(with_caveman_openai(client, runtime=runtime, scope=scope, transport=transport), MappingProxyType(dict(functions)), json.dumps(definitions))
    binding = runtime.recovery(scope)
    tool = {"name": binding.name, "description": binding.description, "parameters": copy.deepcopy(binding.input_schema)}
    definition = {"type": "function", "function": tool} if protocol == "openai-chat" else {"type": "function", **tool}
    definitions.append(definition)
    registry = MappingProxyType({**functions, binding.name: binding.execute})
    registration = (protocol, binding, registry, registry[binding.name], json.dumps(definition, ensure_ascii=False, separators=(",", ":")))
    return CavemanOpenAIToolLoop(_wrap(client, runtime=runtime, scope=scope, registration=registration, transport=transport), registry,
                                json.dumps(definitions, ensure_ascii=False, separators=(",", ":")))


def _wrap(client, *, runtime, scope, registration=None, transport=None):

View on GitHub (pinned to 3ee70a1026)