ag2 0.10.2__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 (423) hide show
  1. ag2-0.10.2.dist-info/METADATA +819 -0
  2. ag2-0.10.2.dist-info/RECORD +423 -0
  3. ag2-0.10.2.dist-info/WHEEL +4 -0
  4. ag2-0.10.2.dist-info/licenses/LICENSE +201 -0
  5. ag2-0.10.2.dist-info/licenses/NOTICE.md +19 -0
  6. autogen/__init__.py +88 -0
  7. autogen/_website/__init__.py +3 -0
  8. autogen/_website/generate_api_references.py +426 -0
  9. autogen/_website/generate_mkdocs.py +1216 -0
  10. autogen/_website/notebook_processor.py +475 -0
  11. autogen/_website/process_notebooks.py +656 -0
  12. autogen/_website/utils.py +413 -0
  13. autogen/a2a/__init__.py +36 -0
  14. autogen/a2a/agent_executor.py +86 -0
  15. autogen/a2a/client.py +357 -0
  16. autogen/a2a/errors.py +18 -0
  17. autogen/a2a/httpx_client_factory.py +79 -0
  18. autogen/a2a/server.py +221 -0
  19. autogen/a2a/utils.py +207 -0
  20. autogen/agentchat/__init__.py +47 -0
  21. autogen/agentchat/agent.py +180 -0
  22. autogen/agentchat/assistant_agent.py +86 -0
  23. autogen/agentchat/chat.py +325 -0
  24. autogen/agentchat/contrib/__init__.py +5 -0
  25. autogen/agentchat/contrib/agent_eval/README.md +7 -0
  26. autogen/agentchat/contrib/agent_eval/agent_eval.py +108 -0
  27. autogen/agentchat/contrib/agent_eval/criterion.py +43 -0
  28. autogen/agentchat/contrib/agent_eval/critic_agent.py +44 -0
  29. autogen/agentchat/contrib/agent_eval/quantifier_agent.py +39 -0
  30. autogen/agentchat/contrib/agent_eval/subcritic_agent.py +45 -0
  31. autogen/agentchat/contrib/agent_eval/task.py +42 -0
  32. autogen/agentchat/contrib/agent_optimizer.py +432 -0
  33. autogen/agentchat/contrib/capabilities/__init__.py +5 -0
  34. autogen/agentchat/contrib/capabilities/agent_capability.py +20 -0
  35. autogen/agentchat/contrib/capabilities/generate_images.py +301 -0
  36. autogen/agentchat/contrib/capabilities/teachability.py +393 -0
  37. autogen/agentchat/contrib/capabilities/text_compressors.py +66 -0
  38. autogen/agentchat/contrib/capabilities/tools_capability.py +22 -0
  39. autogen/agentchat/contrib/capabilities/transform_messages.py +93 -0
  40. autogen/agentchat/contrib/capabilities/transforms.py +578 -0
  41. autogen/agentchat/contrib/capabilities/transforms_util.py +122 -0
  42. autogen/agentchat/contrib/capabilities/vision_capability.py +215 -0
  43. autogen/agentchat/contrib/captainagent/__init__.py +9 -0
  44. autogen/agentchat/contrib/captainagent/agent_builder.py +790 -0
  45. autogen/agentchat/contrib/captainagent/captainagent.py +514 -0
  46. autogen/agentchat/contrib/captainagent/tool_retriever.py +334 -0
  47. autogen/agentchat/contrib/captainagent/tools/README.md +44 -0
  48. autogen/agentchat/contrib/captainagent/tools/__init__.py +5 -0
  49. autogen/agentchat/contrib/captainagent/tools/data_analysis/calculate_correlation.py +40 -0
  50. autogen/agentchat/contrib/captainagent/tools/data_analysis/calculate_skewness_and_kurtosis.py +28 -0
  51. autogen/agentchat/contrib/captainagent/tools/data_analysis/detect_outlier_iqr.py +28 -0
  52. autogen/agentchat/contrib/captainagent/tools/data_analysis/detect_outlier_zscore.py +28 -0
  53. autogen/agentchat/contrib/captainagent/tools/data_analysis/explore_csv.py +21 -0
  54. autogen/agentchat/contrib/captainagent/tools/data_analysis/shapiro_wilk_test.py +30 -0
  55. autogen/agentchat/contrib/captainagent/tools/information_retrieval/arxiv_download.py +27 -0
  56. autogen/agentchat/contrib/captainagent/tools/information_retrieval/arxiv_search.py +53 -0
  57. autogen/agentchat/contrib/captainagent/tools/information_retrieval/extract_pdf_image.py +53 -0
  58. autogen/agentchat/contrib/captainagent/tools/information_retrieval/extract_pdf_text.py +38 -0
  59. autogen/agentchat/contrib/captainagent/tools/information_retrieval/get_wikipedia_text.py +21 -0
  60. autogen/agentchat/contrib/captainagent/tools/information_retrieval/get_youtube_caption.py +34 -0
  61. autogen/agentchat/contrib/captainagent/tools/information_retrieval/image_qa.py +60 -0
  62. autogen/agentchat/contrib/captainagent/tools/information_retrieval/optical_character_recognition.py +61 -0
  63. autogen/agentchat/contrib/captainagent/tools/information_retrieval/perform_web_search.py +47 -0
  64. autogen/agentchat/contrib/captainagent/tools/information_retrieval/scrape_wikipedia_tables.py +33 -0
  65. autogen/agentchat/contrib/captainagent/tools/information_retrieval/transcribe_audio_file.py +21 -0
  66. autogen/agentchat/contrib/captainagent/tools/information_retrieval/youtube_download.py +35 -0
  67. autogen/agentchat/contrib/captainagent/tools/math/calculate_circle_area_from_diameter.py +21 -0
  68. autogen/agentchat/contrib/captainagent/tools/math/calculate_day_of_the_week.py +18 -0
  69. autogen/agentchat/contrib/captainagent/tools/math/calculate_fraction_sum.py +28 -0
  70. autogen/agentchat/contrib/captainagent/tools/math/calculate_matrix_power.py +31 -0
  71. autogen/agentchat/contrib/captainagent/tools/math/calculate_reflected_point.py +16 -0
  72. autogen/agentchat/contrib/captainagent/tools/math/complex_numbers_product.py +25 -0
  73. autogen/agentchat/contrib/captainagent/tools/math/compute_currency_conversion.py +23 -0
  74. autogen/agentchat/contrib/captainagent/tools/math/count_distinct_permutations.py +27 -0
  75. autogen/agentchat/contrib/captainagent/tools/math/evaluate_expression.py +28 -0
  76. autogen/agentchat/contrib/captainagent/tools/math/find_continuity_point.py +34 -0
  77. autogen/agentchat/contrib/captainagent/tools/math/fraction_to_mixed_numbers.py +39 -0
  78. autogen/agentchat/contrib/captainagent/tools/math/modular_inverse_sum.py +23 -0
  79. autogen/agentchat/contrib/captainagent/tools/math/simplify_mixed_numbers.py +36 -0
  80. autogen/agentchat/contrib/captainagent/tools/math/sum_of_digit_factorials.py +15 -0
  81. autogen/agentchat/contrib/captainagent/tools/math/sum_of_primes_below.py +15 -0
  82. autogen/agentchat/contrib/captainagent/tools/requirements.txt +10 -0
  83. autogen/agentchat/contrib/captainagent/tools/tool_description.tsv +34 -0
  84. autogen/agentchat/contrib/gpt_assistant_agent.py +526 -0
  85. autogen/agentchat/contrib/graph_rag/__init__.py +9 -0
  86. autogen/agentchat/contrib/graph_rag/document.py +29 -0
  87. autogen/agentchat/contrib/graph_rag/falkor_graph_query_engine.py +167 -0
  88. autogen/agentchat/contrib/graph_rag/falkor_graph_rag_capability.py +103 -0
  89. autogen/agentchat/contrib/graph_rag/graph_query_engine.py +53 -0
  90. autogen/agentchat/contrib/graph_rag/graph_rag_capability.py +63 -0
  91. autogen/agentchat/contrib/graph_rag/neo4j_graph_query_engine.py +263 -0
  92. autogen/agentchat/contrib/graph_rag/neo4j_graph_rag_capability.py +83 -0
  93. autogen/agentchat/contrib/graph_rag/neo4j_native_graph_query_engine.py +210 -0
  94. autogen/agentchat/contrib/graph_rag/neo4j_native_graph_rag_capability.py +93 -0
  95. autogen/agentchat/contrib/img_utils.py +397 -0
  96. autogen/agentchat/contrib/llamaindex_conversable_agent.py +117 -0
  97. autogen/agentchat/contrib/llava_agent.py +189 -0
  98. autogen/agentchat/contrib/math_user_proxy_agent.py +464 -0
  99. autogen/agentchat/contrib/multimodal_conversable_agent.py +125 -0
  100. autogen/agentchat/contrib/qdrant_retrieve_user_proxy_agent.py +325 -0
  101. autogen/agentchat/contrib/rag/__init__.py +10 -0
  102. autogen/agentchat/contrib/rag/chromadb_query_engine.py +268 -0
  103. autogen/agentchat/contrib/rag/llamaindex_query_engine.py +195 -0
  104. autogen/agentchat/contrib/rag/mongodb_query_engine.py +319 -0
  105. autogen/agentchat/contrib/rag/query_engine.py +76 -0
  106. autogen/agentchat/contrib/retrieve_assistant_agent.py +59 -0
  107. autogen/agentchat/contrib/retrieve_user_proxy_agent.py +704 -0
  108. autogen/agentchat/contrib/society_of_mind_agent.py +200 -0
  109. autogen/agentchat/contrib/swarm_agent.py +1404 -0
  110. autogen/agentchat/contrib/text_analyzer_agent.py +79 -0
  111. autogen/agentchat/contrib/vectordb/__init__.py +5 -0
  112. autogen/agentchat/contrib/vectordb/base.py +224 -0
  113. autogen/agentchat/contrib/vectordb/chromadb.py +316 -0
  114. autogen/agentchat/contrib/vectordb/couchbase.py +405 -0
  115. autogen/agentchat/contrib/vectordb/mongodb.py +551 -0
  116. autogen/agentchat/contrib/vectordb/pgvectordb.py +927 -0
  117. autogen/agentchat/contrib/vectordb/qdrant.py +320 -0
  118. autogen/agentchat/contrib/vectordb/utils.py +126 -0
  119. autogen/agentchat/contrib/web_surfer.py +304 -0
  120. autogen/agentchat/conversable_agent.py +4307 -0
  121. autogen/agentchat/group/__init__.py +67 -0
  122. autogen/agentchat/group/available_condition.py +91 -0
  123. autogen/agentchat/group/context_condition.py +77 -0
  124. autogen/agentchat/group/context_expression.py +238 -0
  125. autogen/agentchat/group/context_str.py +39 -0
  126. autogen/agentchat/group/context_variables.py +182 -0
  127. autogen/agentchat/group/events/transition_events.py +111 -0
  128. autogen/agentchat/group/group_tool_executor.py +324 -0
  129. autogen/agentchat/group/group_utils.py +659 -0
  130. autogen/agentchat/group/guardrails.py +179 -0
  131. autogen/agentchat/group/handoffs.py +303 -0
  132. autogen/agentchat/group/llm_condition.py +93 -0
  133. autogen/agentchat/group/multi_agent_chat.py +291 -0
  134. autogen/agentchat/group/on_condition.py +55 -0
  135. autogen/agentchat/group/on_context_condition.py +51 -0
  136. autogen/agentchat/group/patterns/__init__.py +18 -0
  137. autogen/agentchat/group/patterns/auto.py +160 -0
  138. autogen/agentchat/group/patterns/manual.py +177 -0
  139. autogen/agentchat/group/patterns/pattern.py +295 -0
  140. autogen/agentchat/group/patterns/random.py +106 -0
  141. autogen/agentchat/group/patterns/round_robin.py +117 -0
  142. autogen/agentchat/group/reply_result.py +24 -0
  143. autogen/agentchat/group/safeguards/__init__.py +21 -0
  144. autogen/agentchat/group/safeguards/api.py +241 -0
  145. autogen/agentchat/group/safeguards/enforcer.py +1158 -0
  146. autogen/agentchat/group/safeguards/events.py +140 -0
  147. autogen/agentchat/group/safeguards/validator.py +435 -0
  148. autogen/agentchat/group/speaker_selection_result.py +41 -0
  149. autogen/agentchat/group/targets/__init__.py +4 -0
  150. autogen/agentchat/group/targets/function_target.py +245 -0
  151. autogen/agentchat/group/targets/group_chat_target.py +133 -0
  152. autogen/agentchat/group/targets/group_manager_target.py +151 -0
  153. autogen/agentchat/group/targets/transition_target.py +424 -0
  154. autogen/agentchat/group/targets/transition_utils.py +6 -0
  155. autogen/agentchat/groupchat.py +1832 -0
  156. autogen/agentchat/realtime/__init__.py +3 -0
  157. autogen/agentchat/realtime/experimental/__init__.py +20 -0
  158. autogen/agentchat/realtime/experimental/audio_adapters/__init__.py +8 -0
  159. autogen/agentchat/realtime/experimental/audio_adapters/twilio_audio_adapter.py +148 -0
  160. autogen/agentchat/realtime/experimental/audio_adapters/websocket_audio_adapter.py +139 -0
  161. autogen/agentchat/realtime/experimental/audio_observer.py +42 -0
  162. autogen/agentchat/realtime/experimental/clients/__init__.py +15 -0
  163. autogen/agentchat/realtime/experimental/clients/gemini/__init__.py +7 -0
  164. autogen/agentchat/realtime/experimental/clients/gemini/client.py +274 -0
  165. autogen/agentchat/realtime/experimental/clients/oai/__init__.py +8 -0
  166. autogen/agentchat/realtime/experimental/clients/oai/base_client.py +220 -0
  167. autogen/agentchat/realtime/experimental/clients/oai/rtc_client.py +243 -0
  168. autogen/agentchat/realtime/experimental/clients/oai/utils.py +48 -0
  169. autogen/agentchat/realtime/experimental/clients/realtime_client.py +191 -0
  170. autogen/agentchat/realtime/experimental/function_observer.py +84 -0
  171. autogen/agentchat/realtime/experimental/realtime_agent.py +158 -0
  172. autogen/agentchat/realtime/experimental/realtime_events.py +42 -0
  173. autogen/agentchat/realtime/experimental/realtime_observer.py +100 -0
  174. autogen/agentchat/realtime/experimental/realtime_swarm.py +533 -0
  175. autogen/agentchat/realtime/experimental/websockets.py +21 -0
  176. autogen/agentchat/realtime_agent/__init__.py +21 -0
  177. autogen/agentchat/user_proxy_agent.py +114 -0
  178. autogen/agentchat/utils.py +206 -0
  179. autogen/agents/__init__.py +3 -0
  180. autogen/agents/contrib/__init__.py +10 -0
  181. autogen/agents/contrib/time/__init__.py +8 -0
  182. autogen/agents/contrib/time/time_reply_agent.py +74 -0
  183. autogen/agents/contrib/time/time_tool_agent.py +52 -0
  184. autogen/agents/experimental/__init__.py +27 -0
  185. autogen/agents/experimental/deep_research/__init__.py +7 -0
  186. autogen/agents/experimental/deep_research/deep_research.py +52 -0
  187. autogen/agents/experimental/discord/__init__.py +7 -0
  188. autogen/agents/experimental/discord/discord.py +66 -0
  189. autogen/agents/experimental/document_agent/__init__.py +19 -0
  190. autogen/agents/experimental/document_agent/chroma_query_engine.py +301 -0
  191. autogen/agents/experimental/document_agent/docling_doc_ingest_agent.py +113 -0
  192. autogen/agents/experimental/document_agent/document_agent.py +643 -0
  193. autogen/agents/experimental/document_agent/document_conditions.py +50 -0
  194. autogen/agents/experimental/document_agent/document_utils.py +376 -0
  195. autogen/agents/experimental/document_agent/inmemory_query_engine.py +214 -0
  196. autogen/agents/experimental/document_agent/parser_utils.py +134 -0
  197. autogen/agents/experimental/document_agent/url_utils.py +417 -0
  198. autogen/agents/experimental/reasoning/__init__.py +7 -0
  199. autogen/agents/experimental/reasoning/reasoning_agent.py +1178 -0
  200. autogen/agents/experimental/slack/__init__.py +7 -0
  201. autogen/agents/experimental/slack/slack.py +73 -0
  202. autogen/agents/experimental/telegram/__init__.py +7 -0
  203. autogen/agents/experimental/telegram/telegram.py +76 -0
  204. autogen/agents/experimental/websurfer/__init__.py +7 -0
  205. autogen/agents/experimental/websurfer/websurfer.py +70 -0
  206. autogen/agents/experimental/wikipedia/__init__.py +7 -0
  207. autogen/agents/experimental/wikipedia/wikipedia.py +88 -0
  208. autogen/browser_utils.py +309 -0
  209. autogen/cache/__init__.py +10 -0
  210. autogen/cache/abstract_cache_base.py +71 -0
  211. autogen/cache/cache.py +203 -0
  212. autogen/cache/cache_factory.py +88 -0
  213. autogen/cache/cosmos_db_cache.py +144 -0
  214. autogen/cache/disk_cache.py +97 -0
  215. autogen/cache/in_memory_cache.py +54 -0
  216. autogen/cache/redis_cache.py +119 -0
  217. autogen/code_utils.py +598 -0
  218. autogen/coding/__init__.py +30 -0
  219. autogen/coding/base.py +120 -0
  220. autogen/coding/docker_commandline_code_executor.py +283 -0
  221. autogen/coding/factory.py +56 -0
  222. autogen/coding/func_with_reqs.py +203 -0
  223. autogen/coding/jupyter/__init__.py +23 -0
  224. autogen/coding/jupyter/base.py +36 -0
  225. autogen/coding/jupyter/docker_jupyter_server.py +160 -0
  226. autogen/coding/jupyter/embedded_ipython_code_executor.py +182 -0
  227. autogen/coding/jupyter/import_utils.py +82 -0
  228. autogen/coding/jupyter/jupyter_client.py +224 -0
  229. autogen/coding/jupyter/jupyter_code_executor.py +154 -0
  230. autogen/coding/jupyter/local_jupyter_server.py +164 -0
  231. autogen/coding/local_commandline_code_executor.py +341 -0
  232. autogen/coding/markdown_code_extractor.py +44 -0
  233. autogen/coding/utils.py +55 -0
  234. autogen/coding/yepcode_code_executor.py +197 -0
  235. autogen/doc_utils.py +35 -0
  236. autogen/environments/__init__.py +10 -0
  237. autogen/environments/docker_python_environment.py +365 -0
  238. autogen/environments/python_environment.py +125 -0
  239. autogen/environments/system_python_environment.py +85 -0
  240. autogen/environments/venv_python_environment.py +220 -0
  241. autogen/environments/working_directory.py +74 -0
  242. autogen/events/__init__.py +7 -0
  243. autogen/events/agent_events.py +1016 -0
  244. autogen/events/base_event.py +100 -0
  245. autogen/events/client_events.py +168 -0
  246. autogen/events/helpers.py +44 -0
  247. autogen/events/print_event.py +45 -0
  248. autogen/exception_utils.py +73 -0
  249. autogen/extensions/__init__.py +5 -0
  250. autogen/fast_depends/__init__.py +16 -0
  251. autogen/fast_depends/_compat.py +75 -0
  252. autogen/fast_depends/core/__init__.py +14 -0
  253. autogen/fast_depends/core/build.py +206 -0
  254. autogen/fast_depends/core/model.py +527 -0
  255. autogen/fast_depends/dependencies/__init__.py +15 -0
  256. autogen/fast_depends/dependencies/model.py +30 -0
  257. autogen/fast_depends/dependencies/provider.py +40 -0
  258. autogen/fast_depends/library/__init__.py +10 -0
  259. autogen/fast_depends/library/model.py +46 -0
  260. autogen/fast_depends/py.typed +6 -0
  261. autogen/fast_depends/schema.py +66 -0
  262. autogen/fast_depends/use.py +272 -0
  263. autogen/fast_depends/utils.py +177 -0
  264. autogen/formatting_utils.py +83 -0
  265. autogen/function_utils.py +13 -0
  266. autogen/graph_utils.py +173 -0
  267. autogen/import_utils.py +539 -0
  268. autogen/interop/__init__.py +22 -0
  269. autogen/interop/crewai/__init__.py +7 -0
  270. autogen/interop/crewai/crewai.py +88 -0
  271. autogen/interop/interoperability.py +71 -0
  272. autogen/interop/interoperable.py +46 -0
  273. autogen/interop/langchain/__init__.py +8 -0
  274. autogen/interop/langchain/langchain_chat_model_factory.py +156 -0
  275. autogen/interop/langchain/langchain_tool.py +78 -0
  276. autogen/interop/litellm/__init__.py +7 -0
  277. autogen/interop/litellm/litellm_config_factory.py +178 -0
  278. autogen/interop/pydantic_ai/__init__.py +7 -0
  279. autogen/interop/pydantic_ai/pydantic_ai.py +172 -0
  280. autogen/interop/registry.py +70 -0
  281. autogen/io/__init__.py +15 -0
  282. autogen/io/base.py +151 -0
  283. autogen/io/console.py +56 -0
  284. autogen/io/processors/__init__.py +12 -0
  285. autogen/io/processors/base.py +21 -0
  286. autogen/io/processors/console_event_processor.py +61 -0
  287. autogen/io/run_response.py +294 -0
  288. autogen/io/thread_io_stream.py +63 -0
  289. autogen/io/websockets.py +214 -0
  290. autogen/json_utils.py +42 -0
  291. autogen/llm_clients/MIGRATION_TO_V2.md +782 -0
  292. autogen/llm_clients/__init__.py +77 -0
  293. autogen/llm_clients/client_v2.py +122 -0
  294. autogen/llm_clients/models/__init__.py +55 -0
  295. autogen/llm_clients/models/content_blocks.py +389 -0
  296. autogen/llm_clients/models/unified_message.py +145 -0
  297. autogen/llm_clients/models/unified_response.py +83 -0
  298. autogen/llm_clients/openai_completions_client.py +444 -0
  299. autogen/llm_config/__init__.py +11 -0
  300. autogen/llm_config/client.py +59 -0
  301. autogen/llm_config/config.py +461 -0
  302. autogen/llm_config/entry.py +169 -0
  303. autogen/llm_config/types.py +37 -0
  304. autogen/llm_config/utils.py +223 -0
  305. autogen/logger/__init__.py +11 -0
  306. autogen/logger/base_logger.py +129 -0
  307. autogen/logger/file_logger.py +262 -0
  308. autogen/logger/logger_factory.py +42 -0
  309. autogen/logger/logger_utils.py +57 -0
  310. autogen/logger/sqlite_logger.py +524 -0
  311. autogen/math_utils.py +338 -0
  312. autogen/mcp/__init__.py +7 -0
  313. autogen/mcp/__main__.py +78 -0
  314. autogen/mcp/helpers.py +45 -0
  315. autogen/mcp/mcp_client.py +349 -0
  316. autogen/mcp/mcp_proxy/__init__.py +19 -0
  317. autogen/mcp/mcp_proxy/fastapi_code_generator_helpers.py +62 -0
  318. autogen/mcp/mcp_proxy/mcp_proxy.py +577 -0
  319. autogen/mcp/mcp_proxy/operation_grouping.py +166 -0
  320. autogen/mcp/mcp_proxy/operation_renaming.py +110 -0
  321. autogen/mcp/mcp_proxy/patch_fastapi_code_generator.py +98 -0
  322. autogen/mcp/mcp_proxy/security.py +399 -0
  323. autogen/mcp/mcp_proxy/security_schema_visitor.py +37 -0
  324. autogen/messages/__init__.py +7 -0
  325. autogen/messages/agent_messages.py +946 -0
  326. autogen/messages/base_message.py +108 -0
  327. autogen/messages/client_messages.py +172 -0
  328. autogen/messages/print_message.py +48 -0
  329. autogen/oai/__init__.py +61 -0
  330. autogen/oai/anthropic.py +1516 -0
  331. autogen/oai/bedrock.py +800 -0
  332. autogen/oai/cerebras.py +302 -0
  333. autogen/oai/client.py +1658 -0
  334. autogen/oai/client_utils.py +196 -0
  335. autogen/oai/cohere.py +494 -0
  336. autogen/oai/gemini.py +1045 -0
  337. autogen/oai/gemini_types.py +156 -0
  338. autogen/oai/groq.py +319 -0
  339. autogen/oai/mistral.py +311 -0
  340. autogen/oai/oai_models/__init__.py +23 -0
  341. autogen/oai/oai_models/_models.py +16 -0
  342. autogen/oai/oai_models/chat_completion.py +86 -0
  343. autogen/oai/oai_models/chat_completion_audio.py +32 -0
  344. autogen/oai/oai_models/chat_completion_message.py +97 -0
  345. autogen/oai/oai_models/chat_completion_message_tool_call.py +60 -0
  346. autogen/oai/oai_models/chat_completion_token_logprob.py +62 -0
  347. autogen/oai/oai_models/completion_usage.py +59 -0
  348. autogen/oai/ollama.py +657 -0
  349. autogen/oai/openai_responses.py +451 -0
  350. autogen/oai/openai_utils.py +897 -0
  351. autogen/oai/together.py +387 -0
  352. autogen/remote/__init__.py +18 -0
  353. autogen/remote/agent.py +199 -0
  354. autogen/remote/agent_service.py +197 -0
  355. autogen/remote/errors.py +17 -0
  356. autogen/remote/httpx_client_factory.py +131 -0
  357. autogen/remote/protocol.py +37 -0
  358. autogen/remote/retry.py +102 -0
  359. autogen/remote/runtime.py +96 -0
  360. autogen/retrieve_utils.py +490 -0
  361. autogen/runtime_logging.py +161 -0
  362. autogen/testing/__init__.py +12 -0
  363. autogen/testing/messages.py +45 -0
  364. autogen/testing/test_agent.py +111 -0
  365. autogen/token_count_utils.py +280 -0
  366. autogen/tools/__init__.py +20 -0
  367. autogen/tools/contrib/__init__.py +9 -0
  368. autogen/tools/contrib/time/__init__.py +7 -0
  369. autogen/tools/contrib/time/time.py +40 -0
  370. autogen/tools/dependency_injection.py +249 -0
  371. autogen/tools/experimental/__init__.py +54 -0
  372. autogen/tools/experimental/browser_use/__init__.py +7 -0
  373. autogen/tools/experimental/browser_use/browser_use.py +154 -0
  374. autogen/tools/experimental/code_execution/__init__.py +7 -0
  375. autogen/tools/experimental/code_execution/python_code_execution.py +86 -0
  376. autogen/tools/experimental/crawl4ai/__init__.py +7 -0
  377. autogen/tools/experimental/crawl4ai/crawl4ai.py +150 -0
  378. autogen/tools/experimental/deep_research/__init__.py +7 -0
  379. autogen/tools/experimental/deep_research/deep_research.py +329 -0
  380. autogen/tools/experimental/duckduckgo/__init__.py +7 -0
  381. autogen/tools/experimental/duckduckgo/duckduckgo_search.py +103 -0
  382. autogen/tools/experimental/firecrawl/__init__.py +7 -0
  383. autogen/tools/experimental/firecrawl/firecrawl_tool.py +836 -0
  384. autogen/tools/experimental/google/__init__.py +14 -0
  385. autogen/tools/experimental/google/authentication/__init__.py +11 -0
  386. autogen/tools/experimental/google/authentication/credentials_hosted_provider.py +43 -0
  387. autogen/tools/experimental/google/authentication/credentials_local_provider.py +91 -0
  388. autogen/tools/experimental/google/authentication/credentials_provider.py +35 -0
  389. autogen/tools/experimental/google/drive/__init__.py +9 -0
  390. autogen/tools/experimental/google/drive/drive_functions.py +124 -0
  391. autogen/tools/experimental/google/drive/toolkit.py +88 -0
  392. autogen/tools/experimental/google/model.py +17 -0
  393. autogen/tools/experimental/google/toolkit_protocol.py +19 -0
  394. autogen/tools/experimental/google_search/__init__.py +8 -0
  395. autogen/tools/experimental/google_search/google_search.py +93 -0
  396. autogen/tools/experimental/google_search/youtube_search.py +181 -0
  397. autogen/tools/experimental/messageplatform/__init__.py +17 -0
  398. autogen/tools/experimental/messageplatform/discord/__init__.py +7 -0
  399. autogen/tools/experimental/messageplatform/discord/discord.py +284 -0
  400. autogen/tools/experimental/messageplatform/slack/__init__.py +7 -0
  401. autogen/tools/experimental/messageplatform/slack/slack.py +385 -0
  402. autogen/tools/experimental/messageplatform/telegram/__init__.py +7 -0
  403. autogen/tools/experimental/messageplatform/telegram/telegram.py +271 -0
  404. autogen/tools/experimental/perplexity/__init__.py +7 -0
  405. autogen/tools/experimental/perplexity/perplexity_search.py +249 -0
  406. autogen/tools/experimental/reliable/__init__.py +10 -0
  407. autogen/tools/experimental/reliable/reliable.py +1311 -0
  408. autogen/tools/experimental/searxng/__init__.py +7 -0
  409. autogen/tools/experimental/searxng/searxng_search.py +142 -0
  410. autogen/tools/experimental/tavily/__init__.py +7 -0
  411. autogen/tools/experimental/tavily/tavily_search.py +176 -0
  412. autogen/tools/experimental/web_search_preview/__init__.py +7 -0
  413. autogen/tools/experimental/web_search_preview/web_search_preview.py +120 -0
  414. autogen/tools/experimental/wikipedia/__init__.py +7 -0
  415. autogen/tools/experimental/wikipedia/wikipedia.py +284 -0
  416. autogen/tools/function_utils.py +412 -0
  417. autogen/tools/tool.py +188 -0
  418. autogen/tools/toolkit.py +86 -0
  419. autogen/types.py +29 -0
  420. autogen/version.py +7 -0
  421. templates/client_template/main.jinja2 +72 -0
  422. templates/config_template/config.jinja2 +7 -0
  423. templates/main.jinja2 +61 -0
