JuliusBrussee/caveman · error · ValueError
Expected openai-chat or openai-responses protocol
Error message
Expected openai-chat or openai-responses protocol
What it means
with_caveman_openai_tools accepts only the two supported wire protocols: `openai-chat` (tools nested under a `function` key) and `openai-responses` (flat tool objects). Any other protocol string raises ValueError before any tools are processed.
Solutions
- Pass protocol="openai-chat" when using client.chat.completions-style tool definitions
- Pass protocol="openai-responses" when using the Responses API tool shape
- Fix typos and trim/normalize the protocol string before the call
Example fix
// before loop = with_caveman_openai_tools(client, runtime, scope, protocol="openai", tools=tools, functions=functions) // after loop = with_caveman_openai_tools(client, runtime, scope, protocol="openai-chat", tools=tools, functions=functions)
Defensive patterns
Strategy: validation
Validate before calling
PROTOCOL = "openai-chat" if use_chat_completions else "openai-responses"
assert PROTOCOL in ("openai-chat", "openai-responses") Type guard
def is_openai_protocol(p): return p in ("openai-chat", "openai-responses") Try / catch
try:
loop = with_caveman_openai_tools(client, runtime, scope, protocol=protocol, tools=tools, functions=functions)
except ValueError as e:
if "protocol" in str(e):
protocol = "openai-chat"
loop = with_caveman_openai_tools(client, runtime, scope, protocol=protocol, tools=tools, functions=functions)
else:
raise Prevention
- Define the protocol string once as a module constant
- Pick protocol based on which OpenAI API surface your tools were shaped for
- Never pass user/config input unvalidated as protocol
When it happens
Trigger: Calling with_caveman_openai_tools(protocol="chat"), protocol="openai", protocol="completions", or a misspelled/None protocol value.
Common situations: Copy-pasting adapter setup from Anthropic or generic docs; a config value for the API surface was never normalized; mixing up client SDKs (responses vs chat completions).
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- Every native function definition needs exactly one executor
- artifact_id is required
- ASGI context resolver or request bounds are invalid
- assemble requires model and session_id
- assembly slot id must be non-empty and unique
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/f3268ba3f3340de3.
Report an issue: GitHub.
Appendix: source
Thrown at packages/middleware/python/caveman_middleware/openai.py:64
"""
client: object
functions: object
_definitions: str
@property
def tools(self):
return json.loads(self._definitions)
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}View on GitHub (pinned to 3ee70a1026)