google-genai 1.53.0__py3-none-any.whl → 1.55.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (324) hide show
  1. google/genai/__init__.py +1 -0
  2. google/genai/_api_client.py +6 -6
  3. google/genai/_interactions/__init__.py +117 -0
  4. google/genai/_interactions/_base_client.py +2019 -0
  5. google/genai/_interactions/_client.py +511 -0
  6. google/genai/_interactions/_compat.py +234 -0
  7. google/genai/_interactions/_constants.py +29 -0
  8. google/genai/_interactions/_exceptions.py +122 -0
  9. google/genai/_interactions/_files.py +139 -0
  10. google/genai/_interactions/_models.py +873 -0
  11. google/genai/_interactions/_qs.py +165 -0
  12. google/genai/_interactions/_resource.py +58 -0
  13. google/genai/_interactions/_response.py +847 -0
  14. google/genai/_interactions/_streaming.py +354 -0
  15. google/genai/_interactions/_types.py +276 -0
  16. google/genai/_interactions/_utils/__init__.py +79 -0
  17. google/genai/_interactions/_utils/_compat.py +61 -0
  18. google/genai/_interactions/_utils/_datetime_parse.py +151 -0
  19. google/genai/_interactions/_utils/_logs.py +40 -0
  20. google/genai/_interactions/_utils/_proxy.py +80 -0
  21. google/genai/_interactions/_utils/_reflection.py +57 -0
  22. google/genai/_interactions/_utils/_resources_proxy.py +39 -0
  23. google/genai/_interactions/_utils/_streams.py +27 -0
  24. google/genai/_interactions/_utils/_sync.py +73 -0
  25. google/genai/_interactions/_utils/_transform.py +472 -0
  26. google/genai/_interactions/_utils/_typing.py +172 -0
  27. google/genai/_interactions/_utils/_utils.py +437 -0
  28. google/genai/_interactions/_version.py +18 -0
  29. google/genai/_interactions/resources/__init__.py +34 -0
  30. google/genai/_interactions/resources/interactions.py +1350 -0
  31. google/genai/_interactions/types/__init__.py +107 -0
  32. google/genai/_interactions/types/allowed_tools.py +33 -0
  33. google/genai/_interactions/types/allowed_tools_param.py +35 -0
  34. google/genai/_interactions/types/annotation.py +42 -0
  35. google/genai/_interactions/types/annotation_param.py +42 -0
  36. google/genai/_interactions/types/audio_content.py +38 -0
  37. google/genai/_interactions/types/audio_content_param.py +45 -0
  38. google/genai/_interactions/types/audio_mime_type.py +25 -0
  39. google/genai/_interactions/types/audio_mime_type_param.py +27 -0
  40. google/genai/_interactions/types/code_execution_call_arguments.py +33 -0
  41. google/genai/_interactions/types/code_execution_call_arguments_param.py +32 -0
  42. google/genai/_interactions/types/code_execution_call_content.py +37 -0
  43. google/genai/_interactions/types/code_execution_call_content_param.py +37 -0
  44. google/genai/_interactions/types/code_execution_result_content.py +42 -0
  45. google/genai/_interactions/types/code_execution_result_content_param.py +41 -0
  46. google/genai/_interactions/types/content_delta.py +358 -0
  47. google/genai/_interactions/types/content_start.py +79 -0
  48. google/genai/_interactions/types/content_stop.py +35 -0
  49. google/genai/_interactions/types/deep_research_agent_config.py +33 -0
  50. google/genai/_interactions/types/deep_research_agent_config_param.py +32 -0
  51. google/genai/_interactions/types/document_content.py +36 -0
  52. google/genai/_interactions/types/document_content_param.py +43 -0
  53. google/genai/_interactions/types/dynamic_agent_config.py +44 -0
  54. google/genai/_interactions/types/dynamic_agent_config_param.py +33 -0
  55. google/genai/_interactions/types/error_event.py +46 -0
  56. google/genai/_interactions/types/file_search_result_content.py +46 -0
  57. google/genai/_interactions/types/file_search_result_content_param.py +46 -0
  58. google/genai/_interactions/types/function.py +38 -0
  59. google/genai/_interactions/types/function_call_content.py +39 -0
  60. google/genai/_interactions/types/function_call_content_param.py +39 -0
  61. google/genai/_interactions/types/function_param.py +37 -0
  62. google/genai/_interactions/types/function_result_content.py +52 -0
  63. google/genai/_interactions/types/function_result_content_param.py +54 -0
  64. google/genai/_interactions/types/generation_config.py +57 -0
  65. google/genai/_interactions/types/generation_config_param.py +59 -0
  66. google/genai/_interactions/types/google_search_call_arguments.py +29 -0
  67. google/genai/_interactions/types/google_search_call_arguments_param.py +31 -0
  68. google/genai/_interactions/types/google_search_call_content.py +37 -0
  69. google/genai/_interactions/types/google_search_call_content_param.py +37 -0
  70. google/genai/_interactions/types/google_search_result.py +35 -0
  71. google/genai/_interactions/types/google_search_result_content.py +43 -0
  72. google/genai/_interactions/types/google_search_result_content_param.py +44 -0
  73. google/genai/_interactions/types/google_search_result_param.py +35 -0
  74. google/genai/_interactions/types/image_content.py +41 -0
  75. google/genai/_interactions/types/image_content_param.py +48 -0
  76. google/genai/_interactions/types/image_mime_type.py +23 -0
  77. google/genai/_interactions/types/image_mime_type_param.py +25 -0
  78. google/genai/_interactions/types/interaction.py +165 -0
  79. google/genai/_interactions/types/interaction_create_params.py +212 -0
  80. google/genai/_interactions/types/interaction_event.py +37 -0
  81. google/genai/_interactions/types/interaction_get_params.py +46 -0
  82. google/genai/_interactions/types/interaction_sse_event.py +32 -0
  83. google/genai/_interactions/types/interaction_status_update.py +37 -0
  84. google/genai/_interactions/types/mcp_server_tool_call_content.py +42 -0
  85. google/genai/_interactions/types/mcp_server_tool_call_content_param.py +42 -0
  86. google/genai/_interactions/types/mcp_server_tool_result_content.py +52 -0
  87. google/genai/_interactions/types/mcp_server_tool_result_content_param.py +54 -0
  88. google/genai/_interactions/types/model.py +36 -0
  89. google/genai/_interactions/types/model_param.py +38 -0
  90. google/genai/_interactions/types/speech_config.py +35 -0
  91. google/genai/_interactions/types/speech_config_param.py +35 -0
  92. google/genai/_interactions/types/text_content.py +37 -0
  93. google/genai/_interactions/types/text_content_param.py +38 -0
  94. google/genai/_interactions/types/thinking_level.py +22 -0
  95. google/genai/_interactions/types/thought_content.py +41 -0
  96. google/genai/_interactions/types/thought_content_param.py +47 -0
  97. google/genai/_interactions/types/tool.py +100 -0
  98. google/genai/_interactions/types/tool_choice.py +26 -0
  99. google/genai/_interactions/types/tool_choice_config.py +28 -0
  100. google/genai/_interactions/types/tool_choice_config_param.py +29 -0
  101. google/genai/_interactions/types/tool_choice_param.py +28 -0
  102. google/genai/_interactions/types/tool_choice_type.py +22 -0
  103. google/genai/_interactions/types/tool_param.py +97 -0
  104. google/genai/_interactions/types/turn.py +76 -0
  105. google/genai/_interactions/types/turn_param.py +73 -0
  106. google/genai/_interactions/types/url_context_call_arguments.py +29 -0
  107. google/genai/_interactions/types/url_context_call_arguments_param.py +31 -0
  108. google/genai/_interactions/types/url_context_call_content.py +37 -0
  109. google/genai/_interactions/types/url_context_call_content_param.py +37 -0
  110. google/genai/_interactions/types/url_context_result.py +33 -0
  111. google/genai/_interactions/types/url_context_result_content.py +43 -0
  112. google/genai/_interactions/types/url_context_result_content_param.py +44 -0
  113. google/genai/_interactions/types/url_context_result_param.py +32 -0
  114. google/genai/_interactions/types/usage.py +106 -0
  115. google/genai/_interactions/types/usage_param.py +106 -0
  116. google/genai/_interactions/types/video_content.py +41 -0
  117. google/genai/_interactions/types/video_content_param.py +48 -0
  118. google/genai/_interactions/types/video_mime_type.py +36 -0
  119. google/genai/_interactions/types/video_mime_type_param.py +38 -0
  120. google/genai/_live_converters.py +34 -3
  121. google/genai/_tokens_converters.py +5 -0
  122. google/genai/batches.py +62 -55
  123. google/genai/client.py +223 -0
  124. google/genai/errors.py +16 -1
  125. google/genai/file_search_stores.py +60 -60
  126. google/genai/files.py +56 -56
  127. google/genai/interactions.py +17 -0
  128. google/genai/live.py +4 -3
  129. google/genai/models.py +15 -3
  130. google/genai/tests/__init__.py +21 -0
  131. google/genai/tests/afc/__init__.py +21 -0
  132. google/genai/tests/afc/test_convert_if_exist_pydantic_model.py +309 -0
  133. google/genai/tests/afc/test_convert_number_values_for_function_call_args.py +63 -0
  134. google/genai/tests/afc/test_find_afc_incompatible_tool_indexes.py +240 -0
  135. google/genai/tests/afc/test_generate_content_stream_afc.py +530 -0
  136. google/genai/tests/afc/test_generate_content_stream_afc_thoughts.py +77 -0
  137. google/genai/tests/afc/test_get_function_map.py +176 -0
  138. google/genai/tests/afc/test_get_function_response_parts.py +277 -0
  139. google/genai/tests/afc/test_get_max_remote_calls_for_afc.py +130 -0
  140. google/genai/tests/afc/test_invoke_function_from_dict_args.py +241 -0
  141. google/genai/tests/afc/test_raise_error_for_afc_incompatible_config.py +159 -0
  142. google/genai/tests/afc/test_should_append_afc_history.py +53 -0
  143. google/genai/tests/afc/test_should_disable_afc.py +214 -0
  144. google/genai/tests/batches/__init__.py +17 -0
  145. google/genai/tests/batches/test_cancel.py +77 -0
  146. google/genai/tests/batches/test_create.py +78 -0
  147. google/genai/tests/batches/test_create_with_bigquery.py +113 -0
  148. google/genai/tests/batches/test_create_with_file.py +82 -0
  149. google/genai/tests/batches/test_create_with_gcs.py +125 -0
  150. google/genai/tests/batches/test_create_with_inlined_requests.py +255 -0
  151. google/genai/tests/batches/test_delete.py +86 -0
  152. google/genai/tests/batches/test_embedding.py +157 -0
  153. google/genai/tests/batches/test_get.py +78 -0
  154. google/genai/tests/batches/test_list.py +79 -0
  155. google/genai/tests/caches/__init__.py +17 -0
  156. google/genai/tests/caches/constants.py +29 -0
  157. google/genai/tests/caches/test_create.py +210 -0
  158. google/genai/tests/caches/test_create_custom_url.py +105 -0
  159. google/genai/tests/caches/test_delete.py +54 -0
  160. google/genai/tests/caches/test_delete_custom_url.py +52 -0
  161. google/genai/tests/caches/test_get.py +94 -0
  162. google/genai/tests/caches/test_get_custom_url.py +52 -0
  163. google/genai/tests/caches/test_list.py +68 -0
  164. google/genai/tests/caches/test_update.py +70 -0
  165. google/genai/tests/caches/test_update_custom_url.py +58 -0
  166. google/genai/tests/chats/__init__.py +1 -0
  167. google/genai/tests/chats/test_get_history.py +597 -0
  168. google/genai/tests/chats/test_send_message.py +844 -0
  169. google/genai/tests/chats/test_validate_response.py +90 -0
  170. google/genai/tests/client/__init__.py +17 -0
  171. google/genai/tests/client/test_async_stream.py +427 -0
  172. google/genai/tests/client/test_client_close.py +197 -0
  173. google/genai/tests/client/test_client_initialization.py +1687 -0
  174. google/genai/tests/client/test_client_requests.py +355 -0
  175. google/genai/tests/client/test_custom_client.py +77 -0
  176. google/genai/tests/client/test_http_options.py +178 -0
  177. google/genai/tests/client/test_replay_client_equality.py +168 -0
  178. google/genai/tests/client/test_retries.py +846 -0
  179. google/genai/tests/client/test_upload_errors.py +136 -0
  180. google/genai/tests/common/__init__.py +17 -0
  181. google/genai/tests/common/test_common.py +954 -0
  182. google/genai/tests/conftest.py +162 -0
  183. google/genai/tests/documents/__init__.py +17 -0
  184. google/genai/tests/documents/test_delete.py +51 -0
  185. google/genai/tests/documents/test_get.py +85 -0
  186. google/genai/tests/documents/test_list.py +72 -0
  187. google/genai/tests/errors/__init__.py +1 -0
  188. google/genai/tests/errors/test_api_error.py +417 -0
  189. google/genai/tests/file_search_stores/__init__.py +17 -0
  190. google/genai/tests/file_search_stores/test_create.py +66 -0
  191. google/genai/tests/file_search_stores/test_delete.py +64 -0
  192. google/genai/tests/file_search_stores/test_get.py +94 -0
  193. google/genai/tests/file_search_stores/test_import_file.py +112 -0
  194. google/genai/tests/file_search_stores/test_list.py +57 -0
  195. google/genai/tests/file_search_stores/test_upload_to_file_search_store.py +141 -0
  196. google/genai/tests/files/__init__.py +17 -0
  197. google/genai/tests/files/test_delete.py +46 -0
  198. google/genai/tests/files/test_download.py +85 -0
  199. google/genai/tests/files/test_get.py +46 -0
  200. google/genai/tests/files/test_list.py +72 -0
  201. google/genai/tests/files/test_upload.py +255 -0
  202. google/genai/tests/imports/test_no_optional_imports.py +28 -0
  203. google/genai/tests/interactions/__init__.py +0 -0
  204. google/genai/tests/interactions/test_integration.py +80 -0
  205. google/genai/tests/live/__init__.py +16 -0
  206. google/genai/tests/live/test_live.py +2177 -0
  207. google/genai/tests/live/test_live_music.py +362 -0
  208. google/genai/tests/live/test_live_response.py +163 -0
  209. google/genai/tests/live/test_send_client_content.py +147 -0
  210. google/genai/tests/live/test_send_realtime_input.py +268 -0
  211. google/genai/tests/live/test_send_tool_response.py +222 -0
  212. google/genai/tests/local_tokenizer/__init__.py +17 -0
  213. google/genai/tests/local_tokenizer/test_local_tokenizer.py +343 -0
  214. google/genai/tests/local_tokenizer/test_local_tokenizer_loader.py +235 -0
  215. google/genai/tests/mcp/__init__.py +17 -0
  216. google/genai/tests/mcp/test_has_mcp_tool_usage.py +89 -0
  217. google/genai/tests/mcp/test_mcp_to_gemini_tools.py +191 -0
  218. google/genai/tests/mcp/test_parse_config_for_mcp_sessions.py +201 -0
  219. google/genai/tests/mcp/test_parse_config_for_mcp_usage.py +130 -0
  220. google/genai/tests/mcp/test_set_mcp_usage_header.py +72 -0
  221. google/genai/tests/models/__init__.py +17 -0
  222. google/genai/tests/models/constants.py +8 -0
  223. google/genai/tests/models/test_compute_tokens.py +120 -0
  224. google/genai/tests/models/test_count_tokens.py +159 -0
  225. google/genai/tests/models/test_delete.py +107 -0
  226. google/genai/tests/models/test_edit_image.py +264 -0
  227. google/genai/tests/models/test_embed_content.py +94 -0
  228. google/genai/tests/models/test_function_call_streaming.py +442 -0
  229. google/genai/tests/models/test_generate_content.py +2502 -0
  230. google/genai/tests/models/test_generate_content_cached_content.py +132 -0
  231. google/genai/tests/models/test_generate_content_config_zero_value.py +103 -0
  232. google/genai/tests/models/test_generate_content_from_apikey.py +44 -0
  233. google/genai/tests/models/test_generate_content_http_options.py +40 -0
  234. google/genai/tests/models/test_generate_content_image_generation.py +143 -0
  235. google/genai/tests/models/test_generate_content_mcp.py +343 -0
  236. google/genai/tests/models/test_generate_content_media_resolution.py +97 -0
  237. google/genai/tests/models/test_generate_content_model.py +139 -0
  238. google/genai/tests/models/test_generate_content_part.py +821 -0
  239. google/genai/tests/models/test_generate_content_thought.py +76 -0
  240. google/genai/tests/models/test_generate_content_tools.py +1761 -0
  241. google/genai/tests/models/test_generate_images.py +191 -0
  242. google/genai/tests/models/test_generate_videos.py +759 -0
  243. google/genai/tests/models/test_get.py +104 -0
  244. google/genai/tests/models/test_list.py +233 -0
  245. google/genai/tests/models/test_recontext_image.py +189 -0
  246. google/genai/tests/models/test_segment_image.py +148 -0
  247. google/genai/tests/models/test_update.py +95 -0
  248. google/genai/tests/models/test_upscale_image.py +157 -0
  249. google/genai/tests/operations/__init__.py +17 -0
  250. google/genai/tests/operations/test_get.py +38 -0
  251. google/genai/tests/public_samples/__init__.py +17 -0
  252. google/genai/tests/public_samples/test_gemini_text_only.py +34 -0
  253. google/genai/tests/pytest_helper.py +229 -0
  254. google/genai/tests/shared/__init__.py +16 -0
  255. google/genai/tests/shared/batches/__init__.py +14 -0
  256. google/genai/tests/shared/batches/test_create_delete.py +57 -0
  257. google/genai/tests/shared/batches/test_create_get_cancel.py +56 -0
  258. google/genai/tests/shared/batches/test_list.py +40 -0
  259. google/genai/tests/shared/caches/__init__.py +14 -0
  260. google/genai/tests/shared/caches/test_create_get_delete.py +67 -0
  261. google/genai/tests/shared/caches/test_create_update_get.py +71 -0
  262. google/genai/tests/shared/caches/test_list.py +40 -0
  263. google/genai/tests/shared/chats/__init__.py +14 -0
  264. google/genai/tests/shared/chats/test_send_message.py +48 -0
  265. google/genai/tests/shared/chats/test_send_message_stream.py +50 -0
  266. google/genai/tests/shared/files/__init__.py +14 -0
  267. google/genai/tests/shared/files/test_list.py +41 -0
  268. google/genai/tests/shared/files/test_upload_get_delete.py +54 -0
  269. google/genai/tests/shared/models/__init__.py +14 -0
  270. google/genai/tests/shared/models/test_compute_tokens.py +41 -0
  271. google/genai/tests/shared/models/test_count_tokens.py +40 -0
  272. google/genai/tests/shared/models/test_edit_image.py +67 -0
  273. google/genai/tests/shared/models/test_embed.py +40 -0
  274. google/genai/tests/shared/models/test_generate_content.py +39 -0
  275. google/genai/tests/shared/models/test_generate_content_stream.py +54 -0
  276. google/genai/tests/shared/models/test_generate_images.py +40 -0
  277. google/genai/tests/shared/models/test_generate_videos.py +38 -0
  278. google/genai/tests/shared/models/test_list.py +37 -0
  279. google/genai/tests/shared/models/test_recontext_image.py +55 -0
  280. google/genai/tests/shared/models/test_segment_image.py +52 -0
  281. google/genai/tests/shared/models/test_upscale_image.py +52 -0
  282. google/genai/tests/shared/tunings/__init__.py +16 -0
  283. google/genai/tests/shared/tunings/test_create.py +46 -0
  284. google/genai/tests/shared/tunings/test_create_get_cancel.py +56 -0
  285. google/genai/tests/shared/tunings/test_list.py +39 -0
  286. google/genai/tests/tokens/__init__.py +16 -0
  287. google/genai/tests/tokens/test_create.py +154 -0
  288. google/genai/tests/transformers/__init__.py +17 -0
  289. google/genai/tests/transformers/test_blobs.py +71 -0
  290. google/genai/tests/transformers/test_bytes.py +15 -0
  291. google/genai/tests/transformers/test_duck_type.py +96 -0
  292. google/genai/tests/transformers/test_function_responses.py +72 -0
  293. google/genai/tests/transformers/test_schema.py +653 -0
  294. google/genai/tests/transformers/test_t_batch.py +286 -0
  295. google/genai/tests/transformers/test_t_content.py +160 -0
  296. google/genai/tests/transformers/test_t_contents.py +398 -0
  297. google/genai/tests/transformers/test_t_part.py +85 -0
  298. google/genai/tests/transformers/test_t_parts.py +87 -0
  299. google/genai/tests/transformers/test_t_tool.py +157 -0
  300. google/genai/tests/transformers/test_t_tools.py +195 -0
  301. google/genai/tests/tunings/__init__.py +16 -0
  302. google/genai/tests/tunings/test_cancel.py +39 -0
  303. google/genai/tests/tunings/test_end_to_end.py +106 -0
  304. google/genai/tests/tunings/test_get.py +67 -0
  305. google/genai/tests/tunings/test_list.py +75 -0
  306. google/genai/tests/tunings/test_tune.py +268 -0
  307. google/genai/tests/types/__init__.py +16 -0
  308. google/genai/tests/types/test_bytes_internal.py +271 -0
  309. google/genai/tests/types/test_bytes_type.py +152 -0
  310. google/genai/tests/types/test_future.py +101 -0
  311. google/genai/tests/types/test_optional_types.py +36 -0
  312. google/genai/tests/types/test_part_type.py +616 -0
  313. google/genai/tests/types/test_schema_from_json_schema.py +417 -0
  314. google/genai/tests/types/test_schema_json_schema.py +468 -0
  315. google/genai/tests/types/test_types.py +2903 -0
  316. google/genai/tunings.py +57 -57
  317. google/genai/types.py +229 -121
  318. google/genai/version.py +1 -1
  319. {google_genai-1.53.0.dist-info → google_genai-1.55.0.dist-info}/METADATA +4 -2
  320. google_genai-1.55.0.dist-info/RECORD +345 -0
  321. google_genai-1.53.0.dist-info/RECORD +0 -41
  322. {google_genai-1.53.0.dist-info → google_genai-1.55.0.dist-info}/WHEEL +0 -0
  323. {google_genai-1.53.0.dist-info → google_genai-1.55.0.dist-info}/licenses/LICENSE +0 -0
  324. {google_genai-1.53.0.dist-info → google_genai-1.55.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,2502 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ #
15
+
16
+ import os
17
+ import pathlib
18
+
19
+ from pydantic import BaseModel, ValidationError, Field, ConfigDict
20
+ from typing import Literal, List, Optional, Union, Set
21
+ from datetime import datetime
22
+ import pytest
23
+ import json
24
+ import logging
25
+ import sys
26
+ from ... import _transformers as t
27
+ from ... import errors
28
+ from ... import types
29
+ from .. import pytest_helper
30
+ from enum import Enum
31
+
32
+ GEMINI_FLASH_LATEST = 'gemini-2.5-flash'
33
+ GEMINI_FLASH_2_0 = 'gemini-2.0-flash-001'
34
+ GEMINI_FLASH_IMAGE_LATEST = 'gemini-2.5-flash-image'
35
+
36
+ IMAGE_PNG_FILE_PATH = pathlib.Path(__file__).parent / '../data/google.png'
37
+ image_bytes = IMAGE_PNG_FILE_PATH.read_bytes()
38
+
39
+ AUDIO_WAV_FILE_PATH = pathlib.Path(__file__).parent / '../data/voice_sample.wav'
40
+ audio_bytes = AUDIO_WAV_FILE_PATH.read_bytes()
41
+
42
+
43
+ safety_settings_with_method = [
44
+ {
45
+ 'category': 'HARM_CATEGORY_HATE_SPEECH',
46
+ 'threshold': 'BLOCK_ONLY_HIGH',
47
+ 'method': 'SEVERITY',
48
+ },
49
+ {
50
+ 'category': 'HARM_CATEGORY_DANGEROUS_CONTENT',
51
+ 'threshold': 'BLOCK_LOW_AND_ABOVE',
52
+ 'method': 'PROBABILITY',
53
+ },
54
+ ]
55
+
56
+ test_http_options = {'api_version': 'v1', 'headers': {'test': 'headers'}}
57
+
58
+
59
+ class InstrumentEnum(Enum):
60
+ PERCUSSION = 'Percussion'
61
+ STRING = 'String'
62
+ WOODWIND = 'Woodwind'
63
+ BRASS = 'Brass'
64
+ KEYBOARD = 'Keyboard'
65
+
66
+
67
+ test_table: list[pytest_helper.TestTableItem] = [
68
+ pytest_helper.TestTableItem(
69
+ name='test_http_options_in_method',
70
+ parameters=types._GenerateContentParameters(
71
+ model=GEMINI_FLASH_LATEST,
72
+ contents=t.t_contents('What is your name?'),
73
+ config={
74
+ 'http_options': test_http_options,
75
+ },
76
+ ),
77
+ ),
78
+ pytest_helper.TestTableItem(
79
+ name='test_union_contents_is_string',
80
+ override_replay_id='test_sync',
81
+ parameters=types._GenerateContentParameters(
82
+ model=GEMINI_FLASH_LATEST, contents='Tell me a story in 300 words.'
83
+ ),
84
+ has_union=True,
85
+ ),
86
+ pytest_helper.TestTableItem(
87
+ name='test_union_contents_is_content',
88
+ override_replay_id='test_sync',
89
+ parameters=types._GenerateContentParameters(
90
+ model=GEMINI_FLASH_LATEST,
91
+ contents=types.Content(
92
+ role='user',
93
+ parts=[types.Part(text='Tell me a story in 300 words.')],
94
+ ),
95
+ ),
96
+ has_union=True,
97
+ ),
98
+ pytest_helper.TestTableItem(
99
+ name='test_union_contents_is_parts',
100
+ override_replay_id='test_sync',
101
+ parameters=types._GenerateContentParameters(
102
+ model=GEMINI_FLASH_LATEST,
103
+ contents=[types.Part(text='Tell me a story in 300 words.')],
104
+ ),
105
+ has_union=True,
106
+ ),
107
+ pytest_helper.TestTableItem(
108
+ name='test_union_contents_is_part',
109
+ override_replay_id='test_sync',
110
+ parameters=types._GenerateContentParameters(
111
+ model=GEMINI_FLASH_LATEST,
112
+ contents=types.Part(text='Tell me a story in 300 words.'),
113
+ ),
114
+ has_union=True,
115
+ ),
116
+ pytest_helper.TestTableItem(
117
+ name='test_sync_content_list',
118
+ override_replay_id='test_sync',
119
+ parameters=types._GenerateContentParameters(
120
+ model=GEMINI_FLASH_LATEST,
121
+ contents=[
122
+ types.Content(
123
+ role='user',
124
+ parts=[types.Part(text='Tell me a story in 300 words.')],
125
+ )
126
+ ],
127
+ ),
128
+ ),
129
+ # You need to enable llama API in Vertex AI Model Garden.
130
+ pytest_helper.TestTableItem(
131
+ name='test_llama',
132
+ parameters=types._GenerateContentParameters(
133
+ model='meta/llama-3.2-90b-vision-instruct-maas',
134
+ contents=t.t_contents('What is your name?'),
135
+ ),
136
+ exception_if_mldev='404',
137
+ skip_in_api_mode='it will encounter 403 for api mode',
138
+ ),
139
+ pytest_helper.TestTableItem(
140
+ name='test_system_instructions',
141
+ parameters=types._GenerateContentParameters(
142
+ model=GEMINI_FLASH_LATEST,
143
+ contents=t.t_contents('high'),
144
+ config={
145
+ 'system_instruction': t.t_content(
146
+ 'I say high, you say low'
147
+ )
148
+ },
149
+ ),
150
+ ),
151
+ pytest_helper.TestTableItem(
152
+ name='test_labels',
153
+ exception_if_mldev='not supported',
154
+ parameters=types._GenerateContentParameters(
155
+ model=GEMINI_FLASH_LATEST,
156
+ contents=t.t_contents('What is your name?'),
157
+ config={
158
+ 'labels': {'label1': 'value1', 'label2': 'value2'},
159
+ },
160
+ ),
161
+ ),
162
+ pytest_helper.TestTableItem(
163
+ name='test_simple_shared_generation_config',
164
+ parameters=types._GenerateContentParameters(
165
+ model=GEMINI_FLASH_LATEST,
166
+ contents=t.t_contents('What is your name?'),
167
+ config={
168
+ 'max_output_tokens': 100,
169
+ 'top_k': 2,
170
+ 'temperature': 0.5,
171
+ 'top_p': 0.5,
172
+ 'response_mime_type': 'application/json',
173
+ 'stop_sequences': ['\n'],
174
+ 'candidate_count': 2,
175
+ 'seed': 42,
176
+ },
177
+ ),
178
+ ),
179
+ pytest_helper.TestTableItem(
180
+ name='test_2_candidates_gemini_2_5_flash',
181
+ parameters=types._GenerateContentParameters(
182
+ model=GEMINI_FLASH_LATEST,
183
+ contents=t.t_contents('Tell me a story in 30 words.'),
184
+ config={
185
+ 'candidate_count': 2,
186
+ },
187
+ ),
188
+ ),
189
+ pytest_helper.TestTableItem(
190
+ name='test_safety_settings_on_difference',
191
+ parameters=types._GenerateContentParameters(
192
+ model=GEMINI_FLASH_LATEST,
193
+ contents=t.t_contents('What is your name?'),
194
+ config={
195
+ 'safety_settings': safety_settings_with_method,
196
+ },
197
+ ),
198
+ exception_if_mldev='method',
199
+ ),
200
+ pytest_helper.TestTableItem(
201
+ name='test_penalty',
202
+ parameters=types._GenerateContentParameters(
203
+ model=GEMINI_FLASH_2_0,
204
+ contents=t.t_contents('Tell me a story in 30 words.'),
205
+ config={
206
+ 'presence_penalty': 0.5,
207
+ 'frequency_penalty': 0.5,
208
+ },
209
+ ),
210
+ ),
211
+ pytest_helper.TestTableItem(
212
+ name='test_penalty_gemini_2_0_flash',
213
+ parameters=types._GenerateContentParameters(
214
+ model=GEMINI_FLASH_2_0,
215
+ contents=t.t_contents('Tell me a story in 30 words.'),
216
+ config={
217
+ 'presence_penalty': 0.5,
218
+ 'frequency_penalty': 0.5,
219
+ },
220
+ ),
221
+ ),
222
+ pytest_helper.TestTableItem(
223
+ name='test_google_search_tool',
224
+ parameters=types._GenerateContentParameters(
225
+ model=GEMINI_FLASH_LATEST,
226
+ contents=t.t_contents('Why is the sky blue?'),
227
+ config=types.GenerateContentConfig(
228
+ tools=[types.Tool(google_search=types.GoogleSearch())]
229
+ ),
230
+ ),
231
+ ),
232
+ pytest_helper.TestTableItem(
233
+ name='test_google_maps_tool',
234
+ parameters=types._GenerateContentParameters(
235
+ model=GEMINI_FLASH_LATEST,
236
+ contents=t.t_contents('Find restaurants near me.'),
237
+ config=types.GenerateContentConfig(
238
+ tools=[{'google_maps': {}}],
239
+ tool_config={
240
+ "retrieval_config": {
241
+ "lat_lng": {
242
+ "latitude": 37.421993,
243
+ "longitude": -122.079725,
244
+ }
245
+ }
246
+ }
247
+ ),
248
+ ),
249
+ ),
250
+ pytest_helper.TestTableItem(
251
+ name='test_google_search_tool_with_time_range_filter',
252
+ parameters=types._GenerateContentParameters(
253
+ model=GEMINI_FLASH_LATEST,
254
+ contents=t.t_contents('What is the QQQ stock price?'),
255
+ config=types.GenerateContentConfig(
256
+ tools=[
257
+ types.Tool(
258
+ google_search=types.GoogleSearch(
259
+ time_range_filter=types.Interval(
260
+ start_time=datetime.fromisoformat(
261
+ '2025-05-01T00:00:00Z'
262
+ ),
263
+ end_time=datetime.fromisoformat(
264
+ '2025-05-03T00:00:00Z'
265
+ ),
266
+ )
267
+ )
268
+ )
269
+ ]
270
+ ),
271
+ ),
272
+ ),
273
+ pytest_helper.TestTableItem(
274
+ name='test_google_search_tool_with_exclude_domains',
275
+ parameters=types._GenerateContentParameters(
276
+ model=GEMINI_FLASH_LATEST,
277
+ contents=t.t_contents('Why is the sky blue?'),
278
+ config=types.GenerateContentConfig(
279
+ tools=[
280
+ types.Tool(
281
+ google_search=types.GoogleSearch(
282
+ exclude_domains=['amazon.com', 'facebook.com']
283
+ )
284
+ )
285
+ ]
286
+ ),
287
+ ),
288
+ exception_if_mldev='not supported in',
289
+ ),
290
+ pytest_helper.TestTableItem(
291
+ name='test_google_search_tool_with_blocking_confidence',
292
+ parameters=types._GenerateContentParameters(
293
+ model=GEMINI_FLASH_LATEST,
294
+ contents=t.t_contents('Why is the sky blue?'),
295
+ config=types.GenerateContentConfig(
296
+ tools=[
297
+ types.Tool(
298
+ google_search=types.GoogleSearch(
299
+ blocking_confidence=types.PhishBlockThreshold.BLOCK_LOW_AND_ABOVE,
300
+ )
301
+ )
302
+ ]
303
+ ),
304
+ ),
305
+ exception_if_mldev='not supported in',
306
+ ),
307
+ pytest_helper.TestTableItem(
308
+ name='test_enterprise_web_search_tool',
309
+ parameters=types._GenerateContentParameters(
310
+ model=GEMINI_FLASH_LATEST,
311
+ contents=t.t_contents('Why is the sky blue?'),
312
+ config=types.GenerateContentConfig(
313
+ tools=[
314
+ types.Tool(
315
+ enterprise_web_search=types.EnterpriseWebSearch()
316
+ )
317
+ ]
318
+ ),
319
+ ),
320
+ exception_if_mldev='not supported in',
321
+ ),
322
+ pytest_helper.TestTableItem(
323
+ name='test_enterprise_web_search_tool_with_exclude_domains',
324
+ parameters=types._GenerateContentParameters(
325
+ model=GEMINI_FLASH_LATEST,
326
+ contents=t.t_contents('Why is the sky blue?'),
327
+ config=types.GenerateContentConfig(
328
+ tools=[
329
+ types.Tool(
330
+ enterprise_web_search=types.EnterpriseWebSearch(
331
+ exclude_domains=['amazon.com', 'facebook.com']
332
+ )
333
+ )
334
+ ]
335
+ ),
336
+ ),
337
+ exception_if_mldev='not supported in',
338
+ ),
339
+ pytest_helper.TestTableItem(
340
+ name='test_enterprise_web_search_tool_with_blocking_confidence',
341
+ parameters=types._GenerateContentParameters(
342
+ model=GEMINI_FLASH_LATEST,
343
+ contents=t.t_contents('Why is the sky blue?'),
344
+ config=types.GenerateContentConfig(
345
+ tools=[
346
+ types.Tool(
347
+ enterprise_web_search=types.EnterpriseWebSearch(
348
+ blocking_confidence=types.PhishBlockThreshold.BLOCK_LOW_AND_ABOVE,
349
+ )
350
+ )
351
+ ]
352
+ ),
353
+ ),
354
+ exception_if_mldev='not supported in',
355
+ ),
356
+ pytest_helper.TestTableItem(
357
+ name='test_speech_with_config',
358
+ parameters=types._GenerateContentParameters(
359
+ model='gemini-2.5-flash-preview-tts',
360
+ contents=t.t_contents(
361
+ 'Produce a speech response saying "Cheese"'
362
+ ),
363
+ config=types.GenerateContentConfig(
364
+ response_modalities=['audio'],
365
+ speech_config=types.SpeechConfig(
366
+ voice_config=types.VoiceConfig(
367
+ prebuilt_voice_config=types.PrebuiltVoiceConfig(
368
+ voice_name='charon'
369
+ )
370
+ )
371
+ ),
372
+ ),
373
+ ),
374
+ ),
375
+ pytest_helper.TestTableItem(
376
+ name='test_speech_with_multi_speaker_voice_config',
377
+ exception_if_vertex='not supported',
378
+ parameters=types._GenerateContentParameters(
379
+ model='gemini-2.5-flash-preview-tts',
380
+ contents=t.t_contents(
381
+ 'Alice says "Hi", Bob replies with "what\'s up"?'
382
+ ),
383
+ config=types.GenerateContentConfig(
384
+ response_modalities=['audio'],
385
+ speech_config=types.SpeechConfig(
386
+ multi_speaker_voice_config=types.MultiSpeakerVoiceConfig(
387
+ speaker_voice_configs=[
388
+ types.SpeakerVoiceConfig(
389
+ speaker='Alice',
390
+ voice_config=types.VoiceConfig(
391
+ prebuilt_voice_config=types.PrebuiltVoiceConfig(
392
+ voice_name='leda'
393
+ )
394
+ ),
395
+ ),
396
+ types.SpeakerVoiceConfig(
397
+ speaker='Bob',
398
+ voice_config=types.VoiceConfig(
399
+ prebuilt_voice_config=types.PrebuiltVoiceConfig(
400
+ voice_name='kore'
401
+ )
402
+ ),
403
+ ),
404
+ ],
405
+ )
406
+ ),
407
+ ),
408
+ ),
409
+ ),
410
+ pytest_helper.TestTableItem(
411
+ name='test_speech_error_with_speech_config_and_multi_speech_config',
412
+ exception_if_vertex='not supported',
413
+ exception_if_mldev='mutually exclusive',
414
+ parameters=types._GenerateContentParameters(
415
+ model='gemini-2.5-flash-preview-tts',
416
+ contents=t.t_contents(
417
+ 'Alice says "Hi", Bob replies with "what\'s up"?'
418
+ ),
419
+ config=types.GenerateContentConfig(
420
+ response_modalities=['audio'],
421
+ speech_config=types.SpeechConfig(
422
+ voice_config=types.VoiceConfig(
423
+ prebuilt_voice_config=types.PrebuiltVoiceConfig(
424
+ voice_name='puck'
425
+ )
426
+ ),
427
+ multi_speaker_voice_config=types.MultiSpeakerVoiceConfig(
428
+ speaker_voice_configs=[
429
+ types.SpeakerVoiceConfig(
430
+ speaker='Alice',
431
+ voice_config=types.VoiceConfig(
432
+ prebuilt_voice_config=types.PrebuiltVoiceConfig(
433
+ voice_name='leda'
434
+ )
435
+ ),
436
+ ),
437
+ types.SpeakerVoiceConfig(
438
+ speaker='Bob',
439
+ voice_config=types.VoiceConfig(
440
+ prebuilt_voice_config=types.PrebuiltVoiceConfig(
441
+ voice_name='kore'
442
+ )
443
+ ),
444
+ ),
445
+ ],
446
+ ),
447
+ ),
448
+ ),
449
+ ),
450
+ ),
451
+ pytest_helper.TestTableItem(
452
+ name='test_union_speech_string_config',
453
+ parameters=types._GenerateContentParameters(
454
+ model='gemini-2.5-flash-preview-tts',
455
+ contents='Say hello!',
456
+ config=types.GenerateContentConfig(
457
+ response_modalities=['audio'], speech_config='charon'
458
+ ),
459
+ ),
460
+ has_union=True,
461
+ ),
462
+ pytest_helper.TestTableItem(
463
+ name='test_audio_timestamp',
464
+ parameters=types._GenerateContentParameters(
465
+ model=GEMINI_FLASH_LATEST,
466
+ contents=[
467
+ types.Content(
468
+ role='user',
469
+ parts=[
470
+ types.Part(
471
+ file_data=types.FileData(
472
+ file_uri='gs://cloud-samples-data/generative-ai/audio/pixel.mp3',
473
+ mime_type='audio/mpeg',
474
+ )
475
+ ),
476
+ types.Part(
477
+ text="""Can you transcribe this interview, in the
478
+ format of timecode, speaker, caption. Use speaker A,
479
+ speaker B, etc. to identify speakers."""
480
+ ),
481
+ ],
482
+ )
483
+ ],
484
+ config=types.GenerateContentConfig(audio_timestamp=True),
485
+ ),
486
+ exception_if_mldev='not supported',
487
+ ),
488
+ pytest_helper.TestTableItem(
489
+ name='test_response_schema_with_default',
490
+ parameters=types._GenerateContentParameters(
491
+ model=GEMINI_FLASH_LATEST,
492
+ contents=t.t_contents('What is your name?'),
493
+ config={
494
+ 'response_mime_type': 'application/json',
495
+ 'response_schema': {
496
+ 'type': 'OBJECT',
497
+ 'properties': {
498
+ 'name': {
499
+ 'type': 'STRING',
500
+ 'default': 'default_name',
501
+ },
502
+ },
503
+ },
504
+ },
505
+ ),
506
+ ignore_keys=['parsed'],
507
+ ),
508
+ pytest_helper.TestTableItem(
509
+ name='test_invalid_model_parameter_path',
510
+ parameters=types._GenerateContentParameters(
511
+ model='gemini-2.5-flash/../../upload/v1beta/files',
512
+ contents=t.t_contents('What is your name?'),
513
+ ),
514
+ exception_if_vertex='invalid model parameter',
515
+ exception_if_mldev='invalid model parameter',
516
+ ),
517
+ pytest_helper.TestTableItem(
518
+ name='test_invalid_model_parameter_question_mark',
519
+ parameters=types._GenerateContentParameters(
520
+ model='gemini-2.5-flash?',
521
+ contents=t.t_contents('What is your name?'),
522
+ ),
523
+ exception_if_vertex='invalid model parameter',
524
+ exception_if_mldev='invalid model parameter',
525
+ ),
526
+ pytest_helper.TestTableItem(
527
+ name='test_invalid_model_parameter_ampersand',
528
+ parameters=types._GenerateContentParameters(
529
+ model='gemini-2.5-flash&',
530
+ contents=t.t_contents('What is your name?'),
531
+ ),
532
+ exception_if_vertex='invalid model parameter',
533
+ exception_if_mldev='invalid model parameter',
534
+ ),
535
+ pytest_helper.TestTableItem(
536
+ name='test_enhanced_civic_answers',
537
+ parameters=types._GenerateContentParameters(
538
+ model=GEMINI_FLASH_LATEST,
539
+ contents=t.t_contents(
540
+ 'Summarize the evidence that confirms widespread voter fraud was'
541
+ ' the reason the last national election results were'
542
+ ' inaccurate.'
543
+ ),
544
+ config={
545
+ 'enable_enhanced_civic_answers': True,
546
+ },
547
+ ),
548
+ exception_if_vertex='not supported',
549
+ ),
550
+ ]
551
+
552
+ pytestmark = pytest_helper.setup(
553
+ file=__file__,
554
+ globals_for_file=globals(),
555
+ test_method='models.generate_content',
556
+ test_table=test_table,
557
+ )
558
+ pytest_plugins = ('pytest_asyncio',)
559
+
560
+
561
+ def test_sync_with_headers(client):
562
+ response = client.models.generate_content(
563
+ model=GEMINI_FLASH_LATEST,
564
+ contents='Tell me a story in 300 words.',
565
+ )
566
+ assert response.sdk_http_response.headers is not None
567
+ assert response.sdk_http_response.body is None
568
+
569
+
570
+ def test_sync_with_full_response(client):
571
+ response = client.models.generate_content(
572
+ model=GEMINI_FLASH_LATEST,
573
+ contents='Tell me a story in 300 words.',
574
+ config={
575
+ 'should_return_http_response': True,
576
+ },
577
+ )
578
+ print(response.sdk_http_response.body)
579
+ assert response.sdk_http_response.headers is not None
580
+ assert response.sdk_http_response.body is not None
581
+ assert 'candidates' in response.sdk_http_response.body
582
+ assert 'content' in response.sdk_http_response.body
583
+ assert 'parts' in response.sdk_http_response.body
584
+ assert 'usageMetadata' in response.sdk_http_response.body
585
+
586
+ @pytest.mark.asyncio
587
+ async def test_async(client):
588
+ response = await client.aio.models.generate_content(
589
+ model=GEMINI_FLASH_LATEST,
590
+ contents='Tell me a story in 300 words.',
591
+ config={
592
+ 'http_options': test_http_options,
593
+ },
594
+ )
595
+ assert response.text
596
+
597
+
598
+ @pytest.mark.asyncio
599
+ async def test_async_with_headers(client):
600
+ response = await client.aio.models.generate_content(
601
+ model=GEMINI_FLASH_LATEST,
602
+ contents='Tell me a story in 300 words.',
603
+ )
604
+ assert response.sdk_http_response.headers is not None
605
+ assert response.sdk_http_response.body is None
606
+
607
+
608
+ @pytest.mark.asyncio
609
+ async def test_async_with_full_response(client):
610
+ response = await client.aio.models.generate_content(
611
+ model=GEMINI_FLASH_LATEST,
612
+ contents='Tell me a story in 300 words.',
613
+ config={
614
+ 'should_return_http_response': True,
615
+ },
616
+ )
617
+ assert response.sdk_http_response.headers is not None
618
+ assert response.sdk_http_response.body is not None
619
+ assert 'candidates' in response.sdk_http_response.body
620
+ assert 'content' in response.sdk_http_response.body
621
+ assert 'parts' in response.sdk_http_response.body
622
+ assert 'usageMetadata' in response.sdk_http_response.body
623
+
624
+
625
+ def test_sync_stream(client):
626
+ response = client.models.generate_content_stream(
627
+ model=GEMINI_FLASH_LATEST,
628
+ contents='Tell me a story in 300 words.',
629
+ config={
630
+ 'http_options': test_http_options,
631
+ },
632
+ )
633
+ chunks = 0
634
+ for part in response:
635
+ chunks += 1
636
+ assert part.text is not None or part.candidates[0].finish_reason
637
+
638
+ assert chunks >= 1
639
+
640
+
641
+ def test_sync_stream_with_should_return_http_headers(client):
642
+ response = client.models.generate_content_stream(
643
+ model=GEMINI_FLASH_LATEST,
644
+ contents='Tell me a story in 300 words.',
645
+ config={
646
+ 'http_options': test_http_options,
647
+ },
648
+ )
649
+ chunks = 0
650
+ for part in response:
651
+ chunks += 1
652
+ assert part.text is not None or part.candidates[0].finish_reason
653
+ assert part.sdk_http_response.headers is not None
654
+ assert chunks >= 1
655
+
656
+
657
+ def test_sync_stream_with_non_text_modality(client):
658
+ response = client.models.generate_content_stream(
659
+ model='gemini-2.0-flash-preview-image-generation',
660
+ contents=(
661
+ 'Generate an image of the Eiffel tower with fireworks in the'
662
+ ' background.'
663
+ ),
664
+ config={
665
+ 'response_modalities': ['IMAGE', 'TEXT'],
666
+ },
667
+ )
668
+ chunks = 0
669
+ for chunk in response:
670
+ chunks += 1
671
+ if chunk.candidates[0].finish_reason is not None:
672
+ continue
673
+ for part in chunk.parts:
674
+ assert part.text is not None or part.inline_data is not None
675
+
676
+ assert chunks >= 1
677
+
678
+
679
+ @pytest.mark.asyncio
680
+ async def test_async_stream(client):
681
+ chunks = 0
682
+ async for part in await client.aio.models.generate_content_stream(
683
+ model=GEMINI_FLASH_LATEST, contents='Tell me a story in 300 words.',
684
+ config={
685
+ 'http_options': test_http_options,
686
+ },
687
+ ):
688
+ chunks += 1
689
+ assert part.text is not None or part.candidates[0].finish_reason
690
+
691
+ assert chunks >= 1
692
+
693
+
694
+ @pytest.mark.asyncio
695
+ async def test_async_stream_with_headers(client):
696
+ chunks = 0
697
+ async for part in await client.aio.models.generate_content_stream(
698
+ model=GEMINI_FLASH_LATEST, contents='Tell me a story in 300 words.',
699
+ config={
700
+ 'http_options': test_http_options,
701
+ },
702
+ ):
703
+ chunks += 1
704
+ assert part.text is not None or part.candidates[0].finish_reason
705
+ assert part.sdk_http_response.headers is not None
706
+
707
+ assert chunks >= 1
708
+
709
+
710
+ @pytest.mark.asyncio
711
+ async def test_async_stream_with_non_text_modality(client):
712
+ chunks = 0
713
+ async for chunk in await client.aio.models.generate_content_stream(
714
+ model=GEMINI_FLASH_IMAGE_LATEST,
715
+ contents=(
716
+ 'Generate an image of the Eiffel tower with fireworks in the'
717
+ ' background.'
718
+ ),
719
+ config={
720
+ 'response_modalities': ['IMAGE', 'TEXT'],
721
+ },
722
+ ):
723
+ chunks += 1
724
+ if chunk.candidates[0].finish_reason is not None:
725
+ continue
726
+ for part in chunk.parts:
727
+ assert part.text is not None or part.inline_data is not None
728
+
729
+ assert chunks >= 1
730
+
731
+
732
+ def test_simple_shared_generation_config_stream(client):
733
+ chunks = 0
734
+ for chunk in client.models.generate_content_stream(
735
+ model=GEMINI_FLASH_LATEST,
736
+ contents='tell me a story in 300 words',
737
+ config={
738
+ 'max_output_tokens': 1000,
739
+ 'top_k': 2,
740
+ 'temperature': 0.5,
741
+ 'top_p': 0.5,
742
+ 'response_mime_type': 'application/json',
743
+ 'stop_sequences': ['\n'],
744
+ 'seed': 42,
745
+ },
746
+ ):
747
+ chunks += 1
748
+ assert (
749
+ chunk.text is not None or chunk.candidates[0].finish_reason
750
+ ), f'vertexai: {client._api_client.vertexai}, {chunk.candidate[0]}'
751
+ assert chunks >= 1
752
+
753
+
754
+ @pytest.mark.asyncio
755
+ async def test_simple_shared_generation_config_async(client):
756
+ response = await client.aio.models.generate_content(
757
+ model=GEMINI_FLASH_LATEST,
758
+ contents='tell me a story in 300 words',
759
+ config={
760
+ 'max_output_tokens': 4000,
761
+ 'top_k': 2,
762
+ 'temperature': 0.5,
763
+ 'top_p': 0.5,
764
+ 'response_mime_type': 'application/json',
765
+ 'stop_sequences': ['\n'],
766
+ 'seed': 42,
767
+ },
768
+ )
769
+
770
+
771
+ @pytest.mark.asyncio
772
+ async def test_simple_shared_generation_config_stream_async(client):
773
+ chunks = 0
774
+ async for part in await client.aio.models.generate_content_stream(
775
+ model=GEMINI_FLASH_2_0,
776
+ contents='tell me a story in 300 words',
777
+ config={
778
+ 'max_output_tokens': 400,
779
+ 'top_k': 2,
780
+ 'temperature': 0.5,
781
+ 'top_p': 0.5,
782
+ 'response_mime_type': 'application/json',
783
+ 'stop_sequences': ['\n'],
784
+ 'seed': 42,
785
+ },
786
+ ):
787
+ chunks += 1
788
+ assert part.text is not None or part.candidates[0].finish_reason
789
+ assert chunks >= 1
790
+
791
+
792
+ def test_log_probs(client):
793
+ client.models.generate_content(
794
+ model=GEMINI_FLASH_2_0,
795
+ contents='What is your name?',
796
+ config={
797
+ 'logprobs': 2,
798
+ 'presence_penalty': 0.5,
799
+ 'frequency_penalty': 0.5,
800
+ 'response_logprobs': True,
801
+ },
802
+ )
803
+
804
+
805
+ def test_simple_config(client):
806
+ response = client.models.generate_content(
807
+ model=GEMINI_FLASH_LATEST,
808
+ contents='What is your name?',
809
+ config={
810
+ 'max_output_tokens': 300,
811
+ 'top_k': 2,
812
+ },
813
+ )
814
+ assert response.text
815
+
816
+
817
+ def test_model_selection_config_dict(client):
818
+ if not client.vertexai:
819
+ return
820
+ response = client.models.generate_content(
821
+ model=GEMINI_FLASH_LATEST,
822
+ contents='Give me a Taylor Swift lyric and explain its meaning.',
823
+ config={
824
+ 'model_selection_config': {
825
+ 'feature_selection_preference': 'PRIORITIZE_COST'
826
+ }
827
+ },
828
+ )
829
+ assert response.text
830
+
831
+
832
+ def test_model_selection_config_pydantic(client):
833
+ if not client.vertexai:
834
+ return
835
+ response = client.models.generate_content(
836
+ model=GEMINI_FLASH_LATEST,
837
+ contents='Give me a Taylor Swift lyric and explain its meaning.',
838
+ config=types.GenerateContentConfig(
839
+ model_selection_config=types.ModelSelectionConfig(
840
+ feature_selection_preference=types.FeatureSelectionPreference.PRIORITIZE_QUALITY
841
+ )
842
+ ),
843
+ )
844
+ assert response.text
845
+
846
+
847
+ def test_sdk_logger_logs_warnings_once(client, caplog):
848
+ from ... import types as types_module
849
+
850
+ types_module._response_text_warning_logged = False
851
+
852
+ caplog.set_level(logging.WARNING, logger='google_genai.types')
853
+
854
+ response = client.models.generate_content(
855
+ model=GEMINI_FLASH_LATEST,
856
+ contents='Tell me a 50 word story about cheese.',
857
+ config={
858
+ 'candidate_count': 2,
859
+ }
860
+ )
861
+ assert response.text
862
+ assert 'WARNING' in caplog.text
863
+ assert 'there are 2 candidates' in caplog.text
864
+ caplog_after_first_call = caplog.text
865
+ assert len(caplog.records) == 1
866
+ client.models.generate_content(
867
+ model=GEMINI_FLASH_LATEST,
868
+ contents='Tell me a 50 word story about cheese.',
869
+ config={
870
+ 'candidate_count': 2,
871
+ }
872
+ )
873
+ assert caplog.text == caplog_after_first_call
874
+ assert len(caplog.records) == 1
875
+
876
+
877
+ def test_response_create_time_and_response_id(client):
878
+ if client.vertexai:
879
+ response = client.models.generate_content(
880
+ model=GEMINI_FLASH_LATEST,
881
+ contents='What is your name?',
882
+ config={
883
+ 'max_output_tokens': 300,
884
+ 'top_k': 2,
885
+ },
886
+ )
887
+ # create_time and response_id are not supported in mldev
888
+ assert response.create_time
889
+ assert response.response_id
890
+ assert isinstance(response.create_time, datetime)
891
+
892
+
893
+ def test_safety_settings(client):
894
+ response = client.models.generate_content(
895
+ model=GEMINI_FLASH_LATEST,
896
+ contents='What is your name?',
897
+ config={
898
+ 'safety_settings': [{
899
+ 'category': 'HARM_CATEGORY_HATE_SPEECH',
900
+ 'threshold': 'BLOCK_ONLY_HIGH',
901
+ }]
902
+ },
903
+ )
904
+ assert response.text
905
+
906
+
907
+ def test_safety_settings_on_difference_stream(client):
908
+ safety_settings = [
909
+ {
910
+ 'category': 'HARM_CATEGORY_HATE_SPEECH',
911
+ 'threshold': 'BLOCK_ONLY_HIGH',
912
+ 'method': 'SEVERITY',
913
+ },
914
+ {
915
+ 'category': 'HARM_CATEGORY_DANGEROUS_CONTENT',
916
+ 'threshold': 'BLOCK_LOW_AND_ABOVE',
917
+ 'method': 'PROBABILITY',
918
+ },
919
+ ]
920
+ if client._api_client.vertexai:
921
+ for part in client.models.generate_content_stream(
922
+ model=GEMINI_FLASH_LATEST,
923
+ contents='What is your name?',
924
+ config={
925
+ 'safety_settings': safety_settings,
926
+ },
927
+ ):
928
+ pass
929
+ else:
930
+ with pytest.raises(ValueError) as e:
931
+ for part in client.models.generate_content_stream(
932
+ model=GEMINI_FLASH_LATEST,
933
+ contents='What is your name?',
934
+ config={
935
+ 'safety_settings': safety_settings,
936
+ },
937
+ ):
938
+ pass
939
+ assert 'method' in str(e)
940
+
941
+
942
+ def test_safety_settings_on_difference_stream_with_lower_enum(client):
943
+ safety_settings = [
944
+ {
945
+ 'category': 'harm_category_hate_speech',
946
+ 'threshold': 'block_only_high',
947
+ 'method': 'severity',
948
+ },
949
+ {
950
+ 'category': 'harm_category_dangerous_content',
951
+ 'threshold': 'block_low_and_above',
952
+ 'method': 'probability',
953
+ },
954
+ ]
955
+ if client._api_client.vertexai:
956
+ for part in client.models.generate_content_stream(
957
+ model=GEMINI_FLASH_LATEST,
958
+ contents='What is your name?',
959
+ config={
960
+ 'safety_settings': safety_settings,
961
+ },
962
+ ):
963
+ pass
964
+ else:
965
+ with pytest.raises(ValueError) as e:
966
+ for part in client.models.generate_content_stream(
967
+ model=GEMINI_FLASH_LATEST,
968
+ contents='What is your name?',
969
+ config={
970
+ 'safety_settings': safety_settings,
971
+ },
972
+ ):
973
+ pass
974
+ assert 'method' in str(e)
975
+
976
+
977
+ def test_pydantic_schema(client):
978
+ class CountryInfo(BaseModel):
979
+ # We need at least one test with `title` in properties in case the schema
980
+ # edits go wrong.
981
+ title: str
982
+ population: int
983
+ capital: str
984
+ continent: str
985
+ gdp: int
986
+ official_language: str
987
+ total_area_sq_mi: int
988
+
989
+ response = client.models.generate_content(
990
+ model=GEMINI_FLASH_LATEST,
991
+ contents='Give me information of the United States.',
992
+ config={
993
+ 'response_mime_type': 'application/json',
994
+ 'response_schema': CountryInfo,
995
+ },
996
+ )
997
+ assert isinstance(response.parsed, CountryInfo)
998
+
999
+ def test_json_schema_fields(client):
1000
+ class UserRole(str, Enum):
1001
+ ADMIN = "admin"
1002
+ VIEWER = "viewer"
1003
+ class Address(BaseModel):
1004
+ street: str
1005
+ city: str
1006
+ class UserProfile(BaseModel):
1007
+ username: str = Field(description="User's unique name")
1008
+ age: Optional[int] = Field(ge=0, le=20)
1009
+ roles: Set[UserRole] = Field(min_items=1)
1010
+ contact: Union[Address, str]
1011
+
1012
+ model_config = ConfigDict(
1013
+ title="User Schema", description="A user profile"
1014
+ ) # This is the title of the schema
1015
+
1016
+ response = client.models.generate_content(
1017
+ model=GEMINI_FLASH_LATEST,
1018
+ contents='Give me information of the United States.',
1019
+ config={
1020
+ 'response_mime_type': 'application/json',
1021
+ 'response_json_schema': UserProfile.model_json_schema(),
1022
+ },
1023
+ )
1024
+ print(response.parsed)
1025
+ assert response.parsed != None
1026
+
1027
+
1028
+ def test_pydantic_schema_orders_properties(client):
1029
+ class Restaurant(BaseModel):
1030
+ name: str
1031
+ rating: int
1032
+ fun_fact: str
1033
+
1034
+ response = client.models.generate_content(
1035
+ model=GEMINI_FLASH_LATEST,
1036
+ contents='Give me information about a restaurant in Boston.',
1037
+ config={
1038
+ 'response_mime_type': 'application/json',
1039
+ 'response_schema': Restaurant,
1040
+ },
1041
+ )
1042
+ response_text_json = json.loads(response.text)
1043
+ response_keys = list(response_text_json.keys())
1044
+ assert response_keys[0] == 'name'
1045
+ assert response_keys == list(Restaurant.model_fields.keys())
1046
+
1047
+
1048
+ def test_pydantic_schema_with_default_value(client):
1049
+ class Restaurant(BaseModel):
1050
+ name: str
1051
+ rating: int = 0
1052
+ city: Optional[str] = 'New York'
1053
+
1054
+ response = client.models.generate_content(
1055
+ model=GEMINI_FLASH_LATEST,
1056
+ contents='Can you recommend a restaurant for me?',
1057
+ config={
1058
+ 'response_mime_type': 'application/json',
1059
+ 'response_schema': Restaurant,
1060
+ },
1061
+ )
1062
+ assert isinstance(response.parsed, Restaurant)
1063
+
1064
+
1065
+ def test_repeated_pydantic_schema(client):
1066
+ # This tests the defs handling on the pydantic side.
1067
+ class Person(BaseModel):
1068
+ name: str
1069
+
1070
+ class Relationship(BaseModel):
1071
+ relationship: str
1072
+ person1: Person
1073
+ person2: Person
1074
+
1075
+ response = client.models.generate_content(
1076
+ model=GEMINI_FLASH_LATEST,
1077
+ contents='Create a couple.',
1078
+ config={
1079
+ 'response_mime_type': 'application/json',
1080
+ 'response_schema': Relationship,
1081
+ },
1082
+ )
1083
+ assert isinstance(response.parsed, Relationship)
1084
+
1085
+
1086
+ def test_int_schema(client):
1087
+ response = client.models.generate_content(
1088
+ model=GEMINI_FLASH_LATEST,
1089
+ contents="what's your favorite number?",
1090
+ config={
1091
+ 'response_mime_type': 'application/json',
1092
+ 'response_schema': int,
1093
+ },
1094
+ )
1095
+ assert isinstance(response.parsed, int)
1096
+
1097
+
1098
+ def test_nested_list_of_int_schema(client):
1099
+ response = client.models.generate_content(
1100
+ model=GEMINI_FLASH_LATEST,
1101
+ contents="Can you return two matrices, a 2x3 and a 3x4?",
1102
+ config={
1103
+ 'response_mime_type': 'application/json',
1104
+ 'response_schema': list[list[list[int]]],
1105
+ },
1106
+ )
1107
+ assert isinstance(response.parsed[0][0][0], int)
1108
+
1109
+
1110
+ def test_literal_schema(client):
1111
+ response = client.models.generate_content(
1112
+ model=GEMINI_FLASH_LATEST,
1113
+ contents='Which ice cream flavor should I order?',
1114
+ config={
1115
+ 'response_mime_type': 'application/json',
1116
+ 'response_schema': Literal['chocolate', 'vanilla', 'cookie dough'],
1117
+ },
1118
+ )
1119
+
1120
+ allowed_values = ['chocolate', 'vanilla', 'cookie dough']
1121
+ assert isinstance(response.parsed, str)
1122
+ assert response.parsed in allowed_values
1123
+
1124
+
1125
+ def test_literal_schema_with_non_string_types_raises(client):
1126
+ with pytest.raises(ValueError) as e:
1127
+ client.models.generate_content(
1128
+ model=GEMINI_FLASH_LATEST,
1129
+ contents='Which ice cream flavor should I order?',
1130
+ config={
1131
+ 'response_mime_type': 'application/json',
1132
+ 'response_schema': Literal['chocolate', 'vanilla', 1],
1133
+ },
1134
+ )
1135
+ assert 'validation error' in str(e)
1136
+
1137
+
1138
+ @pytest.mark.skipif(
1139
+ sys.version_info < (3, 10),
1140
+ reason='| is not supported in Python 3.9',
1141
+ )
1142
+ def test_pydantic_schema_with_literal(client):
1143
+ class Movie(BaseModel):
1144
+ name: str
1145
+ genre: Literal['action', 'comedy', 'drama']
1146
+
1147
+ response = client.models.generate_content(
1148
+ model=GEMINI_FLASH_LATEST,
1149
+ contents='Give me information about the movie "Mean Girls"',
1150
+ config={
1151
+ 'response_mime_type': 'application/json',
1152
+ 'response_schema': Movie,
1153
+ },
1154
+ )
1155
+ assert isinstance(response.parsed, Movie)
1156
+ assert isinstance(response.parsed.genre, str)
1157
+ assert response.parsed.genre in ['action', 'comedy', 'drama']
1158
+
1159
+
1160
+ @pytest.mark.skipif(
1161
+ sys.version_info < (3, 10),
1162
+ reason='| is not supported in Python 3.9',
1163
+ )
1164
+ def test_pydantic_schema_with_single_value_literal(client):
1165
+ class Movie(BaseModel):
1166
+ name: str
1167
+ genre: Literal['action']
1168
+
1169
+ response = client.models.generate_content(
1170
+ model=GEMINI_FLASH_LATEST,
1171
+ contents='Give me information about the movie "The Matrix"',
1172
+ config={
1173
+ 'response_mime_type': 'application/json',
1174
+ 'response_schema': Movie,
1175
+ },
1176
+ )
1177
+ assert isinstance(response.parsed, Movie)
1178
+ assert response.parsed.genre == 'action'
1179
+
1180
+
1181
+ @pytest.mark.skipif(
1182
+ sys.version_info < (3, 10),
1183
+ reason='| is not supported in Python 3.9',
1184
+ )
1185
+ def test_pydantic_schema_with_none(client):
1186
+ class CountryInfo(BaseModel):
1187
+ name: str
1188
+ total_area_sq_mi: int | None = None
1189
+
1190
+ response = client.models.generate_content(
1191
+ model=GEMINI_FLASH_LATEST,
1192
+ contents='Give me information of the United States.',
1193
+ config={
1194
+ 'response_mime_type': 'application/json',
1195
+ 'response_schema': CountryInfo,
1196
+ },
1197
+ )
1198
+ assert isinstance(response.parsed, CountryInfo)
1199
+ assert type(response.parsed.total_area_sq_mi) in [int, None]
1200
+
1201
+
1202
+ def test_pydantic_schema_with_optional_none(client):
1203
+ class CountryInfo(BaseModel):
1204
+ name: str
1205
+ total_area_sq_mi: Optional[int] = None
1206
+
1207
+ response = client.models.generate_content(
1208
+ model=GEMINI_FLASH_LATEST,
1209
+ contents='Give me information of the United States but don\'t include the total area.',
1210
+ config={
1211
+ 'response_mime_type': 'application/json',
1212
+ 'response_schema': CountryInfo,
1213
+ },
1214
+ )
1215
+ assert isinstance(response.parsed, CountryInfo)
1216
+ assert response.parsed.total_area_sq_mi is None
1217
+
1218
+
1219
+ def test_pydantic_schema_from_json(client):
1220
+ class CountryInfo(BaseModel):
1221
+ name: str
1222
+ pupulation: int
1223
+ capital: str
1224
+ continent: str
1225
+ gdp: int
1226
+ official_language: str
1227
+ total_area_sq_mi: int
1228
+
1229
+ schema = types.Schema.model_validate(CountryInfo.model_json_schema())
1230
+
1231
+ response = client.models.generate_content(
1232
+ model=GEMINI_FLASH_LATEST,
1233
+ contents='Give me information of the United States.',
1234
+ config=types.GenerateContentConfig(
1235
+ response_mime_type='application/json',
1236
+ response_schema=schema,
1237
+ ),
1238
+ )
1239
+
1240
+ assert response.text
1241
+
1242
+
1243
+ @pytest.mark.skipif(
1244
+ sys.version_info < (3, 10),
1245
+ reason='| is not supported in Python 3.9',
1246
+ )
1247
+ def test_schema_with_union_type(client):
1248
+ response = client.models.generate_content(
1249
+ model=GEMINI_FLASH_LATEST,
1250
+ contents='Give me a random number, either as an integers or written out as words.',
1251
+ config=types.GenerateContentConfig.model_validate(dict(
1252
+ response_mime_type='application/json',
1253
+ response_schema=int | str,
1254
+ ))
1255
+ )
1256
+ assert type(response.parsed) in (int, str)
1257
+
1258
+
1259
+ def test_schema_with_union_type_all_py_versions(client):
1260
+ response = client.models.generate_content(
1261
+ model=GEMINI_FLASH_LATEST,
1262
+ contents="Give me a random number, either an integer or a float.",
1263
+ config={
1264
+ 'response_mime_type': 'application/json',
1265
+ 'response_schema': Union[int, float],
1266
+ },
1267
+ )
1268
+ assert type(response.parsed) in (int, float)
1269
+
1270
+
1271
+ @pytest.mark.skipif(
1272
+ sys.version_info < (3, 10),
1273
+ reason='| is not supported in Python 3.9',
1274
+ )
1275
+ def test_list_schema_with_union_type(client):
1276
+ response = client.models.generate_content(
1277
+ model=GEMINI_FLASH_LATEST,
1278
+ contents='Give me a list of 5 random numbers, including some integers and some written out as words.',
1279
+ config=types.GenerateContentConfig(
1280
+ response_mime_type='application/json',
1281
+ response_schema=list[int | str],
1282
+ )
1283
+ )
1284
+ for item in response.parsed:
1285
+ assert isinstance(item, int) or isinstance(item, str)
1286
+
1287
+
1288
+ def test_list_schema_with_union_type_all_py_versions(client):
1289
+ response = client.models.generate_content(
1290
+ model=GEMINI_FLASH_LATEST,
1291
+ contents='Give me a list of 5 random numbers, including some integers and some written out as words.',
1292
+ config=types.GenerateContentConfig(
1293
+ response_mime_type='application/json',
1294
+ response_schema=list[Union[int, str]],
1295
+ )
1296
+ )
1297
+ for item in response.parsed:
1298
+ assert isinstance(item, int) or isinstance(item, str)
1299
+
1300
+
1301
+ def test_pydantic_schema_with_optional_generic_alias(client):
1302
+ class CountryInfo(BaseModel):
1303
+ name: str
1304
+ population: int
1305
+ capital: str
1306
+ continent: str
1307
+ gdp: int
1308
+ official_languages: Optional[List[str]]
1309
+ total_area_sq_mi: int
1310
+
1311
+ response = client.models.generate_content(
1312
+ model=GEMINI_FLASH_LATEST,
1313
+ contents='Give me information of the United States.',
1314
+ config={
1315
+ 'response_mime_type': 'application/json',
1316
+ 'response_schema': CountryInfo,
1317
+ },
1318
+ )
1319
+ assert isinstance(response.parsed, CountryInfo)
1320
+ assert isinstance(response.parsed.official_languages, list) or response.parsed.official_languages is None
1321
+
1322
+
1323
+ def test_pydantic_schema_with_optional_pydantic(client):
1324
+ class TestPerson(BaseModel):
1325
+ first_name: Optional[str] = Field(
1326
+ description='First name of the person', default=None
1327
+ )
1328
+ last_name: Optional[str] = Field(
1329
+ description='Last name of the person', default=None
1330
+ )
1331
+
1332
+ class TestDocument(BaseModel):
1333
+ case_number: Optional[str] = Field(
1334
+ description='Case number assigned to the claim', default=None
1335
+ )
1336
+ filed_by: Optional[TestPerson] = Field(
1337
+ description='Name of the party that filed or submitted the statement',
1338
+ default=None,
1339
+ )
1340
+
1341
+ test_prompt = """
1342
+ Carefully examine the following document and extract the metadata.
1343
+ Be sure to include the party that filed the document.
1344
+
1345
+ Document Text:
1346
+ --------------
1347
+ Case Number: 20-12345
1348
+ File by: John Doe
1349
+ """
1350
+
1351
+ response = client.models.generate_content(
1352
+ model=GEMINI_FLASH_LATEST,
1353
+ contents=test_prompt,
1354
+ config={
1355
+ 'response_mime_type': 'application/json',
1356
+ 'response_schema': TestDocument,
1357
+ },
1358
+ )
1359
+ assert isinstance(response.parsed, TestDocument)
1360
+ assert isinstance(response.parsed.filed_by, TestPerson)
1361
+
1362
+
1363
+ def test_list_of_pydantic_schema(client):
1364
+ class CountryInfo(BaseModel):
1365
+ name: str
1366
+ population: int
1367
+ capital: str
1368
+ continent: str
1369
+ gdp: int
1370
+ official_language: str
1371
+ total_area_sq_mi: int
1372
+
1373
+ response = client.models.generate_content(
1374
+ model=GEMINI_FLASH_LATEST,
1375
+ contents='Give me information for the United States, Canada, and Mexico.',
1376
+ config=types.GenerateContentConfig(
1377
+ response_mime_type='application/json',
1378
+ response_schema=list[CountryInfo],
1379
+ )
1380
+ )
1381
+ assert isinstance(response.parsed, list)
1382
+ assert len(response.parsed) == 3
1383
+ assert isinstance(response.parsed[0], CountryInfo)
1384
+
1385
+
1386
+ def test_nested_list_of_pydantic_schema(client):
1387
+ class Recipe(BaseModel):
1388
+ name: str
1389
+ cook_time: str
1390
+
1391
+ response = client.models.generate_content(
1392
+ model=GEMINI_FLASH_LATEST,
1393
+ contents="I\'m writing three recipe books, one each for United States, Canada, and Mexico. "
1394
+ "Can you give some recipe ideas, at least 2 per book?",
1395
+ config=types.GenerateContentConfig(
1396
+ response_mime_type='application/json',
1397
+ response_schema=list[list[Recipe]],
1398
+ )
1399
+ )
1400
+ assert isinstance(response.parsed, list)
1401
+ assert len(response.parsed) == 3
1402
+ assert isinstance(response.parsed[0][0], Recipe)
1403
+
1404
+
1405
+ def test_list_of_pydantic_schema_with_dict_config(client):
1406
+ class CountryInfo(BaseModel):
1407
+ name: str
1408
+ population: int
1409
+ capital: str
1410
+ continent: str
1411
+ gdp: int
1412
+ official_language: str
1413
+ total_area_sq_mi: int
1414
+
1415
+ response = client.models.generate_content(
1416
+ model=GEMINI_FLASH_LATEST,
1417
+ contents='Give me information for the United States, Canada, and Mexico.',
1418
+ config={
1419
+ 'response_mime_type': 'application/json',
1420
+ 'response_schema': list[CountryInfo],
1421
+ }
1422
+ )
1423
+ assert isinstance(response.parsed, list)
1424
+ assert len(response.parsed) == 3
1425
+ assert isinstance(response.parsed[0], CountryInfo)
1426
+
1427
+
1428
+ def test_pydantic_schema_with_nested_class(client):
1429
+ class CurrencyInfo(BaseModel):
1430
+ name: str
1431
+
1432
+ class CountryInfo(BaseModel):
1433
+ name: str
1434
+ currency: CurrencyInfo
1435
+
1436
+ response = client.models.generate_content(
1437
+ model=GEMINI_FLASH_LATEST,
1438
+ contents='Give me information for the United States',
1439
+ config=types.GenerateContentConfig(
1440
+ response_mime_type='application/json',
1441
+ response_schema=CountryInfo,
1442
+ )
1443
+ )
1444
+ assert isinstance(response.parsed, CountryInfo)
1445
+ assert isinstance(response.parsed.currency, CurrencyInfo)
1446
+
1447
+
1448
+ @pytest.mark.skipif(
1449
+ sys.version_info < (3, 10),
1450
+ reason='| is not supported in Python 3.9',
1451
+ )
1452
+ def test_pydantic_schema_with_union_type(client):
1453
+
1454
+ class CountryInfo(BaseModel):
1455
+ name: str
1456
+ restaurants_per_capita: int | float
1457
+
1458
+ response = client.models.generate_content(
1459
+ model=GEMINI_FLASH_LATEST,
1460
+ contents='Give me information for the United States',
1461
+ config=types.GenerateContentConfig(
1462
+ response_mime_type='application/json',
1463
+ response_schema=CountryInfo,
1464
+ )
1465
+ )
1466
+ assert isinstance(response.parsed, CountryInfo)
1467
+ assert type(response.parsed.restaurants_per_capita) in (int, float)
1468
+
1469
+
1470
+ def test_pydantic_schema_with_union_type_all_py_versions(client):
1471
+
1472
+ class CountryInfo(BaseModel):
1473
+ name: str
1474
+ restaurants_per_capita: Union[int, float]
1475
+
1476
+ response = client.models.generate_content(
1477
+ model=GEMINI_FLASH_LATEST,
1478
+ contents='Give me information for the United States',
1479
+ config=types.GenerateContentConfig(
1480
+ response_mime_type='application/json',
1481
+ response_schema=CountryInfo,
1482
+ )
1483
+ )
1484
+ assert isinstance(response.parsed, CountryInfo)
1485
+ assert type(response.parsed.restaurants_per_capita) in (int, float)
1486
+
1487
+
1488
+ @pytest.mark.skipif(
1489
+ sys.version_info < (3, 10),
1490
+ reason='| is not supported in Python 3.9',
1491
+ )
1492
+ def test_union_of_pydantic_schema(client):
1493
+
1494
+ class SongLyric(BaseModel):
1495
+ song_name: str
1496
+ lyric: str
1497
+ artist: str
1498
+
1499
+ class FunFact(BaseModel):
1500
+ fun_fact: str
1501
+
1502
+ response = client.models.generate_content(
1503
+ model=GEMINI_FLASH_LATEST,
1504
+ contents='Can you give me a Taylor Swift song lyric or a fun fact?',
1505
+ config=types.GenerateContentConfig(
1506
+ response_mime_type='application/json',
1507
+ response_schema=SongLyric | FunFact,
1508
+ )
1509
+ )
1510
+ assert type(response.parsed) in (SongLyric, FunFact)
1511
+
1512
+
1513
+ def test_union_of_pydantic_schema_all_py_versions(client):
1514
+
1515
+ class SongLyric(BaseModel):
1516
+ song_name: str
1517
+ lyric: str
1518
+ artist: str
1519
+
1520
+ class FunFact(BaseModel):
1521
+ fun_fact: str
1522
+
1523
+ response = client.models.generate_content(
1524
+ model=GEMINI_FLASH_LATEST,
1525
+ contents='Can you give me a Taylor Swift song lyric or a fun fact?',
1526
+ config=types.GenerateContentConfig(
1527
+ response_mime_type='application/json',
1528
+ response_schema=Union[SongLyric, FunFact],
1529
+ )
1530
+ )
1531
+ assert type(response.parsed) in (SongLyric, FunFact)
1532
+
1533
+
1534
+ def test_pydantic_schema_with_nested_enum(client):
1535
+ class Continent(Enum):
1536
+ ASIA = 'Asia'
1537
+ AFRICA = 'Africa'
1538
+ ANTARCTICA = 'Antarctica'
1539
+ EUROPE = 'Europe'
1540
+ NORTH_AMERICA = 'North America'
1541
+ SOUTH_AMERICA = 'South America'
1542
+ AUSTRALIA = 'Australia'
1543
+
1544
+ class CountryInfo(BaseModel):
1545
+ name: str
1546
+ continent: Continent
1547
+
1548
+ response = client.models.generate_content(
1549
+ model=GEMINI_FLASH_LATEST,
1550
+ contents='Give me information for the United States',
1551
+ config=types.GenerateContentConfig(
1552
+ response_mime_type='application/json',
1553
+ response_schema=CountryInfo,
1554
+ )
1555
+ )
1556
+ assert isinstance(response.parsed, CountryInfo)
1557
+ assert isinstance(response.parsed.continent, Continent)
1558
+
1559
+
1560
+ def test_pydantic_schema_with_nested_list_class(client):
1561
+ class CurrencyInfo(BaseModel):
1562
+ name: str
1563
+
1564
+ class CountryInfo(BaseModel):
1565
+ name: str
1566
+ currency: list[CurrencyInfo]
1567
+
1568
+ response = client.models.generate_content(
1569
+ model=GEMINI_FLASH_LATEST,
1570
+ contents='Give me information for the United States.',
1571
+ config=types.GenerateContentConfig(
1572
+ response_mime_type='application/json',
1573
+ response_schema=CountryInfo,
1574
+ )
1575
+ )
1576
+ assert isinstance(response.parsed, CountryInfo)
1577
+ assert isinstance(response.parsed.currency[0], CurrencyInfo)
1578
+
1579
+
1580
+ def test_list_of_pydantic_schema_with_nested_class(client):
1581
+ class CurrencyInfo(BaseModel):
1582
+ name: str
1583
+ code: str
1584
+ symbol: str
1585
+
1586
+ class CountryInfo(BaseModel):
1587
+ name: str
1588
+ population: int
1589
+ capital: str
1590
+ continent: str
1591
+ gdp: int
1592
+ official_language: str
1593
+ total_area_sq_mi: int
1594
+ currency: CurrencyInfo
1595
+
1596
+ response = client.models.generate_content(
1597
+ model=GEMINI_FLASH_LATEST,
1598
+ contents='Give me information for the United States, Canada, and Mexico.',
1599
+ config=types.GenerateContentConfig(
1600
+ response_mime_type='application/json',
1601
+ response_schema=list[CountryInfo],
1602
+ )
1603
+ )
1604
+ assert isinstance(response.parsed, list)
1605
+ assert isinstance(response.parsed[0], CountryInfo)
1606
+ assert isinstance(response.parsed[0].currency, CurrencyInfo)
1607
+
1608
+
1609
+ def test_list_of_pydantic_schema_with_nested_list_class(client):
1610
+ class CurrencyInfo(BaseModel):
1611
+ name: str
1612
+ code: str
1613
+ symbol: str
1614
+
1615
+ class CountryInfo(BaseModel):
1616
+ name: str
1617
+ population: int
1618
+ capital: str
1619
+ continent: str
1620
+ gdp: int
1621
+ official_language: str
1622
+ total_area_sq_mi: int
1623
+ currency: list[CurrencyInfo]
1624
+
1625
+ response = client.models.generate_content(
1626
+ model=GEMINI_FLASH_LATEST,
1627
+ contents='Give me information for the United States, Canada, and Mexico.',
1628
+ config=types.GenerateContentConfig(
1629
+ response_mime_type='application/json',
1630
+ response_schema=list[CountryInfo],
1631
+ )
1632
+ )
1633
+ assert isinstance(response.parsed, list)
1634
+ assert isinstance(response.parsed[0], CountryInfo)
1635
+ assert isinstance(response.parsed[0].currency, list)
1636
+ assert isinstance(response.parsed[0].currency[0], CurrencyInfo)
1637
+
1638
+
1639
+ def test_response_schema_with_dict_of_pydantic_schema(client):
1640
+ class CountryInfo(BaseModel):
1641
+ population: int
1642
+ capital: str
1643
+ continent: str
1644
+ gdp: int
1645
+ official_language: str
1646
+ total_area_sq_mi: int
1647
+
1648
+ if not client.vertexai:
1649
+ with pytest.raises(ValueError) as e:
1650
+ client.models.generate_content(
1651
+ model=GEMINI_FLASH_LATEST,
1652
+ contents='Give me information for the United States, Canada, and Mexico.',
1653
+ config=types.GenerateContentConfig(
1654
+ response_mime_type='application/json',
1655
+ response_schema=dict[str, CountryInfo],
1656
+ )
1657
+ )
1658
+ else:
1659
+ response = client.models.generate_content(
1660
+ model=GEMINI_FLASH_LATEST,
1661
+ contents='Give me information for the United States, Canada, and Mexico.',
1662
+ config=types.GenerateContentConfig(
1663
+ response_mime_type='application/json',
1664
+ response_schema=dict[str, CountryInfo],
1665
+ )
1666
+ )
1667
+ assert response.text
1668
+
1669
+
1670
+ def test_schema_with_unsupported_type_raises(client):
1671
+ with pytest.raises(ValueError) as e:
1672
+ client.models.generate_content(
1673
+ model=GEMINI_FLASH_LATEST,
1674
+ contents='Give me information for the United States, Canada, and Mexico.',
1675
+ config=types.GenerateContentConfig(
1676
+ response_mime_type='application/json',
1677
+ response_schema=types.Schema(),
1678
+ )
1679
+ )
1680
+ assert 'Unsupported schema type' in str(e)
1681
+
1682
+
1683
+ def test_enum_schema_with_enum_mime_type(client):
1684
+ response = client.models.generate_content(
1685
+ model=GEMINI_FLASH_2_0,
1686
+ contents='What instrument plays multiple notes at once?',
1687
+ config={
1688
+ 'response_mime_type': 'text/x.enum',
1689
+ 'response_schema': InstrumentEnum,
1690
+ },
1691
+ )
1692
+
1693
+ instrument_values = {member.value for member in InstrumentEnum}
1694
+
1695
+ assert response.text in instrument_values
1696
+ assert isinstance(response.parsed, InstrumentEnum)
1697
+
1698
+
1699
+ def test_list_of_enum_schema_with_enum_mime_type(client):
1700
+ with pytest.raises(errors.ClientError) as e:
1701
+ client.models.generate_content(
1702
+ model=GEMINI_FLASH_2_0,
1703
+ contents='What instrument plays single note at once?',
1704
+ config={
1705
+ 'response_mime_type': 'text/x.enum',
1706
+ 'response_schema': list[InstrumentEnum],
1707
+ },
1708
+ )
1709
+ assert '400' in str(e)
1710
+
1711
+
1712
+ def test_list_of_enum_schema_with_json_mime_type(client):
1713
+ response = client.models.generate_content(
1714
+ model=GEMINI_FLASH_LATEST,
1715
+ contents='What instrument plays single note at once?',
1716
+ config={
1717
+ 'response_mime_type': 'application/json',
1718
+ 'response_schema': list[InstrumentEnum],
1719
+ },
1720
+ )
1721
+
1722
+ assert isinstance(response.parsed, list)
1723
+ assert response.parsed
1724
+ for item in response.parsed:
1725
+ assert isinstance(item, InstrumentEnum)
1726
+
1727
+
1728
+ def test_optional_enum_in_pydantic_schema_with_json_mime_type(client):
1729
+ class InstrumentInfo(BaseModel):
1730
+ instrument: Optional[InstrumentEnum]
1731
+ fun_fact: str
1732
+
1733
+ response = client.models.generate_content(
1734
+ model=GEMINI_FLASH_LATEST,
1735
+ contents='What instrument plays single note at once? Include the name of the instrument in your response.',
1736
+ config={
1737
+ 'response_mime_type': 'application/json',
1738
+ 'response_schema': InstrumentInfo,
1739
+ },
1740
+ )
1741
+
1742
+ assert isinstance(response.parsed, InstrumentInfo)
1743
+ assert isinstance(response.parsed.instrument, InstrumentEnum)
1744
+
1745
+
1746
+ def test_enum_schema_with_json_mime_type(client):
1747
+ response = client.models.generate_content(
1748
+ model=GEMINI_FLASH_LATEST,
1749
+ contents='What instrument plays multiple notes at once?',
1750
+ config={
1751
+ 'response_mime_type': 'application/json',
1752
+ 'response_schema': InstrumentEnum,
1753
+ },
1754
+ )
1755
+ # "application/json" returns response in double quotes.
1756
+ removed_quotes = response.text.replace('"', '')
1757
+ instrument_values = {member.value for member in InstrumentEnum}
1758
+
1759
+ assert removed_quotes in instrument_values
1760
+ assert isinstance(response.parsed, InstrumentEnum)
1761
+
1762
+
1763
+ def test_non_string_enum_schema_with_enum_mime_type(client):
1764
+ class IntegerEnum(Enum):
1765
+ PERCUSSION = 1
1766
+ STRING = 2
1767
+ WOODWIND = 3
1768
+ BRASS = 4
1769
+ KEYBOARD = 5
1770
+
1771
+ response =client.models.generate_content(
1772
+ model=GEMINI_FLASH_LATEST,
1773
+ contents='What instrument plays multiple notes at once?',
1774
+ config={
1775
+ 'response_mime_type': 'text/x.enum',
1776
+ 'response_schema': IntegerEnum,
1777
+ },
1778
+ )
1779
+
1780
+ instrument_values = {str(member.value) for member in IntegerEnum}
1781
+
1782
+ assert response.text in instrument_values
1783
+
1784
+
1785
+ def test_json_schema(client):
1786
+ response = client.models.generate_content(
1787
+ model=GEMINI_FLASH_LATEST,
1788
+ contents='Give me information of the United States.',
1789
+ config={
1790
+ 'response_mime_type': 'application/json',
1791
+ 'response_schema': {
1792
+ 'required': [
1793
+ 'name',
1794
+ 'population',
1795
+ 'capital',
1796
+ 'continent',
1797
+ 'gdp',
1798
+ 'official_language',
1799
+ 'total_area_sq_mi',
1800
+ ],
1801
+ 'properties': {
1802
+ 'name': {'type': 'STRING'},
1803
+ 'population': {'type': 'INTEGER'},
1804
+ 'capital': {'type': 'STRING'},
1805
+ 'continent': {'type': 'STRING'},
1806
+ 'gdp': {'type': 'INTEGER'},
1807
+ 'official_language': {'type': 'STRING'},
1808
+ 'total_area_sq_mi': {'type': 'INTEGER'},
1809
+ },
1810
+ 'type': 'OBJECT',
1811
+ },
1812
+ },
1813
+ )
1814
+ assert isinstance(response.parsed, dict)
1815
+
1816
+
1817
+ def test_json_schema_with_lower_enum(client):
1818
+ response = client.models.generate_content(
1819
+ model=GEMINI_FLASH_LATEST,
1820
+ contents='Give me information of the United States.',
1821
+ config={
1822
+ 'response_mime_type': 'application/json',
1823
+ 'response_schema': {
1824
+ 'required': [
1825
+ 'name',
1826
+ 'pupulation',
1827
+ 'capital',
1828
+ 'continent',
1829
+ 'gdp',
1830
+ 'official_language',
1831
+ 'total_area_sq_mi',
1832
+ ],
1833
+ 'properties': {
1834
+ 'name': {'type': 'string'},
1835
+ 'pupulation': {'type': 'integer'},
1836
+ 'capital': {'type': 'string'},
1837
+ 'continent': {'type': 'string'},
1838
+ 'gdp': {'type': 'integer'},
1839
+ 'official_language': {'type': 'string'},
1840
+ 'total_area_sq_mi': {'type': 'integer'},
1841
+ },
1842
+ 'type': 'OBJECT',
1843
+ },
1844
+ },
1845
+ )
1846
+ assert isinstance(response.parsed, dict)
1847
+
1848
+
1849
+ def test_json_schema_with_any_of(client):
1850
+ response = client.models.generate_content(
1851
+ model=GEMINI_FLASH_LATEST,
1852
+ contents='Give me a fruit basket.',
1853
+ config={
1854
+ 'response_mime_type': 'application/json',
1855
+ 'response_schema': {
1856
+ 'type': 'OBJECT',
1857
+ 'title': 'Fruit Basket',
1858
+ 'description': 'A structured representation of a fruit basket',
1859
+ 'required': ['fruit'],
1860
+ 'properties': {
1861
+ 'fruit': {
1862
+ 'type': 'ARRAY',
1863
+ 'description': (
1864
+ 'An ordered list of the fruit in the basket'
1865
+ ),
1866
+ 'items': {
1867
+ 'description': 'A piece of fruit',
1868
+ 'any_of': [
1869
+ {
1870
+ 'title': 'Apple',
1871
+ 'description': 'Describes an apple',
1872
+ 'type': 'OBJECT',
1873
+ 'properties': {
1874
+ 'type': {
1875
+ 'type': 'STRING',
1876
+ 'description': "Always 'apple'",
1877
+ },
1878
+ 'color': {
1879
+ 'type': 'STRING',
1880
+ 'description': (
1881
+ 'The color of the apple (e.g.,'
1882
+ " 'red')"
1883
+ ),
1884
+ },
1885
+ },
1886
+ 'property_ordering': ['type', 'color'],
1887
+ 'required': ['type', 'color'],
1888
+ },
1889
+ {
1890
+ 'title': 'Orange',
1891
+ 'description': 'Describes an orange',
1892
+ 'type': 'OBJECT',
1893
+ 'properties': {
1894
+ 'type': {
1895
+ 'type': 'STRING',
1896
+ 'description': "Always 'orange'",
1897
+ },
1898
+ 'size': {
1899
+ 'type': 'STRING',
1900
+ 'description': (
1901
+ 'The size of the orange (e.g.,'
1902
+ " 'medium')"
1903
+ ),
1904
+ },
1905
+ },
1906
+ 'property_ordering': ['type', 'size'],
1907
+ 'required': ['type', 'size'],
1908
+ },
1909
+ ],
1910
+ },
1911
+ }
1912
+ },
1913
+ },
1914
+ },
1915
+ )
1916
+ assert isinstance(response.parsed, dict)
1917
+ assert 'fruit' in response.parsed
1918
+ assert isinstance(response.parsed['fruit'], list)
1919
+ assert 'type' in response.parsed['fruit'][0]
1920
+
1921
+
1922
+ def test_schema_with_any_of(client):
1923
+ response_schema=types.Schema(
1924
+ type=types.Type.OBJECT,
1925
+ title='Fruit Basket',
1926
+ description='A structured representation of a fruit basket',
1927
+ properties={
1928
+ 'fruit': types.Schema(
1929
+ type=types.Type.ARRAY,
1930
+ description='An ordered list of the fruit in the basket',
1931
+ items=types.Schema(
1932
+ any_of=[
1933
+ types.Schema(
1934
+ title='Apple',
1935
+ description='Describes an apple',
1936
+ type=types.Type.OBJECT,
1937
+ properties={
1938
+ 'type': types.Schema(type=types.Type.STRING, description='Always "apple"'),
1939
+ 'variety': types.Schema(
1940
+ type=types.Type.STRING,
1941
+ description='The variety of apple (e.g., "Granny Smith")',
1942
+ ),
1943
+ },
1944
+ property_ordering=['type', 'variety'],
1945
+ required=['type', 'variety'],
1946
+ ),
1947
+ types.Schema(
1948
+ title='Orange',
1949
+ description='Describes an orange',
1950
+ type=types.Type.OBJECT,
1951
+ properties={
1952
+ 'type': types.Schema(type=types.Type.STRING, description='Always "orange"'),
1953
+ 'variety': types.Schema(
1954
+ type=types.Type.STRING,
1955
+ description='The variety of orange (e.g.,"Navel orange")',
1956
+ ),
1957
+ },
1958
+ property_ordering=['type', 'variety'],
1959
+ required=['type', 'variety'],
1960
+ ),
1961
+ ],
1962
+ ),
1963
+ ),
1964
+ },
1965
+ required=['fruit'],
1966
+ )
1967
+ response = client.models.generate_content(
1968
+ model=GEMINI_FLASH_LATEST,
1969
+ contents='Give me a fruit basket.',
1970
+ config=types.GenerateContentConfig(
1971
+ response_mime_type='application/json',
1972
+ response_schema=response_schema,
1973
+ ),
1974
+ )
1975
+ assert isinstance(response.parsed, dict)
1976
+ assert 'fruit' in response.parsed
1977
+ assert isinstance(response.parsed['fruit'], list)
1978
+ assert 'type' in response.parsed['fruit'][0]
1979
+
1980
+
1981
+ def test_replicated_voice_config(client):
1982
+ with pytest_helper.exception_if_vertex(client, errors.ClientError):
1983
+ client.models.generate_content(
1984
+ model='gemini-2.5-flash-preview-tts-voice-replication-rev22-2025-10-28',
1985
+ contents=t.t_contents(
1986
+ 'Produce a speech response saying "Cheese"'
1987
+ ),
1988
+ config=types.GenerateContentConfig(
1989
+ response_modalities=['audio'],
1990
+ speech_config=types.SpeechConfig(
1991
+ voice_config=types.VoiceConfig(
1992
+ replicated_voice_config=types.ReplicatedVoiceConfig(
1993
+ voice_sample_audio=audio_bytes,
1994
+ mime_type='audio/wav',
1995
+ )
1996
+ )
1997
+ ),
1998
+ ),
1999
+ )
2000
+
2001
+
2002
+ def test_json_schema_with_streaming(client):
2003
+
2004
+ response = client.models.generate_content_stream(
2005
+ model=GEMINI_FLASH_LATEST,
2006
+ contents='Give me information of the United States.',
2007
+ config={
2008
+ 'response_mime_type': 'application/json',
2009
+ 'response_schema': {
2010
+ 'properties': {
2011
+ 'name': {'type': 'STRING'},
2012
+ 'population': {'type': 'INTEGER'},
2013
+ 'capital': {'type': 'STRING'},
2014
+ 'continent': {'type': 'STRING'},
2015
+ 'gdp': {'type': 'INTEGER'},
2016
+ 'official_language': {'type': 'STRING'},
2017
+ 'total_area_sq_mi': {'type': 'INTEGER'},
2018
+ },
2019
+ 'type': 'OBJECT',
2020
+ },
2021
+ },
2022
+ )
2023
+
2024
+ for r in response:
2025
+ parts = r.parts
2026
+ for p in parts:
2027
+ assert p.text
2028
+
2029
+
2030
+ def test_pydantic_schema_with_streaming(client):
2031
+
2032
+ class CountryInfo(BaseModel):
2033
+ name: str
2034
+ population: int
2035
+ capital: str
2036
+ continent: str
2037
+ gdp: int
2038
+ official_language: str
2039
+ total_area_sq_mi: int
2040
+
2041
+ response = client.models.generate_content_stream(
2042
+ model=GEMINI_FLASH_LATEST,
2043
+ contents='Give me information of the United States.',
2044
+ config={
2045
+ 'response_mime_type': 'application/json',
2046
+ 'response_schema': CountryInfo
2047
+ },
2048
+ )
2049
+
2050
+ for r in response:
2051
+ parts = r.parts
2052
+ for p in parts:
2053
+ assert p.text
2054
+
2055
+
2056
+ def test_schema_from_json(client):
2057
+
2058
+ class Foo(BaseModel):
2059
+ bar: str
2060
+ baz: int
2061
+ qux: list[str]
2062
+
2063
+ schema = types.Schema.model_validate(Foo.model_json_schema())
2064
+
2065
+ response = client.models.generate_content(
2066
+ model=GEMINI_FLASH_LATEST,
2067
+ contents='Fill in the Foo.',
2068
+ config=types.GenerateContentConfig(
2069
+ response_mime_type='application/json',
2070
+ response_schema=schema
2071
+ ),
2072
+ )
2073
+
2074
+ assert response.text
2075
+
2076
+
2077
+ def test_schema_from_model_schema(client):
2078
+
2079
+ class Foo(BaseModel):
2080
+ bar: str
2081
+ baz: int
2082
+ qux: list[str]
2083
+
2084
+ response = client.models.generate_content(
2085
+ model=GEMINI_FLASH_LATEST,
2086
+ contents='Fill in the Foo.',
2087
+ config=types.GenerateContentConfig(
2088
+ response_mime_type='application/json',
2089
+ response_schema=Foo.model_json_schema(),
2090
+ ),
2091
+ )
2092
+
2093
+ response.text
2094
+
2095
+
2096
+ def test_schema_with_additional_properties(client):
2097
+
2098
+ class Foo(BaseModel):
2099
+ bar: str
2100
+ baz: int
2101
+ qux: dict[str, str]
2102
+
2103
+ if client.vertexai:
2104
+ response = client.models.generate_content(
2105
+ model=GEMINI_FLASH_LATEST,
2106
+ contents='What is your name?',
2107
+ config=types.GenerateContentConfig(
2108
+ response_mime_type='application/json',
2109
+ response_schema=Foo,
2110
+ ),
2111
+ )
2112
+ assert response.text
2113
+ else:
2114
+ with pytest.raises(ValueError) as e:
2115
+ client.models.generate_content(
2116
+ model=GEMINI_FLASH_LATEST,
2117
+ contents='What is your name?',
2118
+ config=types.GenerateContentConfig(
2119
+ response_mime_type='application/json',
2120
+ response_schema=Foo,
2121
+ ),
2122
+ )
2123
+ assert 'additionalProperties is not supported in the Gemini API.' in str(e)
2124
+
2125
+
2126
+ def test_function(client):
2127
+ def get_weather(city: str) -> str:
2128
+ """Returns the weather in a city."""
2129
+ return f'The weather in {city} is sunny and 100 degrees.'
2130
+
2131
+ response = client.models.generate_content(
2132
+ model=GEMINI_FLASH_LATEST,
2133
+ contents=(
2134
+ 'What is the weather like in Sunnyvale? Answer in very short'
2135
+ ' sentence.'
2136
+ ),
2137
+ config={
2138
+ 'tools': [get_weather],
2139
+ },
2140
+ )
2141
+ assert '100' in response.text
2142
+
2143
+
2144
+ def test_invalid_input_without_transformer(client):
2145
+ with pytest.raises(ValidationError) as e:
2146
+ client.models.generate_content(
2147
+ model=GEMINI_FLASH_LATEST,
2148
+ contents='What is your name',
2149
+ config={
2150
+ 'input_that_does_not_exist': 'what_ever_value',
2151
+ },
2152
+ )
2153
+ assert 'input_that_does_not_exist' in str(e)
2154
+ assert 'Extra inputs are not permitted' in str(e)
2155
+
2156
+
2157
+ def test_invalid_input_with_transformer_dict(client):
2158
+ with pytest.raises(ValidationError) as e:
2159
+ client.models.generate_content(
2160
+ model=GEMINI_FLASH_LATEST,
2161
+ contents={'invalid_key': 'invalid_value'},
2162
+ )
2163
+ assert 'invalid_key' in str(e.value)
2164
+
2165
+
2166
+ def test_invalid_input_with_transformer_list(client):
2167
+ with pytest.raises(ValidationError) as e:
2168
+ client.models.generate_content(
2169
+ model=GEMINI_FLASH_LATEST,
2170
+ contents=[{'invalid_key': 'invalid_value'}],
2171
+ )
2172
+ assert 'invalid_key' in str(e.value)
2173
+
2174
+
2175
+ def test_invalid_input_for_simple_parameter(client):
2176
+ with pytest.raises(ValidationError) as e:
2177
+ client.models.generate_content(
2178
+ model=5,
2179
+ contents='What is your name?',
2180
+ )
2181
+ assert 'model' in str(e)
2182
+
2183
+
2184
+ def test_catch_stack_trace_in_error_handling(client):
2185
+ try:
2186
+ client.models.generate_content(
2187
+ model=GEMINI_FLASH_LATEST,
2188
+ contents='What is your name?',
2189
+ config={'response_modalities': ['AUDIO']},
2190
+ )
2191
+ except errors.ClientError as e:
2192
+ # Note that the stack trace is truncated in replay file, therefore this is
2193
+ # the best we can do in testing error handling. In api mode, the stack trace
2194
+ # is:
2195
+ # {
2196
+ # 'error': {
2197
+ # 'code': 400,
2198
+ # 'message': 'Multi-modal output is not supported.',
2199
+ # 'status': 'INVALID_ARGUMENT',
2200
+ # 'details': [{
2201
+ # '@type': 'type.googleapis.com/google.rpc.DebugInfo',
2202
+ # 'detail': '[ORIGINAL ERROR] generic::invalid_argument: '
2203
+ # 'Multi-modal output is not supported. '
2204
+ # '[google.rpc.error_details_ext] '
2205
+ # '{ message: "Multi-modal output is not supported." }'
2206
+ # }]
2207
+ # }
2208
+ # }
2209
+ if 'error' in e.details:
2210
+ details = e.details['error']
2211
+ else:
2212
+ details = e.details
2213
+ assert details['code'] == 400
2214
+ assert details['status'] == 'INVALID_ARGUMENT'
2215
+
2216
+
2217
+ def test_multiple_strings(client):
2218
+ class SummaryResponses(BaseModel):
2219
+ summary: str
2220
+ person: str
2221
+
2222
+ response = client.models.generate_content(
2223
+ model=GEMINI_FLASH_LATEST,
2224
+ contents=[
2225
+ "Summarize Shakespeare's life work in a few sentences",
2226
+ "Summarize Hemingway's life work",
2227
+ ],
2228
+ config={
2229
+ 'response_mime_type': 'application/json',
2230
+ 'response_schema': list[SummaryResponses],
2231
+ },
2232
+ )
2233
+
2234
+ assert 'Shakespeare' in response.text
2235
+ assert 'Hemingway' in response.text
2236
+ assert 'Shakespeare' in response.parsed[0].person
2237
+ assert 'Hemingway' in response.parsed[1].person
2238
+
2239
+
2240
+ def test_multiple_parts(client):
2241
+ class SummaryResponses(BaseModel):
2242
+ summary: str
2243
+ person: str
2244
+
2245
+ response = client.models.generate_content(
2246
+ model=GEMINI_FLASH_LATEST,
2247
+ contents=[
2248
+ types.Part(
2249
+ text="Summarize Shakespeare's life work in a few sentences"
2250
+ ),
2251
+ types.Part(text="Summarize Hemingway's life work"),
2252
+ ],
2253
+ config={
2254
+ 'response_mime_type': 'application/json',
2255
+ 'response_schema': list[SummaryResponses],
2256
+ },
2257
+ )
2258
+
2259
+ assert 'Shakespeare' in response.text
2260
+ assert 'Hemingway' in response.text
2261
+ assert 'Shakespeare' in response.parsed[0].person
2262
+ assert 'Hemingway' in response.parsed[1].person
2263
+
2264
+
2265
+ def test_multiple_function_calls(client):
2266
+ response = client.models.generate_content(
2267
+ model=GEMINI_FLASH_LATEST,
2268
+ contents=[
2269
+ 'What is the weather in Boston?',
2270
+ 'What is the stock price of GOOG?',
2271
+ types.Part.from_function_call(
2272
+ name='get_weather',
2273
+ args={'location': 'Boston'},
2274
+ ),
2275
+ types.Part.from_function_call(
2276
+ name='get_stock_price',
2277
+ args={'symbol': 'GOOG'},
2278
+ ),
2279
+ types.Part.from_function_response(
2280
+ name='get_weather',
2281
+ response={'response': 'It is sunny and 100 degrees.'},
2282
+ ),
2283
+ types.Part.from_function_response(
2284
+ name='get_stock_price',
2285
+ response={'response': 'The stock price is $100.'},
2286
+ ),
2287
+ ],
2288
+ config=types.GenerateContentConfig(
2289
+ tools=[
2290
+ types.Tool(
2291
+ function_declarations=[
2292
+ types.FunctionDeclaration(
2293
+ name='get_weather',
2294
+ description='Get the weather in a city.',
2295
+ parameters=types.Schema(
2296
+ type=types.Type.OBJECT,
2297
+ properties={
2298
+ 'location': types.Schema(
2299
+ type=types.Type.STRING
2300
+ )
2301
+ },
2302
+ ),
2303
+ ),
2304
+ types.FunctionDeclaration(
2305
+ name='get_stock_price',
2306
+ description='Get the stock price of a symbol.',
2307
+ parameters=types.Schema(
2308
+ type=types.Type.OBJECT,
2309
+ properties={
2310
+ 'symbol': types.Schema(
2311
+ type=types.Type.STRING
2312
+ )
2313
+ },
2314
+ ),
2315
+ ),
2316
+ ]
2317
+ ),
2318
+ ]
2319
+ ),
2320
+ )
2321
+
2322
+ assert 'Boston' in response.text
2323
+ assert 'sunny' in response.text
2324
+ assert '100 degrees' in response.text
2325
+ assert '$100' in response.text
2326
+
2327
+
2328
+ def test_usage_metadata_part_types(client):
2329
+ contents = [
2330
+ 'Hello world.',
2331
+ types.Part.from_bytes(
2332
+ data=image_bytes,
2333
+ mime_type='image/png',
2334
+ ),
2335
+ ]
2336
+
2337
+ response = client.models.generate_content(
2338
+ model=GEMINI_FLASH_2_0, contents=contents
2339
+ )
2340
+ usage_metadata = response.usage_metadata
2341
+
2342
+ assert usage_metadata.candidates_token_count
2343
+ assert usage_metadata.candidates_tokens_details
2344
+ modalities = sorted(
2345
+ [d.modality.name for d in usage_metadata.candidates_tokens_details]
2346
+ )
2347
+ assert modalities == ['TEXT']
2348
+ assert isinstance(
2349
+ usage_metadata.candidates_tokens_details[0].modality, types.MediaModality)
2350
+
2351
+ assert usage_metadata.prompt_token_count
2352
+ assert usage_metadata.prompt_tokens_details
2353
+ modalities = sorted(
2354
+ [d.modality.name for d in usage_metadata.prompt_tokens_details]
2355
+ )
2356
+ assert modalities == ['IMAGE', 'TEXT']
2357
+
2358
+
2359
+ def test_error_handling_stream(client):
2360
+ if client.vertexai:
2361
+ return
2362
+
2363
+ try:
2364
+ for chunk in client.models.generate_content_stream(
2365
+ model=GEMINI_FLASH_IMAGE_LATEST,
2366
+ contents=[
2367
+ types.Content(
2368
+ role='user',
2369
+ parts=[
2370
+ types.Part.from_bytes(
2371
+ data=image_bytes, mime_type='image/png'
2372
+ ),
2373
+ types.Part.from_text(text='Make sky more beautiful.'),
2374
+ ],
2375
+ ),
2376
+ ],
2377
+ config=types.GenerateContentConfig(
2378
+ response_mime_type='text/plain',
2379
+ response_modalities=['IMAGE', 'TEXT'],
2380
+ system_instruction='make the sky more beautiful.',
2381
+ ),
2382
+ ):
2383
+ continue
2384
+
2385
+ except errors.ClientError as e:
2386
+ assert (
2387
+ e.message
2388
+ == 'Developer instruction is not enabled for'
2389
+ ' models/gemini-2.5-flash-image'
2390
+ )
2391
+
2392
+
2393
+ def test_error_handling_unary(client):
2394
+ if client.vertexai:
2395
+ return
2396
+
2397
+ try:
2398
+ client.models.generate_content(
2399
+ model=GEMINI_FLASH_IMAGE_LATEST,
2400
+ contents=[
2401
+ types.Content(
2402
+ role='user',
2403
+ parts=[
2404
+ types.Part.from_bytes(
2405
+ data=image_bytes, mime_type='image/png'
2406
+ ),
2407
+ types.Part.from_text(text='Make sky more beautiful.'),
2408
+ ],
2409
+ ),
2410
+ ],
2411
+ config=types.GenerateContentConfig(
2412
+ response_mime_type='text/plain',
2413
+ response_modalities=['IMAGE', 'TEXT'],
2414
+ system_instruction='make the sky more beautiful.',
2415
+ ),
2416
+ )
2417
+
2418
+ except errors.ClientError as e:
2419
+ assert (
2420
+ e.message
2421
+ == 'Developer instruction is not enabled for'
2422
+ ' models/gemini-2.5-flash-image'
2423
+ )
2424
+
2425
+
2426
+ def test_provisioned_output_dedicated(client):
2427
+ response = client.models.generate_content(
2428
+ model=GEMINI_FLASH_LATEST,
2429
+ contents='What is 1 + 1?',
2430
+ config=types.GenerateContentConfig(
2431
+ http_options={'headers': {'X-Vertex-AI-LLM-Request-Type': 'dedicated'}}
2432
+ ),
2433
+ )
2434
+ if client.vertexai:
2435
+ assert response.usage_metadata.traffic_type == types.TrafficType.PROVISIONED_THROUGHPUT
2436
+ else:
2437
+ assert not response.usage_metadata.traffic_type
2438
+
2439
+
2440
+ @pytest.mark.asyncio
2441
+ async def test_error_handling_unary_async(client):
2442
+ if client.vertexai:
2443
+ return
2444
+
2445
+ try:
2446
+ await client.aio.models.generate_content(
2447
+ model=GEMINI_FLASH_IMAGE_LATEST,
2448
+ contents=[
2449
+ types.Content(
2450
+ role='user',
2451
+ parts=[
2452
+ types.Part.from_bytes(
2453
+ data=image_bytes, mime_type='image/png'
2454
+ ),
2455
+ types.Part.from_text(text='Make sky more beautiful.'),
2456
+ ],
2457
+ ),
2458
+ ],
2459
+ config=types.GenerateContentConfig(
2460
+ response_mime_type='text/plain',
2461
+ response_modalities=['IMAGE', 'TEXT'],
2462
+ system_instruction='make the sky more beautiful.',
2463
+ ),
2464
+ )
2465
+
2466
+ except errors.ClientError as e:
2467
+ assert (
2468
+ e.message
2469
+ == 'Developer instruction is not enabled for'
2470
+ ' models/gemini-2.5-flash-image'
2471
+ )
2472
+
2473
+
2474
+ @pytest.mark.asyncio
2475
+ async def test_error_handling_stream_async(client):
2476
+ if client.vertexai:
2477
+ return
2478
+
2479
+ try:
2480
+ async for part in await client.aio.models.generate_content_stream(
2481
+ model=GEMINI_FLASH_IMAGE_LATEST,
2482
+ contents=[
2483
+ types.Content(
2484
+ role='user',
2485
+ parts=[
2486
+ types.Part.from_bytes(
2487
+ data=image_bytes, mime_type='image/png'
2488
+ ),
2489
+ types.Part.from_text(text='Make sky more beautiful.'),
2490
+ ],
2491
+ ),
2492
+ ],
2493
+ config=types.GenerateContentConfig(
2494
+ response_mime_type='text/plain',
2495
+ response_modalities=['IMAGE', 'TEXT'],
2496
+ system_instruction='make the sky more beautiful.',
2497
+ ),
2498
+ ):
2499
+ continue
2500
+
2501
+ except errors.ClientError as e:
2502
+ assert ('Developer instruction is not enabled' in e.message)