autogen/oai/gemini.py ADDED
@@ -0,0 +1,1045 @@
1
+ # Copyright (c) 2023 - 2025, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ #
5
+ # Portions derived from https://github.com/microsoft/autogen are under the MIT License.
6
+ # SPDX-License-Identifier: MIT
7
+ """Create a OpenAI-compatible client for Gemini features.
8
+
9
+ Example:
10
+ ```python
11
+ llm_config = {
12
+ "config_list": [
13
+ {
14
+ "api_type": "google",
15
+ "model": "gemini-pro",
16
+ "api_key": os.environ.get("GOOGLE_GEMINI_API_KEY"),
17
+ "safety_settings": [
18
+ {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_ONLY_HIGH"},
19
+ {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_ONLY_HIGH"},
20
+ {"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_ONLY_HIGH"},
21
+ {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_ONLY_HIGH"},
22
+ ],
23
+ "top_p": 0.5,
24
+ "max_tokens": 2048,
25
+ "temperature": 1.0,
26
+ "top_k": 5,
27
+ }
28
+ ]
29
+ }
30
+
31
+ agent = autogen.AssistantAgent("my_agent", llm_config=llm_config)
32
+ ```
33
+
34
+ Resources:
35
+ - https://ai.google.dev/docs
36
+ - https://cloud.google.com/vertex-ai/generative-ai/docs/migrate/migrate-from-azure-to-gemini
37
+ - https://blog.google/technology/ai/google-gemini-pro-imagen-duet-ai-update/
38
+ - https://ai.google.dev/api/python/google/generativeai/ChatSession
39
+ """
40
+
41
+ from __future__ import annotations
42
+
43
+ import asyncio
44
+ import base64
45
+ import copy
46
+ import json
47
+ import logging
48
+ import os
49
+ import random
50
+ import re
51
+ import time
52
+ import warnings
53
+ from io import BytesIO
54
+ from typing import Any, Literal
55
+
56
+ import requests
57
+ from pydantic import BaseModel, Field
58
+ from typing_extensions import Unpack
59
+
60
+ from ..import_utils import optional_import_block, require_optional_import
61
+ from ..json_utils import resolve_json_references
62
+ from ..llm_config.entry import LLMConfigEntry, LLMConfigEntryDict
63
+ from .client_utils import FormatterProtocol
64
+ from .gemini_types import ToolConfig
65
+ from .oai_models import ChatCompletion, ChatCompletionMessage, ChatCompletionMessageToolCall, Choice, CompletionUsage
66
+
67
+ with optional_import_block():
68
+ import google.genai as genai
69
+ import vertexai
70
+ from PIL import Image
71
+ from google.auth.credentials import Credentials
72
+ from google.genai import types
73
+ from google.genai.types import (
74
+ Content,
75
+ FinishReason,
76
+ FunctionCall,
77
+ FunctionDeclaration,
78
+ FunctionResponse,
79
+ GenerateContentConfig,
80
+ GenerateContentResponse,
81
+ GoogleSearch,
82
+ Part,
83
+ Schema,
84
+ Tool,
85
+ Type,
86
+ )
87
+ from jsonschema import ValidationError
88
+ from vertexai.generative_models import Content as VertexAIContent
89
+ from vertexai.generative_models import FunctionDeclaration as vaiFunctionDeclaration
90
+ from vertexai.generative_models import GenerationConfig, GenerativeModel
91
+ from vertexai.generative_models import (
92
+ GenerationResponse as VertexAIGenerationResponse,
93
+ )
94
+ from vertexai.generative_models import HarmBlockThreshold as VertexAIHarmBlockThreshold
95
+ from vertexai.generative_models import HarmCategory as VertexAIHarmCategory
96
+ from vertexai.generative_models import Part as VertexAIPart
97
+ from vertexai.generative_models import SafetySetting as VertexAISafetySetting
98
+ from vertexai.generative_models import (
99
+ Tool as vaiTool,
100
+ )
101
+
102
+ logger = logging.getLogger(__name__)
103
+
104
+
105
+ class GeminiEntryDict(LLMConfigEntryDict, total=False):
106
+ api_type: Literal["google"]
107
+
108
+ project_id: str | None
109
+ location: str | None
110
+ google_application_credentials: str | None
111
+ credentials: Any | str | None
112
+ stream: bool
113
+ safety_settings: list[dict[str, Any]] | dict[str, Any] | None
114
+ price: list[float] | None
115
+ tool_config: ToolConfig | None
116
+ proxy: str | None
117
+
118
+
119
+ class GeminiLLMConfigEntry(LLMConfigEntry):
120
+ api_type: Literal["google"] = "google"
121
+ project_id: str | None = None
122
+ location: str | None = None
123
+ # google_application_credentials points to the path of the JSON Keyfile
124
+ google_application_credentials: str | None = None
125
+ # credentials is a google.auth.credentials.Credentials object
126
+ credentials: Any | str | None = None
127
+ stream: bool = False
128
+ safety_settings: list[dict[str, Any]] | dict[str, Any] | None = None
129
+ price: list[float] | None = Field(default=None, min_length=2, max_length=2)
130
+ tool_config: ToolConfig | None = None
131
+ proxy: str | None = None
132
+ """A valid HTTP(S) proxy URL"""
133
+
134
+ def create_client(self):
135
+ raise NotImplementedError("GeminiLLMConfigEntry.create_client() is not implemented.")
136
+
137
+
138
+ @require_optional_import(["google", "vertexai", "PIL", "jsonschema"], "gemini")
139
+ class GeminiClient:
140
+ """Client for Google's Gemini API."""
141
+
142
+ RESPONSE_USAGE_KEYS: list[str] = ["prompt_tokens", "completion_tokens", "total_tokens", "cost", "model"]
143
+
144
+ # Mapping, where Key is a term used by Autogen, and Value is a term used by Gemini
145
+ PARAMS_MAPPING = {
146
+ "max_tokens": "max_output_tokens",
147
+ # "n": "candidate_count", # Gemini supports only `n=1`
148
+ "seed": "seed",
149
+ "stop_sequences": "stop_sequences",
150
+ "temperature": "temperature",
151
+ "top_p": "top_p",
152
+ "top_k": "top_k",
153
+ "max_output_tokens": "max_output_tokens",
154
+ }
155
+
156
+ def _initialize_vertexai(self, **params: Unpack[GeminiEntryDict]):
157
+ if "google_application_credentials" in params:
158
+ # Path to JSON Keyfile
159
+ os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = params["google_application_credentials"]
160
+ vertexai_init_args = {}
161
+ if "project_id" in params:
162
+ vertexai_init_args["project"] = params["project_id"]
163
+ if "location" in params:
164
+ vertexai_init_args["location"] = params["location"]
165
+ if "credentials" in params:
166
+ assert isinstance(params["credentials"], Credentials), (
167
+ "Object type google.auth.credentials.Credentials is expected!"
168
+ )
169
+ vertexai_init_args["credentials"] = params["credentials"]
170
+ if vertexai_init_args:
171
+ vertexai.init(**vertexai_init_args)
172
+
173
+ def __init__(self, **kwargs):
174
+ """Uses either either api_key for authentication from the LLM config
175
+ (specifying the GOOGLE_GEMINI_API_KEY environment variable also works),
176
+ or follows the Google authentication mechanism for VertexAI in Google Cloud if no api_key is specified,
177
+ where project_id and location can also be passed as parameters. Previously created credentials object can be provided,
178
+ or a Service account key file can also be used. If neither a service account key file, nor the api_key are passed,
179
+ then the default credentials will be used, which could be a personal account if the user is already authenticated in,
180
+ like in Google Cloud Shell.
181
+
182
+ Args:
183
+ **kwargs: The keyword arguments to initialize the Gemini client.
184
+ """
185
+ self.api_key = kwargs.get("api_key")
186
+ if not self.api_key:
187
+ self.api_key = os.getenv("GOOGLE_GEMINI_API_KEY")
188
+ if self.api_key is None:
189
+ self.use_vertexai = True
190
+ self._initialize_vertexai(**kwargs)
191
+ else:
192
+ self.use_vertexai = False
193
+ else:
194
+ self.use_vertexai = False
195
+ if not self.use_vertexai:
196
+ assert ("project_id" not in kwargs) and ("location" not in kwargs), (
197
+ "Google Cloud project and compute location cannot be set when using an API Key!"
198
+ )
199
+
200
+ self.api_version = kwargs.get("api_version")
201
+ self.proxy = kwargs.get("proxy")
202
+
203
+ # Store the response format, if provided (for structured outputs)
204
+ self._response_format: type[BaseModel] | None = None
205
+
206
+ def message_retrieval(self, response) -> list:
207
+ """Retrieve and return a list of strings or a list of Choice.Message from the response.
208
+
209
+ NOTE: if a list of Choice.Message is returned, it currently needs to contain the fields of OpenAI's ChatCompletion Message object,
210
+ since that is expected for function or tool calling in the rest of the codebase at the moment, unless a custom agent is being used.
211
+ """
212
+ return [choice.message for choice in response.choices]
213
+
214
+ def cost(self, response) -> float:
215
+ return response.cost
216
+
217
+ @staticmethod
218
+ def get_usage(response) -> dict:
219
+ """Return usage summary of the response using RESPONSE_USAGE_KEYS."""
220
+ # ... # pragma: no cover
221
+ return {
222
+ "prompt_tokens": response.usage.prompt_tokens,
223
+ "completion_tokens": response.usage.completion_tokens,
224
+ "total_tokens": response.usage.total_tokens,
225
+ "cost": response.cost,
226
+ "model": response.model,
227
+ }
228
+
229
+ def create(self, params: dict) -> ChatCompletion:
230
+ # When running in async context via run_in_executor from ConversableAgent.a_generate_oai_reply,
231
+ # this method runs in a new thread that doesn't have an event loop by default. The Google Genai
232
+ # client requires an event loop even for synchronous operations, so we need to ensure one exists.
233
+ try:
234
+ asyncio.get_running_loop()
235
+ except RuntimeError:
236
+ # No event loop exists in this thread (which happens when called from an executor)
237
+ # Create a new event loop for this thread to satisfy Genai client requirements
238
+ loop = asyncio.new_event_loop()
239
+ asyncio.set_event_loop(loop)
240
+
241
+ if self.use_vertexai:
242
+ self._initialize_vertexai(**params)
243
+ else:
244
+ assert ("project_id" not in params) and ("location" not in params), (
245
+ "Google Cloud project and compute location cannot be set when using an API Key!"
246
+ )
247
+ model_name = params.get("model", "gemini-pro")
248
+
249
+ if model_name == "gemini-pro-vision":
250
+ raise ValueError(
251
+ "Gemini 1.0 Pro vision ('gemini-pro-vision') has been deprecated, please consider switching to a different model, for example 'gemini-2.5-flash'."
252
+ )
253
+ elif not model_name:
254
+ raise ValueError(
255
+ "Please provide a model name for the Gemini Client. "
256
+ "You can configure it in the OAI Config List file. "
257
+ "See this [LLM configuration tutorial](https://docs.ag2.ai/latest/docs/user-guide/basic-concepts/llm-configuration/) for more details."
258
+ )
259
+
260
+ http_options = types.HttpOptions()
261
+ if proxy := params.get("proxy", self.proxy):
262
+ http_options.client_args = {"proxy": proxy}
263
+ http_options.async_client_args = {"proxy": proxy}
264
+
265
+ if self.api_version:
266
+ http_options.api_version = self.api_version
267
+
268
+ messages = params.get("messages", [])
269
+ stream = params.get("stream", False)
270
+ n_response = params.get("n", 1)
271
+ system_instruction = self._extract_system_instruction(messages)
272
+ response_validation = params.get("response_validation", True)
273
+ tools = self._tools_to_gemini_tools(params["tools"]) if "tools" in params else None
274
+ tool_config = params.get("tool_config")
275
+
276
+ generation_config = {
277
+ gemini_term: params[autogen_term]
278
+ for autogen_term, gemini_term in self.PARAMS_MAPPING.items()
279
+ if autogen_term in params
280
+ }
281
+ if self.use_vertexai:
282
+ safety_settings = GeminiClient._to_vertexai_safety_settings(params.get("safety_settings", []))
283
+ else:
284
+ safety_settings = params.get("safety_settings", [])
285
+
286
+ if stream:
287
+ warnings.warn(
288
+ "Streaming is not supported for Gemini yet, and it will have no effect. Please set stream=False.",
289
+ UserWarning,
290
+ )
291
+ stream = False
292
+
293
+ if n_response > 1:
294
+ warnings.warn("Gemini only supports `n=1` for now. We only generate one response.", UserWarning)
295
+
296
+ autogen_tool_calls = []
297
+
298
+ # Maps the function call ids to function names so we can inject it into FunctionResponse messages
299
+ self.tool_call_function_map: dict[str, str] = {}
300
+
301
+ # If response_format exists, we want structured outputs
302
+ # Based on
303
+ # https://ai.google.dev/gemini-api/docs/structured-output?lang=python#supply-schema-in-config
304
+ if params.get("response_format"):
305
+ self._response_format = params.get("response_format")
306
+ generation_config["response_mime_type"] = "application/json"
307
+
308
+ response_format_schema_raw = params.get("response_format")
309
+
310
+ if isinstance(response_format_schema_raw, dict):
311
+ response_schema = resolve_json_references(response_format_schema_raw)
312
+ else:
313
+ response_schema = resolve_json_references(params.get("response_format").model_json_schema())
314
+ if "$defs" in response_schema:
315
+ response_schema.pop("$defs")
316
+ generation_config["response_schema"] = response_schema
317
+
318
+ # A. create and call the chat model.
319
+ gemini_messages = self._oai_messages_to_gemini_messages(messages)
320
+ if self.use_vertexai:
321
+ model = GenerativeModel(
322
+ model_name,
323
+ generation_config=GenerationConfig(**generation_config),
324
+ safety_settings=safety_settings,
325
+ system_instruction=system_instruction,
326
+ tool_config=tool_config,
327
+ tools=tools,
328
+ )
329
+
330
+ chat = model.start_chat(history=gemini_messages[:-1], response_validation=response_validation)
331
+ response = chat.send_message(gemini_messages[-1].parts, stream=stream, safety_settings=safety_settings)
332
+ else:
333
+ client = genai.Client(api_key=self.api_key, http_options=http_options)
334
+ generate_content_config = GenerateContentConfig(
335
+ safety_settings=safety_settings,
336
+ system_instruction=system_instruction,
337
+ tools=tools,
338
+ tool_config=tool_config,
339
+ **generation_config,
340
+ )
341
+ chat = client.chats.create(model=model_name, config=generate_content_config, history=gemini_messages[:-1])
342
+ response = chat.send_message(message=gemini_messages[-1].parts)
343
+
344
+ # Extract text and tools from response
345
+ ans = ""
346
+ random_id = random.randint(0, 10000)
347
+ prev_function_calls = []
348
+ error_finish_reason = None
349
+
350
+ if isinstance(response, GenerateContentResponse):
351
+ if len(response.candidates) != 1:
352
+ raise ValueError(
353
+ f"Unexpected number of candidates in the response. Expected 1, got {len(response.candidates)}"
354
+ )
355
+
356
+ # Look at https://cloud.google.com/vertex-ai/generative-ai/docs/reference/python/latest/vertexai.generative_models.FinishReason
357
+ if response.candidates[0].finish_reason and response.candidates[0].finish_reason == FinishReason.RECITATION:
358
+ recitation_part = Part(text="Unsuccessful Finish Reason: RECITATION")
359
+ parts = [recitation_part]
360
+ error_finish_reason = "content_filter" # As per available finish_reason in Choice
361
+ elif not response.candidates[0].content or not response.candidates[0].content.parts:
362
+ error_part = Part(
363
+ text=f"Unsuccessful Finish Reason: ({str(response.candidates[0].finish_reason)}) NO CONTENT RETURNED"
364
+ )
365
+ parts = [error_part]
366
+ error_finish_reason = "content_filter" # No other option in Choice in chat_completion.py
367
+ else:
368
+ parts = response.candidates[0].content.parts
369
+ elif isinstance(response, VertexAIGenerationResponse): # or hasattr(response, "candidates"):
370
+ # google.generativeai also raises an error len(candidates) != 1:
371
+ if len(response.candidates) != 1:
372
+ raise ValueError(
373
+ f"Unexpected number of candidates in the response. Expected 1, got {len(response.candidates)}"
374
+ )
375
+ parts = response.candidates[0].content.parts
376
+ else:
377
+ raise ValueError(f"Unexpected response type: {type(response)}")
378
+
379
+ for part in parts:
380
+ # Function calls
381
+ if fn_call := part.function_call:
382
+ # If we have a repeated function call, ignore it
383
+ if fn_call not in prev_function_calls:
384
+ autogen_tool_calls.append(
385
+ ChatCompletionMessageToolCall(
386
+ id=str(random_id),
387
+ function={
388
+ "name": fn_call.name,
389
+ "arguments": (
390
+ json.dumps(dict(fn_call.args.items())) if fn_call.args is not None else ""
391
+ ),
392
+ },
393
+ type="function",
394
+ )
395
+ )
396
+
397
+ prev_function_calls.append(fn_call)
398
+ random_id += 1
399
+
400
+ # Plain text content
401
+ elif text := part.text:
402
+ ans += text
403
+
404
+ # If we have function calls, ignore the text
405
+ # as it can be Gemini guessing the function response
406
+ if len(autogen_tool_calls) != 0:
407
+ ans = ""
408
+ else:
409
+ autogen_tool_calls = None
410
+
411
+ if self._response_format and ans:
412
+ try:
413
+ parsed_response = self._convert_json_response(ans)
414
+ ans = _format_json_response(parsed_response, ans)
415
+ except ValueError as e:
416
+ ans = str(e)
417
+
418
+ # 3. convert output
419
+ message = ChatCompletionMessage(
420
+ role="assistant", content=ans, function_call=None, tool_calls=autogen_tool_calls
421
+ )
422
+ choices = [
423
+ Choice(
424
+ finish_reason="tool_calls"
425
+ if autogen_tool_calls is not None
426
+ else error_finish_reason
427
+ if error_finish_reason
428
+ else "stop",
429
+ index=0,
430
+ message=message,
431
+ )
432
+ ]
433
+
434
+ prompt_tokens = response.usage_metadata.prompt_token_count
435
+ completion_tokens = (
436
+ response.usage_metadata.candidates_token_count if response.usage_metadata.candidates_token_count else 0
437
+ )
438
+
439
+ response_oai = ChatCompletion(
440
+ id=str(random.randint(0, 1000)),
441
+ model=model_name,
442
+ created=int(time.time()),
443
+ object="chat.completion",
444
+ choices=choices,
445
+ usage=CompletionUsage(
446
+ prompt_tokens=prompt_tokens,
447
+ completion_tokens=completion_tokens,
448
+ total_tokens=prompt_tokens + completion_tokens,
449
+ ),
450
+ cost=calculate_gemini_cost(self.use_vertexai, prompt_tokens, completion_tokens, model_name),
451
+ )
452
+
453
+ return response_oai
454
+
455
+ def _extract_system_instruction(self, messages: list[dict]) -> str | None:
456
+ """Extract system instruction if provided."""
457
+ if messages is None or len(messages) == 0 or messages[0].get("role") != "system":
458
+ return None
459
+
460
+ message = messages.pop(0)
461
+ content = message["content"]
462
+
463
+ # Multi-model uses a list of dictionaries as content with text for the system message
464
+ # Otherwise normal agents will have strings as content
465
+ content = content[0].get("text", "").strip() if isinstance(content, list) else content.strip()
466
+
467
+ content = content if len(content) > 0 else None
468
+ return content
469
+
470
+ def _oai_content_to_gemini_content(self, message: dict[str, Any]) -> tuple[list[Any], str]:
471
+ """Convert AG2 content to Gemini parts, catering for text and tool calls"""
472
+ rst = []
473
+
474
+ if "role" in message and message["role"] == "tool":
475
+ # Tool call recommendation
476
+
477
+ function_name = self.tool_call_function_map[message["tool_call_id"]]
478
+
479
+ if self.use_vertexai:
480
+ rst.append(
481
+ VertexAIPart.from_function_response(
482
+ name=function_name, response={"result": self._to_json_or_str(message["content"])}
483
+ )
484
+ )
485
+ else:
486
+ rst.append(
487
+ Part(
488
+ function_response=FunctionResponse(
489
+ name=function_name, response={"result": self._to_json_or_str(message["content"])}
490
+ )
491
+ )
492
+ )
493
+
494
+ return rst, "tool"
495
+ elif "tool_calls" in message and len(message["tool_calls"]) != 0:
496
+ for tool_call in message["tool_calls"]:
497
+ function_id = tool_call["id"]
498
+ function_name = tool_call["function"]["name"]
499
+ self.tool_call_function_map[function_id] = function_name
500
+
501
+ if self.use_vertexai:
502
+ rst.append(
503
+ VertexAIPart.from_dict({
504
+ "functionCall": {
505
+ "name": function_name,
506
+ "args": json.loads(tool_call["function"]["arguments"]),
507
+ }
508
+ })
509
+ )
510
+ else:
511
+ rst.append(
512
+ Part(
513
+ function_call=FunctionCall(
514
+ name=function_name,
515
+ args=json.loads(tool_call["function"]["arguments"]),
516
+ )
517
+ )
518
+ )
519
+
520
+ return rst, "tool_call"
521
+
522
+ elif isinstance(message["content"], str):
523
+ content = message["content"]
524
+ if content == "":
525
+ content = "empty" # Empty content is not allowed.
526
+ if self.use_vertexai:
527
+ rst.append(VertexAIPart.from_text(content))
528
+ else:
529
+ rst.append(Part(text=content))
530
+
531
+ return rst, "text"
532
+
533
+ # For images the message contains a list of text items
534
+ if isinstance(message["content"], list):
535
+ has_image = False
536
+ for msg in message["content"]:
537
+ if isinstance(msg, dict):
538
+ assert "type" in msg, f"Missing 'type' field in message: {msg}"
539
+ if msg["type"] == "text":
540
+ if self.use_vertexai:
541
+ rst.append(VertexAIPart.from_text(text=msg["text"]))
542
+ else:
543
+ rst.append(Part(text=msg["text"]))
544
+ elif msg["type"] == "image_url":
545
+ if self.use_vertexai:
546
+ img_url = msg["image_url"]["url"]
547
+ img_part = VertexAIPart.from_uri(img_url, mime_type="image/png")
548
+ rst.append(img_part)
549
+ else:
550
+ b64_img = get_image_data(msg["image_url"]["url"])
551
+ rst.append(Part(inline_data={"mime_type": "image/png", "data": b64_img}))
552
+
553
+ has_image = True
554
+ else:
555
+ raise ValueError(f"Unsupported message type: {msg['type']}")
556
+ else:
557
+ raise ValueError(f"Unsupported message type: {type(msg)}")
558
+ return rst, "image" if has_image else "text"
559
+ else:
560
+ raise Exception("Unable to convert content to Gemini format.")
561
+
562
+ def _concat_parts(self, parts: list[Part]) -> list:
563
+ """Concatenate parts with the same type.
564
+ If two adjacent parts both have the "text" attribute, then it will be joined into one part.
565
+ """
566
+ if not parts:
567
+ return []
568
+
569
+ concatenated_parts = []
570
+ previous_part = parts[0]
571
+
572
+ for current_part in parts[1:]:
573
+ if previous_part.text != "":
574
+ if self.use_vertexai:
575
+ previous_part = VertexAIPart.from_text(previous_part.text + current_part.text)
576
+ else:
577
+ previous_part.text += current_part.text
578
+ else:
579
+ concatenated_parts.append(previous_part)
580
+ previous_part = current_part
581
+
582
+ if previous_part.text == "":
583
+ if self.use_vertexai:
584
+ previous_part = VertexAIPart.from_text("empty")
585
+ else:
586
+ previous_part.text = "empty" # Empty content is not allowed.
587
+ concatenated_parts.append(previous_part)
588
+
589
+ return concatenated_parts
590
+
591
+ def _oai_messages_to_gemini_messages(self, messages: list[dict[str, Any]]) -> list[dict[str, Any]]:
592
+ """Convert messages from OAI format to Gemini format.
593
+ Make sure the "user" role and "model" role are interleaved.
594
+ Also, make sure the last item is from the "user" role.
595
+ """
596
+ rst = []
597
+ for message in messages:
598
+ parts, part_type = self._oai_content_to_gemini_content(message)
599
+ role = "user" if message["role"] in ["user", "system"] else "model"
600
+
601
+ if part_type == "text":
602
+ rst.append(
603
+ VertexAIContent(parts=parts, role=role) if self.use_vertexai else Content(parts=parts, role=role)
604
+ )
605
+ elif part_type == "tool_call":
606
+ # Function calls should be from the model/assistant
607
+ role = "model"
608
+ rst.append(
609
+ VertexAIContent(parts=parts, role=role) if self.use_vertexai else Content(parts=parts, role=role)
610
+ )
611
+ elif part_type == "tool":
612
+ # Function responses should be from the user
613
+ role = "user"
614
+ rst.append(
615
+ VertexAIContent(parts=parts, role=role) if self.use_vertexai else Content(parts=parts, role=role)
616
+ )
617
+ elif part_type == "image":
618
+ # Image has multiple parts, some can be text and some can be image based
619
+ text_parts = []
620
+ image_parts = []
621
+ for part in parts:
622
+ if isinstance(part, Part):
623
+ # Text or non-Vertex AI image part
624
+ text_parts.append(part)
625
+ elif isinstance(part, VertexAIPart):
626
+ # Image
627
+ image_parts.append(part)
628
+ else:
629
+ raise Exception("Unable to process image part")
630
+
631
+ if len(text_parts) > 0:
632
+ rst.append(
633
+ VertexAIContent(parts=text_parts, role=role)
634
+ if self.use_vertexai
635
+ else Content(parts=text_parts, role=role)
636
+ )
637
+
638
+ if len(image_parts) > 0:
639
+ rst.append(
640
+ VertexAIContent(parts=image_parts, role=role)
641
+ if self.use_vertexai
642
+ else Content(parts=image_parts, role=role)
643
+ )
644
+
645
+ if len(rst) != 0 and rst[-1] is None:
646
+ rst.pop()
647
+
648
+ # The Gemini is restrict on order of roles, such that
649
+ # 1. The first message must be from the user role.
650
+ # 2. The last message must be from the user role.
651
+ # 3. The messages should be interleaved between user and model.
652
+ # We add a dummy message "start chat" if the first role is not the user.
653
+ # We add a dummy message "continue" if the last role is not the user.
654
+ if rst[0].role != "user":
655
+ text_part, _ = self._oai_content_to_gemini_content({"content": "start chat"})
656
+ rst.insert(
657
+ 0,
658
+ VertexAIContent(parts=text_part, role="user")
659
+ if self.use_vertexai
660
+ else Content(parts=text_part, role="user"),
661
+ )
662
+
663
+ if rst[-1].role != "user":
664
+ text_part, _ = self._oai_content_to_gemini_content({"content": "continue"})
665
+ rst.append(
666
+ VertexAIContent(parts=text_part, role="user")
667
+ if self.use_vertexai
668
+ else Content(parts=text_part, role="user")
669
+ )
670
+
671
+ return rst
672
+
673
+ def _convert_json_response(self, response: str) -> Any:
674
+ """Extract and validate JSON response from the output for structured outputs.
675
+
676
+ Args:
677
+ response (str): The response from the API.
678
+
679
+ Returns:
680
+ Any: The parsed JSON response.
681
+ """
682
+ if not self._response_format:
683
+ return response
684
+
685
+ try:
686
+ # Parse JSON and validate against the Pydantic model if Pydantic model was provided
687
+ json_data = json.loads(response)
688
+ if isinstance(self._response_format, dict):
689
+ return json_data
690
+ else:
691
+ return self._response_format.model_validate(json_data)
692
+ except Exception as e:
693
+ raise ValueError(f"Failed to parse response as valid JSON matching the schema for Structured Output: {e!s}")
694
+
695
+ @staticmethod
696
+ def _convert_type_null_to_nullable(schema: Any) -> Any:
697
+ """Recursively converts all occurrences of {"type": "null"} to {"nullable": True} in a schema."""
698
+ if isinstance(schema, dict):
699
+ # If schema matches {"type": "null"}, replace it
700
+ if schema == {"type": "null"}:
701
+ return {"nullable": True}
702
+ # Otherwise, recursively process dictionary
703
+ return {key: GeminiClient._convert_type_null_to_nullable(value) for key, value in schema.items()}
704
+ elif isinstance(schema, list):
705
+ # Recursively process list elements
706
+ return [GeminiClient._convert_type_null_to_nullable(item) for item in schema]
707
+ return schema
708
+
709
+ @staticmethod
710
+ def _check_if_prebuilt_google_search_tool_exists(tools: list[dict[str, Any]]) -> bool:
711
+ """Check if the Google Search tool is present in the tools list."""
712
+ exists = False
713
+ for tool in tools:
714
+ if tool["function"]["name"] == "prebuilt_google_search":
715
+ exists = True
716
+ break
717
+
718
+ if exists and len(tools) > 1:
719
+ raise ValueError(
720
+ "Google Search tool can be used only by itself. Please remove other tools from the tools list."
721
+ )
722
+
723
+ return exists
724
+
725
+ @staticmethod
726
+ def _unwrap_references(function_parameters: dict[str, Any]) -> dict[str, Any]:
727
+ if "properties" not in function_parameters:
728
+ return function_parameters
729
+
730
+ function_parameters_copy = copy.deepcopy(function_parameters)
731
+
732
+ for property_name, property_value in function_parameters["properties"].items():
733
+ if "$defs" in property_value:
734
+ function_parameters_copy["properties"][property_name] = resolve_json_references(property_value)
735
+ function_parameters_copy["properties"][property_name].pop("$defs")
736
+
737
+ return function_parameters_copy
738
+
739
+ def _tools_to_gemini_tools(self, tools: list[dict[str, Any]]) -> list[Tool]:
740
+ """Create Gemini tools (as typically requires Callables)"""
741
+ if self._check_if_prebuilt_google_search_tool_exists(tools) and not self.use_vertexai:
742
+ return [Tool(google_search=GoogleSearch())]
743
+
744
+ functions = []
745
+ for tool in tools:
746
+ if self.use_vertexai:
747
+ tool["function"]["parameters"] = GeminiClient._convert_type_null_to_nullable(
748
+ tool["function"]["parameters"]
749
+ )
750
+ function_parameters = GeminiClient._unwrap_references(tool["function"]["parameters"])
751
+ function = vaiFunctionDeclaration(
752
+ name=tool["function"]["name"],
753
+ description=tool["function"]["description"],
754
+ parameters=function_parameters,
755
+ )
756
+ else:
757
+ function = GeminiClient._create_gemini_function_declaration(tool)
758
+ functions.append(function)
759
+
760
+ if self.use_vertexai:
761
+ return [vaiTool(function_declarations=functions)]
762
+ else:
763
+ return [Tool(function_declarations=functions)]
764
+
765
+ @staticmethod
766
+ def _create_gemini_function_declaration(tool: dict) -> FunctionDeclaration:
767
+ function_declaration = FunctionDeclaration()
768
+ function_declaration.name = tool["function"]["name"]
769
+ function_declaration.description = tool["function"]["description"]
770
+ if len(tool["function"]["parameters"]["properties"]) != 0:
771
+ function_declaration.parameters = GeminiClient._create_gemini_function_parameters(
772
+ copy.deepcopy(tool["function"]["parameters"])
773
+ )
774
+
775
+ return function_declaration
776
+
777
+ @staticmethod
778
+ def _create_gemini_function_declaration_schema(json_data) -> Schema:
779
+ """Recursively creates Schema objects for FunctionDeclaration."""
780
+ param_schema = Schema()
781
+ param_type = json_data["type"]
782
+
783
+ """
784
+ TYPE_UNSPECIFIED = 0
785
+ STRING = 1
786
+ INTEGER = 2
787
+ NUMBER = 3
788
+ OBJECT = 4
789
+ ARRAY = 5
790
+ BOOLEAN = 6
791
+ """
792
+
793
+ if param_type == "integer":
794
+ param_schema.type = Type.INTEGER
795
+ elif param_type == "number":
796
+ param_schema.type = Type.NUMBER
797
+ elif param_type == "string":
798
+ param_schema.type = Type.STRING
799
+ elif param_type == "boolean":
800
+ param_schema.type = Type.BOOLEAN
801
+ elif param_type == "array":
802
+ param_schema.type = Type.ARRAY
803
+ if "items" in json_data:
804
+ param_schema.items = GeminiClient._create_gemini_function_declaration_schema(json_data["items"])
805
+ else:
806
+ print("Warning: Array schema missing 'items' definition.")
807
+ elif param_type == "object":
808
+ param_schema.type = Type.OBJECT
809
+ param_schema.properties = {}
810
+ if "properties" in json_data:
811
+ for prop_name, prop_data in json_data["properties"].items():
812
+ param_schema.properties[prop_name] = GeminiClient._create_gemini_function_declaration_schema(
813
+ prop_data
814
+ )
815
+ else:
816
+ print("Warning: Object schema missing 'properties' definition.")
817
+
818
+ elif param_type in ("null", "any"):
819
+ param_schema.type = Type.STRING # Treating these as strings for simplicity
820
+ else:
821
+ print(f"Warning: Unsupported parameter type '{param_type}'.")
822
+
823
+ if "description" in json_data:
824
+ param_schema.description = json_data["description"]
825
+
826
+ return param_schema
827
+
828
+ @staticmethod
829
+ def _create_gemini_function_parameters(function_parameter: dict[str, any]) -> dict[str, any]:
830
+ """Convert function parameters to Gemini format, recursive"""
831
+ function_parameter = GeminiClient._unwrap_references(function_parameter)
832
+
833
+ if "type" in function_parameter:
834
+ function_parameter["type"] = function_parameter["type"].upper()
835
+ # If the schema was created from pydantic BaseModel, it will "title" attribute which needs to be removed
836
+ function_parameter.pop("title", None)
837
+
838
+ # Parameter properties and items
839
+ if "properties" in function_parameter:
840
+ for key in function_parameter["properties"]:
841
+ function_parameter["properties"][key] = GeminiClient._create_gemini_function_parameters(
842
+ function_parameter["properties"][key]
843
+ )
844
+
845
+ if "items" in function_parameter:
846
+ function_parameter["items"] = GeminiClient._create_gemini_function_parameters(function_parameter["items"])
847
+
848
+ # Remove any attributes not needed
849
+ for attr in ["default"]:
850
+ if attr in function_parameter:
851
+ del function_parameter[attr]
852
+
853
+ return function_parameter
854
+
855
+ @staticmethod
856
+ def _to_vertexai_safety_settings(safety_settings):
857
+ """Convert safety settings to VertexAI format if needed,
858
+ like when specifying them in the OAI_CONFIG_LIST
859
+ """
860
+ if isinstance(safety_settings, list) and all(
861
+ isinstance(safety_setting, dict) and not isinstance(safety_setting, VertexAISafetySetting)
862
+ for safety_setting in safety_settings
863
+ ):
864
+ vertexai_safety_settings = []
865
+ for safety_setting in safety_settings:
866
+ if safety_setting["category"] not in VertexAIHarmCategory.__members__:
867
+ invalid_category = safety_setting["category"]
868
+ logger.error(f"Safety setting category {invalid_category} is invalid")
869
+ elif safety_setting["threshold"] not in VertexAIHarmBlockThreshold.__members__:
870
+ invalid_threshold = safety_setting["threshold"]
871
+ logger.error(f"Safety threshold {invalid_threshold} is invalid")
872
+ else:
873
+ vertexai_safety_setting = VertexAISafetySetting(
874
+ category=safety_setting["category"],
875
+ threshold=safety_setting["threshold"],
876
+ )
877
+ vertexai_safety_settings.append(vertexai_safety_setting)
878
+ return vertexai_safety_settings
879
+ else:
880
+ return safety_settings
881
+
882
+ @staticmethod
883
+ def _to_json_or_str(data: str) -> dict | str:
884
+ try:
885
+ json_data = json.loads(data)
886
+ return json_data
887
+ except (json.JSONDecodeError, ValidationError):
888
+ return data
889
+
890
+
891
+ @require_optional_import(["PIL"], "gemini")
892
+ def get_image_data(image_file: str, use_b64=True) -> bytes:
893
+ if image_file.startswith("http://") or image_file.startswith("https://"):
894
+ response = requests.get(image_file)
895
+ content = response.content
896
+ elif re.match(r"data:image/(?:png|jpeg);base64,", image_file):
897
+ return re.sub(r"data:image/(?:png|jpeg);base64,", "", image_file)
898
+ else:
899
+ image = Image.open(image_file).convert("RGB")
900
+ buffered = BytesIO()
901
+ image.save(buffered, format="PNG")
902
+ content = buffered.getvalue()
903
+
904
+ if use_b64:
905
+ return base64.b64encode(content).decode("utf-8")
906
+ else:
907
+ return content
908
+
909
+
910
+ def _format_json_response(response: Any, original_answer: str) -> str:
911
+ """Formats the JSON response for structured outputs using the format method if it exists."""
912
+ return response.format() if isinstance(response, FormatterProtocol) else original_answer
913
+
914
+
915
+ def calculate_gemini_cost(use_vertexai: bool, input_tokens: int, output_tokens: int, model_name: str) -> float:
916
+ def total_cost_mil(cost_per_mil_input: float, cost_per_mil_output: float):
917
+ # Cost per million
918
+ return cost_per_mil_input * input_tokens / 1e6 + cost_per_mil_output * output_tokens / 1e6
919
+
920
+ def total_cost_k(cost_per_k_input: float, cost_per_k_output: float):
921
+ # Cost per thousand
922
+ return cost_per_k_input * input_tokens / 1e3 + cost_per_k_output * output_tokens / 1e3
923
+
924
+ model_name = model_name.lower()
925
+ up_to_128k = input_tokens <= 128000
926
+ up_to_200k = input_tokens <= 200000
927
+
928
+ if use_vertexai:
929
+ # Vertex AI pricing - based on Text input
930
+ # https://cloud.google.com/vertex-ai/generative-ai/pricing#vertex-ai-pricing
931
+
932
+ if (
933
+ model_name == "gemini-2.5-pro"
934
+ or "gemini-2.5-pro-preview-06-05" in model_name
935
+ or "gemini-2.5-pro-preview-05-06" in model_name
936
+ or "gemini-2.5-pro-preview-03-25" in model_name
937
+ ):
938
+ if up_to_200k:
939
+ return total_cost_mil(1.25, 10)
940
+ else:
941
+ return total_cost_mil(2.5, 15)
942
+
943
+ elif "gemini-2.5-flash" in model_name:
944
+ return total_cost_mil(0.3, 2.5)
945
+
946
+ elif "gemini-2.5-flash-preview-04-17" in model_name or "gemini-2.5-flash-preview-05-20" in model_name:
947
+ return total_cost_mil(0.15, 0.6) # NON-THINKING OUTPUT PRICE, $3 FOR THINKING!
948
+
949
+ elif "gemini-2.5-flash-lite-preview-06-17" in model_name:
950
+ return total_cost_mil(0.1, 0.4)
951
+
952
+ elif "gemini-2.0-flash-lite" in model_name:
953
+ return total_cost_mil(0.075, 0.3)
954
+
955
+ elif "gemini-2.0-flash" in model_name:
956
+ return total_cost_mil(0.15, 0.6)
957
+
958
+ elif "gemini-1.5-flash" in model_name:
959
+ if up_to_128k:
960
+ return total_cost_k(0.00001875, 0.000075)
961
+ else:
962
+ return total_cost_k(0.0000375, 0.00015)
963
+
964
+ elif "gemini-1.5-pro" in model_name:
965
+ if up_to_128k:
966
+ return total_cost_k(0.0003125, 0.00125)
967
+ else:
968
+ return total_cost_k(0.000625, 0.0025)
969
+
970
+ elif "gemini-1.0-pro" in model_name:
971
+ return total_cost_k(0.000125, 0.00001875)
972
+
973
+ else:
974
+ warnings.warn(
975
+ f"Cost calculation is not implemented for model {model_name}. Cost will be calculated zero.",
976
+ UserWarning,
977
+ )
978
+ return 0
979
+
980
+ else:
981
+ # Non-Vertex AI pricing
982
+
983
+ if (
984
+ model_name == "gemini-2.5-pro"
985
+ or "gemini-2.5-pro-preview-06-05" in model_name
986
+ or "gemini-2.5-pro-preview-05-06" in model_name
987
+ or "gemini-2.5-pro-preview-03-25" in model_name
988
+ ):
989
+ # https://ai.google.dev/gemini-api/docs/pricing#gemini-2.5-pro-preview
990
+ if up_to_200k:
991
+ return total_cost_mil(1.25, 10)
992
+ else:
993
+ return total_cost_mil(2.5, 15)
994
+
995
+ elif "gemini-2.5-flash" in model_name:
996
+ # https://ai.google.dev/gemini-api/docs/pricing#gemini-2.5-flash
997
+ return total_cost_mil(0.3, 2.5)
998
+
999
+ elif "gemini-2.5-flash-preview-04-17" in model_name or "gemini-2.5-flash-preview-05-20" in model_name:
1000
+ # https://ai.google.dev/gemini-api/docs/pricing#gemini-2.5-flash
1001
+ return total_cost_mil(0.15, 0.6)
1002
+
1003
+ elif "gemini-2.5-flash-lite-preview-06-17" in model_name:
1004
+ # https://ai.google.dev/gemini-api/docs/pricing#gemini-2.5-flash-lite
1005
+ return total_cost_mil(0.1, 0.4)
1006
+
1007
+ elif "gemini-2.0-flash-lite" in model_name:
1008
+ # https://ai.google.dev/gemini-api/docs/pricing#gemini-2.0-flash-lite
1009
+ return total_cost_mil(0.075, 0.3)
1010
+
1011
+ elif "gemini-2.0-flash" in model_name:
1012
+ # https://ai.google.dev/gemini-api/docs/pricing#gemini-2.0-flash
1013
+ return total_cost_mil(0.1, 0.4)
1014
+
1015
+ elif "gemini-1.5-flash-8b" in model_name:
1016
+ # https://ai.google.dev/pricing#1_5flash-8B
1017
+ if up_to_128k:
1018
+ return total_cost_mil(0.0375, 0.15)
1019
+ else:
1020
+ return total_cost_mil(0.075, 0.3)
1021
+
1022
+ elif "gemini-1.5-flash" in model_name:
1023
+ # https://ai.google.dev/pricing#1_5flash
1024
+ if up_to_128k:
1025
+ return total_cost_mil(0.075, 0.3)
1026
+ else:
1027
+ return total_cost_mil(0.15, 0.6)
1028
+
1029
+ elif "gemini-1.5-pro" in model_name:
1030
+ # https://ai.google.dev/pricing#1_5pro
1031
+ if up_to_128k:
1032
+ return total_cost_mil(1.25, 5.0)
1033
+ else:
1034
+ return total_cost_mil(2.50, 10.0)
1035
+
1036
+ elif "gemini-1.0-pro" in model_name:
1037
+ # https://ai.google.dev/pricing#1_5pro
1038
+ return total_cost_mil(0.50, 1.5)
1039
+
1040
+ else:
1041
+ warnings.warn(
1042
+ f"Cost calculation is not implemented for model {model_name}. Cost will be calculated zero.",
1043
+ UserWarning,
1044
+ )
1045
+ return 0