aip-agents-binary 0.5.20__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 (280) hide show
  1. aip_agents/__init__.py +65 -0
  2. aip_agents/a2a/__init__.py +19 -0
  3. aip_agents/a2a/server/__init__.py +10 -0
  4. aip_agents/a2a/server/base_executor.py +1086 -0
  5. aip_agents/a2a/server/google_adk_executor.py +198 -0
  6. aip_agents/a2a/server/langflow_executor.py +180 -0
  7. aip_agents/a2a/server/langgraph_executor.py +270 -0
  8. aip_agents/a2a/types.py +232 -0
  9. aip_agents/agent/__init__.py +27 -0
  10. aip_agents/agent/base_agent.py +970 -0
  11. aip_agents/agent/base_langgraph_agent.py +2942 -0
  12. aip_agents/agent/google_adk_agent.py +926 -0
  13. aip_agents/agent/google_adk_constants.py +6 -0
  14. aip_agents/agent/hitl/__init__.py +24 -0
  15. aip_agents/agent/hitl/config.py +28 -0
  16. aip_agents/agent/hitl/langgraph_hitl_mixin.py +515 -0
  17. aip_agents/agent/hitl/manager.py +532 -0
  18. aip_agents/agent/hitl/models.py +18 -0
  19. aip_agents/agent/hitl/prompt/__init__.py +9 -0
  20. aip_agents/agent/hitl/prompt/base.py +42 -0
  21. aip_agents/agent/hitl/prompt/deferred.py +73 -0
  22. aip_agents/agent/hitl/registry.py +149 -0
  23. aip_agents/agent/interface.py +138 -0
  24. aip_agents/agent/interfaces.py +65 -0
  25. aip_agents/agent/langflow_agent.py +464 -0
  26. aip_agents/agent/langgraph_memory_enhancer_agent.py +433 -0
  27. aip_agents/agent/langgraph_react_agent.py +2514 -0
  28. aip_agents/agent/system_instruction_context.py +34 -0
  29. aip_agents/clients/__init__.py +10 -0
  30. aip_agents/clients/langflow/__init__.py +10 -0
  31. aip_agents/clients/langflow/client.py +477 -0
  32. aip_agents/clients/langflow/types.py +18 -0
  33. aip_agents/constants.py +23 -0
  34. aip_agents/credentials/manager.py +132 -0
  35. aip_agents/examples/__init__.py +5 -0
  36. aip_agents/examples/compare_streaming_client.py +783 -0
  37. aip_agents/examples/compare_streaming_server.py +142 -0
  38. aip_agents/examples/demo_memory_recall.py +401 -0
  39. aip_agents/examples/hello_world_a2a_google_adk_client.py +49 -0
  40. aip_agents/examples/hello_world_a2a_google_adk_client_agent.py +48 -0
  41. aip_agents/examples/hello_world_a2a_google_adk_client_streaming.py +60 -0
  42. aip_agents/examples/hello_world_a2a_google_adk_server.py +79 -0
  43. aip_agents/examples/hello_world_a2a_langchain_client.py +39 -0
  44. aip_agents/examples/hello_world_a2a_langchain_client_agent.py +39 -0
  45. aip_agents/examples/hello_world_a2a_langchain_client_lm_invoker.py +37 -0
  46. aip_agents/examples/hello_world_a2a_langchain_client_streaming.py +41 -0
  47. aip_agents/examples/hello_world_a2a_langchain_reference_client_streaming.py +60 -0
  48. aip_agents/examples/hello_world_a2a_langchain_reference_server.py +105 -0
  49. aip_agents/examples/hello_world_a2a_langchain_server.py +79 -0
  50. aip_agents/examples/hello_world_a2a_langchain_server_lm_invoker.py +78 -0
  51. aip_agents/examples/hello_world_a2a_langflow_client.py +83 -0
  52. aip_agents/examples/hello_world_a2a_langflow_server.py +82 -0
  53. aip_agents/examples/hello_world_a2a_langgraph_artifact_client.py +73 -0
  54. aip_agents/examples/hello_world_a2a_langgraph_artifact_client_streaming.py +76 -0
  55. aip_agents/examples/hello_world_a2a_langgraph_artifact_server.py +92 -0
  56. aip_agents/examples/hello_world_a2a_langgraph_client.py +54 -0
  57. aip_agents/examples/hello_world_a2a_langgraph_client_agent.py +54 -0
  58. aip_agents/examples/hello_world_a2a_langgraph_client_agent_lm_invoker.py +32 -0
  59. aip_agents/examples/hello_world_a2a_langgraph_client_streaming.py +50 -0
  60. aip_agents/examples/hello_world_a2a_langgraph_client_streaming_lm_invoker.py +44 -0
  61. aip_agents/examples/hello_world_a2a_langgraph_client_streaming_tool_streaming.py +92 -0
  62. aip_agents/examples/hello_world_a2a_langgraph_server.py +84 -0
  63. aip_agents/examples/hello_world_a2a_langgraph_server_lm_invoker.py +79 -0
  64. aip_agents/examples/hello_world_a2a_langgraph_server_tool_streaming.py +132 -0
  65. aip_agents/examples/hello_world_a2a_mcp_langgraph.py +196 -0
  66. aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_client.py +244 -0
  67. aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_server.py +251 -0
  68. aip_agents/examples/hello_world_a2a_with_metadata_langchain_client.py +57 -0
  69. aip_agents/examples/hello_world_a2a_with_metadata_langchain_server_lm_invoker.py +80 -0
  70. aip_agents/examples/hello_world_google_adk.py +41 -0
  71. aip_agents/examples/hello_world_google_adk_mcp_http.py +34 -0
  72. aip_agents/examples/hello_world_google_adk_mcp_http_stream.py +40 -0
  73. aip_agents/examples/hello_world_google_adk_mcp_sse.py +44 -0
  74. aip_agents/examples/hello_world_google_adk_mcp_sse_stream.py +48 -0
  75. aip_agents/examples/hello_world_google_adk_mcp_stdio.py +44 -0
  76. aip_agents/examples/hello_world_google_adk_mcp_stdio_stream.py +48 -0
  77. aip_agents/examples/hello_world_google_adk_stream.py +44 -0
  78. aip_agents/examples/hello_world_langchain.py +28 -0
  79. aip_agents/examples/hello_world_langchain_lm_invoker.py +15 -0
  80. aip_agents/examples/hello_world_langchain_mcp_http.py +34 -0
  81. aip_agents/examples/hello_world_langchain_mcp_http_interactive.py +130 -0
  82. aip_agents/examples/hello_world_langchain_mcp_http_stream.py +42 -0
  83. aip_agents/examples/hello_world_langchain_mcp_multi_server.py +155 -0
  84. aip_agents/examples/hello_world_langchain_mcp_sse.py +34 -0
  85. aip_agents/examples/hello_world_langchain_mcp_sse_stream.py +40 -0
  86. aip_agents/examples/hello_world_langchain_mcp_stdio.py +30 -0
  87. aip_agents/examples/hello_world_langchain_mcp_stdio_stream.py +41 -0
  88. aip_agents/examples/hello_world_langchain_stream.py +36 -0
  89. aip_agents/examples/hello_world_langchain_stream_lm_invoker.py +39 -0
  90. aip_agents/examples/hello_world_langflow_agent.py +163 -0
  91. aip_agents/examples/hello_world_langgraph.py +39 -0
  92. aip_agents/examples/hello_world_langgraph_bosa_twitter.py +41 -0
  93. aip_agents/examples/hello_world_langgraph_mcp_http.py +31 -0
  94. aip_agents/examples/hello_world_langgraph_mcp_http_stream.py +34 -0
  95. aip_agents/examples/hello_world_langgraph_mcp_sse.py +35 -0
  96. aip_agents/examples/hello_world_langgraph_mcp_sse_stream.py +50 -0
  97. aip_agents/examples/hello_world_langgraph_mcp_stdio.py +35 -0
  98. aip_agents/examples/hello_world_langgraph_mcp_stdio_stream.py +50 -0
  99. aip_agents/examples/hello_world_langgraph_stream.py +43 -0
  100. aip_agents/examples/hello_world_langgraph_stream_lm_invoker.py +37 -0
  101. aip_agents/examples/hello_world_model_switch_cli.py +210 -0
  102. aip_agents/examples/hello_world_multi_agent_adk.py +75 -0
  103. aip_agents/examples/hello_world_multi_agent_langchain.py +54 -0
  104. aip_agents/examples/hello_world_multi_agent_langgraph.py +66 -0
  105. aip_agents/examples/hello_world_multi_agent_langgraph_lm_invoker.py +69 -0
  106. aip_agents/examples/hello_world_pii_logger.py +21 -0
  107. aip_agents/examples/hello_world_sentry.py +133 -0
  108. aip_agents/examples/hello_world_step_limits.py +273 -0
  109. aip_agents/examples/hello_world_stock_a2a_server.py +103 -0
  110. aip_agents/examples/hello_world_tool_output_client.py +46 -0
  111. aip_agents/examples/hello_world_tool_output_server.py +114 -0
  112. aip_agents/examples/hitl_demo.py +724 -0
  113. aip_agents/examples/mcp_configs/configs.py +63 -0
  114. aip_agents/examples/mcp_servers/common.py +76 -0
  115. aip_agents/examples/mcp_servers/mcp_name.py +29 -0
  116. aip_agents/examples/mcp_servers/mcp_server_http.py +19 -0
  117. aip_agents/examples/mcp_servers/mcp_server_sse.py +19 -0
  118. aip_agents/examples/mcp_servers/mcp_server_stdio.py +19 -0
  119. aip_agents/examples/mcp_servers/mcp_time.py +10 -0
  120. aip_agents/examples/pii_demo_langgraph_client.py +69 -0
  121. aip_agents/examples/pii_demo_langgraph_server.py +126 -0
  122. aip_agents/examples/pii_demo_multi_agent_client.py +80 -0
  123. aip_agents/examples/pii_demo_multi_agent_server.py +247 -0
  124. aip_agents/examples/todolist_planning_a2a_langchain_client.py +70 -0
  125. aip_agents/examples/todolist_planning_a2a_langgraph_server.py +88 -0
  126. aip_agents/examples/tools/__init__.py +27 -0
  127. aip_agents/examples/tools/adk_arithmetic_tools.py +36 -0
  128. aip_agents/examples/tools/adk_weather_tool.py +60 -0
  129. aip_agents/examples/tools/data_generator_tool.py +103 -0
  130. aip_agents/examples/tools/data_visualization_tool.py +312 -0
  131. aip_agents/examples/tools/image_artifact_tool.py +136 -0
  132. aip_agents/examples/tools/langchain_arithmetic_tools.py +26 -0
  133. aip_agents/examples/tools/langchain_currency_exchange_tool.py +88 -0
  134. aip_agents/examples/tools/langchain_graph_artifact_tool.py +172 -0
  135. aip_agents/examples/tools/langchain_weather_tool.py +48 -0
  136. aip_agents/examples/tools/langgraph_streaming_tool.py +130 -0
  137. aip_agents/examples/tools/mock_retrieval_tool.py +56 -0
  138. aip_agents/examples/tools/pii_demo_tools.py +189 -0
  139. aip_agents/examples/tools/random_chart_tool.py +142 -0
  140. aip_agents/examples/tools/serper_tool.py +202 -0
  141. aip_agents/examples/tools/stock_tools.py +82 -0
  142. aip_agents/examples/tools/table_generator_tool.py +167 -0
  143. aip_agents/examples/tools/time_tool.py +82 -0
  144. aip_agents/examples/tools/weather_forecast_tool.py +38 -0
  145. aip_agents/executor/agent_executor.py +473 -0
  146. aip_agents/executor/base.py +48 -0
  147. aip_agents/mcp/__init__.py +1 -0
  148. aip_agents/mcp/client/__init__.py +14 -0
  149. aip_agents/mcp/client/base_mcp_client.py +369 -0
  150. aip_agents/mcp/client/connection_manager.py +193 -0
  151. aip_agents/mcp/client/google_adk/__init__.py +11 -0
  152. aip_agents/mcp/client/google_adk/client.py +381 -0
  153. aip_agents/mcp/client/langchain/__init__.py +11 -0
  154. aip_agents/mcp/client/langchain/client.py +265 -0
  155. aip_agents/mcp/client/persistent_session.py +359 -0
  156. aip_agents/mcp/client/session_pool.py +351 -0
  157. aip_agents/mcp/client/transports.py +215 -0
  158. aip_agents/mcp/utils/__init__.py +7 -0
  159. aip_agents/mcp/utils/config_validator.py +139 -0
  160. aip_agents/memory/__init__.py +14 -0
  161. aip_agents/memory/adapters/__init__.py +10 -0
  162. aip_agents/memory/adapters/base_adapter.py +717 -0
  163. aip_agents/memory/adapters/mem0.py +84 -0
  164. aip_agents/memory/base.py +84 -0
  165. aip_agents/memory/constants.py +49 -0
  166. aip_agents/memory/factory.py +86 -0
  167. aip_agents/memory/guidance.py +20 -0
  168. aip_agents/memory/simple_memory.py +47 -0
  169. aip_agents/middleware/__init__.py +17 -0
  170. aip_agents/middleware/base.py +88 -0
  171. aip_agents/middleware/manager.py +128 -0
  172. aip_agents/middleware/todolist.py +274 -0
  173. aip_agents/schema/__init__.py +69 -0
  174. aip_agents/schema/a2a.py +56 -0
  175. aip_agents/schema/agent.py +111 -0
  176. aip_agents/schema/hitl.py +157 -0
  177. aip_agents/schema/langgraph.py +37 -0
  178. aip_agents/schema/model_id.py +97 -0
  179. aip_agents/schema/step_limit.py +108 -0
  180. aip_agents/schema/storage.py +40 -0
  181. aip_agents/sentry/__init__.py +11 -0
  182. aip_agents/sentry/sentry.py +151 -0
  183. aip_agents/storage/__init__.py +41 -0
  184. aip_agents/storage/base.py +85 -0
  185. aip_agents/storage/clients/__init__.py +12 -0
  186. aip_agents/storage/clients/minio_client.py +318 -0
  187. aip_agents/storage/config.py +62 -0
  188. aip_agents/storage/providers/__init__.py +15 -0
  189. aip_agents/storage/providers/base.py +106 -0
  190. aip_agents/storage/providers/memory.py +114 -0
  191. aip_agents/storage/providers/object_storage.py +214 -0
  192. aip_agents/tools/__init__.py +33 -0
  193. aip_agents/tools/bosa_tools.py +105 -0
  194. aip_agents/tools/browser_use/__init__.py +82 -0
  195. aip_agents/tools/browser_use/action_parser.py +103 -0
  196. aip_agents/tools/browser_use/browser_use_tool.py +1112 -0
  197. aip_agents/tools/browser_use/llm_config.py +120 -0
  198. aip_agents/tools/browser_use/minio_storage.py +198 -0
  199. aip_agents/tools/browser_use/schemas.py +119 -0
  200. aip_agents/tools/browser_use/session.py +76 -0
  201. aip_agents/tools/browser_use/session_errors.py +132 -0
  202. aip_agents/tools/browser_use/steel_session_recording.py +317 -0
  203. aip_agents/tools/browser_use/streaming.py +813 -0
  204. aip_agents/tools/browser_use/structured_data_parser.py +257 -0
  205. aip_agents/tools/browser_use/structured_data_recovery.py +204 -0
  206. aip_agents/tools/browser_use/types.py +78 -0
  207. aip_agents/tools/code_sandbox/__init__.py +26 -0
  208. aip_agents/tools/code_sandbox/constant.py +13 -0
  209. aip_agents/tools/code_sandbox/e2b_cloud_sandbox_extended.py +257 -0
  210. aip_agents/tools/code_sandbox/e2b_sandbox_tool.py +411 -0
  211. aip_agents/tools/constants.py +165 -0
  212. aip_agents/tools/document_loader/__init__.py +44 -0
  213. aip_agents/tools/document_loader/base_reader.py +302 -0
  214. aip_agents/tools/document_loader/docx_reader_tool.py +68 -0
  215. aip_agents/tools/document_loader/excel_reader_tool.py +171 -0
  216. aip_agents/tools/document_loader/pdf_reader_tool.py +79 -0
  217. aip_agents/tools/document_loader/pdf_splitter.py +169 -0
  218. aip_agents/tools/gl_connector/__init__.py +5 -0
  219. aip_agents/tools/gl_connector/tool.py +351 -0
  220. aip_agents/tools/memory_search/__init__.py +22 -0
  221. aip_agents/tools/memory_search/base.py +200 -0
  222. aip_agents/tools/memory_search/mem0.py +258 -0
  223. aip_agents/tools/memory_search/schema.py +48 -0
  224. aip_agents/tools/memory_search_tool.py +26 -0
  225. aip_agents/tools/time_tool.py +117 -0
  226. aip_agents/tools/tool_config_injector.py +300 -0
  227. aip_agents/tools/web_search/__init__.py +15 -0
  228. aip_agents/tools/web_search/serper_tool.py +187 -0
  229. aip_agents/types/__init__.py +70 -0
  230. aip_agents/types/a2a_events.py +13 -0
  231. aip_agents/utils/__init__.py +79 -0
  232. aip_agents/utils/a2a_connector.py +1757 -0
  233. aip_agents/utils/artifact_helpers.py +502 -0
  234. aip_agents/utils/constants.py +22 -0
  235. aip_agents/utils/datetime/__init__.py +34 -0
  236. aip_agents/utils/datetime/normalization.py +231 -0
  237. aip_agents/utils/datetime/timezone.py +206 -0
  238. aip_agents/utils/env_loader.py +27 -0
  239. aip_agents/utils/event_handler_registry.py +58 -0
  240. aip_agents/utils/file_prompt_utils.py +176 -0
  241. aip_agents/utils/final_response_builder.py +211 -0
  242. aip_agents/utils/formatter_llm_client.py +231 -0
  243. aip_agents/utils/langgraph/__init__.py +19 -0
  244. aip_agents/utils/langgraph/converter.py +128 -0
  245. aip_agents/utils/langgraph/tool_managers/__init__.py +15 -0
  246. aip_agents/utils/langgraph/tool_managers/a2a_tool_manager.py +99 -0
  247. aip_agents/utils/langgraph/tool_managers/base_tool_manager.py +66 -0
  248. aip_agents/utils/langgraph/tool_managers/delegation_tool_manager.py +1071 -0
  249. aip_agents/utils/langgraph/tool_output_management.py +967 -0
  250. aip_agents/utils/logger.py +195 -0
  251. aip_agents/utils/metadata/__init__.py +27 -0
  252. aip_agents/utils/metadata/activity_metadata_helper.py +407 -0
  253. aip_agents/utils/metadata/activity_narrative/__init__.py +35 -0
  254. aip_agents/utils/metadata/activity_narrative/builder.py +817 -0
  255. aip_agents/utils/metadata/activity_narrative/constants.py +51 -0
  256. aip_agents/utils/metadata/activity_narrative/context.py +49 -0
  257. aip_agents/utils/metadata/activity_narrative/formatters.py +230 -0
  258. aip_agents/utils/metadata/activity_narrative/utils.py +35 -0
  259. aip_agents/utils/metadata/schemas/__init__.py +16 -0
  260. aip_agents/utils/metadata/schemas/activity_schema.py +29 -0
  261. aip_agents/utils/metadata/schemas/thinking_schema.py +31 -0
  262. aip_agents/utils/metadata/thinking_metadata_helper.py +38 -0
  263. aip_agents/utils/metadata_helper.py +358 -0
  264. aip_agents/utils/name_preprocessor/__init__.py +17 -0
  265. aip_agents/utils/name_preprocessor/base_name_preprocessor.py +73 -0
  266. aip_agents/utils/name_preprocessor/google_name_preprocessor.py +100 -0
  267. aip_agents/utils/name_preprocessor/name_preprocessor.py +87 -0
  268. aip_agents/utils/name_preprocessor/openai_name_preprocessor.py +48 -0
  269. aip_agents/utils/pii/__init__.py +25 -0
  270. aip_agents/utils/pii/pii_handler.py +397 -0
  271. aip_agents/utils/pii/pii_helper.py +207 -0
  272. aip_agents/utils/pii/uuid_deanonymizer_mapping.py +195 -0
  273. aip_agents/utils/reference_helper.py +273 -0
  274. aip_agents/utils/sse_chunk_transformer.py +831 -0
  275. aip_agents/utils/step_limit_manager.py +265 -0
  276. aip_agents/utils/token_usage_helper.py +156 -0
  277. aip_agents_binary-0.5.20.dist-info/METADATA +681 -0
  278. aip_agents_binary-0.5.20.dist-info/RECORD +280 -0
  279. aip_agents_binary-0.5.20.dist-info/WHEEL +5 -0
  280. aip_agents_binary-0.5.20.dist-info/top_level.txt +1 -0
