JuliusBrussee/caveman · error · TypeError
Expected an installed CrewAI BaseLLM
Error message
Expected an installed CrewAI BaseLLM
What it means
CavemanLLM (the CrewAI middleware LLM wrapper) is a pydantic-model wrapper around a native CrewAI BaseLLM instance. Its __init__ type-checks the delegate and raises TypeError if the object passed as delegate is not an instance of crewai BaseLLM, because all delegation (model, provider, stream, stop, is_litellm) relies on BaseLLM attributes.
Solutions
- Pass an instantiated crewai BaseLLM (e.g. from crewai.llms import LLM; LLM(model=...)) as the delegate.
- Check isinstance(delegate, BaseLLM) before constructing the wrapper.
- If on an older/newer CrewAI where the base class moved, verify the import 'from crewai.llms.base_llm import BaseLLM' resolves and use that class.
Example fix
// before
wrapped = CavemanLLM("gpt-4o", runtime=runtime, scope=scope)
// after
from crewai.llms import LLM
wrapped = CavemanLLM(LLM(model="gpt-4o"), runtime=runtime, scope=scope) Defensive patterns
Strategy: type-guard
Validate before calling
from crewai.llms.base_llm import BaseLLM
if not isinstance(delegate, BaseLLM):
raise TypeError("delegate must be a crewai BaseLLM instance") Type guard
def is_valid_delegate(obj) -> bool:
from crewai.llms.base_llm import BaseLLM
return isinstance(obj, BaseLLM) Try / catch
try:
wrapped = CavemanLLM(delegate, runtime=runtime, scope=scope)
except TypeError as e:
if "BaseLLM" in str(e):
delegate = LLM(model=delegate) if isinstance(delegate, str) else None
wrapped = CavemanLLM(delegate, runtime=runtime, scope=scope)
else:
raise Prevention
- Always instantiate the CrewAI LLM before wrapping it.
- Never pass a model-name string where an LLM instance is expected.
- Avoid double-wrapping: check isinstance(obj, CavemanLLM) before wrapping.
- Pin crewai to a supported version so BaseLLM's import path is stable.
When it happens
Trigger: Passing a non-BaseLLM object as the first positional argument to CavemanLLM's __init__ — e.g. a raw litellm/openai client, a string model name, a Crew (agent) object, or a None value instead of the constructed LLM.
Common situations: Constructing the wrapper before building the actual CrewAI LLM; confusing the model name string with the LLM instance; passing an already-wrapped CavemanLLM back in (double wrapping); older CrewAI versions with different LLM base classes so isinstance fails.
Related errors
- CrewAI requires a MiddlewareRuntime and a stable Scope per…
- apiKey, baseURL, and agent are required
- caveman agent: Standard Schema emitted invalid input JSON…
- Expected a Google Client and synchronous MiddlewareRuntime
- Expected a native Google Chat or AsyncChat
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/c0fac66717606e0a.
Report an issue: GitHub.
Appendix: source
Thrown at packages/middleware/python/caveman_middleware/crewai.py:111
Use ``with_caveman_agent`` to register the matching recovery BaseTool.
Register before creating the Agent, whose executor snapshots native hooks.
``close`` removes only this delegate's registrations. The application keeps
ownership of the native provider client and the Caveman runtime.
"""
llm_type: str = "caveman"
delegate: BaseLLM = Field(exclude=True, repr=False)
runtime: Any = Field(exclude=True, repr=False)
scope: Scope
closed: bool = Field(default=False, exclude=True)
_context: Any = PrivateAttr()
_hook: Any = PrivateAttr(default=None)
_completed: Any = PrivateAttr(default=None)
_recovery: Any = PrivateAttr(default=None)
def __init__(self, delegate, *, runtime, scope):
if not isinstance(delegate, BaseLLM):
raise TypeError("Expected an installed CrewAI BaseLLM")
if not isinstance(runtime, MiddlewareRuntime) or not isinstance(scope, Scope):
raise TypeError("CrewAI requires a MiddlewareRuntime and a stable Scope per agent context")
super().__init__(delegate=delegate, runtime=runtime, scope=scope, model=delegate.model,
provider=delegate.provider, stream=delegate.stream, stop=list(delegate.stop),
is_litellm=delegate.is_litellm)
self._context = contextvars.ContextVar(f"caveman_crewai_context_{id(self)}", default=None)
supported = matches_framework(("crewai", "1.15", "2"))
if not supported and runtime.mode != "off":
runtime.decline("unsupported_version")
if not supported or runtime.mode == "off":
return
reference = weakref.ref(self)
@on(InterceptionPoint.PRE_MODEL_CALL)
def before(context):
model = reference()
if model is not None and not model.closed and context.llm is model:
# Store only a weak executor reference. Aborted hooks and staleView on GitHub (pinned to 3ee70a1026)