JuliusBrussee/caveman · error · TypeError

Expected a native Google Chat or AsyncChat

Error message

Expected a native Google Chat or AsyncChat

What it means

with_caveman_google_chat instruments a google.genai Chat or AsyncChat. The chat argument is type-checked against both classes and this TypeError is raised for anything else, since the wrapper needs the chat's send_message/send_message_stream methods to register hooks.

Solutions

  1. Pass the Chat returned by client.chats.create(...) (or AsyncChat) to with_caveman_google_chat.
  2. Use with_caveman_google on the Client if you want client-level (models) instrumentation instead.
  3. Ensure your chat object is a genuine google.genai.chats.Chat/AsyncChat instance, not a duck-typed substitute.

Example fix

// before
with_caveman_google_chat(client, runtime, scope)
// after
chat = client.chats.create(model="gemini-2.0-flash")
with_caveman_google_chat(chat, runtime, scope)
Defensive patterns

Strategy: type-guard

Validate before calling

from google.genai.chats import Chat, AsyncChat
if not isinstance(chat, (Chat, AsyncChat)):
    raise TypeError("with_caveman_google_chat requires a Chat or AsyncChat")

Type guard

def is_native_chat(obj) -> bool:
    from google.genai.chats import Chat, AsyncChat
    return isinstance(obj, (Chat, AsyncChat))

Try / catch

try:
    chat = with_caveman_google_chat(chat, runtime, scope)
except TypeError as e:
    if "native Google Chat" in str(e):
        raise ConfigError("Wrap the chat from client.chats.create(), not the client") from e
    raise

Prevention

When it happens

Trigger: Passing a genai.Client, a Models interface, or any non-Chat object as `chat` to with_caveman_google_chat — e.g. wrapping the client instead of the chat session created via client.chats.create().

Common situations: Confusing Client-level instrumentation with Chat-level instrumentation; passing a custom chat-like wrapper class that does not subclass Chat/AsyncChat; calling the chat entrypoint before creating the chat.

Related errors


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

Appendix: source

Thrown at packages/middleware/python/caveman_middleware/google.py:181

    """
    if not isinstance(client, genai.Client) or not isinstance(runtime, MiddlewareRuntime):
        raise TypeError("Expected a Google Client and synchronous MiddlewareRuntime")
    if not supports_framework(runtime, ("google-genai", "2.22", "3")):
        return client
    _wrap_model(client.models, runtime, scope)
    _wrap_model(client.aio.models, runtime.as_async(), scope, True)
    return client


def with_caveman_google_chat(chat, *, runtime, scope, config=None):
    """Register recovery in an existing native Chat/AsyncChat's own AFC loop.

    Pass the original default config here, or supply config on each send. No
    private chat configuration/history is read or modified.
    """
    asynchronous = isinstance(chat, AsyncChat)
    if not isinstance(chat, (Chat, AsyncChat)):
        raise TypeError("Expected a native Google Chat or AsyncChat")
    if asynchronous and isinstance(runtime, MiddlewareRuntime):
        runtime = runtime.as_async()
    if not isinstance(runtime, AsyncMiddlewareRuntime if asynchronous else MiddlewareRuntime):
        raise TypeError("Match the sync/async runtime to the chat")
    if not supports_framework(runtime, ("google-genai", "2.22", "3")):
        return chat
    send, stream, default = chat.send_message, chat.send_message_stream, config
    if asynchronous:
        @functools.wraps(send)
        async def send_message(message, config=None):
            selected, context = _bind(config if config is not None else default, runtime, scope, asynchronous=True)
            token = _invocation.set(context)
            try:
                return await send(message, config=selected)
            finally:
                _invocation.reset(token)

        @functools.wraps(stream)

View on GitHub (pinned to 3ee70a1026)