google-adk 0.0.1__py3-none-any.whl → 0.0.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 (312) hide show
  1. google/adk/__init__.py +20 -0
  2. google/adk/agents/__init__.py +32 -0
  3. google/adk/agents/active_streaming_tool.py +38 -0
  4. google/adk/agents/base_agent.py +345 -0
  5. google/adk/agents/callback_context.py +112 -0
  6. google/adk/agents/invocation_context.py +181 -0
  7. google/adk/agents/langgraph_agent.py +140 -0
  8. google/adk/agents/live_request_queue.py +64 -0
  9. google/adk/agents/llm_agent.py +376 -0
  10. google/adk/agents/loop_agent.py +62 -0
  11. google/adk/agents/parallel_agent.py +96 -0
  12. google/adk/agents/readonly_context.py +46 -0
  13. google/adk/agents/remote_agent.py +50 -0
  14. google/adk/agents/run_config.py +87 -0
  15. google/adk/agents/sequential_agent.py +45 -0
  16. google/adk/agents/transcription_entry.py +34 -0
  17. google/adk/artifacts/__init__.py +23 -0
  18. google/adk/artifacts/base_artifact_service.py +128 -0
  19. google/adk/artifacts/gcs_artifact_service.py +195 -0
  20. google/adk/artifacts/in_memory_artifact_service.py +133 -0
  21. google/adk/auth/__init__.py +22 -0
  22. google/adk/auth/auth_credential.py +220 -0
  23. google/adk/auth/auth_handler.py +268 -0
  24. google/adk/auth/auth_preprocessor.py +116 -0
  25. google/adk/auth/auth_schemes.py +67 -0
  26. google/adk/auth/auth_tool.py +55 -0
  27. google/adk/cli/__init__.py +15 -0
  28. google/adk/cli/__main__.py +18 -0
  29. google/adk/cli/agent_graph.py +122 -0
  30. google/adk/cli/browser/adk_favicon.svg +17 -0
  31. google/adk/cli/browser/assets/audio-processor.js +51 -0
  32. google/adk/cli/browser/assets/config/runtime-config.json +3 -0
  33. google/adk/cli/browser/index.html +33 -0
  34. google/adk/cli/browser/main-XUU6OGCC.js +75 -0
  35. google/adk/cli/browser/polyfills-FFHMD2TL.js +18 -0
  36. google/adk/cli/browser/styles-4VDSPQ37.css +17 -0
  37. google/adk/cli/cli.py +181 -0
  38. google/adk/cli/cli_deploy.py +181 -0
  39. google/adk/cli/cli_eval.py +282 -0
  40. google/adk/cli/cli_tools_click.py +479 -0
  41. google/adk/cli/fast_api.py +774 -0
  42. google/adk/cli/media_streamer/__init__.py +19 -0
  43. google/adk/cli/media_streamer/index.html +228 -0
  44. google/adk/cli/utils/__init__.py +49 -0
  45. google/adk/cli/utils/envs.py +57 -0
  46. google/adk/cli/utils/evals.py +93 -0
  47. google/adk/cli/utils/logs.py +72 -0
  48. google/adk/code_executors/__init__.py +49 -0
  49. google/adk/code_executors/base_code_executor.py +97 -0
  50. google/adk/code_executors/code_execution_utils.py +256 -0
  51. google/adk/code_executors/code_executor_context.py +202 -0
  52. google/adk/code_executors/container_code_executor.py +196 -0
  53. google/adk/code_executors/unsafe_local_code_executor.py +71 -0
  54. google/adk/code_executors/vertex_ai_code_executor.py +234 -0
  55. google/adk/evaluation/__init__.py +31 -0
  56. google/adk/evaluation/agent_evaluator.py +329 -0
  57. google/adk/evaluation/evaluation_constants.py +24 -0
  58. google/adk/evaluation/evaluation_generator.py +270 -0
  59. google/adk/evaluation/response_evaluator.py +135 -0
  60. google/adk/evaluation/trajectory_evaluator.py +184 -0
  61. google/adk/events/__init__.py +21 -0
  62. google/adk/events/event.py +130 -0
  63. google/adk/events/event_actions.py +55 -0
  64. google/adk/examples/__init__.py +28 -0
  65. google/adk/examples/base_example_provider.py +35 -0
  66. google/adk/examples/example.py +27 -0
  67. google/adk/examples/example_util.py +123 -0
  68. google/adk/examples/vertex_ai_example_store.py +104 -0
  69. google/adk/flows/__init__.py +14 -0
  70. google/adk/flows/llm_flows/__init__.py +20 -0
  71. google/adk/flows/llm_flows/_base_llm_processor.py +52 -0
  72. google/adk/flows/llm_flows/_code_execution.py +458 -0
  73. google/adk/flows/llm_flows/_nl_planning.py +129 -0
  74. google/adk/flows/llm_flows/agent_transfer.py +132 -0
  75. google/adk/flows/llm_flows/audio_transcriber.py +109 -0
  76. google/adk/flows/llm_flows/auto_flow.py +49 -0
  77. google/adk/flows/llm_flows/base_llm_flow.py +559 -0
  78. google/adk/flows/llm_flows/basic.py +72 -0
  79. google/adk/flows/llm_flows/contents.py +370 -0
  80. google/adk/flows/llm_flows/functions.py +486 -0
  81. google/adk/flows/llm_flows/identity.py +47 -0
  82. google/adk/flows/llm_flows/instructions.py +137 -0
  83. google/adk/flows/llm_flows/single_flow.py +57 -0
  84. google/adk/memory/__init__.py +35 -0
  85. google/adk/memory/base_memory_service.py +74 -0
  86. google/adk/memory/in_memory_memory_service.py +62 -0
  87. google/adk/memory/vertex_ai_rag_memory_service.py +177 -0
  88. google/adk/models/__init__.py +31 -0
  89. google/adk/models/anthropic_llm.py +243 -0
  90. google/adk/models/base_llm.py +87 -0
  91. google/adk/models/base_llm_connection.py +76 -0
  92. google/adk/models/gemini_llm_connection.py +200 -0
  93. google/adk/models/google_llm.py +331 -0
  94. google/adk/models/lite_llm.py +673 -0
  95. google/adk/models/llm_request.py +98 -0
  96. google/adk/models/llm_response.py +111 -0
  97. google/adk/models/registry.py +102 -0
  98. google/adk/planners/__init__.py +23 -0
  99. google/adk/planners/base_planner.py +66 -0
  100. google/adk/planners/built_in_planner.py +75 -0
  101. google/adk/planners/plan_re_act_planner.py +208 -0
  102. google/adk/runners.py +456 -0
  103. google/adk/sessions/__init__.py +41 -0
  104. google/adk/sessions/base_session_service.py +133 -0
  105. google/adk/sessions/database_session_service.py +522 -0
  106. google/adk/sessions/in_memory_session_service.py +206 -0
  107. google/adk/sessions/session.py +54 -0
  108. google/adk/sessions/state.py +71 -0
  109. google/adk/sessions/vertex_ai_session_service.py +356 -0
  110. google/adk/telemetry.py +189 -0
  111. google/adk/tests/__init__.py +14 -0
  112. google/adk/tests/integration/.env.example +10 -0
  113. google/adk/tests/integration/__init__.py +18 -0
  114. google/adk/tests/integration/conftest.py +119 -0
  115. google/adk/tests/integration/fixture/__init__.py +14 -0
  116. google/adk/tests/integration/fixture/agent_with_config/__init__.py +15 -0
  117. google/adk/tests/integration/fixture/agent_with_config/agent.py +88 -0
  118. google/adk/tests/integration/fixture/callback_agent/__init__.py +15 -0
  119. google/adk/tests/integration/fixture/callback_agent/agent.py +105 -0
  120. google/adk/tests/integration/fixture/context_update_test/OWNERS +1 -0
  121. google/adk/tests/integration/fixture/context_update_test/__init__.py +15 -0
  122. google/adk/tests/integration/fixture/context_update_test/agent.py +43 -0
  123. google/adk/tests/integration/fixture/context_update_test/successful_test.session.json +582 -0
  124. google/adk/tests/integration/fixture/context_variable_agent/__init__.py +15 -0
  125. google/adk/tests/integration/fixture/context_variable_agent/agent.py +115 -0
  126. google/adk/tests/integration/fixture/customer_support_ma/__init__.py +15 -0
  127. google/adk/tests/integration/fixture/customer_support_ma/agent.py +172 -0
  128. google/adk/tests/integration/fixture/ecommerce_customer_service_agent/__init__.py +15 -0
  129. google/adk/tests/integration/fixture/ecommerce_customer_service_agent/agent.py +338 -0
  130. google/adk/tests/integration/fixture/ecommerce_customer_service_agent/order_query.test.json +69 -0
  131. google/adk/tests/integration/fixture/ecommerce_customer_service_agent/test_config.json +6 -0
  132. google/adk/tests/integration/fixture/flow_complex_spark/__init__.py +15 -0
  133. google/adk/tests/integration/fixture/flow_complex_spark/agent.py +182 -0
  134. google/adk/tests/integration/fixture/flow_complex_spark/sample.debug.log +243 -0
  135. google/adk/tests/integration/fixture/flow_complex_spark/sample.session.json +190 -0
  136. google/adk/tests/integration/fixture/hello_world_agent/__init__.py +15 -0
  137. google/adk/tests/integration/fixture/hello_world_agent/agent.py +95 -0
  138. google/adk/tests/integration/fixture/hello_world_agent/roll_die.test.json +24 -0
  139. google/adk/tests/integration/fixture/hello_world_agent/test_config.json +6 -0
  140. google/adk/tests/integration/fixture/home_automation_agent/__init__.py +15 -0
  141. google/adk/tests/integration/fixture/home_automation_agent/agent.py +304 -0
  142. google/adk/tests/integration/fixture/home_automation_agent/simple_test.test.json +5 -0
  143. google/adk/tests/integration/fixture/home_automation_agent/simple_test2.test.json +5 -0
  144. google/adk/tests/integration/fixture/home_automation_agent/test_config.json +5 -0
  145. google/adk/tests/integration/fixture/home_automation_agent/test_files/dependent_tool_calls.test.json +18 -0
  146. google/adk/tests/integration/fixture/home_automation_agent/test_files/memorizing_past_events/eval_data.test.json +17 -0
  147. google/adk/tests/integration/fixture/home_automation_agent/test_files/memorizing_past_events/test_config.json +6 -0
  148. google/adk/tests/integration/fixture/home_automation_agent/test_files/simple_multi_turn_conversation.test.json +18 -0
  149. google/adk/tests/integration/fixture/home_automation_agent/test_files/simple_test.test.json +17 -0
  150. google/adk/tests/integration/fixture/home_automation_agent/test_files/simple_test2.test.json +5 -0
  151. google/adk/tests/integration/fixture/home_automation_agent/test_files/test_config.json +5 -0
  152. google/adk/tests/integration/fixture/tool_agent/__init__.py +15 -0
  153. google/adk/tests/integration/fixture/tool_agent/agent.py +218 -0
  154. google/adk/tests/integration/fixture/tool_agent/files/Agent_test_plan.pdf +0 -0
  155. google/adk/tests/integration/fixture/trip_planner_agent/__init__.py +15 -0
  156. google/adk/tests/integration/fixture/trip_planner_agent/agent.py +110 -0
  157. google/adk/tests/integration/fixture/trip_planner_agent/initial.session.json +13 -0
  158. google/adk/tests/integration/fixture/trip_planner_agent/test_config.json +5 -0
  159. google/adk/tests/integration/fixture/trip_planner_agent/test_files/initial.session.json +13 -0
  160. google/adk/tests/integration/fixture/trip_planner_agent/test_files/test_config.json +5 -0
  161. google/adk/tests/integration/fixture/trip_planner_agent/test_files/trip_inquiry_sub_agent.test.json +7 -0
  162. google/adk/tests/integration/fixture/trip_planner_agent/trip_inquiry.test.json +19 -0
  163. google/adk/tests/integration/models/__init__.py +14 -0
  164. google/adk/tests/integration/models/test_google_llm.py +65 -0
  165. google/adk/tests/integration/test_callback.py +70 -0
  166. google/adk/tests/integration/test_context_variable.py +67 -0
  167. google/adk/tests/integration/test_evalute_agent_in_fixture.py +76 -0
  168. google/adk/tests/integration/test_multi_agent.py +28 -0
  169. google/adk/tests/integration/test_multi_turn.py +42 -0
  170. google/adk/tests/integration/test_single_agent.py +23 -0
  171. google/adk/tests/integration/test_sub_agent.py +26 -0
  172. google/adk/tests/integration/test_system_instruction.py +177 -0
  173. google/adk/tests/integration/test_tools.py +287 -0
  174. google/adk/tests/integration/test_with_test_file.py +34 -0
  175. google/adk/tests/integration/tools/__init__.py +14 -0
  176. google/adk/tests/integration/utils/__init__.py +16 -0
  177. google/adk/tests/integration/utils/asserts.py +75 -0
  178. google/adk/tests/integration/utils/test_runner.py +97 -0
  179. google/adk/tests/unittests/__init__.py +14 -0
  180. google/adk/tests/unittests/agents/__init__.py +14 -0
  181. google/adk/tests/unittests/agents/test_base_agent.py +407 -0
  182. google/adk/tests/unittests/agents/test_langgraph_agent.py +191 -0
  183. google/adk/tests/unittests/agents/test_llm_agent_callbacks.py +138 -0
  184. google/adk/tests/unittests/agents/test_llm_agent_fields.py +231 -0
  185. google/adk/tests/unittests/agents/test_loop_agent.py +136 -0
  186. google/adk/tests/unittests/agents/test_parallel_agent.py +92 -0
  187. google/adk/tests/unittests/agents/test_sequential_agent.py +114 -0
  188. google/adk/tests/unittests/artifacts/__init__.py +14 -0
  189. google/adk/tests/unittests/artifacts/test_artifact_service.py +276 -0
  190. google/adk/tests/unittests/auth/test_auth_handler.py +575 -0
  191. google/adk/tests/unittests/conftest.py +73 -0
  192. google/adk/tests/unittests/fast_api/__init__.py +14 -0
  193. google/adk/tests/unittests/fast_api/test_fast_api.py +269 -0
  194. google/adk/tests/unittests/flows/__init__.py +14 -0
  195. google/adk/tests/unittests/flows/llm_flows/__init__.py +14 -0
  196. google/adk/tests/unittests/flows/llm_flows/_test_examples.py +142 -0
  197. google/adk/tests/unittests/flows/llm_flows/test_agent_transfer.py +311 -0
  198. google/adk/tests/unittests/flows/llm_flows/test_functions_long_running.py +244 -0
  199. google/adk/tests/unittests/flows/llm_flows/test_functions_request_euc.py +346 -0
  200. google/adk/tests/unittests/flows/llm_flows/test_functions_sequential.py +93 -0
  201. google/adk/tests/unittests/flows/llm_flows/test_functions_simple.py +258 -0
  202. google/adk/tests/unittests/flows/llm_flows/test_identity.py +66 -0
  203. google/adk/tests/unittests/flows/llm_flows/test_instructions.py +164 -0
  204. google/adk/tests/unittests/flows/llm_flows/test_model_callbacks.py +142 -0
  205. google/adk/tests/unittests/flows/llm_flows/test_other_configs.py +46 -0
  206. google/adk/tests/unittests/flows/llm_flows/test_tool_callbacks.py +269 -0
  207. google/adk/tests/unittests/models/__init__.py +14 -0
  208. google/adk/tests/unittests/models/test_google_llm.py +224 -0
  209. google/adk/tests/unittests/models/test_litellm.py +804 -0
  210. google/adk/tests/unittests/models/test_models.py +60 -0
  211. google/adk/tests/unittests/sessions/__init__.py +14 -0
  212. google/adk/tests/unittests/sessions/test_session_service.py +227 -0
  213. google/adk/tests/unittests/sessions/test_vertex_ai_session_service.py +246 -0
  214. google/adk/tests/unittests/streaming/__init__.py +14 -0
  215. google/adk/tests/unittests/streaming/test_streaming.py +50 -0
  216. google/adk/tests/unittests/tools/__init__.py +14 -0
  217. google/adk/tests/unittests/tools/apihub_tool/clients/test_apihub_client.py +499 -0
  218. google/adk/tests/unittests/tools/apihub_tool/test_apihub_toolset.py +204 -0
  219. google/adk/tests/unittests/tools/application_integration_tool/clients/test_connections_client.py +600 -0
  220. google/adk/tests/unittests/tools/application_integration_tool/clients/test_integration_client.py +630 -0
  221. google/adk/tests/unittests/tools/application_integration_tool/test_application_integration_toolset.py +345 -0
  222. google/adk/tests/unittests/tools/google_api_tool/__init__.py +13 -0
  223. google/adk/tests/unittests/tools/google_api_tool/test_googleapi_to_openapi_converter.py +657 -0
  224. google/adk/tests/unittests/tools/openapi_tool/auth/credential_exchangers/test_auto_auth_credential_exchanger.py +145 -0
  225. google/adk/tests/unittests/tools/openapi_tool/auth/credential_exchangers/test_base_auth_credential_exchanger.py +68 -0
  226. google/adk/tests/unittests/tools/openapi_tool/auth/credential_exchangers/test_oauth2_exchanger.py +153 -0
  227. google/adk/tests/unittests/tools/openapi_tool/auth/credential_exchangers/test_service_account_exchanger.py +196 -0
  228. google/adk/tests/unittests/tools/openapi_tool/auth/test_auth_helper.py +573 -0
  229. google/adk/tests/unittests/tools/openapi_tool/common/test_common.py +436 -0
  230. google/adk/tests/unittests/tools/openapi_tool/openapi_spec_parser/test.yaml +1367 -0
  231. google/adk/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_openapi_spec_parser.py +628 -0
  232. google/adk/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_openapi_toolset.py +139 -0
  233. google/adk/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_operation_parser.py +406 -0
  234. google/adk/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_rest_api_tool.py +966 -0
  235. google/adk/tests/unittests/tools/openapi_tool/openapi_spec_parser/test_tool_auth_handler.py +201 -0
  236. google/adk/tests/unittests/tools/retrieval/__init__.py +14 -0
  237. google/adk/tests/unittests/tools/retrieval/test_vertex_ai_rag_retrieval.py +147 -0
  238. google/adk/tests/unittests/tools/test_agent_tool.py +167 -0
  239. google/adk/tests/unittests/tools/test_base_tool.py +141 -0
  240. google/adk/tests/unittests/tools/test_build_function_declaration.py +277 -0
  241. google/adk/tests/unittests/utils.py +304 -0
  242. google/adk/tools/__init__.py +51 -0
  243. google/adk/tools/_automatic_function_calling_util.py +346 -0
  244. google/adk/tools/agent_tool.py +176 -0
  245. google/adk/tools/apihub_tool/__init__.py +19 -0
  246. google/adk/tools/apihub_tool/apihub_toolset.py +209 -0
  247. google/adk/tools/apihub_tool/clients/__init__.py +13 -0
  248. google/adk/tools/apihub_tool/clients/apihub_client.py +332 -0
  249. google/adk/tools/apihub_tool/clients/secret_client.py +115 -0
  250. google/adk/tools/application_integration_tool/__init__.py +19 -0
  251. google/adk/tools/application_integration_tool/application_integration_toolset.py +230 -0
  252. google/adk/tools/application_integration_tool/clients/connections_client.py +903 -0
  253. google/adk/tools/application_integration_tool/clients/integration_client.py +253 -0
  254. google/adk/tools/base_tool.py +144 -0
  255. google/adk/tools/built_in_code_execution_tool.py +59 -0
  256. google/adk/tools/crewai_tool.py +72 -0
  257. google/adk/tools/example_tool.py +62 -0
  258. google/adk/tools/exit_loop_tool.py +23 -0
  259. google/adk/tools/function_parameter_parse_util.py +307 -0
  260. google/adk/tools/function_tool.py +87 -0
  261. google/adk/tools/get_user_choice_tool.py +28 -0
  262. google/adk/tools/google_api_tool/__init__.py +14 -0
  263. google/adk/tools/google_api_tool/google_api_tool.py +59 -0
  264. google/adk/tools/google_api_tool/google_api_tool_set.py +107 -0
  265. google/adk/tools/google_api_tool/google_api_tool_sets.py +55 -0
  266. google/adk/tools/google_api_tool/googleapi_to_openapi_converter.py +521 -0
  267. google/adk/tools/google_search_tool.py +68 -0
  268. google/adk/tools/langchain_tool.py +86 -0
  269. google/adk/tools/load_artifacts_tool.py +113 -0
  270. google/adk/tools/load_memory_tool.py +58 -0
  271. google/adk/tools/load_web_page.py +41 -0
  272. google/adk/tools/long_running_tool.py +39 -0
  273. google/adk/tools/mcp_tool/__init__.py +42 -0
  274. google/adk/tools/mcp_tool/conversion_utils.py +161 -0
  275. google/adk/tools/mcp_tool/mcp_tool.py +113 -0
  276. google/adk/tools/mcp_tool/mcp_toolset.py +272 -0
  277. google/adk/tools/openapi_tool/__init__.py +21 -0
  278. google/adk/tools/openapi_tool/auth/__init__.py +19 -0
  279. google/adk/tools/openapi_tool/auth/auth_helpers.py +498 -0
  280. google/adk/tools/openapi_tool/auth/credential_exchangers/__init__.py +25 -0
  281. google/adk/tools/openapi_tool/auth/credential_exchangers/auto_auth_credential_exchanger.py +105 -0
  282. google/adk/tools/openapi_tool/auth/credential_exchangers/base_credential_exchanger.py +55 -0
  283. google/adk/tools/openapi_tool/auth/credential_exchangers/oauth2_exchanger.py +117 -0
  284. google/adk/tools/openapi_tool/auth/credential_exchangers/service_account_exchanger.py +97 -0
  285. google/adk/tools/openapi_tool/common/__init__.py +19 -0
  286. google/adk/tools/openapi_tool/common/common.py +300 -0
  287. google/adk/tools/openapi_tool/openapi_spec_parser/__init__.py +32 -0
  288. google/adk/tools/openapi_tool/openapi_spec_parser/openapi_spec_parser.py +231 -0
  289. google/adk/tools/openapi_tool/openapi_spec_parser/openapi_toolset.py +144 -0
  290. google/adk/tools/openapi_tool/openapi_spec_parser/operation_parser.py +260 -0
  291. google/adk/tools/openapi_tool/openapi_spec_parser/rest_api_tool.py +496 -0
  292. google/adk/tools/openapi_tool/openapi_spec_parser/tool_auth_handler.py +268 -0
  293. google/adk/tools/preload_memory_tool.py +72 -0
  294. google/adk/tools/retrieval/__init__.py +36 -0
  295. google/adk/tools/retrieval/base_retrieval_tool.py +37 -0
  296. google/adk/tools/retrieval/files_retrieval.py +33 -0
  297. google/adk/tools/retrieval/llama_index_retrieval.py +41 -0
  298. google/adk/tools/retrieval/vertex_ai_rag_retrieval.py +107 -0
  299. google/adk/tools/tool_context.py +90 -0
  300. google/adk/tools/toolbox_tool.py +46 -0
  301. google/adk/tools/transfer_to_agent_tool.py +21 -0
  302. google/adk/tools/vertex_ai_search_tool.py +96 -0
  303. google/adk/version.py +16 -0
  304. google_adk-0.0.1.dist-info/LICENSE.txt → google_adk-0.0.2.dist-info/LICENSE +32 -0
  305. google_adk-0.0.2.dist-info/METADATA +73 -0
  306. google_adk-0.0.2.dist-info/RECORD +308 -0
  307. {google_adk-0.0.1.dist-info → google_adk-0.0.2.dist-info}/WHEEL +1 -2
  308. google_adk-0.0.2.dist-info/entry_points.txt +3 -0
  309. agent_kit/__init__.py +0 -0
  310. google_adk-0.0.1.dist-info/METADATA +0 -15
  311. google_adk-0.0.1.dist-info/RECORD +0 -6
  312. google_adk-0.0.1.dist-info/top_level.txt +0 -1
