JuliusBrussee/caveman · error · TypeError

Expected a native LangChain BaseChatModel

Error message

Expected a native LangChain BaseChatModel

What it means

with_caveman_model wraps a LangChain chat model by copying it (model_copy) and must receive an instance of langchain_core BaseChatModel. Anything else — a wrapped pipeline, a string model name, a non-native provider wrapper — raises this TypeError.

Solutions

  1. Pass an actual BaseChatModel instance, e.g. ChatOpenAI(model='gpt-4o').
  2. Wrap only the LLM node of the chain, not the full RunnableSequence.
  3. For custom providers, subclass BaseChatModel or use a community class that extends it.

Example fix

// before
wrapped = with_caveman_model(runtime, scope, 'gpt-4o')
// after
from langchain_openai import ChatOpenAI
wrapped = with_caveman_model(runtime, scope, ChatOpenAI(model='gpt-4o'))
Defensive patterns

Strategy: type-guard

Validate before calling

from langchain_core.language_models import BaseChatModel
if not isinstance(model, BaseChatModel):
    raise TypeError('with_caveman_model expects a BaseChatModel instance')

Type guard

def is_chat_model(m): return isinstance(m, BaseChatModel)

Try / catch

try:
    wrapped = with_caveman_model(runtime, scope, model)
except TypeError as e:
    if 'BaseChatModel' in str(e):
        raise RuntimeError(f'{model!r} is not a BaseChatModel; wrap the LLM, not the chain') from e
    raise

Prevention

When it happens

Trigger: Calling with_caveman_model(runtime, scope, model=...) with a non-BaseChatModel: a model name string, a ChatPromptTemplate, a RunnableSequence (prompt | llm), or a third-party chat wrapper not subclassing BaseChatModel.

Common situations: Passing 'gpt-4o' instead of ChatOpenAI('gpt-4o'); wrapping the whole chain instead of just the LLM; using a community model class that subclasses Runnable rather than BaseChatModel.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/fbd18e9b1fa10911. Report an issue: GitHub.

Appendix: source

Thrown at packages/middleware/python/caveman_middleware/langchain.py:272

def with_caveman_agent(options: dict, *, runtime, scope) -> dict:
    """Return native create_agent keyword arguments; do not run another loop."""
    middleware = CavemanMiddleware(runtime=runtime, scope=scope)
    tools = list(options.get("tools", []))
    collision = any((tool.get("name") if plain(tool) else getattr(tool, "name", None)) == "caveman_retrieve" for tool in tools)
    if not collision and runtime.mode == "compress" and middleware.recovery_tool is not None:
        tools.append(middleware.recovery_tool)
    return {**options, "tools": tools, "middleware": [*options.get("middleware", []), middleware]}


def with_caveman_model(model: BaseChatModel, *, runtime, scope):
    """Native model clone preserving bind_tools/structured helpers and callbacks.

    The pinned providers' public batch APIs call invoke/ainvoke for each item,
    so every item resolves its own RunnableConfig scope. This variant has no
    recovery executor; use native agent middleware for recoverable lossiness.
    """
    if not isinstance(model, BaseChatModel):
        raise TypeError("Expected a native LangChain BaseChatModel")
    if not _supported(runtime) and runtime.mode != "off":
        runtime.decline("unsupported_version")
    native = model.model_copy()
    connection = _Connection(runtime, scope)

    def messages(input):
        if isinstance(input, PromptValue):
            return input.to_messages()
        if isinstance(input, str):
            return convert_to_messages([("human", input)])
        return convert_to_messages(input)

    def wrap_call(method):
        @functools.wraps(method)
        def call(input, config=None, **kwargs):
            view, attempt = connection.prepare(messages(input), config)
            if attempt is None:
                return method(input, config, **kwargs)

View on GitHub (pinned to 3ee70a1026)