{"record":{"id":"cf6f0253c6f8f9b5","repo":"JuliusBrussee/caveman","slug":"synchronous-langchain-calls-require-middlewareruntime","errorCode":null,"errorMessage":"Synchronous LangChain calls require MiddlewareRuntime","messagePattern":"Synchronous LangChain calls require MiddlewareRuntime","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/middleware/python/caveman_middleware/langchain.py","lineNumber":117,"sourceCode":"            return None\n        reason = \"off\" if runtime.mode == \"off\" else \"unsupported_version\" if not _supported(runtime) else None\n        if reason:\n            return Attempt(runtime, Scope(\"caveman-passive\", \"report-only\"), str(uuid.uuid4()), str(uuid.uuid4()),\n                           passive=True, reason=reason, adapter=\"langchain\"), {}, None\n        view = _message_view(messages, prefix)\n        if view is None:\n            return Attempt(runtime, Scope(\"caveman-passive\", \"report-only\"), str(uuid.uuid4()), str(uuid.uuid4()),\n                           passive=True, reason=\"unsupported_shape\", adapter=\"langchain\"), {}, None\n        scope = _scope(self.scope, config)\n        attempt = Attempt(runtime, scope, str(uuid.uuid4()), str(uuid.uuid4()), adapter=\"langchain\")\n        context, candidates, setters = view\n        options = dict(scope=scope, adapter=ADAPTER, manifest=context, candidates=candidates, binding=binding,\n                       model=model, recovery_overhead_text=overhead, logical_call_id=attempt.logical_call_id, attempt_id=attempt.attempt_id)\n        return attempt, setters, options\n\n    def prepare(self, messages, config=None, binding=None, model=None, overhead=None, prefix=()):\n        if self.sync is None:\n            raise TypeError(\"Synchronous LangChain calls require MiddlewareRuntime\")\n        state = self.state(messages, config, self.sync, binding, model, overhead, prefix)\n        if state is None:\n            return messages, None\n        attempt, setters, options = state\n        if options is None:\n            return messages, attempt\n        attempt.optimization = self.sync.optimize(**options)\n        if not all(replacement[\"segment_id\"] in setters for replacement in attempt.optimization.replacements):\n            attempt.optimization, attempt.reason = None, \"invalid_replacement_plan\"\n            return messages, attempt\n        return _apply(messages, attempt.optimization, setters), attempt\n\n    async def prepare_async(self, messages, config=None, binding=None, model=None, overhead=None, prefix=()):\n        state = self.state(messages, config, self.async_runtime, binding, model, overhead, prefix)\n        if state is None:\n            return messages, None\n        attempt, setters, options = state\n        if options is None:","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/middleware/python/caveman_middleware/langchain.py#L99-L135","documentation":"prepare() drives the synchronous LangChain invoke path and requires self.sync (a MiddlewareRuntime) to be set. When the connection was built only with an async runtime, sync calls cannot proceed and a TypeError is raised.","triggerScenarios":"Calling synchronous methods (invoke / prepare path) on an adapter constructed with only an AsyncMiddlewareRuntime, or with the sync runtime slot left None.","commonSituations":"Mixing async setup with sync model.invoke(); constructing _Connection with runtime=None for 'off' mode then invoking sync; framework code paths that call sync invoke inside an async app.","solutions":["Construct the adapter with a MiddlewareRuntime so connection.sync is populated.","Use the async API (ainvoke/arecover) when only an async runtime is available.","If mode is intentionally off, gate your calls instead of invoking through the adapter."],"exampleFix":"// before\nadapter = with_caveman_model(async_runtime, scope)\nchain.invoke(messages)\n// after\nadapter = with_caveman_model(MiddlewareRuntime(...), scope)\nchain.invoke(messages)  # or use ainvoke with the async runtime","handlingStrategy":"type-guard","validationCode":"if connection.sync is None:\n    raise RuntimeError('sync runtime not configured; use ainvoke or build with MiddlewareRuntime')\nchain.invoke(messages, config=config)","typeGuard":"def supports_sync(conn): return getattr(conn, 'sync', None) is not None","tryCatchPattern":"try:\n    out = chain.invoke(messages, config=config)\nexcept TypeError as e:\n    if 'Synchronous LangChain calls' in str(e):\n        out = await chain.ainvoke(messages, config=config)\n    else:\n        raise","preventionTips":["Pair sync runtimes with sync APIs and async runtimes with ainvoke consistently.","Build the adapter with both runtimes if the codebase mixes sync and async call sites.","Annotate adapter constructors to reject async-only setups for sync wrappers."],"tags":["type-mismatch","sync-async","langchain","python"],"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"}