{"record":{"id":"b4ec5fdc7db3607b","repo":"JuliusBrussee/caveman","slug":"match-the-sync-async-caveman-transport-to-the-native-client","errorCode":null,"errorMessage":"Match the sync/async Caveman transport to the native client","messagePattern":"Match the sync/async Caveman transport to the native client","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/middleware/python/caveman_middleware/openai.py","lineNumber":97,"sourceCode":"        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)\n    if not isinstance(runtime, AsyncMiddlewareRuntime if is_async else MiddlewareRuntime):\n        raise TypeError(\"Match the sync/async middleware runtime to the native client\")\n    if transport is not None and not isinstance(transport, CavemanAsyncOpenAITransport if is_async else CavemanOpenAITransport):\n        raise TypeError(\"Match the sync/async Caveman transport to the native client\")\n    version_supported = in_range(__version__, \"3.10\", \"4\")\n    if not version_supported and runtime.mode != \"off\":\n        runtime.decline(\"unsupported_version\")\n    native = client.with_options()\n    post = native.post\n    sessions = {}\n    for path, protocol in ((\"/chat/completions\", \"openai-chat\"), (\"/responses\", \"openai-responses\")):\n        bound = registration is not None and registration[0] == protocol\n        sessions[path] = NativeSession(runtime, scope, adapter_id=\"openai-sdk\", framework_version=\"3.10.0\", protocol=protocol,\n                                      binding=registration[1] if bound else None, overhead=registration[4] if bound else None,\n                                      is_registered=(lambda: registration[2].get(registration[1].name) is registration[3] and registration[1].execute is registration[3]) if bound else None,\n                                      passive_reason=None if version_supported else \"unsupported_version\")\n    passive_session = sessions[\"/chat/completions\"]\n\n    def session_for(path, kwargs):\n        if not isinstance(path, str) or not plain(kwargs.get(\"options\", {})) or kwargs.get(\"options\", {}).get(\"extra_json\") or kwargs.get(\"files\"):\n            return None\n        target = urlsplit(path)","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/middleware/python/caveman_middleware/openai.py#L79-L115","documentation":"with_caveman_openai validates that the Caveman transport class matches the sync/async flavor of the native OpenAI client. Passing CavemanOpenAITransport (sync) for an AsyncOpenAI client, or vice versa, makes request wrapping impossible, so the wrapper raises TypeError at setup time.","triggerScenarios":"Calling with_caveman_openai(client, transport=...) where the client is AsyncOpenAI but transport is CavemanOpenAITransport, or the client is sync OpenAI but transport is CavemanAsyncOpenAITransport; also via with_caveman_openai_tools or copy_client which delegate to _wrap.","commonSituations":"Migrating a codebase from sync to async OpenAI usage and reusing the old transport instance; constructing both sync and async clients in one app and sharing a single transport; copy/pasting setup code between a script and an async FastAPI handler.","solutions":["Construct the transport matching the client type: CavemanAsyncOpenAITransport for AsyncOpenAI, CavemanOpenAITransport for OpenAI","Pass transport=None (the default) and let the wrapper create the correct transport automatically","Check isinstance(client, AsyncOpenAI) before choosing the transport in generic setup code"],"exampleFix":"// before\nclient = AsyncOpenAI()\nwrapped = with_caveman_openai(client, transport=CavemanOpenAITransport())\n// after\nclient = AsyncOpenAI()\nwrapped = with_caveman_openai(client, transport=CavemanAsyncOpenAITransport())","handlingStrategy":"type-guard","validationCode":"from openai import OpenAI, AsyncOpenAI\nfrom caveman_middleware.openai import CavemanOpenAITransport, CavemanAsyncOpenAITransport\nassert isinstance(client, (OpenAI, AsyncOpenAI))\nexpected = CavemanAsyncOpenAITransport if isinstance(client, AsyncOpenAI) else CavemanOpenAITransport\nassert transport is None or isinstance(transport, expected), \"sync/async transport mismatch\"","typeGuard":"def transport_matches(client, transport):\n    is_async = isinstance(client, AsyncOpenAI)\n    expected = CavemanAsyncOpenAITransport if is_async else CavemanOpenAITransport\n    return transport is None or isinstance(transport, expected)","tryCatchPattern":"try:\n    wrapped = with_caveman_openai(client, transport=transport)\nexcept TypeError as e:\n    if \"sync/async\" in str(e):\n        transport = CavemanAsyncOpenAITransport() if isinstance(client, AsyncOpenAI) else CavemanOpenAITransport()\n        wrapped = with_caveman_openai(client, transport=transport)\n    else:\n        raise","preventionTips":["Always pick the transport class from the same sync/async branch as the client","Let transport stay None and let the wrapper build the right one","Centralize client+transport construction in one factory function","Add an isinstance assertion in shared setup helpers"],"tags":["python","type-mismatch","openai","async"],"backgroundTag":"type-mismatch","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"}