{"record":{"id":"2738fa9e8d2bf3ce","repo":"JuliusBrussee/caveman","slug":"duplicate-or-reserved-caveman-retrieve-tool-name-openai","errorCode":null,"errorMessage":"Duplicate or reserved caveman_retrieve tool name","messagePattern":"Duplicate or reserved caveman_retrieve tool name","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"packages/middleware/python/caveman_middleware/openai.py","lineNumber":75,"sourceCode":"    \"\"\"Bind real application dispatch without introducing a second scheduler.\n\n    ``protocol`` is ``openai-chat`` or ``openai-responses``. ``tools`` contains\n    the corresponding native definitions; ``functions`` maps each native tool\n    name to a callable accepting the decoded arguments dictionary.\n    \"\"\"\n    if protocol not in (\"openai-chat\", \"openai-responses\"):\n        raise ValueError(\"Expected openai-chat or openai-responses protocol\")\n    definitions = copy.deepcopy(list(tools))\n    if not plain(functions) or any(type(name) is not str or not callable(fn) for name, fn in functions.items()):\n        raise TypeError(\"functions must map native tool names to callables\")\n    names = []\n    for definition in definitions:\n        tool = definition.get(\"function\") if plain(definition) and protocol == \"openai-chat\" else definition\n        if not plain(definition) or definition.get(\"type\") != \"function\" or not plain(tool) or type(tool.get(\"name\")) is not str:\n            raise TypeError(\"Expected native client function tool definitions\")\n        names.append(tool[\"name\"])\n    if len(set(names)) != len(names) or \"caveman_retrieve\" in names or \"caveman_retrieve\" in functions:\n        raise ValueError(\"Duplicate or reserved caveman_retrieve tool name\")\n    if set(names) != set(functions):\n        raise ValueError(\"Every native function definition needs exactly one executor\")\n    if runtime.mode != \"compress\" or not in_range(__version__, \"3.10\", \"4\"):\n        return CavemanOpenAIToolLoop(with_caveman_openai(client, runtime=runtime, scope=scope, transport=transport), MappingProxyType(dict(functions)), json.dumps(definitions))\n    binding = runtime.recovery(scope)\n    tool = {\"name\": binding.name, \"description\": binding.description, \"parameters\": copy.deepcopy(binding.input_schema)}\n    definition = {\"type\": \"function\", \"function\": tool} if protocol == \"openai-chat\" else {\"type\": \"function\", **tool}\n    definitions.append(definition)\n    registry = MappingProxyType({**functions, binding.name: binding.execute})\n    registration = (protocol, binding, registry, registry[binding.name], json.dumps(definition, ensure_ascii=False, separators=(\",\", \":\")))\n    return CavemanOpenAIToolLoop(_wrap(client, runtime=runtime, scope=scope, registration=registration, transport=transport), registry,\n                                json.dumps(definitions, ensure_ascii=False, separators=(\",\", \":\")))\n\n\ndef _wrap(client, *, runtime, scope, registration=None, transport=None):\n    if not isinstance(client, (OpenAI, AsyncOpenAI)):\n        raise TypeError(\"Expected an OpenAI or AsyncOpenAI client\")\n    is_async = isinstance(client, AsyncOpenAI)","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/middleware/python/caveman_middleware/openai.py#L57-L93","documentation":"Tool names must be unique, and the reserved name `caveman_retrieve` may not be used by caller-supplied tools or executors because the adapter injects its own caveman_retrieve tool in compress mode. Duplicates or use of the reserved name raise ValueError.","triggerScenarios":"Passing two native definitions with the same `name`, or a tool/executor named `caveman_retrieve`, in with_caveman_openai_tools.","commonSituations":"Generating tools programmatically where several endpoints share a name; migrating code that already defined its own retrieval helper called caveman_retrieve from an earlier integration.","solutions":["Rename duplicate tools so every native definition has a unique name","Rename any caller tool/executor away from `caveman_retrieve`; it is reserved by the adapter","Deduplicate the tools list before calling (e.g. {t[\"function\"][\"name\"]: t for t in tools}.values())"],"exampleFix":"// before\nfunctions = {\"search\": search, \"caveman_retrieve\": my_retrieve}\n// after\nfunctions = {\"search\": search, \"docs_lookup\": my_retrieve}","handlingStrategy":"validation","validationCode":"names = [t.get(\"function\", t).get(\"name\") for t in tools]\nassert len(set(names)) == len(names) and \"caveman_retrieve\" not in names and \"caveman_retrieve\" not in functions","typeGuard":"def names_are_unique_and_unreserved(tools, functions):\n    names = [(t.get(\"function\") or t).get(\"name\") for t in tools]\n    return len(set(names)) == len(names) and \"caveman_retrieve\" not in names and \"caveman_retrieve\" not in functions","tryCatchPattern":"try:\n    loop = with_caveman_openai_tools(..., tools=tools, functions=functions)\nexcept ValueError as e:\n    if \"caveman_retrieve\" in str(e) or \"Duplicate\" in str(e):\n        seen, deduped = set(), []\n        for t in tools:\n            n = (t.get(\"function\") or t).get(\"name\")\n            if n not in seen:\n                seen.add(n); deduped.append(t)\n        tools = deduped\n        loop = with_caveman_openai_tools(..., tools=tools, functions=functions)\n    else:\n        raise","preventionTips":["Never name tools caveman_retrieve; it is reserved","Deduplicate generated tools before registration","Prefix tool names with your feature to avoid collisions"],"tags":["python","naming","openai"],"backgroundTag":"invalid-argument-value","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}