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
- Pass the Chat returned by client.chats.create(...) (or AsyncChat) to with_caveman_google_chat.
- Use with_caveman_google on the Client if you want client-level (models) instrumentation instead.
- 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
- Create chats via client.chats.create(...) and wrap that object.
- Use with_caveman_google for Client-level instrumentation instead.
- Avoid custom duck-typed chat wrappers; subclass or use native Chat.
- Instrument at one level (client OR chat) per application path.
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
- Expected a Google Client and synchronous MiddlewareRuntime
- Match the sync/async runtime to the chat
- Install caveman-middleware[google] to use the Google adapter
- caveman agent: Standard Schema emitted invalid input JSON…
- CrewAI requires a MiddlewareRuntime and a stable Scope per…
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)