@@ -0,0 +1,358 @@
1
+ """Helper functions for creating and handling metadata for A2A communication.
2
+
3
+ Authors:
4
+ Fachriza Adhiatma (fachriza.d.adhiatma@gdplabs.id)
5
+ Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
6
+ Raymond Christopher (raymond.christopher@gdplabs.id)
7
+ """
8
+
9
+ from contextvars import ContextVar
10
+ from enum import StrEnum
11
+ from typing import Any
12
+
13
+ from aip_agents.utils.logger import get_logger
14
+ from aip_agents.utils.metadata import (
15
+ create_tool_activity_info,
16
+ )
17
+ from aip_agents.utils.token_usage_helper import (
18
+ STEP_USAGE_KEY,
19
+ TOTAL_USAGE_KEY,
20
+ )
21
+
22
+ logger = get_logger(__name__)
23
+
24
+ _STEP_COUNTER_STATE_CVAR: ContextVar[dict[str, int] | None] = ContextVar("gllm_step_counter_state", default=None)
25
+
26
+
27
+ def _ensure_step_counter_state() -> dict[str, int]:
28
+ """Get or initialize step counter state for the current async context.
29
+
30
+ Returns:
31
+ dict[str, int]: Dictionary containing 'count' and 'depth' keys for step tracking.
32
+ """
33
+ state = _STEP_COUNTER_STATE_CVAR.get()
34
+ if state is None:
35
+ state = {"count": 0, "depth": 0}
36
+ _STEP_COUNTER_STATE_CVAR.set(state)
37
+ return state
38
+
39
+
40
+ def start_step_counter_scope(start: int = 1) -> None:
41
+ """Begin a step counter scope, resetting only when entering the outermost scope.
42
+
43
+ Args:
44
+ start: Starting step number for the counter. Defaults to 1.
45
+ """
46
+ state = _ensure_step_counter_state()
47
+ if state["depth"] == 0:
48
+ state["count"] = start - 1
49
+ state["depth"] += 1
50
+
51
+
52
+ def end_step_counter_scope() -> None:
53
+ """Exit a step counter scope, maintaining parent scope counters.
54
+
55
+ Decrements the depth counter while preserving the step count for parent scopes.
56
+ """
57
+ state = _ensure_step_counter_state()
58
+ if state["depth"] > 0:
59
+ state["depth"] -= 1
60
+
61
+
62
+ def get_next_step_number() -> int:
63
+ """Return the next step number within the active scope.
64
+
65
+ Returns:
66
+ int: The next sequential step number in the current scope.
67
+ """
68
+ state = _ensure_step_counter_state()
69
+ state["count"] += 1
70
+ return state["count"]
71
+
72
+
73
+ class DefaultStepMessages(StrEnum):
74
+ """Constants for default step indicator messages."""
75
+
76
+ EN = "Performing agent tasks"
77
+ ID = "Melakukan tugas agen"
78
+
79
+
80
+ class Kind(StrEnum):
81
+ """Constants for metadata kind values."""
82
+
83
+ AGENT_STEP = "agent_step"
84
+ AGENT_THINKING_STEP = "agent_thinking_step"
85
+ FINAL_RESPONSE = "final_response"
86
+ FINAL_THINKING_STEP = "final_agent_thinking_step"
87
+ AGENT_DEFAULT = "agent_default"
88
+ TOKEN = "token"
89
+
90
+
91
+ class Status(StrEnum):
92
+ """Constants for metadata status values."""
93
+
94
+ RUNNING = "running"
95
+ FINISHED = "finished"
96
+ STOPPED = "stopped"
97
+
98
+
99
+ class MetadataFieldKeys(StrEnum):
100
+ """Enumeration of standard metadata field keys used in A2A events."""
101
+
102
+ KIND = "kind"
103
+ STATUS = "status"
104
+ TIME = "time"
105
+ MESSAGE = "message"
106
+ TOOL_INFO = "tool_info"
107
+ REFERENCES = "references"
108
+ THINKING_AND_ACTIVITY_INFO = "thinking_and_activity_info"
109
+ HITL = "hitl"
110
+ STEP_USAGE = STEP_USAGE_KEY
111
+ TOTAL_USAGE = TOTAL_USAGE_KEY
112
+ TOKEN_STREAMING = "token_streaming"
113
+ PII_MAPPING = "pii_mapping"
114
+
115
+
116
+ class MetadataTimeTracker:
117
+ """Tracks cumulative execution time across agent steps for final response metadata.
118
+
119
+ This class provides a clean way to accumulate execution times from individual
120
+ agent steps and apply the total time to final response metadata.
121
+
122
+ Attributes:
123
+ FLOAT_EPSILON: Epsilon value for floating point comparisons to avoid precision issues.
124
+ """
125
+
126
+ # Epsilon value for floating point comparisons to avoid precision issues
127
+ FLOAT_EPSILON = 1e-10
128
+
129
+ def __init__(self):
130
+ """Initialize the time tracker with zero accumulated time."""
131
+ self._total_agent_step_time = 0.0
132
+ self._last_seen_time: float = 0.0
133
+
134
+ def _track_response_time(self, metadata: dict[str, Any]) -> None:
135
+ """Track and update time from response metadata.
136
+
137
+ Args:
138
+ metadata: Response metadata dictionary containing time information.
139
+ """
140
+ try:
141
+ t = metadata.get(MetadataFieldKeys.TIME) if isinstance(metadata, dict) else None
142
+ # Accept both enum key and string key variants
143
+ if t is None and isinstance(metadata, dict):
144
+ t = metadata.get(MetadataFieldKeys.TIME)
145
+ if isinstance(t, int | float) and t > 0:
146
+ self._last_seen_time = float(t)
147
+ except Exception:
148
+ pass
149
+
150
+ def _set_final_response_time(self, metadata: dict[str, Any]) -> None:
151
+ """Set time for final responses if missing or zero.
152
+
153
+ Args:
154
+ metadata: Response metadata dictionary to update with time information.
155
+ """
156
+ current_time = metadata.get(MetadataFieldKeys.TIME, 0.0) if isinstance(metadata, dict) else 0.0
157
+ if not isinstance(current_time, int | float) or abs(current_time) < self.FLOAT_EPSILON:
158
+ # Prefer last seen non-zero time from stream; otherwise fall back to accumulated agent step time
159
+ metadata[MetadataFieldKeys.TIME] = (
160
+ self._last_seen_time if self._last_seen_time > 0 else self._get_total_time()
161
+ )
162
+
163
+ def _accumulate_agent_step_time(self, metadata: dict[str, Any]) -> None:
164
+ """Accumulate execution time from agent step metadata only.
165
+
166
+ Args:
167
+ metadata: Metadata dictionary from an agent step response containing time information.
168
+ """
169
+ try:
170
+ kind = metadata.get(MetadataFieldKeys.KIND)
171
+ status = metadata.get(MetadataFieldKeys.STATUS)
172
+ # Only accumulate time for agent steps, not final responses
173
+ if kind == Kind.AGENT_STEP and status == Status.FINISHED:
174
+ self._total_agent_step_time += float(metadata.get(MetadataFieldKeys.TIME, 0.0))
175
+ except Exception:
176
+ pass
177
+
178
+ def _get_total_time(self) -> float:
179
+ """Get the current total accumulated time.
180
+
181
+ Returns:
182
+ float: The total accumulated execution time in seconds.
183
+ """
184
+ return self._total_agent_step_time
185
+
186
+ def update_response_metadata(self, response: dict[str, Any]) -> dict[str, Any]:
187
+ """Update response metadata with accumulated time tracking.
188
+
189
+ Args:
190
+ response: Response dictionary containing metadata to update.
191
+
192
+ Returns:
193
+ dict[str, Any]: Response with updated metadata for final responses. If any error occurs,
194
+ returns the original response unchanged.
195
+ """
196
+ try:
197
+ if self._should_skip_metadata_update(response):
198
+ return response
199
+
200
+ metadata = response["metadata"]
201
+
202
+ # Track time from response metadata
203
+ self._track_response_time(metadata)
204
+
205
+ # Maintain legacy accumulation for agent steps
206
+ self._accumulate_agent_step_time(metadata)
207
+
208
+ # Set time for final responses if needed
209
+ if self._is_final_response(response, metadata):
210
+ self._set_final_response_time(metadata)
211
+
212
+ return response
213
+ except Exception as e:
214
+ logger.warning(f"Failed to update response metadata with time tracking: {e}")
215
+ return response
216
+
217
+ def _should_skip_metadata_update(self, response: dict[str, Any]) -> bool:
218
+ """Check if metadata update should be skipped.
219
+
220
+ Args:
221
+ response: Response dictionary to check.
222
+
223
+ Returns:
224
+ bool: True if update should be skipped, False otherwise.
225
+ """
226
+ return response is None or "metadata" not in response
227
+
228
+ def _is_final_response(self, response: dict[str, Any], metadata: dict[str, Any]) -> bool:
229
+ """Check if this is a final response that needs time setting.
230
+
231
+ Args:
232
+ response: Response dictionary to check.
233
+ metadata: Response metadata dictionary to check.
234
+
235
+ Returns:
236
+ bool: True if this is a final response requiring time update.
237
+ """
238
+ return response.get("final", False) and metadata.get(MetadataFieldKeys.KIND) == Kind.FINAL_RESPONSE
239
+
240
+
241
+ def _create_message_metadata(
242
+ en_content: str = DefaultStepMessages.EN.value, id_content: str = DefaultStepMessages.ID.value
243
+ ) -> dict[str, str]:
244
+ """Create a message metadata dictionary with English and Indonesian content.
245
+
246
+ Args:
247
+ en_content: English content. Defaults to DefaultStepMessages.EN.value.
248
+ id_content: Indonesian content. Defaults to DefaultStepMessages.ID.value.
249
+
250
+ Returns:
251
+ dict[str, str]: Dictionary with language codes as keys and translated content as values.
252
+ """
253
+ return {"en": en_content, "id": id_content}
254
+
255
+
256
+ def create_metadata(
257
+ content: str = "",
258
+ status: Status = Status.RUNNING,
259
+ is_final: bool = False,
260
+ existing_metadata: dict[str, Any] | None = None,
261
+ ) -> dict[str, Any]:
262
+ """Create metadata for A2A responses with content-based message.
263
+
264
+ Args:
265
+ content: The content to create metadata for.
266
+ status: The status of the content.
267
+ is_final: Whether the content is final.
268
+ existing_metadata: Optional existing metadata to merge with. Existing metadata
269
+ takes precedence over generated metadata for conflicting keys.
270
+
271
+ Returns:
272
+ dict[str, Any]: The metadata for the content, merged with existing metadata if provided.
273
+ """
274
+ if is_final:
275
+ detected_kind = Kind.FINAL_RESPONSE
276
+ message_content = _create_message_metadata(
277
+ en_content=DefaultStepMessages.EN.value, id_content=DefaultStepMessages.ID.value
278
+ )
279
+ final_status = Status.FINISHED if status == Status.RUNNING else status
280
+ base_metadata = {
281
+ MetadataFieldKeys.KIND: detected_kind,
282
+ MetadataFieldKeys.MESSAGE: message_content,
283
+ MetadataFieldKeys.STATUS: final_status,
284
+ }
285
+ else:
286
+ detected_kind = Kind.AGENT_DEFAULT
287
+ final_status = status
288
+ base_metadata = {
289
+ MetadataFieldKeys.KIND: detected_kind,
290
+ MetadataFieldKeys.STATUS: final_status,
291
+ }
292
+
293
+ # Merge with existing metadata if provided
294
+ if existing_metadata and isinstance(existing_metadata, dict):
295
+ if is_final:
296
+ return {**existing_metadata, **base_metadata}
297
+ return {**base_metadata, **existing_metadata}
298
+
299
+ return base_metadata
300
+
301
+
302
+ def create_tool_processing_metadata(original_metadata: dict[str, Any] | None = None) -> dict[str, Any]:
303
+ """Create metadata for tool processing events (tool_call and tool_result).
304
+
305
+ Args:
306
+ original_metadata: Optional original metadata to merge with.
307
+
308
+ Returns:
309
+ dict[str, Any]: Metadata dictionary with agent_thinking_step kind and no message/time/status.
310
+ """
311
+ metadata = {
312
+ MetadataFieldKeys.KIND: Kind.AGENT_THINKING_STEP,
313
+ }
314
+
315
+ if original_metadata:
316
+ original = original_metadata.copy()
317
+ original.update(metadata)
318
+ metadata = original
319
+
320
+ if MetadataFieldKeys.THINKING_AND_ACTIVITY_INFO not in metadata:
321
+ thinking_and_activity_info = create_tool_activity_info(original_metadata)
322
+ metadata[MetadataFieldKeys.THINKING_AND_ACTIVITY_INFO] = thinking_and_activity_info
323
+
324
+ return metadata
325
+
326
+
327
+ def create_status_update_metadata(content: str, custom_metadata: dict[str, Any] | None = None) -> dict[str, Any]:
328
+ """Create metadata for status update events with content-based rules.
329
+
330
+ Args:
331
+ content: The content of the status update.
332
+ custom_metadata: Optional custom metadata to merge with.
333
+
334
+ Returns:
335
+ dict[str, Any]: Metadata dictionary following the specific rules for different content types.
336
+ """
337
+ if content == DefaultStepMessages.EN.value:
338
+ metadata = {
339
+ MetadataFieldKeys.KIND: Kind.AGENT_STEP,
340
+ MetadataFieldKeys.MESSAGE: _create_message_metadata(content, DefaultStepMessages.ID.value),
341
+ MetadataFieldKeys.STATUS: Status.RUNNING,
342
+ MetadataFieldKeys.TIME: 0.0,
343
+ }
344
+ # Merge any custom metadata (e.g., step_id, agent_name) for initial status too
345
+ if custom_metadata and isinstance(custom_metadata, dict):
346
+ metadata = {**metadata, **custom_metadata}
347
+ else:
348
+ metadata = create_metadata(
349
+ content=content,
350
+ is_final=False,
351
+ status=Status.RUNNING,
352
+ existing_metadata=custom_metadata,
353
+ )
354
+
355
+ if custom_metadata and content not in [DefaultStepMessages.EN.value]:
356
+ metadata = {**metadata, **custom_metadata}
357
+
358
+ return metadata
@@ -0,0 +1,17 @@
1
+ """This module provides the name preprocessing utilities.
2
+
3
+ Authors:
4
+ Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
5
+ """
6
+
7
+ from aip_agents.utils.name_preprocessor.base_name_preprocessor import BaseNamePreprocessor
8
+ from aip_agents.utils.name_preprocessor.google_name_preprocessor import GoogleNamePreprocessor
9
+ from aip_agents.utils.name_preprocessor.name_preprocessor import NamePreprocessor
10
+ from aip_agents.utils.name_preprocessor.openai_name_preprocessor import OpenAINamePreprocessor
11
+
12
+ __all__ = [
13
+ "GoogleNamePreprocessor",
14
+ "OpenAINamePreprocessor",
15
+ "BaseNamePreprocessor",
16
+ "NamePreprocessor",
17
+ ]
@@ -0,0 +1,73 @@
1
+ """Base class for name preprocessing.
2
+
3
+ This module provides the abstract base class for name preprocessing.
4
+
5
+ Authors:
6
+ Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
7
+ """
8
+
9
+ import re
10
+ from abc import ABC, abstractmethod
11
+
12
+
13
+ class BaseNamePreprocessor(ABC):
14
+ """Base class for name preprocessing.
15
+
16
+ It contains the common methods for name preprocessing.
17
+ """
18
+
19
+ def regex_substitute(self, name: str, regex: str, replacement: str) -> str:
20
+ """Substitute a regex pattern in a name.
21
+
22
+ Args:
23
+ name: The input name to preprocess.
24
+ regex: The regex pattern to substitute.
25
+ replacement: The replacement string.
26
+
27
+ Returns:
28
+ A name that is valid for the name processor.
29
+ """
30
+ return re.sub(regex, replacement, name)
31
+
32
+ def clean_up_name(self, name: str) -> str:
33
+ """Process a processed name.
34
+
35
+ Step:
36
+ 1. collapse multiple underscores to single underscore
37
+ 2. remove trailing underscores
38
+ 3. if name is empty after clean up, add a single underscore
39
+
40
+ Args:
41
+ name: The input name to preprocess.
42
+
43
+ Returns:
44
+ A name that starts with a letter or an underscore.
45
+ """
46
+ name = self.regex_substitute(name, r"_{2,}", "_").rstrip("_")
47
+ if not name:
48
+ name = "_"
49
+ return name
50
+
51
+ @abstractmethod
52
+ def sanitize_agent_name(self, name: str) -> str:
53
+ """Process a name according to the rules of the name processor.
54
+
55
+ Args:
56
+ name: The input name to preprocess.
57
+
58
+ Returns:
59
+ A name that is valid for the name processor.
60
+ """
61
+ raise NotImplementedError
62
+
63
+ @abstractmethod
64
+ def sanitize_tool_name(self, name: str) -> str:
65
+ """Process a name according to the rules of the name processor.
66
+
67
+ Args:
68
+ name: The input name to preprocess.
69
+
70
+ Returns:
71
+ A name that is valid for the name processor.
72
+ """
73
+ raise NotImplementedError
@@ -0,0 +1,100 @@
1
+ """Concrete implementation of NamePreprocessor according to Google's name requirements.
2
+
3
+ This implementation provides the correction of name according to Google's name requirements.
4
+
5
+ Authors:
6
+ Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
7
+ """
8
+
9
+ from aip_agents.utils.logger import get_logger
10
+ from aip_agents.utils.name_preprocessor import BaseNamePreprocessor
11
+
12
+ logger = get_logger(__name__)
13
+
14
+
15
+ class GoogleNamePreprocessor(BaseNamePreprocessor):
16
+ """Concrete implementation of NamePreprocessor according to Google's name requirements."""
17
+
18
+ NAME_SETTINGS = {
19
+ "tool": {
20
+ "prefix_fix": "Tool_",
21
+ "regex": r"[^a-zA-Z0-9_-]",
22
+ },
23
+ "agent": {
24
+ "prefix_fix": "Agent_",
25
+ "regex": r"\W",
26
+ },
27
+ }
28
+ CUT_OFF_INDEX = 63
29
+
30
+ def _ensure_name_starts_with_letter_or_underscore(self, prefix_fix: str, name: str) -> str:
31
+ """Preprocess an input name to be fixed if it doesn't start with a letter or an underscore.
32
+
33
+ Args:
34
+ prefix_fix: The prefix to add to the name if it doesn't start with a letter or an underscore.
35
+ name: The input name to preprocess.
36
+
37
+ Returns:
38
+ A name that starts with a letter or an underscore.
39
+
40
+ Notes:
41
+ Name should start with a letter (a-z, A-Z) or an underscore (_),
42
+ and can only contain letters, digits (0-9), and underscores.
43
+ """
44
+ if not name or (not name[0].isalpha() and name[0] != "_"):
45
+ logger.warning(f"Invalid agent name: {name}. Agent name should start with a letter or an underscore.")
46
+ prefix_fix = self._ensure_name_starts_with_letter_or_underscore("_", prefix_fix)
47
+ name = prefix_fix + name
48
+ return name
49
+
50
+ def _common_process(self, name_type: str, name: str) -> str:
51
+ """Preprocess an input name according to Google's name requirements.
52
+
53
+ Args:
54
+ name_type: The type of the name to preprocess ("tool" or "agent").
55
+ name: The input name to preprocess.
56
+
57
+ Returns:
58
+ A name that is valid for Google.
59
+ """
60
+ prefix_fix = self.NAME_SETTINGS[name_type]["prefix_fix"]
61
+ regex = self.NAME_SETTINGS[name_type]["regex"]
62
+ name = self._ensure_name_starts_with_letter_or_underscore(prefix_fix, name)
63
+ name = self.regex_substitute(name, regex, "_")
64
+ name = self.clean_up_name(name)
65
+ return name
66
+
67
+ def sanitize_agent_name(self, name: str) -> str:
68
+ """Preprocess an input name according to Google's name requirements for agents.
69
+
70
+ Args:
71
+ name: The input name to preprocess.
72
+
73
+ Returns:
74
+ A name that is valid for Google.
75
+
76
+ Notes:
77
+ Name should start with a letter (a-z, A-Z) or an underscore (_),
78
+ and can only contain letters, digits (0-9), and underscores.
79
+ It has no length limit. ( based on experiment )
80
+ """
81
+ name = self._common_process("agent", name)
82
+ return name
83
+
84
+ def sanitize_tool_name(self, name: str) -> str:
85
+ """Preprocess an input name according to Google's name requirements for tools.
86
+
87
+ Args:
88
+ name: The input name to preprocess.
89
+
90
+ Returns:
91
+ A name that is valid for Google.
92
+
93
+ Notes:
94
+ Name should start with a letter (a-z, A-Z) or an underscore (_),
95
+ and can only contain letters, digits (0-9), underscores, and dashes.
96
+ It has a length limit of 64 characters, but google throws error if the length is exactly 64, so cut to 63.
97
+ """
98
+ name = self._common_process("tool", name)
99
+ name = name[: self.CUT_OFF_INDEX]
100
+ return name
@@ -0,0 +1,87 @@
1
+ """Utility functions for name preprocessing.
2
+
3
+ This module provides functions to preprocess names according to Google's name requirements.
4
+
5
+ References:
6
+ https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/function-calling
7
+
8
+ Rules:
9
+ - For GoogleADK Agent Name:
10
+ - Must start with a letter (a-z, A-Z) or an underscore (_),
11
+ - Can only contain letters, digits (0-9), and underscores.
12
+ - Has no length limit.
13
+ - For Tool Name:
14
+ - Must start with a letter (a-z, A-Z) or an underscore (_),
15
+ - Can only contain letters, digits (0-9), underscores, and dashes.
16
+ - Has a length limit of 64 characters, but google throws error if the length is exactly 64, so cut to 63.
17
+ - For OpenAI,
18
+ - they only have rule for tool name, and it is the same as Google's tool name rule,
19
+ - except it can start with any character as long as it is a valid character (alphanumeric, _, -)
20
+
21
+ Authors:
22
+ Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
23
+ """
24
+
25
+ from aip_agents.utils.logger import get_logger
26
+ from aip_agents.utils.name_preprocessor.base_name_preprocessor import BaseNamePreprocessor
27
+ from aip_agents.utils.name_preprocessor.google_name_preprocessor import GoogleNamePreprocessor
28
+ from aip_agents.utils.name_preprocessor.openai_name_preprocessor import OpenAINamePreprocessor
29
+
30
+ logger = get_logger(__name__)
31
+
32
+
33
+ class NamePreprocessor:
34
+ """Name Preprocessor for Google ADK and OpenAI compatible models.
35
+
36
+ Args:
37
+ provider: The provider of the model.
38
+ """
39
+
40
+ PROVIDER_TO_NAME_PREPROCESSOR_MAP = {
41
+ "openai": OpenAINamePreprocessor,
42
+ "openai-compatible": OpenAINamePreprocessor,
43
+ "google": GoogleNamePreprocessor,
44
+ }
45
+
46
+ def __init__(self, provider: str):
47
+ """Initialize the name preprocessor.
48
+
49
+ Args:
50
+ provider: The provider of the model.
51
+ """
52
+ self.provider = provider
53
+ self.preprocessor = self._get_preprocessor()
54
+
55
+ def _get_preprocessor(self) -> BaseNamePreprocessor:
56
+ """Get the name processor for the given provider.
57
+
58
+ Args:
59
+ provider: The provider of the model, i.e. openai, google, etc.
60
+ This is used to determine which name processor to use.
61
+
62
+ Returns:
63
+ A name processor for the given provider.
64
+ """
65
+ return self.PROVIDER_TO_NAME_PREPROCESSOR_MAP.get(self.provider, GoogleNamePreprocessor)()
66
+
67
+ def sanitize_agent_name(self, name: str) -> str:
68
+ """Preprocess an input name according to the rules of the name processor.
69
+
70
+ Args:
71
+ name: The input name to preprocess.
72
+
73
+ Returns:
74
+ A name that is valid for the name processor.
75
+ """
76
+ return self.preprocessor.sanitize_agent_name(name)
77
+
78
+ def sanitize_tool_name(self, name: str) -> str:
79
+ """Preprocess an input name according to the rules of the name processor.
80
+
81
+ Args:
82
+ name: The input name to preprocess.
83
+
84
+ Returns:
85
+ A name that is valid for the name processor.
86
+ """
87
+ return self.preprocessor.sanitize_tool_name(name)
@@ -0,0 +1,48 @@
1
+ """Concrete implementation of NamePreprocessor according to OpenAI's name requirements.
2
+
3
+ Authors:
4
+ Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
5
+ """
6
+
7
+ from aip_agents.utils.logger import get_logger
8
+ from aip_agents.utils.name_preprocessor import BaseNamePreprocessor
9
+
10
+ logger = get_logger(__name__)
11
+
12
+
13
+ class OpenAINamePreprocessor(BaseNamePreprocessor):
14
+ """Concrete implementation of NamePreprocessor according to OpenAI's name requirements."""
15
+
16
+ CUT_OFF_INDEX = 64
17
+
18
+ def sanitize_agent_name(self, name: str) -> str:
19
+ """Preprocess an input name according to OpenAI's name requirements for agents.
20
+
21
+ As of now, OpenAI only has rule for tool name, and it is the same as Google's tool name rule,
22
+ so just return the name as is.
23
+
24
+ Args:
25
+ name: The input name to preprocess.
26
+
27
+ Returns:
28
+ A name that is valid for OpenAI.
29
+ """
30
+ return name
31
+
32
+ def sanitize_tool_name(self, name: str) -> str:
33
+ """Preprocess an input name according to OpenAI's name requirements for tools.
34
+
35
+ Args:
36
+ name: The input name to preprocess.
37
+
38
+ Returns:
39
+ A name that is valid for OpenAI.
40
+
41
+ Notes:
42
+ Only contain letters (a-z, A-Z), digits (0-9), underscores (_), and dashes (-).
43
+ It has a length limit of 64 characters.
44
+ """
45
+ name = self.regex_substitute(name, r"[^a-zA-Z0-9_-]", "_")
46
+ name = self.clean_up_name(name)
47
+ name = name[: self.CUT_OFF_INDEX]
48
+ return name