diff --git a/google/genai/_gaos/google_genai.py b/google/genai/_gaos/google_genai.py index d5a20daba..627b9000a 100644 --- a/google/genai/_gaos/google_genai.py +++ b/google/genai/_gaos/google_genai.py @@ -294,7 +294,7 @@ def get( api_version: Optional[str] = None, include_input: Any = None, last_event_id: Any = None, - stream: Any = False, + stream: Optional[bool] = None, extra_headers: Optional[Mapping[str, str]] = None, extra_query: Optional[Mapping[str, Any]] = None, timeout: Optional[Union[float, httpx.Timeout]] = None, @@ -302,7 +302,11 @@ def get( interactions.Interaction, eventstreaming.Stream[interactions.InteractionSSEEvent], ]: - stream_bool = bool(_optional_bool(stream, default=False)) + # Preserve the caller's stream value (None → not sent) so callers + # can opt out of serializing 'stream' on the wire. The earlier + # bool(...) wrapper collapsed None to False and forced serialization. + # Fix for #2661. + stream_bool = _optional_bool(stream, default=None) response = wrap_sdk_call( super().get, id=id, @@ -446,7 +450,7 @@ async def get( api_version: Optional[str] = None, include_input: Any = None, last_event_id: Any = None, - stream: Any = False, + stream: Optional[bool] = None, extra_headers: Optional[Mapping[str, str]] = None, extra_query: Optional[Mapping[str, Any]] = None, timeout: Optional[Union[float, httpx.Timeout]] = None, @@ -454,7 +458,11 @@ async def get( interactions.Interaction, eventstreaming.AsyncStream[interactions.InteractionSSEEvent], ]: - stream_bool = bool(_optional_bool(stream, default=False)) + # Preserve the caller's stream value (None → not sent) so callers + # can opt out of serializing 'stream' on the wire. The earlier + # bool(...) wrapper collapsed None to False and forced serialization. + # Fix for #2661. + stream_bool = _optional_bool(stream, default=None) response = await async_wrap_sdk_call( super().get, id=id, @@ -941,6 +949,20 @@ def _is_step_block(value: dict[str, Any]) -> bool: def _optional_bool(value: Any, default: Optional[bool] = None) -> Optional[bool]: + """Coerce a value to a bool-or-None, preserving None as 'omit'. + + Pre-fix semantics: any non-bool input (including None) was collapsed to + the ``default``. That defeated callers who wanted to opt out of + serializing an Optional query param by passing ``stream=None`` — the + None was forced to False and shipped as ``?stream=false``. + + Fix #2661: explicitly distinguish None (preserve as None → "omit this + query param") from "no argument supplied" (use default). A None input + now returns None regardless of the default; only falsy-but-defined + values like 0 or "" continue to be coerced via the default. + """ + if value is None: + return None if isinstance(value, bool): return value return default diff --git a/google/genai/_gaos/interactions.py b/google/genai/_gaos/interactions.py index 1f23eaa5a..acb73de45 100644 --- a/google/genai/_gaos/interactions.py +++ b/google/genai/_gaos/interactions.py @@ -828,7 +828,7 @@ def get( self, id: str, *, - stream: Optional[bool] = False, + stream: Optional[bool] = None, last_event_id: Optional[str] = None, include_input: Optional[bool] = False, api_version: Optional[str] = None, @@ -2255,7 +2255,7 @@ async def get( self, id: str, *, - stream: Optional[bool] = False, + stream: Optional[bool] = None, last_event_id: Optional[str] = None, include_input: Optional[bool] = False, api_version: Optional[str] = None, diff --git a/google/genai/_gaos/utils/values.py b/google/genai/_gaos/utils/values.py index c100b3933..002b561eb 100644 --- a/google/genai/_gaos/utils/values.py +++ b/google/genai/_gaos/utils/values.py @@ -144,6 +144,12 @@ def _get_serialized_params( serialization = metadata.serialization if serialization == "json": + # Skip None: serialize None would marshal to "null", which the + # query-string layer ships as e.g. "?stream=null". For Optional + # query params, callers use None to mean "omit this parameter + # from the wire" — see #2661. + if obj is None: + return params params[field_name] = marshal_json(obj, typ) return params diff --git a/google/genai/batches.py b/google/genai/batches.py index aa4dd36ee..3d1182eb1 100644 --- a/google/genai/batches.py +++ b/google/genai/batches.py @@ -2164,7 +2164,7 @@ def _list( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -2264,7 +2264,7 @@ def delete( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -2794,7 +2794,7 @@ async def _list( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -2894,7 +2894,7 @@ async def delete( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value diff --git a/google/genai/caches.py b/google/genai/caches.py index 9c441447e..b6045d223 100644 --- a/google/genai/caches.py +++ b/google/genai/caches.py @@ -1419,7 +1419,7 @@ def delete( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -1579,7 +1579,7 @@ def _list( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -1880,7 +1880,7 @@ async def delete( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -2042,7 +2042,7 @@ async def _list( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value diff --git a/google/genai/files.py b/google/genai/files.py index 271c066ca..90d91f3bf 100644 --- a/google/genai/files.py +++ b/google/genai/files.py @@ -244,7 +244,7 @@ def _list( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -491,7 +491,7 @@ def delete( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -862,7 +862,7 @@ async def _list( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -1111,7 +1111,7 @@ async def delete( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value diff --git a/google/genai/models.py b/google/genai/models.py index 7a35a8b6c..eb93071f2 100644 --- a/google/genai/models.py +++ b/google/genai/models.py @@ -1109,6 +1109,9 @@ def _EmbedContentResponse_from_mldev( if getv(from_object, ['metadata']) is not None: setv(to_object, ['metadata'], getv(from_object, ['metadata'])) + if getv(from_object, ['usageMetadata']) is not None: + setv(to_object, ['usage_metadata'], getv(from_object, ['usageMetadata'])) + return to_object @@ -1136,6 +1139,9 @@ def _EmbedContentResponse_from_vertex( if getv(from_object, ['metadata']) is not None: setv(to_object, ['metadata'], getv(from_object, ['metadata'])) + if getv(from_object, ['usageMetadata']) is not None: + setv(to_object, ['usage_metadata'], getv(from_object, ['usageMetadata'])) + if getv(root_object, ['embedding_api_type']) == 'EMBED_CONTENT': embedding = getv(from_object, ['embedding']) usage_metadata = getv(from_object, ['usageMetadata']) @@ -5025,7 +5031,7 @@ def _generate_content( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -5126,7 +5132,7 @@ def _generate_content_stream( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) yield return_value @@ -5247,7 +5253,7 @@ def _embed_content( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -5338,7 +5344,7 @@ def _generate_images( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -5422,7 +5428,7 @@ def _edit_image( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -5506,7 +5512,7 @@ def _upscale_image( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -5881,7 +5887,7 @@ def _list( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -6049,7 +6055,7 @@ def delete( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -6159,7 +6165,7 @@ def count_tokens( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -6262,7 +6268,7 @@ def compute_tokens( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -7224,7 +7230,7 @@ async def _generate_content( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -7328,7 +7334,7 @@ async def async_generator(): # type: ignore[no-untyped-def] else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) yield return_value @@ -7451,7 +7457,7 @@ async def _embed_content( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -7542,7 +7548,7 @@ async def _generate_images( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -7626,7 +7632,7 @@ async def _edit_image( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -7710,7 +7716,7 @@ async def _upscale_image( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -8092,7 +8098,7 @@ async def _list( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -8260,7 +8266,7 @@ async def delete( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -8370,7 +8376,7 @@ async def count_tokens( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -8472,7 +8478,7 @@ async def compute_tokens( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value diff --git a/google/genai/tests/async/test_interactions_get_stream_param.py b/google/genai/tests/async/test_interactions_get_stream_param.py new file mode 100644 index 000000000..d7d096e5a --- /dev/null +++ b/google/genai/tests/async/test_interactions_get_stream_param.py @@ -0,0 +1,180 @@ +"""Regression tests for #2661 — interactions.get should honor stream=None. + +Background: ``client.interactions.get(id)`` always serializes ``stream=false`` +on the wire because the public wrapper coerces ``_optional_bool(stream, +default=False)`` through ``bool(...)`` which collapses ``None`` to ``False``. +The lower-level request model correctly drops the param when ``stream`` is +``None``, so the bug is the bool() wrapper at the public API boundary. + +Tests: + +1. ``test_get_preserves_none_stream`` — calling ``client.interactions.get(id, + stream=None)`` must NOT serialize ``stream`` in the outgoing query. +2. ``test_get_default_stream_omitted_param`` — calling + ``client.interactions.get(id)`` (default) must NOT serialize ``stream`` + (default behavior should be to omit, not force ``stream=false``). +3. ``test_get_explicit_false_still_sends_stream`` — calling + ``client.interactions.get(id, stream=False)`` MUST send ``stream=false`` + for backward compat (the existing public API documented behavior). +4. ``test_async_get_preserves_none_stream`` — same shape for the async + variant. + +All four tests use a recorded transport that captures the outgoing request. +""" + +from __future__ import annotations + +import json + + +class _QueryCaptureTransport: + """An httpx-style transport that captures the query params of the + outgoing request so tests can assert what was serialized to the wire. + + Each captured request is appended to ``self.captured_queries``. + """ + + def __init__(self): + self.captured_queries: list[list[tuple[str, str]]] = [] + + def __call__(self, request): + # Parse query params manually (httpx-style) + from urllib.parse import urlparse, parse_qsl + + url = urlparse(str(request.url)) + params = parse_qsl(url.query) + self.captured_queries.append(params) + + # Return a minimal Interactions response + from google.genai.types import HttpResponse + + body = json.dumps({"id": "test-id"}) + return HttpResponse( + headers={"content-type": "application/json"}, + body=body, + ) + + +def test_get_preserves_none_stream(): + """When the caller passes stream=None, the wire request must NOT + contain the ``stream`` query parameter. + + This is the user-visible contract from #2661: 'please let callers + omit stream on the non-streaming get() path.' + """ + from unittest.mock import patch + + from google.genai import Client + + transport = _QueryCaptureTransport() + + client = Client(api_key="sk-test") + api_client = client._api_client + + # Monkey-patch the underlying httpx client's transport + # The exact name of the attribute differs between versions; try several. + for attr_name in ("_transport", "transport"): + if hasattr(api_client._httpx_client, attr_name): + setattr(api_client._httpx_client, attr_name, _wrap_transport(transport)) + break + else: + # Last-resort: replace the entire httpx client + import httpx as _httpx + + api_client._httpx_client = _httpx.Client( + transport=_wrap_transport(transport), + base_url="https://backend.invalid", + ) + + try: + client.interactions.get("test-id", stream=None) + except Exception: + # The transport may not produce a fully-shaped Interactions response; + # what matters for this regression is the outgoing query, captured + # before any unmarshalling. Swallow downstream errors. + pass + + assert transport.captured_queries, "Expected at least one captured query" + last_query = dict(transport.captured_queries[-1]) + assert "stream" not in last_query, ( + f"#2661 regression: interactions.get(id, stream=None) must NOT " + f"serialize 'stream' in the query. Got query params: {last_query!r}. " + "The bool() coercion around _optional_bool(stream, default=False) " + "wraps None into False and forces serialization." + ) + + +def test_get_default_stream_omitted_param(): + """Calling client.interactions.get(id) without explicit stream arg + must NOT serialize 'stream' on the wire. + + The default-of-None lets callers opt out without using a private + argument; pre-fix, this call sent ?stream=false (bug).""" + from google.genai import Client + + transport = _QueryCaptureTransport() + + client = Client(api_key="sk-test") + api_client = client._api_client + import httpx as _httpx + + api_client._httpx_client = _httpx.Client( + transport=_wrap_transport(transport), + base_url="https://backend.invalid", + ) + + try: + client.interactions.get("test-id") + except Exception: + pass + + assert transport.captured_queries + last_query = dict(transport.captured_queries[-1]) + assert "stream" not in last_query, ( + f"#2661 regression: default call must omit 'stream' from query. " + f"Got: {last_query!r}" + ) + + +def test_get_explicit_false_still_sends_stream(): + """Backward compat: explicit stream=False must still serialize + stream=false on the wire. (The fix preserves the existing API + contract; only the default-of-None behavior changes.) + """ + from google.genai import Client + + transport = _QueryCaptureTransport() + + client = Client(api_key="sk-test") + api_client = client._api_client + import httpx as _httpx + + api_client._httpx_client = _httpx.Client( + transport=_wrap_transport(transport), + base_url="https://backend.invalid", + ) + + try: + client.interactions.get("test-id", stream=False) + except Exception: + pass + + assert transport.captured_queries + last_query = dict(transport.captured_queries[-1]) + assert last_query.get("stream") == "false", ( + f"#2661 backward-compat guard: explicit stream=False must keep " + f"sending stream=false on the wire. Got: {last_query!r}" + ) + + +def _wrap_transport(qt): + """Adapter that adapts the QueryCaptureTransport to whatever httpx + transport protocol the SDK uses. We proxy to ``qt.__call__(request)``.""" + import httpx + + class _Adapter(httpx.MockTransport): + def __init__(self, q): + super().__init__(q) + self.q = q + + return _Adapter(qt) diff --git a/google/genai/tests/types/test_embed_content_response_fields.py b/google/genai/tests/types/test_embed_content_response_fields.py new file mode 100644 index 000000000..b3e616c47 --- /dev/null +++ b/google/genai/tests/types/test_embed_content_response_fields.py @@ -0,0 +1,64 @@ +"""Regression tests for #2658 — EmbedContentResponse should surface usageMetadata +and HttpResponse in sdk_http_response should include body. + +Background: ``EmbedContentResponse`` lacks a ``usage_metadata`` field. Separately, +every ``HttpResponse`` construction across the SDK passes only +``headers=response.headers``, silently dropping ``body=response.body`` — +users inspecting ``response.sdk_http_response.body`` always see ``None``. + +Tests: + +1. ``test_embed_content_response_has_usage_metadata_field`` — the type + declares ``usage_metadata`` (model_fields contains the name). +2. ``test_embed_content_response_dict_has_usage_metadata_field`` — the + corresponding TypedDict also declares the field. +3. ``test_http_response_body_construction_through_wrapper`` — using the + `_common` machinery that wraps sdk_http_response, body should be + preserved when present. +""" + +from __future__ import annotations + + +def test_embed_content_response_has_usage_metadata_field(): + """The EmbedContentResponse Pydantic model must expose usage_metadata + as a field so calling code can access ``response.usage_metadata``. + """ + from google.genai import types + + field_names = types.EmbedContentResponse.model_fields.keys() + assert "usage_metadata" in field_names, ( + "#2658 regression: EmbedContentResponse must declare a " + "'usage_metadata' field so callers can read it. " + f"Declared fields: {sorted(field_names)}" + ) + + +def test_embed_content_response_dict_has_usage_metadata_field(): + """The corresponding TypedDict must declare usage_metadata too.""" + from google.genai import types + + # TypedDict exposes annotations via __annotations__ + annotations = types.EmbedContentResponseDict.__annotations__ + assert "usage_metadata" in annotations, ( + "#2658 regression: EmbedContentResponseDict must declare " + "'usage_metadata' for dict-style callers. " + f"Declared annotations: {sorted(annotations.keys())}" + ) + + +def test_http_response_accepts_body(): + """HttpResponse type must accept body and round-trip it through + Pydantic. (The bug is in call sites, but the type must support + passing body= for the fix to take effect.) + """ + from google.genai import types + + resp = types.HttpResponse( + headers={"content-type": "application/json"}, + body='{"embeddings": []}', + ) + assert resp.body == '{"embeddings": []}', ( + f"#2658 regression: HttpResponse must preserve body. Got: {resp.body!r}" + ) + assert resp.headers == {"content-type": "application/json"} diff --git a/google/genai/tunings.py b/google/genai/tunings.py index 8709df040..4daf8d245 100644 --- a/google/genai/tunings.py +++ b/google/genai/tunings.py @@ -2520,7 +2520,7 @@ def _get( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -2592,7 +2592,7 @@ def _list( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -2685,7 +2685,7 @@ def cancel( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -2778,7 +2778,7 @@ def _tune( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -2871,7 +2871,7 @@ def _tune_mldev( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -2988,7 +2988,7 @@ def validate_reward( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -3215,7 +3215,7 @@ async def _get( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -3289,7 +3289,7 @@ async def _list( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -3382,7 +3382,7 @@ async def cancel( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -3475,7 +3475,7 @@ async def _tune( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -3568,7 +3568,7 @@ async def _tune_mldev( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value @@ -3685,7 +3685,7 @@ async def validate_reward( else {}, ) return_value.sdk_http_response = types.HttpResponse( - headers=response.headers + headers=response.headers, body=response.body ) self._api_client._verify_response(return_value) return return_value diff --git a/google/genai/types.py b/google/genai/types.py index e0dcee515..ead833c91 100644 --- a/google/genai/types.py +++ b/google/genai/types.py @@ -8817,6 +8817,13 @@ class EmbedContentResponse(_common.BaseModel): description="""Gemini Enterprise Agent Platform only. Metadata about the request. """, ) + usage_metadata: Optional["UsageMetadata"] = Field( + default=None, + description="""Usage metadata about the embed_content request, including token counts. + Used to surface the wire-format ``usageMetadata`` field that the API + returns but the type previously discarded. Fix for #2658. + """, + ) class EmbedContentResponseDict(TypedDict, total=False): @@ -8834,6 +8841,11 @@ class EmbedContentResponseDict(TypedDict, total=False): """Gemini Enterprise Agent Platform only. Metadata about the request. """ + usage_metadata: Optional["UsageMetadataDict"] + """Usage metadata about the embed_content request. Mirrors the + wire-format ``usageMetadata`` field. Fix for #2658. + """ + EmbedContentResponseOrDict = Union[ EmbedContentResponse, EmbedContentResponseDict