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.
- aip_agents/__init__.py +65 -0
- aip_agents/a2a/__init__.py +19 -0
- aip_agents/a2a/server/__init__.py +10 -0
- aip_agents/a2a/server/base_executor.py +1086 -0
- aip_agents/a2a/server/google_adk_executor.py +198 -0
- aip_agents/a2a/server/langflow_executor.py +180 -0
- aip_agents/a2a/server/langgraph_executor.py +270 -0
- aip_agents/a2a/types.py +232 -0
- aip_agents/agent/__init__.py +27 -0
- aip_agents/agent/base_agent.py +970 -0
- aip_agents/agent/base_langgraph_agent.py +2942 -0
- aip_agents/agent/google_adk_agent.py +926 -0
- aip_agents/agent/google_adk_constants.py +6 -0
- aip_agents/agent/hitl/__init__.py +24 -0
- aip_agents/agent/hitl/config.py +28 -0
- aip_agents/agent/hitl/langgraph_hitl_mixin.py +515 -0
- aip_agents/agent/hitl/manager.py +532 -0
- aip_agents/agent/hitl/models.py +18 -0
- aip_agents/agent/hitl/prompt/__init__.py +9 -0
- aip_agents/agent/hitl/prompt/base.py +42 -0
- aip_agents/agent/hitl/prompt/deferred.py +73 -0
- aip_agents/agent/hitl/registry.py +149 -0
- aip_agents/agent/interface.py +138 -0
- aip_agents/agent/interfaces.py +65 -0
- aip_agents/agent/langflow_agent.py +464 -0
- aip_agents/agent/langgraph_memory_enhancer_agent.py +433 -0
- aip_agents/agent/langgraph_react_agent.py +2514 -0
- aip_agents/agent/system_instruction_context.py +34 -0
- aip_agents/clients/__init__.py +10 -0
- aip_agents/clients/langflow/__init__.py +10 -0
- aip_agents/clients/langflow/client.py +477 -0
- aip_agents/clients/langflow/types.py +18 -0
- aip_agents/constants.py +23 -0
- aip_agents/credentials/manager.py +132 -0
- aip_agents/examples/__init__.py +5 -0
- aip_agents/examples/compare_streaming_client.py +783 -0
- aip_agents/examples/compare_streaming_server.py +142 -0
- aip_agents/examples/demo_memory_recall.py +401 -0
- aip_agents/examples/hello_world_a2a_google_adk_client.py +49 -0
- aip_agents/examples/hello_world_a2a_google_adk_client_agent.py +48 -0
- aip_agents/examples/hello_world_a2a_google_adk_client_streaming.py +60 -0
- aip_agents/examples/hello_world_a2a_google_adk_server.py +79 -0
- aip_agents/examples/hello_world_a2a_langchain_client.py +39 -0
- aip_agents/examples/hello_world_a2a_langchain_client_agent.py +39 -0
- aip_agents/examples/hello_world_a2a_langchain_client_lm_invoker.py +37 -0
- aip_agents/examples/hello_world_a2a_langchain_client_streaming.py +41 -0
- aip_agents/examples/hello_world_a2a_langchain_reference_client_streaming.py +60 -0
- aip_agents/examples/hello_world_a2a_langchain_reference_server.py +105 -0
- aip_agents/examples/hello_world_a2a_langchain_server.py +79 -0
- aip_agents/examples/hello_world_a2a_langchain_server_lm_invoker.py +78 -0
- aip_agents/examples/hello_world_a2a_langflow_client.py +83 -0
- aip_agents/examples/hello_world_a2a_langflow_server.py +82 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_client.py +73 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_client_streaming.py +76 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_server.py +92 -0
- aip_agents/examples/hello_world_a2a_langgraph_client.py +54 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_agent.py +54 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_agent_lm_invoker.py +32 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming.py +50 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming_lm_invoker.py +44 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming_tool_streaming.py +92 -0
- aip_agents/examples/hello_world_a2a_langgraph_server.py +84 -0
- aip_agents/examples/hello_world_a2a_langgraph_server_lm_invoker.py +79 -0
- aip_agents/examples/hello_world_a2a_langgraph_server_tool_streaming.py +132 -0
- aip_agents/examples/hello_world_a2a_mcp_langgraph.py +196 -0
- aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_client.py +244 -0
- aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_server.py +251 -0
- aip_agents/examples/hello_world_a2a_with_metadata_langchain_client.py +57 -0
- aip_agents/examples/hello_world_a2a_with_metadata_langchain_server_lm_invoker.py +80 -0
- aip_agents/examples/hello_world_google_adk.py +41 -0
- aip_agents/examples/hello_world_google_adk_mcp_http.py +34 -0
- aip_agents/examples/hello_world_google_adk_mcp_http_stream.py +40 -0
- aip_agents/examples/hello_world_google_adk_mcp_sse.py +44 -0
- aip_agents/examples/hello_world_google_adk_mcp_sse_stream.py +48 -0
- aip_agents/examples/hello_world_google_adk_mcp_stdio.py +44 -0
- aip_agents/examples/hello_world_google_adk_mcp_stdio_stream.py +48 -0
- aip_agents/examples/hello_world_google_adk_stream.py +44 -0
- aip_agents/examples/hello_world_langchain.py +28 -0
- aip_agents/examples/hello_world_langchain_lm_invoker.py +15 -0
- aip_agents/examples/hello_world_langchain_mcp_http.py +34 -0
- aip_agents/examples/hello_world_langchain_mcp_http_interactive.py +130 -0
- aip_agents/examples/hello_world_langchain_mcp_http_stream.py +42 -0
- aip_agents/examples/hello_world_langchain_mcp_multi_server.py +155 -0
- aip_agents/examples/hello_world_langchain_mcp_sse.py +34 -0
- aip_agents/examples/hello_world_langchain_mcp_sse_stream.py +40 -0
- aip_agents/examples/hello_world_langchain_mcp_stdio.py +30 -0
- aip_agents/examples/hello_world_langchain_mcp_stdio_stream.py +41 -0
- aip_agents/examples/hello_world_langchain_stream.py +36 -0
- aip_agents/examples/hello_world_langchain_stream_lm_invoker.py +39 -0
- aip_agents/examples/hello_world_langflow_agent.py +163 -0
- aip_agents/examples/hello_world_langgraph.py +39 -0
- aip_agents/examples/hello_world_langgraph_bosa_twitter.py +41 -0
- aip_agents/examples/hello_world_langgraph_mcp_http.py +31 -0
- aip_agents/examples/hello_world_langgraph_mcp_http_stream.py +34 -0
- aip_agents/examples/hello_world_langgraph_mcp_sse.py +35 -0
- aip_agents/examples/hello_world_langgraph_mcp_sse_stream.py +50 -0
- aip_agents/examples/hello_world_langgraph_mcp_stdio.py +35 -0
- aip_agents/examples/hello_world_langgraph_mcp_stdio_stream.py +50 -0
- aip_agents/examples/hello_world_langgraph_stream.py +43 -0
- aip_agents/examples/hello_world_langgraph_stream_lm_invoker.py +37 -0
- aip_agents/examples/hello_world_model_switch_cli.py +210 -0
- aip_agents/examples/hello_world_multi_agent_adk.py +75 -0
- aip_agents/examples/hello_world_multi_agent_langchain.py +54 -0
- aip_agents/examples/hello_world_multi_agent_langgraph.py +66 -0
- aip_agents/examples/hello_world_multi_agent_langgraph_lm_invoker.py +69 -0
- aip_agents/examples/hello_world_pii_logger.py +21 -0
- aip_agents/examples/hello_world_sentry.py +133 -0
- aip_agents/examples/hello_world_step_limits.py +273 -0
- aip_agents/examples/hello_world_stock_a2a_server.py +103 -0
- aip_agents/examples/hello_world_tool_output_client.py +46 -0
- aip_agents/examples/hello_world_tool_output_server.py +114 -0
- aip_agents/examples/hitl_demo.py +724 -0
- aip_agents/examples/mcp_configs/configs.py +63 -0
- aip_agents/examples/mcp_servers/common.py +76 -0
- aip_agents/examples/mcp_servers/mcp_name.py +29 -0
- aip_agents/examples/mcp_servers/mcp_server_http.py +19 -0
- aip_agents/examples/mcp_servers/mcp_server_sse.py +19 -0
- aip_agents/examples/mcp_servers/mcp_server_stdio.py +19 -0
- aip_agents/examples/mcp_servers/mcp_time.py +10 -0
- aip_agents/examples/pii_demo_langgraph_client.py +69 -0
- aip_agents/examples/pii_demo_langgraph_server.py +126 -0
- aip_agents/examples/pii_demo_multi_agent_client.py +80 -0
- aip_agents/examples/pii_demo_multi_agent_server.py +247 -0
- aip_agents/examples/todolist_planning_a2a_langchain_client.py +70 -0
- aip_agents/examples/todolist_planning_a2a_langgraph_server.py +88 -0
- aip_agents/examples/tools/__init__.py +27 -0
- aip_agents/examples/tools/adk_arithmetic_tools.py +36 -0
- aip_agents/examples/tools/adk_weather_tool.py +60 -0
- aip_agents/examples/tools/data_generator_tool.py +103 -0
- aip_agents/examples/tools/data_visualization_tool.py +312 -0
- aip_agents/examples/tools/image_artifact_tool.py +136 -0
- aip_agents/examples/tools/langchain_arithmetic_tools.py +26 -0
- aip_agents/examples/tools/langchain_currency_exchange_tool.py +88 -0
- aip_agents/examples/tools/langchain_graph_artifact_tool.py +172 -0
- aip_agents/examples/tools/langchain_weather_tool.py +48 -0
- aip_agents/examples/tools/langgraph_streaming_tool.py +130 -0
- aip_agents/examples/tools/mock_retrieval_tool.py +56 -0
- aip_agents/examples/tools/pii_demo_tools.py +189 -0
- aip_agents/examples/tools/random_chart_tool.py +142 -0
- aip_agents/examples/tools/serper_tool.py +202 -0
- aip_agents/examples/tools/stock_tools.py +82 -0
- aip_agents/examples/tools/table_generator_tool.py +167 -0
- aip_agents/examples/tools/time_tool.py +82 -0
- aip_agents/examples/tools/weather_forecast_tool.py +38 -0
- aip_agents/executor/agent_executor.py +473 -0
- aip_agents/executor/base.py +48 -0
- aip_agents/mcp/__init__.py +1 -0
- aip_agents/mcp/client/__init__.py +14 -0
- aip_agents/mcp/client/base_mcp_client.py +369 -0
- aip_agents/mcp/client/connection_manager.py +193 -0
- aip_agents/mcp/client/google_adk/__init__.py +11 -0
- aip_agents/mcp/client/google_adk/client.py +381 -0
- aip_agents/mcp/client/langchain/__init__.py +11 -0
- aip_agents/mcp/client/langchain/client.py +265 -0
- aip_agents/mcp/client/persistent_session.py +359 -0
- aip_agents/mcp/client/session_pool.py +351 -0
- aip_agents/mcp/client/transports.py +215 -0
- aip_agents/mcp/utils/__init__.py +7 -0
- aip_agents/mcp/utils/config_validator.py +139 -0
- aip_agents/memory/__init__.py +14 -0
- aip_agents/memory/adapters/__init__.py +10 -0
- aip_agents/memory/adapters/base_adapter.py +717 -0
- aip_agents/memory/adapters/mem0.py +84 -0
- aip_agents/memory/base.py +84 -0
- aip_agents/memory/constants.py +49 -0
- aip_agents/memory/factory.py +86 -0
- aip_agents/memory/guidance.py +20 -0
- aip_agents/memory/simple_memory.py +47 -0
- aip_agents/middleware/__init__.py +17 -0
- aip_agents/middleware/base.py +88 -0
- aip_agents/middleware/manager.py +128 -0
- aip_agents/middleware/todolist.py +274 -0
- aip_agents/schema/__init__.py +69 -0
- aip_agents/schema/a2a.py +56 -0
- aip_agents/schema/agent.py +111 -0
- aip_agents/schema/hitl.py +157 -0
- aip_agents/schema/langgraph.py +37 -0
- aip_agents/schema/model_id.py +97 -0
- aip_agents/schema/step_limit.py +108 -0
- aip_agents/schema/storage.py +40 -0
- aip_agents/sentry/__init__.py +11 -0
- aip_agents/sentry/sentry.py +151 -0
- aip_agents/storage/__init__.py +41 -0
- aip_agents/storage/base.py +85 -0
- aip_agents/storage/clients/__init__.py +12 -0
- aip_agents/storage/clients/minio_client.py +318 -0
- aip_agents/storage/config.py +62 -0
- aip_agents/storage/providers/__init__.py +15 -0
- aip_agents/storage/providers/base.py +106 -0
- aip_agents/storage/providers/memory.py +114 -0
- aip_agents/storage/providers/object_storage.py +214 -0
- aip_agents/tools/__init__.py +33 -0
- aip_agents/tools/bosa_tools.py +105 -0
- aip_agents/tools/browser_use/__init__.py +82 -0
- aip_agents/tools/browser_use/action_parser.py +103 -0
- aip_agents/tools/browser_use/browser_use_tool.py +1112 -0
- aip_agents/tools/browser_use/llm_config.py +120 -0
- aip_agents/tools/browser_use/minio_storage.py +198 -0
- aip_agents/tools/browser_use/schemas.py +119 -0
- aip_agents/tools/browser_use/session.py +76 -0
- aip_agents/tools/browser_use/session_errors.py +132 -0
- aip_agents/tools/browser_use/steel_session_recording.py +317 -0
- aip_agents/tools/browser_use/streaming.py +813 -0
- aip_agents/tools/browser_use/structured_data_parser.py +257 -0
- aip_agents/tools/browser_use/structured_data_recovery.py +204 -0
- aip_agents/tools/browser_use/types.py +78 -0
- aip_agents/tools/code_sandbox/__init__.py +26 -0
- aip_agents/tools/code_sandbox/constant.py +13 -0
- aip_agents/tools/code_sandbox/e2b_cloud_sandbox_extended.py +257 -0
- aip_agents/tools/code_sandbox/e2b_sandbox_tool.py +411 -0
- aip_agents/tools/constants.py +165 -0
- aip_agents/tools/document_loader/__init__.py +44 -0
- aip_agents/tools/document_loader/base_reader.py +302 -0
- aip_agents/tools/document_loader/docx_reader_tool.py +68 -0
- aip_agents/tools/document_loader/excel_reader_tool.py +171 -0
- aip_agents/tools/document_loader/pdf_reader_tool.py +79 -0
- aip_agents/tools/document_loader/pdf_splitter.py +169 -0
- aip_agents/tools/gl_connector/__init__.py +5 -0
- aip_agents/tools/gl_connector/tool.py +351 -0
- aip_agents/tools/memory_search/__init__.py +22 -0
- aip_agents/tools/memory_search/base.py +200 -0
- aip_agents/tools/memory_search/mem0.py +258 -0
- aip_agents/tools/memory_search/schema.py +48 -0
- aip_agents/tools/memory_search_tool.py +26 -0
- aip_agents/tools/time_tool.py +117 -0
- aip_agents/tools/tool_config_injector.py +300 -0
- aip_agents/tools/web_search/__init__.py +15 -0
- aip_agents/tools/web_search/serper_tool.py +187 -0
- aip_agents/types/__init__.py +70 -0
- aip_agents/types/a2a_events.py +13 -0
- aip_agents/utils/__init__.py +79 -0
- aip_agents/utils/a2a_connector.py +1757 -0
- aip_agents/utils/artifact_helpers.py +502 -0
- aip_agents/utils/constants.py +22 -0
- aip_agents/utils/datetime/__init__.py +34 -0
- aip_agents/utils/datetime/normalization.py +231 -0
- aip_agents/utils/datetime/timezone.py +206 -0
- aip_agents/utils/env_loader.py +27 -0
- aip_agents/utils/event_handler_registry.py +58 -0
- aip_agents/utils/file_prompt_utils.py +176 -0
- aip_agents/utils/final_response_builder.py +211 -0
- aip_agents/utils/formatter_llm_client.py +231 -0
- aip_agents/utils/langgraph/__init__.py +19 -0
- aip_agents/utils/langgraph/converter.py +128 -0
- aip_agents/utils/langgraph/tool_managers/__init__.py +15 -0
- aip_agents/utils/langgraph/tool_managers/a2a_tool_manager.py +99 -0
- aip_agents/utils/langgraph/tool_managers/base_tool_manager.py +66 -0
- aip_agents/utils/langgraph/tool_managers/delegation_tool_manager.py +1071 -0
- aip_agents/utils/langgraph/tool_output_management.py +967 -0
- aip_agents/utils/logger.py +195 -0
- aip_agents/utils/metadata/__init__.py +27 -0
- aip_agents/utils/metadata/activity_metadata_helper.py +407 -0
- aip_agents/utils/metadata/activity_narrative/__init__.py +35 -0
- aip_agents/utils/metadata/activity_narrative/builder.py +817 -0
- aip_agents/utils/metadata/activity_narrative/constants.py +51 -0
- aip_agents/utils/metadata/activity_narrative/context.py +49 -0
- aip_agents/utils/metadata/activity_narrative/formatters.py +230 -0
- aip_agents/utils/metadata/activity_narrative/utils.py +35 -0
- aip_agents/utils/metadata/schemas/__init__.py +16 -0
- aip_agents/utils/metadata/schemas/activity_schema.py +29 -0
- aip_agents/utils/metadata/schemas/thinking_schema.py +31 -0
- aip_agents/utils/metadata/thinking_metadata_helper.py +38 -0
- aip_agents/utils/metadata_helper.py +358 -0
- aip_agents/utils/name_preprocessor/__init__.py +17 -0
- aip_agents/utils/name_preprocessor/base_name_preprocessor.py +73 -0
- aip_agents/utils/name_preprocessor/google_name_preprocessor.py +100 -0
- aip_agents/utils/name_preprocessor/name_preprocessor.py +87 -0
- aip_agents/utils/name_preprocessor/openai_name_preprocessor.py +48 -0
- aip_agents/utils/pii/__init__.py +25 -0
- aip_agents/utils/pii/pii_handler.py +397 -0
- aip_agents/utils/pii/pii_helper.py +207 -0
- aip_agents/utils/pii/uuid_deanonymizer_mapping.py +195 -0
- aip_agents/utils/reference_helper.py +273 -0
- aip_agents/utils/sse_chunk_transformer.py +831 -0
- aip_agents/utils/step_limit_manager.py +265 -0
- aip_agents/utils/token_usage_helper.py +156 -0
- aip_agents_binary-0.5.20.dist-info/METADATA +681 -0
- aip_agents_binary-0.5.20.dist-info/RECORD +280 -0
- aip_agents_binary-0.5.20.dist-info/WHEEL +5 -0
- 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
|