@@ -0,0 +1,256 @@
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
+ """Utility functions for code execution."""
16
+
17
+ import base64
18
+ import binascii
19
+ import copy
20
+ import dataclasses
21
+ import re
22
+ from typing import List, Optional
23
+
24
+ from google.genai import types
25
+
26
+
27
+ @dataclasses.dataclass(frozen=True)
28
+ class File:
29
+ """A structure that contains a file name and its content."""
30
+
31
+ name: str
32
+ """
33
+ The name of the file with file extension (e.g., "file.csv").
34
+ """
35
+
36
+ content: str
37
+ """
38
+ The base64-encoded bytes of the file content.
39
+ """
40
+
41
+ mime_type: str = 'text/plain'
42
+ """
43
+ The mime type of the file (e.g., "image/png").
44
+ """
45
+
46
+
47
+ @dataclasses.dataclass
48
+ class CodeExecutionInput:
49
+ """A structure that contains the input of code execution."""
50
+
51
+ code: str
52
+ """
53
+ The code to execute.
54
+ """
55
+
56
+ input_files: list[File] = dataclasses.field(default_factory=list)
57
+ """
58
+ The input files available to the code.
59
+ """
60
+
61
+ execution_id: Optional[str] = None
62
+ """
63
+ The execution ID for the stateful code execution.
64
+ """
65
+
66
+
67
+ @dataclasses.dataclass
68
+ class CodeExecutionResult:
69
+ """A structure that contains the result of code execution."""
70
+
71
+ stdout: str = ''
72
+ """
73
+ The standard output of the code execution.
74
+ """
75
+
76
+ stderr: str = ''
77
+ """
78
+ The standard error of the code execution.
79
+ """
80
+
81
+ output_files: list[File] = dataclasses.field(default_factory=list)
82
+ """
83
+ The output files from the code execution.
84
+ """
85
+
86
+
87
+ class CodeExecutionUtils:
88
+ """Utility functions for code execution."""
89
+
90
+ @staticmethod
91
+ def get_encoded_file_content(data: bytes) -> bytes:
92
+ """Gets the file content as a base64-encoded bytes.
93
+
94
+ Args:
95
+ data: The file content bytes.
96
+
97
+ Returns:
98
+ The file content as a base64-encoded bytes.
99
+ """
100
+
101
+ def _is_base64_encoded(data: bytes) -> bool:
102
+ try:
103
+ return base64.b64encode(base64.b64decode(data)) == data
104
+ except binascii.Error:
105
+ return False
106
+
107
+ return data if _is_base64_encoded(data) else base64.b64encode(data)
108
+
109
+ @staticmethod
110
+ def extract_code_and_truncate_content(
111
+ content: types.Content,
112
+ code_block_delimiters: List[tuple[str, str]],
113
+ ) -> Optional[str]:
114
+ """Extracts the first code block from the content and truncate everything after it.
115
+
116
+ Args:
117
+ content: The mutable content to extract the code from.
118
+ code_block_delimiters: The list of the enclosing delimiters to identify
119
+ the code blocks.
120
+
121
+ Returns:
122
+ The first code block if found, otherwise None.
123
+ """
124
+ if not content or not content.parts:
125
+ return
126
+
127
+ # Extract the code from the executable code parts if there're no associated
128
+ # code execution result parts.
129
+ for idx, part in enumerate(content.parts):
130
+ if part.executable_code and (
131
+ idx == len(content.parts) - 1
132
+ or not content.parts[idx + 1].code_execution_result
133
+ ):
134
+ content.parts = content.parts[: idx + 1]
135
+ return part.executable_code.code
136
+
137
+ # Extract the code from the text parts.
138
+ text_parts = [p for p in content.parts if p.text]
139
+ if not text_parts:
140
+ return
141
+
142
+ first_text_part = copy.deepcopy(text_parts[0])
143
+ response_text = '\n'.join([p.text for p in text_parts])
144
+
145
+ # Find the first code block.
146
+ leading_delimiter_pattern = '|'.join(d[0] for d in code_block_delimiters)
147
+ trailing_delimiter_pattern = '|'.join(d[1] for d in code_block_delimiters)
148
+ pattern = re.compile(
149
+ (
150
+ rf'(?P<prefix>.*?)({leading_delimiter_pattern})(?P<code>.*?)({trailing_delimiter_pattern})(?P<suffix>.*?)$'
151
+ ).encode(),
152
+ re.DOTALL,
153
+ )
154
+ pattern_match = pattern.search(response_text.encode())
155
+ if pattern_match is None:
156
+ return
157
+
158
+ code_str = pattern_match.group('code').decode()
159
+ if not code_str:
160
+ return
161
+
162
+ content.parts = []
163
+ if pattern_match.group('prefix'):
164
+ first_text_part.text = pattern_match.group('prefix').decode()
165
+ content.parts.append(first_text_part)
166
+ content.parts.append(
167
+ CodeExecutionUtils.build_executable_code_part(code_str)
168
+ )
169
+ return pattern_match.group('code').decode()
170
+
171
+ @staticmethod
172
+ def build_executable_code_part(code: str) -> types.Part:
173
+ """Builds an executable code part with code string.
174
+
175
+ Args:
176
+ code: The code string.
177
+
178
+ Returns:
179
+ The constructed executable code part.
180
+ """
181
+ return types.Part.from_executable_code(
182
+ code=code,
183
+ language='PYTHON',
184
+ )
185
+
186
+ @staticmethod
187
+ def build_code_execution_result_part(
188
+ code_execution_result: CodeExecutionResult,
189
+ ) -> types.Part:
190
+ """Builds the code execution result part from the code execution result.
191
+
192
+ Args:
193
+ code_execution_result: The code execution result.
194
+
195
+ Returns:
196
+ The constructed code execution result part.
197
+ """
198
+ if code_execution_result.stderr:
199
+ return types.Part.from_code_execution_result(
200
+ outcome='OUTCOME_FAILED',
201
+ output=code_execution_result.stderr,
202
+ )
203
+ final_result = []
204
+ if code_execution_result.stdout or not code_execution_result.output_files:
205
+ final_result.append(
206
+ 'Code execution result:\n' + '%s\n' % code_execution_result.stdout
207
+ )
208
+ if code_execution_result.output_files:
209
+ final_result.append(
210
+ 'Saved artifacts:\n'
211
+ + ','.join(
212
+ ['`%s`' % f.name for f in code_execution_result.output_files]
213
+ )
214
+ )
215
+ return types.Part.from_code_execution_result(
216
+ outcome='OUTCOME_OK',
217
+ output='\n\n'.join(final_result),
218
+ )
219
+
220
+ @staticmethod
221
+ def convert_code_execution_parts(
222
+ content: types.Content,
223
+ code_block_delimiter: tuple[str, str],
224
+ execution_result_delimiters: tuple[str, str],
225
+ ):
226
+ """Converts the code execution parts to text parts in a Content.
227
+
228
+ Args:
229
+ content: The mutable content to convert the code execution parts to text
230
+ parts.
231
+ code_block_delimiter: The delimiter to format the code block.
232
+ execution_result_delimiters: The delimiter to format the code execution
233
+ result.
234
+ """
235
+ if not content.parts:
236
+ return
237
+
238
+ # Handle the conversion of trailing executable code parts.
239
+ if content.parts[-1].executable_code:
240
+ content.parts[-1] = types.Part(
241
+ text=(
242
+ code_block_delimiter[0]
243
+ + content.parts[-1].executable_code.code
244
+ + code_block_delimiter[1]
245
+ )
246
+ )
247
+ # Handle the conversion of trailing code execution result parts.
248
+ # Skip if the Content has multiple parts, which means the Content is
249
+ # likely generated by the model.
250
+ elif len(content.parts) == 1 and content.parts[-1].code_execution_result:
251
+ content.parts[-1] = types.Part(
252
+ text=execution_result_delimiters[0]
253
+ + content.parts[-1].code_execution_result.output
254
+ + execution_result_delimiters[1]
255
+ )
256
+ content.role = 'user'
@@ -0,0 +1,202 @@
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
+ """The persistent context used to configure the code executor."""
16
+
17
+ import copy
18
+ import dataclasses
19
+ import datetime
20
+ from typing import Any
21
+ from typing import Optional
22
+
23
+ from ..sessions.state import State
24
+ from .code_execution_utils import File
25
+
26
+ _CONTEXT_KEY = '_code_execution_context'
27
+ _SESSION_ID_KEY = 'execution_session_id'
28
+ _PROCESSED_FILE_NAMES_KEY = 'processed_input_files'
29
+ _INPUT_FILE_KEY = '_code_executor_input_files'
30
+ _ERROR_COUNT_KEY = '_code_executor_error_counts'
31
+
32
+ _CODE_EXECUTION_RESULTS_KEY = '_code_execution_results'
33
+
34
+
35
+ class CodeExecutorContext:
36
+ """The persistent context used to configure the code executor."""
37
+
38
+ _context: dict[str, Any]
39
+
40
+ def __init__(self, session_state: State):
41
+ """Initializes the code executor context.
42
+
43
+ Args:
44
+ session_state: The session state to get the code executor context from.
45
+ """
46
+ self._context = self._get_code_executor_context(session_state)
47
+ self._session_state = session_state
48
+
49
+ def get_state_delta(self) -> dict[str, Any]:
50
+ """Gets the state delta to update in the persistent session state.
51
+
52
+ Returns:
53
+ The state delta to update in the persistent session state.
54
+ """
55
+ context_to_update = copy.deepcopy(self._context)
56
+ return {_CONTEXT_KEY: context_to_update}
57
+
58
+ def get_execution_id(self) -> Optional[str]:
59
+ """Gets the session ID for the code executor.
60
+
61
+ Returns:
62
+ The session ID for the code executor context.
63
+ """
64
+ if _SESSION_ID_KEY not in self._context:
65
+ return None
66
+ return self._context[_SESSION_ID_KEY]
67
+
68
+ def set_execution_id(self, session_id: str):
69
+ """Sets the session ID for the code executor.
70
+
71
+ Args:
72
+ session_id: The session ID for the code executor.
73
+ """
74
+ self._context[_SESSION_ID_KEY] = session_id
75
+
76
+ def get_processed_file_names(self) -> list[str]:
77
+ """Gets the processed file names from the session state.
78
+
79
+ Returns:
80
+ A list of processed file names in the code executor context.
81
+ """
82
+ if _PROCESSED_FILE_NAMES_KEY not in self._context:
83
+ return []
84
+ return self._context[_PROCESSED_FILE_NAMES_KEY]
85
+
86
+ def add_processed_file_names(self, file_names: [str]):
87
+ """Adds the processed file name to the session state.
88
+
89
+ Args:
90
+ file_names: The processed file names to add to the session state.
91
+ """
92
+ if _PROCESSED_FILE_NAMES_KEY not in self._context:
93
+ self._context[_PROCESSED_FILE_NAMES_KEY] = []
94
+ self._context[_PROCESSED_FILE_NAMES_KEY].extend(file_names)
95
+
96
+ def get_input_files(self) -> list[File]:
97
+ """Gets the code executor input file names from the session state.
98
+
99
+ Returns:
100
+ A list of input files in the code executor context.
101
+ """
102
+ if _INPUT_FILE_KEY not in self._session_state:
103
+ return []
104
+ return [File(**file) for file in self._session_state[_INPUT_FILE_KEY]]
105
+
106
+ def add_input_files(
107
+ self,
108
+ input_files: list[File],
109
+ ):
110
+ """Adds the input files to the code executor context.
111
+
112
+ Args:
113
+ input_files: The input files to add to the code executor context.
114
+ """
115
+ if _INPUT_FILE_KEY not in self._session_state:
116
+ self._session_state[_INPUT_FILE_KEY] = []
117
+ for input_file in input_files:
118
+ self._session_state[_INPUT_FILE_KEY].append(
119
+ dataclasses.asdict(input_file)
120
+ )
121
+
122
+ def clear_input_files(self):
123
+ """Removes the input files and processed file names to the code executor context."""
124
+ if _INPUT_FILE_KEY in self._session_state:
125
+ self._session_state[_INPUT_FILE_KEY] = []
126
+ if _PROCESSED_FILE_NAMES_KEY in self._context:
127
+ self._context[_PROCESSED_FILE_NAMES_KEY] = []
128
+
129
+ def get_error_count(self, invocation_id: str) -> int:
130
+ """Gets the error count from the session state.
131
+
132
+ Args:
133
+ invocation_id: The invocation ID to get the error count for.
134
+
135
+ Returns:
136
+ The error count for the given invocation ID.
137
+ """
138
+ if _ERROR_COUNT_KEY not in self._session_state:
139
+ return 0
140
+ return self._session_state[_ERROR_COUNT_KEY].get(invocation_id, 0)
141
+
142
+ def increment_error_count(self, invocation_id: str):
143
+ """Increments the error count from the session state.
144
+
145
+ Args:
146
+ invocation_id: The invocation ID to increment the error count for.
147
+ """
148
+ if _ERROR_COUNT_KEY not in self._session_state:
149
+ self._session_state[_ERROR_COUNT_KEY] = {}
150
+ self._session_state[_ERROR_COUNT_KEY][invocation_id] = (
151
+ self.get_error_count(invocation_id) + 1
152
+ )
153
+
154
+ def reset_error_count(self, invocation_id: str):
155
+ """Resets the error count from the session state.
156
+
157
+ Args:
158
+ invocation_id: The invocation ID to reset the error count for.
159
+ """
160
+ if _ERROR_COUNT_KEY not in self._session_state:
161
+ return
162
+ if invocation_id in self._session_state[_ERROR_COUNT_KEY]:
163
+ del self._session_state[_ERROR_COUNT_KEY][invocation_id]
164
+
165
+ def update_code_execution_result(
166
+ self,
167
+ invocation_id: str,
168
+ code: str,
169
+ result_stdout: str,
170
+ result_stderr: str,
171
+ ):
172
+ """Updates the code execution result.
173
+
174
+ Args:
175
+ invocation_id: The invocation ID to update the code execution result for.
176
+ code: The code to execute.
177
+ result_stdout: The standard output of the code execution.
178
+ result_stderr: The standard error of the code execution.
179
+ """
180
+ if _CODE_EXECUTION_RESULTS_KEY not in self._session_state:
181
+ self._session_state[_CODE_EXECUTION_RESULTS_KEY] = {}
182
+ if invocation_id not in self._session_state[_CODE_EXECUTION_RESULTS_KEY]:
183
+ self._session_state[_CODE_EXECUTION_RESULTS_KEY][invocation_id] = []
184
+ self._session_state[_CODE_EXECUTION_RESULTS_KEY][invocation_id].append({
185
+ 'code': code,
186
+ 'result_stdout': result_stdout,
187
+ 'result_stderr': result_stderr,
188
+ 'timestamp': int(datetime.datetime.now().timestamp()),
189
+ })
190
+
191
+ def _get_code_executor_context(self, session_state: State) -> dict[str, Any]:
192
+ """Gets the code executor context from the session state.
193
+
194
+ Args:
195
+ session_state: The session state to get the code executor context from.
196
+
197
+ Returns:
198
+ A dict of code executor context.
199
+ """
200
+ if _CONTEXT_KEY not in session_state:
201
+ session_state[_CONTEXT_KEY] = {}
202
+ return session_state[_CONTEXT_KEY]
@@ -0,0 +1,196 @@
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
+ import atexit
16
+ import os
17
+ from typing import Optional
18
+
19
+ import docker
20
+ from docker.client import DockerClient
21
+ from docker.models.containers import Container
22
+ from pydantic import Field
23
+ from typing_extensions import override
24
+
25
+ from ..agents.invocation_context import InvocationContext
26
+ from .base_code_executor import BaseCodeExecutor
27
+ from .code_execution_utils import CodeExecutionInput
28
+ from .code_execution_utils import CodeExecutionResult
29
+
30
+
31
+ DEFAULT_IMAGE_TAG = 'adk-code-executor:latest'
32
+
33
+
34
+ class ContainerCodeExecutor(BaseCodeExecutor):
35
+ """A code executor that uses a custom container to execute code.
36
+
37
+ Attributes:
38
+ base_url: Optional. The base url of the user hosted Docker client.
39
+ image: The tag of the predefined image or custom image to run on the
40
+ container. Either docker_path or image must be set.
41
+ docker_path: The path to the directory containing the Dockerfile. If set,
42
+ build the image from the dockerfile path instead of using the predefined
43
+ image. Either docker_path or image must be set.
44
+ """
45
+
46
+ base_url: Optional[str] = None
47
+ """
48
+ Optional. The base url of the user hosted Docker client.
49
+ """
50
+
51
+ image: str = None
52
+ """
53
+ The tag of the predefined image or custom image to run on the container.
54
+ Either docker_path or image must be set.
55
+ """
56
+
57
+ docker_path: str = None
58
+ """
59
+ The path to the directory containing the Dockerfile.
60
+ If set, build the image from the dockerfile path instead of using the
61
+ predefined image. Either docker_path or image must be set.
62
+ """
63
+
64
+ # Overrides the BaseCodeExecutor attribute: this executor cannot be stateful.
65
+ stateful: bool = Field(default=False, frozen=True, exclude=True)
66
+
67
+ # Overrides the BaseCodeExecutor attribute: this executor cannot
68
+ # optimize_data_file.
69
+ optimize_data_file: bool = Field(default=False, frozen=True, exclude=True)
70
+
71
+ _client: DockerClient = None
72
+ _container: Container = None
73
+
74
+ def __init__(
75
+ self,
76
+ base_url: Optional[str] = None,
77
+ image: Optional[str] = None,
78
+ docker_path: Optional[str] = None,
79
+ **data,
80
+ ):
81
+ """Initializes the ContainerCodeExecutor.
82
+
83
+ Args:
84
+ base_url: Optional. The base url of the user hosted Docker client.
85
+ image: The tag of the predefined image or custom image to run on the
86
+ container. Either docker_path or image must be set.
87
+ docker_path: The path to the directory containing the Dockerfile. If set,
88
+ build the image from the dockerfile path instead of using the predefined
89
+ image. Either docker_path or image must be set.
90
+ **data: The data to initialize the ContainerCodeExecutor.
91
+ """
92
+ if not image and not docker_path:
93
+ raise ValueError(
94
+ 'Either image or docker_path must be set for ContainerCodeExecutor.'
95
+ )
96
+ if 'stateful' in data and data['stateful']:
97
+ raise ValueError('Cannot set `stateful=True` in ContainerCodeExecutor.')
98
+ if 'optimize_data_file' in data and data['optimize_data_file']:
99
+ raise ValueError(
100
+ 'Cannot set `optimize_data_file=True` in ContainerCodeExecutor.'
101
+ )
102
+
103
+ super().__init__(**data)
104
+ self.base_url = base_url
105
+ self.image = image if image else DEFAULT_IMAGE_TAG
106
+ self.docker_path = os.path.abspath(docker_path) if docker_path else None
107
+
108
+ self._client = (
109
+ docker.from_env()
110
+ if not self.base_url
111
+ else docker.DockerClient(base_url=self.base_url)
112
+ )
113
+ # Initialize the container.
114
+ self.__init_container()
115
+
116
+ # Close the container when the on exit.
117
+ atexit.register(self.__cleanup_container)
118
+
119
+ @override
120
+ def execute_code(
121
+ self,
122
+ invocation_context: InvocationContext,
123
+ code_execution_input: CodeExecutionInput,
124
+ ) -> CodeExecutionResult:
125
+ output = ''
126
+ error = ''
127
+ exec_result = self._container.exec_run(
128
+ ['python3', '-c', code_execution_input.code],
129
+ demux=True,
130
+ )
131
+
132
+ if exec_result.output and exec_result.output[0]:
133
+ output = exec_result.output[0].decode('utf-8')
134
+ if (
135
+ exec_result.output
136
+ and len(exec_result.output) > 1
137
+ and exec_result.output[1]
138
+ ):
139
+ error = exec_result.output[1].decode('utf-8')
140
+
141
+ # Collect the final result.
142
+ return CodeExecutionResult(
143
+ stdout=output,
144
+ stderr=error,
145
+ output_files=[],
146
+ )
147
+
148
+ def _build_docker_image(self):
149
+ """Builds the Docker image."""
150
+ if not self.docker_path:
151
+ raise ValueError('Docker path is not set.')
152
+ if not os.path.exists(self.docker_path):
153
+ raise FileNotFoundError(f'Invalid Docker path: {self.docker_path}')
154
+
155
+ print('Building Docker image...')
156
+ self._client.images.build(
157
+ path=self.docker_path,
158
+ tag=self.image,
159
+ rm=True,
160
+ )
161
+ print(f'Docker image: {self.image} built.')
162
+
163
+ def _verify_python_installation(self):
164
+ """Verifies the container has python3 installed."""
165
+ exec_result = self._container.exec_run(['which', 'python3'])
166
+ if exec_result.exit_code != 0:
167
+ raise ValueError('python3 is not installed in the container.')
168
+
169
+ def __init_container(self):
170
+ """Initializes the container."""
171
+ if not self._client:
172
+ raise RuntimeError('Docker client is not initialized.')
173
+
174
+ if self.docker_path:
175
+ self._build_docker_image()
176
+
177
+ print('Starting container for ContainerCodeExecutor...')
178
+ self._container = self._client.containers.run(
179
+ image=self.image,
180
+ detach=True,
181
+ tty=True,
182
+ )
183
+ print(f'Container {self._container.id} started.')
184
+
185
+ # Verify the container is able to run python3.
186
+ self._verify_python_installation()
187
+
188
+ def __cleanup_container(self):
189
+ """Closes the container on exit."""
190
+ if not self._container:
191
+ return
192
+
193
+ print('[Cleanup] Stopping the container...')
194
+ self._container.stop()
195
+ self._container.remove()
196
+ print(f'Container {self._container.id} stopped and removed.')