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,198 @@
1
+ """A2A server-side executor for Google ADK agent instances.
2
+
3
+ Authors:
4
+ Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
5
+ """
6
+
7
+ import asyncio
8
+ from typing import Any
9
+
10
+ from a2a.server.agent_execution import RequestContext
11
+ from a2a.server.events.event_queue import EventQueue
12
+ from a2a.server.tasks import TaskUpdater
13
+ from a2a.types import TaskState
14
+ from a2a.utils import new_agent_text_message
15
+
16
+ from aip_agents.a2a.server.base_executor import BaseA2AExecutor, StatusUpdateParams
17
+ from aip_agents.agent.google_adk_constants import DEFAULT_AUTH_URL
18
+ from aip_agents.agent.interfaces import GoogleADKAgentProtocol
19
+ from aip_agents.utils.logger import get_logger
20
+
21
+ logger = get_logger(__name__)
22
+
23
+
24
+ class GoogleADKExecutor(BaseA2AExecutor):
25
+ """A2A Executor for serving a `GoogleADKAgent`.
26
+
27
+ This executor bridges the A2A server protocol with a `aip_agents.agent.GoogleADKAgent`.
28
+ It handles incoming requests by invoking the agent's `arun_a2a_stream` method,
29
+ which is specifically designed to yield ADK events in an A2A-compatible dictionary
30
+ format. This executor's `_process_stream` method is tailored to handle this stream,
31
+ including ADK-specific statuses like "auth_required", before delegating common
32
+ status handling to `BaseA2AExecutor._handle_stream_event`.
33
+
34
+ It leverages common functionality from `BaseA2AExecutor` for task management,
35
+ initial request checks, and cancellation.
36
+
37
+ Attributes:
38
+ agent (GoogleADKAgentProtocol): The instance of `GoogleADKAgent`-compatible class to execute.
39
+ """
40
+
41
+ agent: GoogleADKAgentProtocol
42
+
43
+ def __init__(self, agent: GoogleADKAgentProtocol) -> None:
44
+ """Initializes the GoogleADKExecutor.
45
+
46
+ Args:
47
+ agent: Component implementing `GoogleADKAgentProtocol`.
48
+
49
+ Raises:
50
+ TypeError: If the provided agent does not satisfy `GoogleADKAgentProtocol`.
51
+ """
52
+ super().__init__()
53
+
54
+ if not isinstance(agent, GoogleADKAgentProtocol):
55
+ raise TypeError(
56
+ f"GoogleADKExecutor expected an agent implementing GoogleADKAgentProtocol, got {type(agent).__name__}"
57
+ )
58
+ self.agent = agent
59
+ self._default_auth_url = DEFAULT_AUTH_URL
60
+
61
+ async def execute(
62
+ self,
63
+ context: RequestContext,
64
+ event_queue: EventQueue,
65
+ ) -> None:
66
+ """Processes an incoming agent request using the `GoogleADKAgent`.
67
+
68
+ This method first performs initial checks using `_handle_initial_execute_checks`
69
+ from the base class. If successful, it prepares the `_process_stream` coroutine
70
+ and passes it to `_execute_agent_processing` (also from the base class) to
71
+ manage its execution lifecycle. The `_process_stream` method is responsible for
72
+ calling the agent's `arun_a2a_stream` and handling its ADK-specific output.
73
+
74
+ Args:
75
+ context (RequestContext): The A2A request context containing message details,
76
+ task ID, and context ID.
77
+ event_queue (EventQueue): The queue for sending A2A events (task status,
78
+ artifacts) back to the server.
79
+ """
80
+ updater, query, metadata = await self._handle_initial_execute_checks(context, event_queue)
81
+ if not updater or query is None: # Checks failed, status already sent by base method
82
+ return
83
+
84
+ agent_processing_coro = self._process_stream(
85
+ query=query,
86
+ updater=updater,
87
+ task_id=context.task_id,
88
+ context_id=context.context_id,
89
+ event_queue=event_queue,
90
+ metadata=metadata,
91
+ )
92
+
93
+ await self._execute_agent_processing(
94
+ agent_processing_coro=agent_processing_coro,
95
+ updater=updater,
96
+ task_id=context.task_id,
97
+ context_id=context.context_id,
98
+ )
99
+
100
+ async def _process_stream( # noqa: PLR0913
101
+ self,
102
+ query: str,
103
+ updater: TaskUpdater,
104
+ task_id: str,
105
+ context_id: str,
106
+ event_queue: EventQueue,
107
+ metadata: dict[str, Any] | None = None,
108
+ ) -> None:
109
+ """Processes the streaming response from the `GoogleADKAgent`.
110
+
111
+ This coroutine invokes `self.agent.arun_a2a_stream`, which is designed to yield
112
+ dictionary chunks adapting native Google ADK `Event` objects into an A2A-compatible
113
+ format. This method specifically handles the "auth_required" status that can be
114
+ yielded by the agent's stream. For all other statuses, it delegates to the
115
+ `self._handle_stream_event` method from `BaseA2AExecutor` for common processing.
116
+
117
+ The `GoogleADKAgent.arun_a2a_stream` and its helper methods are responsible for
118
+ the ADK-specific event transformation. This executor's role here is to consume
119
+ that adapted stream.
120
+
121
+ If `asyncio.CancelledError` is raised (e.g., by the task managed by
122
+ `_execute_agent_processing`), it is re-raised to be handled by the base class.
123
+ Other exceptions during streaming are caught, logged, an A2A 'failed' status
124
+ is sent, and the exception is re-raised.
125
+
126
+ Args:
127
+ query (str): The query string to be processed by the agent.
128
+ updater (TaskUpdater): The `TaskUpdater` instance for sending status updates.
129
+ task_id (str): The A2A task ID.
130
+ context_id (str): The A2A context ID.
131
+ event_queue (EventQueue): The A2A event queue, used by the base handler
132
+ for sending artifact events.
133
+ metadata (dict[str, Any] | None): Optional metadata from the A2A request.
134
+
135
+ Raises:
136
+ asyncio.CancelledError: If the task is cancelled externally.
137
+ Exception: If any other error occurs during the agent's stream processing.
138
+ """
139
+ try:
140
+ async for chunk in self.agent.arun_a2a_stream(
141
+ query=query,
142
+ configurable={"thread_id": task_id, "context_id": context_id},
143
+ ):
144
+ # Handle ADK-specific statuses first
145
+ if chunk.get("status") == "auth_required":
146
+ auth_content = chunk.get("content", {})
147
+ auth_url = (
148
+ auth_content.get("auth_url", self._default_auth_url)
149
+ if isinstance(auth_content, dict)
150
+ else self._default_auth_url
151
+ )
152
+ auth_message = (
153
+ auth_content.get("message", "Authorization is required.")
154
+ if isinstance(auth_content, dict)
155
+ else "Authorization is required."
156
+ )
157
+ full_message = f"{auth_message} Visit {auth_url}"
158
+
159
+ await self._update_status(
160
+ updater,
161
+ TaskState.auth_required,
162
+ message=new_agent_text_message(full_message, context_id=context_id, task_id=task_id),
163
+ params=StatusUpdateParams(final=True, task_id=task_id, context_id=context_id),
164
+ )
165
+ return # Terminate stream processing as auth is required
166
+
167
+ # For other statuses, use the common handler from BaseA2AExecutor
168
+ should_terminate = await self._handle_stream_event(
169
+ chunk=chunk,
170
+ updater=updater,
171
+ task_id=task_id,
172
+ context_id=context_id,
173
+ event_queue=event_queue,
174
+ metadata=metadata,
175
+ )
176
+ if should_terminate:
177
+ return
178
+
179
+ except asyncio.CancelledError:
180
+ logger.info(f"ADK Stream processing for task {task_id} was cancelled.")
181
+ # Re-raise for _execute_agent_processing to handle and set A2A status
182
+ raise
183
+ except Exception as e:
184
+ logger.error(
185
+ f"Error during ADK agent streaming for task {task_id}: {e}",
186
+ exc_info=True,
187
+ )
188
+ await self._update_status(
189
+ updater,
190
+ TaskState.failed,
191
+ message=new_agent_text_message(
192
+ f"Error during streaming: {str(e)}",
193
+ context_id=context_id,
194
+ task_id=task_id,
195
+ ),
196
+ params=StatusUpdateParams(final=True, task_id=task_id, context_id=context_id),
197
+ )
198
+ raise
@@ -0,0 +1,180 @@
1
+ """A2A executor for Langflow agents.
2
+
3
+ This module provides the LangflowA2AExecutor class that extends BaseA2AExecutor
4
+ to handle A2A requests for Langflow agents, similar to how LangGraphA2AExecutor
5
+ works for LangGraph agents.
6
+
7
+ Authors:
8
+ Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
9
+ """
10
+
11
+ import typing
12
+ from abc import ABC
13
+
14
+ from a2a.server.agent_execution import RequestContext
15
+ from a2a.server.events.event_queue import EventQueue
16
+ from a2a.server.tasks import TaskUpdater
17
+ from a2a.types import TaskState
18
+ from a2a.utils import new_agent_text_message
19
+
20
+ from aip_agents.a2a.server.base_executor import BaseA2AExecutor, StatusUpdateParams
21
+ from aip_agents.agent.interfaces import LangflowAgentProtocol
22
+ from aip_agents.utils.logger import get_logger
23
+
24
+ logger = get_logger(__name__)
25
+
26
+
27
+ class LangflowA2AExecutor(BaseA2AExecutor, ABC):
28
+ """A2A executor for Langflow agents.
29
+
30
+ This class extends BaseA2AExecutor to provide A2A execution capabilities
31
+ for Langflow agents. It follows the same patterns as LangGraphA2AExecutor
32
+ but handles Langflow-specific streaming and execution logic.
33
+
34
+ Attributes:
35
+ agent: The LangflowAgent-compatible instance to be executed.
36
+ """
37
+
38
+ agent: LangflowAgentProtocol
39
+
40
+ def __init__(self, langflow_agent_instance: LangflowAgentProtocol) -> None:
41
+ """Initialize the LangflowA2AExecutor.
42
+
43
+ Args:
44
+ langflow_agent_instance: Component implementing `LangflowAgentProtocol`.
45
+
46
+ Raises:
47
+ TypeError: If the agent does not satisfy `LangflowAgentProtocol`.
48
+ """
49
+ super().__init__()
50
+
51
+ if not isinstance(langflow_agent_instance, LangflowAgentProtocol):
52
+ type_name = type(langflow_agent_instance).__name__
53
+ raise TypeError(
54
+ f"LangflowA2AExecutor expected an agent implementing LangflowAgentProtocol, got {type_name}"
55
+ )
56
+
57
+ self.agent = langflow_agent_instance
58
+ logger.info(f"Initialized LangflowA2AExecutor for agent '{self.agent.name}'")
59
+
60
+ async def execute(
61
+ self,
62
+ context: RequestContext,
63
+ event_queue: EventQueue,
64
+ ) -> None:
65
+ """Process an incoming agent request using a Langflow agent.
66
+
67
+ This method handles the execution lifecycle for Langflow agents:
68
+ 1. Performs initial validation and setup
69
+ 2. Creates agent processing coroutine
70
+ 3. Manages the execution lifecycle through BaseA2AExecutor
71
+
72
+ Args:
73
+ context: The A2A request context containing message details,
74
+ task ID, and context ID.
75
+ event_queue: The queue for sending A2A events back to the server.
76
+ """
77
+ updater, query, metadata = await self._handle_initial_execute_checks(context, event_queue)
78
+ if not updater or query is None:
79
+ return
80
+
81
+ agent_processing_coro = self._process_stream(
82
+ query=query,
83
+ updater=updater,
84
+ task_id=context.task_id,
85
+ context_id=context.context_id,
86
+ event_queue=event_queue,
87
+ metadata=metadata,
88
+ )
89
+
90
+ await self._execute_agent_processing(
91
+ agent_processing_coro=agent_processing_coro,
92
+ updater=updater,
93
+ task_id=context.task_id,
94
+ context_id=context.context_id,
95
+ )
96
+
97
+ def _get_configurable_kwargs(self, task_id: str) -> dict[str, typing.Any]:
98
+ """Get configurable kwargs for agent execution.
99
+
100
+ For Langflow agents, we use thread_id for session management.
101
+
102
+ Args:
103
+ task_id: The A2A task ID to use as thread_id.
104
+
105
+ Returns:
106
+ Dictionary with configurable parameters for the agent.
107
+ """
108
+ return {"configurable": {"thread_id": task_id}}
109
+
110
+ async def _process_stream( # noqa: PLR0913
111
+ self,
112
+ query: str,
113
+ updater: TaskUpdater,
114
+ task_id: str,
115
+ context_id: str,
116
+ event_queue: EventQueue,
117
+ metadata: dict[str, typing.Any] | None = None,
118
+ ) -> None:
119
+ """Process the streaming response from a Langflow agent.
120
+
121
+ This method invokes the agent's arun_a2a_stream method and processes
122
+ the A2A events it yields. It handles event routing through the base
123
+ class's _handle_stream_event method.
124
+
125
+ Args:
126
+ query: The query string to be processed by the agent.
127
+ updater: The TaskUpdater instance for sending status updates.
128
+ task_id: The A2A task ID.
129
+ context_id: The A2A context ID.
130
+ event_queue: The A2A event queue for sending artifact events.
131
+ metadata: Optional metadata from the A2A request.
132
+
133
+ Raises:
134
+ asyncio.CancelledError: If the task is cancelled externally.
135
+ Exception: If any other error occurs during streaming.
136
+ """
137
+ try:
138
+ kwargs = self._get_configurable_kwargs(task_id)
139
+ if metadata:
140
+ kwargs["metadata"] = metadata
141
+
142
+ logger.debug(f"Starting Langflow agent stream for task {task_id}")
143
+
144
+ current_metadata: dict[str, typing.Any] = metadata.copy() if metadata else {}
145
+
146
+ async for chunk in self.agent.arun_a2a_stream(query=query, **kwargs):
147
+ chunk_metadata = chunk.get("metadata")
148
+ if chunk_metadata and isinstance(chunk_metadata, dict):
149
+ try:
150
+ current_metadata.update(chunk_metadata)
151
+ except Exception as e:
152
+ logger.warning(f"Invalid metadata in chunk: {chunk_metadata}, error: {e}")
153
+
154
+ should_terminate = await self._handle_stream_event(
155
+ chunk=chunk,
156
+ updater=updater,
157
+ task_id=task_id,
158
+ context_id=context_id,
159
+ event_queue=event_queue,
160
+ metadata=current_metadata if current_metadata else None,
161
+ )
162
+
163
+ if should_terminate:
164
+ logger.debug(f"Stream terminated for task {task_id}")
165
+ return
166
+
167
+ except Exception as e:
168
+ logger.error(f"Error during Langflow agent streaming for task {task_id}: {e}", exc_info=True)
169
+
170
+ await self._update_status(
171
+ updater,
172
+ TaskState.failed,
173
+ message=new_agent_text_message(
174
+ f"Error during Langflow execution: {str(e)}",
175
+ context_id=context_id,
176
+ task_id=task_id,
177
+ ),
178
+ params=StatusUpdateParams(final=True, task_id=task_id, context_id=context_id),
179
+ )
180
+ raise
@@ -0,0 +1,270 @@
1
+ """Base executor class for LangChain-based A2A executors.
2
+
3
+ This module provides a common base class for executors that work with LangChain-based
4
+ agents, such as LangChainAgent and LangGraphAgent. It implements shared functionality
5
+ for handling streaming responses and managing agent execution.
6
+
7
+ Authors:
8
+ Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
9
+ Putu Ravindra Wiguna (putu.r.wiguna@gdplabs.id)
10
+ """
11
+
12
+ import asyncio
13
+ from abc import ABC
14
+ from typing import Any
15
+
16
+ from a2a.server.agent_execution import RequestContext
17
+ from a2a.server.events.event_queue import EventQueue
18
+ from a2a.server.tasks import TaskUpdater
19
+ from a2a.types import TaskState
20
+ from a2a.utils import new_agent_text_message
21
+
22
+ from aip_agents.a2a.server.base_executor import BaseA2AExecutor, StatusUpdateParams
23
+ from aip_agents.agent.interfaces import LangGraphAgentProtocol
24
+ from aip_agents.schema.step_limit import StepLimitConfig
25
+ from aip_agents.utils.logger import get_logger
26
+
27
+ logger = get_logger(__name__)
28
+
29
+
30
+ class LangGraphA2AExecutor(BaseA2AExecutor, ABC):
31
+ """Base class for LangChain-based A2A executors.
32
+
33
+ This class extends BaseA2AExecutor to provide common functionality for executors
34
+ that work with LangChain-based agents (LangChainAgent and LangGraphAgent).
35
+ It implements shared methods for handling streaming responses and managing
36
+ agent execution, while leaving agent-specific initialization to subclasses.
37
+
38
+ Attributes:
39
+ agent (LangGraphAgentProtocol): The LangChain-based agent instance to be executed.
40
+ """
41
+
42
+ agent: LangGraphAgentProtocol
43
+
44
+ def __init__(self, langgraph_agent_instance: LangGraphAgentProtocol) -> None:
45
+ """Initializes the LangGraphA2AExecutor.
46
+
47
+ Args:
48
+ langgraph_agent_instance: Component implementing `LangGraphAgentProtocol`.
49
+
50
+ Raises:
51
+ TypeError: If the provided agent does not satisfy `LangGraphAgentProtocol`.
52
+ """
53
+ super().__init__()
54
+
55
+ if not isinstance(langgraph_agent_instance, LangGraphAgentProtocol):
56
+ _type_name = type(langgraph_agent_instance).__name__
57
+ raise TypeError(
58
+ f"LangGraphA2AExecutor expected an agent implementing LangGraphAgentProtocol, got {_type_name}"
59
+ )
60
+ self.agent = langgraph_agent_instance
61
+
62
+ async def execute(
63
+ self,
64
+ context: RequestContext,
65
+ event_queue: EventQueue,
66
+ ) -> None:
67
+ """Processes an incoming agent request using a LangChain-based agent.
68
+
69
+ This method first performs initial checks using _handle_initial_execute_checks.
70
+ If successful, it prepares the _process_stream coroutine and passes it to
71
+ _execute_agent_processing from the base class to manage its lifecycle.
72
+ The _process_stream method is responsible for calling the agent's
73
+ arun_a2a_stream and handling its output.
74
+
75
+ Args:
76
+ context (RequestContext): The A2A request context containing message details,
77
+ task ID, and context ID.
78
+ event_queue (EventQueue): The queue for sending A2A events (task status,
79
+ artifacts) back to the server.
80
+ """
81
+ updater, query, metadata = await self._handle_initial_execute_checks(context, event_queue)
82
+ if not updater or query is None: # Checks failed, status already sent
83
+ return
84
+
85
+ agent_processing_coro = self._process_stream(
86
+ query=query,
87
+ updater=updater,
88
+ task_id=context.task_id,
89
+ context_id=context.context_id,
90
+ event_queue=event_queue,
91
+ metadata=metadata,
92
+ )
93
+
94
+ await self._execute_agent_processing(
95
+ agent_processing_coro=agent_processing_coro,
96
+ updater=updater,
97
+ task_id=context.task_id,
98
+ context_id=context.context_id,
99
+ )
100
+
101
+ def _get_configurable_kwargs(self, task_id: str) -> dict[str, Any]:
102
+ """Get configurable kwargs for agent delegation.
103
+
104
+ Args:
105
+ task_id: The A2A task ID.
106
+
107
+ Returns:
108
+ dict[str, Any]: A dictionary with 'configurable' key if the agent
109
+ has 'thread_id_key', otherwise an empty dictionary.
110
+ """
111
+ if hasattr(self.agent, "thread_id_key"):
112
+ return {"configurable": {self.agent.thread_id_key: task_id}}
113
+ return {}
114
+
115
+ def _build_agent_kwargs(
116
+ self,
117
+ task_id: str,
118
+ metadata: dict[str, Any] | None,
119
+ ) -> dict[str, Any]:
120
+ """Build kwargs for agent stream execution from task and metadata.
121
+
122
+ Args:
123
+ task_id: The A2A task ID for configurable threading settings.
124
+ metadata: Optional request metadata, including files and overrides.
125
+
126
+ Returns:
127
+ dict[str, Any]: Keyword arguments to pass to the agent's stream method.
128
+ """
129
+ kwargs = self._get_configurable_kwargs(task_id)
130
+
131
+ files: list[str | dict[str, Any]] = self._extract_files_from_metadata(metadata)
132
+ if metadata is not None:
133
+ kwargs["metadata"] = metadata
134
+ if isinstance(metadata, dict):
135
+ raw_user_id = metadata.get("memory_user_id") or metadata.get("user_id")
136
+ if raw_user_id:
137
+ kwargs["memory_user_id"] = str(raw_user_id)
138
+
139
+ raw_pii_mapping = metadata.get("pii_mapping")
140
+ if isinstance(raw_pii_mapping, dict) and raw_pii_mapping:
141
+ kwargs["pii_mapping"] = dict(raw_pii_mapping)
142
+
143
+ # Extract invocation-level step limit overrides (Docs-1)
144
+ raw_step_limit_config = metadata.get("step_limit_config")
145
+ if isinstance(raw_step_limit_config, dict | StepLimitConfig):
146
+ kwargs["step_limit_config"] = raw_step_limit_config
147
+ if files:
148
+ kwargs["files"] = files
149
+
150
+ return kwargs
151
+
152
+ async def _process_stream( # noqa: PLR0913
153
+ self,
154
+ query: str,
155
+ updater: TaskUpdater,
156
+ task_id: str,
157
+ context_id: str,
158
+ event_queue: EventQueue,
159
+ metadata: dict[str, Any] | None = None,
160
+ ) -> None:
161
+ """Processes the streaming response from a LangChain-based agent.
162
+
163
+ This coroutine invokes the agent.arun_a2a_stream method with the given query and metadata.
164
+ It then iterates over the asynchronous stream of dictionary chunks yielded by
165
+ the agent. Each chunk is passed to _handle_stream_event from the base class
166
+ to interpret common A2A statuses (working, completed, failed, etc.) and update
167
+ the A2A task accordingly.
168
+
169
+ If asyncio.CancelledError is raised (typically from the task managed by
170
+ _execute_agent_processing), it is re-raised to be handled by the base class.
171
+ Other exceptions during streaming are caught, logged, an A2A 'failed' status
172
+ is sent, and the exception is re-raised.
173
+
174
+ Args:
175
+ query (str): The query string to be processed by the agent.
176
+ updater (TaskUpdater): The TaskUpdater instance for sending status updates.
177
+ task_id (str): The A2A task ID.
178
+ context_id (str): The A2A context ID.
179
+ event_queue (EventQueue): The A2A event queue for sending artifact events.
180
+ metadata (dict[str, Any] | None): Optional metadata from the A2A request.
181
+
182
+ Raises:
183
+ asyncio.CancelledError: If the task is cancelled externally.
184
+ Exception: If any other error occurs during the agent's stream processing.
185
+ """
186
+ stream = None
187
+ try:
188
+ kwargs = self._build_agent_kwargs(task_id=task_id, metadata=metadata)
189
+
190
+ stream = self.agent.arun_a2a_stream(query=query, **kwargs)
191
+
192
+ current_metadata: dict[str, Any] = metadata.copy() if metadata else {}
193
+
194
+ async for chunk in stream:
195
+ chunk_metadata = chunk.get("metadata")
196
+ if chunk_metadata is not None:
197
+ try:
198
+ current_metadata.update(chunk_metadata)
199
+ except Exception as e:
200
+ logger.warning(f"Invalid metadata payload from chunk: {chunk_metadata}, error: {e}")
201
+
202
+ should_terminate = await self._handle_stream_event(
203
+ chunk=chunk,
204
+ updater=updater,
205
+ task_id=task_id,
206
+ context_id=context_id,
207
+ event_queue=event_queue,
208
+ metadata=current_metadata if current_metadata else None,
209
+ )
210
+ if should_terminate:
211
+ return
212
+
213
+ except asyncio.CancelledError:
214
+ logger.info(f"LangChain stream processing for task {task_id} was cancelled.")
215
+ raise
216
+ except Exception as e:
217
+ logger.error(
218
+ f"Error during LangChain agent streaming for task {task_id}: {e}",
219
+ exc_info=True,
220
+ )
221
+ await self._update_status(
222
+ updater,
223
+ TaskState.failed,
224
+ message=new_agent_text_message(
225
+ f"Error during streaming: {str(e)}",
226
+ context_id=context_id,
227
+ task_id=task_id,
228
+ ),
229
+ params=StatusUpdateParams(final=True, task_id=task_id, context_id=context_id),
230
+ )
231
+ raise
232
+
233
+ @staticmethod
234
+ def _extract_files_from_metadata(metadata: dict[str, Any] | None) -> list[str | dict[str, Any]]:
235
+ """Extract file paths from metadata, removing them since they are passed via kwargs.
236
+
237
+ Args:
238
+ metadata: Metadata dict from the request, potentially containing files.
239
+
240
+ Returns:
241
+ List of non-empty file path strings or file metadata dictionaries.
242
+ """
243
+ if not isinstance(metadata, dict):
244
+ return []
245
+
246
+ try:
247
+ raw_files = metadata.pop("files", None)
248
+ except AttributeError:
249
+ return []
250
+ if raw_files is None:
251
+ return []
252
+
253
+ if not isinstance(raw_files, list):
254
+ logger.warning("Invalid 'files' metadata received; expected list of strings or dicts.")
255
+ return []
256
+
257
+ normalized_files: list[str | dict[str, Any]] = []
258
+ invalid_entry_logged = False
259
+ for entry in raw_files:
260
+ if isinstance(entry, str) and entry:
261
+ normalized_files.append(entry)
262
+ continue
263
+ if isinstance(entry, dict):
264
+ normalized_files.append(entry)
265
+ continue
266
+ if not invalid_entry_logged:
267
+ logger.warning("Invalid file metadata entry received; expected string or dict.")
268
+ invalid_entry_logged = True
269
+
270
+ return normalized_files