aip-agents-binary 0.5.25b1__py3-none-macosx_13_0_arm64.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/__init__.pyi +19 -0
- aip_agents/a2a/__init__.py +19 -0
- aip_agents/a2a/__init__.pyi +3 -0
- aip_agents/a2a/server/__init__.py +10 -0
- aip_agents/a2a/server/__init__.pyi +4 -0
- aip_agents/a2a/server/base_executor.py +1086 -0
- aip_agents/a2a/server/base_executor.pyi +73 -0
- aip_agents/a2a/server/google_adk_executor.py +198 -0
- aip_agents/a2a/server/google_adk_executor.pyi +51 -0
- aip_agents/a2a/server/langflow_executor.py +180 -0
- aip_agents/a2a/server/langflow_executor.pyi +43 -0
- aip_agents/a2a/server/langgraph_executor.py +270 -0
- aip_agents/a2a/server/langgraph_executor.pyi +47 -0
- aip_agents/a2a/types.py +232 -0
- aip_agents/a2a/types.pyi +132 -0
- aip_agents/agent/__init__.py +27 -0
- aip_agents/agent/__init__.pyi +9 -0
- aip_agents/agent/base_agent.py +970 -0
- aip_agents/agent/base_agent.pyi +221 -0
- aip_agents/agent/base_langgraph_agent.py +2948 -0
- aip_agents/agent/base_langgraph_agent.pyi +232 -0
- aip_agents/agent/google_adk_agent.py +926 -0
- aip_agents/agent/google_adk_agent.pyi +141 -0
- aip_agents/agent/google_adk_constants.py +6 -0
- aip_agents/agent/google_adk_constants.pyi +3 -0
- aip_agents/agent/hitl/__init__.py +24 -0
- aip_agents/agent/hitl/__init__.pyi +6 -0
- aip_agents/agent/hitl/config.py +28 -0
- aip_agents/agent/hitl/config.pyi +15 -0
- aip_agents/agent/hitl/langgraph_hitl_mixin.py +515 -0
- aip_agents/agent/hitl/langgraph_hitl_mixin.pyi +42 -0
- aip_agents/agent/hitl/manager.py +532 -0
- aip_agents/agent/hitl/manager.pyi +200 -0
- aip_agents/agent/hitl/models.py +18 -0
- aip_agents/agent/hitl/models.pyi +3 -0
- aip_agents/agent/hitl/prompt/__init__.py +9 -0
- aip_agents/agent/hitl/prompt/__init__.pyi +4 -0
- aip_agents/agent/hitl/prompt/base.py +42 -0
- aip_agents/agent/hitl/prompt/base.pyi +24 -0
- aip_agents/agent/hitl/prompt/deferred.py +73 -0
- aip_agents/agent/hitl/prompt/deferred.pyi +30 -0
- aip_agents/agent/hitl/registry.py +149 -0
- aip_agents/agent/hitl/registry.pyi +101 -0
- aip_agents/agent/interface.py +138 -0
- aip_agents/agent/interface.pyi +81 -0
- aip_agents/agent/interfaces.py +65 -0
- aip_agents/agent/interfaces.pyi +44 -0
- aip_agents/agent/langflow_agent.py +464 -0
- aip_agents/agent/langflow_agent.pyi +133 -0
- aip_agents/agent/langgraph_memory_enhancer_agent.py +433 -0
- aip_agents/agent/langgraph_memory_enhancer_agent.pyi +49 -0
- aip_agents/agent/langgraph_react_agent.py +2596 -0
- aip_agents/agent/langgraph_react_agent.pyi +131 -0
- aip_agents/agent/system_instruction_context.py +34 -0
- aip_agents/agent/system_instruction_context.pyi +13 -0
- aip_agents/clients/__init__.py +10 -0
- aip_agents/clients/__init__.pyi +4 -0
- aip_agents/clients/langflow/__init__.py +10 -0
- aip_agents/clients/langflow/__init__.pyi +4 -0
- aip_agents/clients/langflow/client.py +477 -0
- aip_agents/clients/langflow/client.pyi +140 -0
- aip_agents/clients/langflow/types.py +18 -0
- aip_agents/clients/langflow/types.pyi +7 -0
- aip_agents/constants.py +23 -0
- aip_agents/constants.pyi +7 -0
- aip_agents/credentials/manager.py +132 -0
- aip_agents/examples/__init__.py +5 -0
- aip_agents/examples/__init__.pyi +0 -0
- aip_agents/examples/compare_streaming_client.py +783 -0
- aip_agents/examples/compare_streaming_client.pyi +48 -0
- aip_agents/examples/compare_streaming_server.py +142 -0
- aip_agents/examples/compare_streaming_server.pyi +18 -0
- aip_agents/examples/demo_memory_recall.py +401 -0
- aip_agents/examples/demo_memory_recall.pyi +58 -0
- aip_agents/examples/hello_world_a2a_google_adk_client.py +49 -0
- aip_agents/examples/hello_world_a2a_google_adk_client.pyi +9 -0
- aip_agents/examples/hello_world_a2a_google_adk_client_agent.py +48 -0
- aip_agents/examples/hello_world_a2a_google_adk_client_agent.pyi +9 -0
- aip_agents/examples/hello_world_a2a_google_adk_client_streaming.py +60 -0
- aip_agents/examples/hello_world_a2a_google_adk_client_streaming.pyi +9 -0
- aip_agents/examples/hello_world_a2a_google_adk_server.py +79 -0
- aip_agents/examples/hello_world_a2a_google_adk_server.pyi +15 -0
- aip_agents/examples/hello_world_a2a_langchain_client.py +39 -0
- aip_agents/examples/hello_world_a2a_langchain_client.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langchain_client_agent.py +39 -0
- aip_agents/examples/hello_world_a2a_langchain_client_agent.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langchain_client_lm_invoker.py +37 -0
- aip_agents/examples/hello_world_a2a_langchain_client_lm_invoker.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langchain_client_streaming.py +41 -0
- aip_agents/examples/hello_world_a2a_langchain_client_streaming.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langchain_reference_client_streaming.py +60 -0
- aip_agents/examples/hello_world_a2a_langchain_reference_client_streaming.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langchain_reference_server.py +105 -0
- aip_agents/examples/hello_world_a2a_langchain_reference_server.pyi +15 -0
- aip_agents/examples/hello_world_a2a_langchain_server.py +79 -0
- aip_agents/examples/hello_world_a2a_langchain_server.pyi +15 -0
- aip_agents/examples/hello_world_a2a_langchain_server_lm_invoker.py +78 -0
- aip_agents/examples/hello_world_a2a_langchain_server_lm_invoker.pyi +15 -0
- aip_agents/examples/hello_world_a2a_langflow_client.py +83 -0
- aip_agents/examples/hello_world_a2a_langflow_client.pyi +9 -0
- aip_agents/examples/hello_world_a2a_langflow_server.py +82 -0
- aip_agents/examples/hello_world_a2a_langflow_server.pyi +14 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_client.py +73 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_client.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_client_streaming.py +76 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_client_streaming.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_server.py +92 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_server.pyi +16 -0
- aip_agents/examples/hello_world_a2a_langgraph_client.py +54 -0
- aip_agents/examples/hello_world_a2a_langgraph_client.pyi +9 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_agent.py +54 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_agent.pyi +9 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_agent_lm_invoker.py +32 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_agent_lm_invoker.pyi +2 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming.py +50 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming.pyi +9 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming_lm_invoker.py +44 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming_lm_invoker.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming_tool_streaming.py +92 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming_tool_streaming.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langgraph_server.py +84 -0
- aip_agents/examples/hello_world_a2a_langgraph_server.pyi +14 -0
- aip_agents/examples/hello_world_a2a_langgraph_server_lm_invoker.py +79 -0
- aip_agents/examples/hello_world_a2a_langgraph_server_lm_invoker.pyi +15 -0
- aip_agents/examples/hello_world_a2a_langgraph_server_tool_streaming.py +132 -0
- aip_agents/examples/hello_world_a2a_langgraph_server_tool_streaming.pyi +15 -0
- aip_agents/examples/hello_world_a2a_mcp_langgraph.py +196 -0
- aip_agents/examples/hello_world_a2a_mcp_langgraph.pyi +48 -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_client.pyi +48 -0
- aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_server.py +251 -0
- aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_server.pyi +45 -0
- aip_agents/examples/hello_world_a2a_with_metadata_langchain_client.py +57 -0
- aip_agents/examples/hello_world_a2a_with_metadata_langchain_client.pyi +5 -0
- aip_agents/examples/hello_world_a2a_with_metadata_langchain_server_lm_invoker.py +80 -0
- aip_agents/examples/hello_world_a2a_with_metadata_langchain_server_lm_invoker.pyi +15 -0
- aip_agents/examples/hello_world_google_adk.py +41 -0
- aip_agents/examples/hello_world_google_adk.pyi +5 -0
- aip_agents/examples/hello_world_google_adk_mcp_http.py +34 -0
- aip_agents/examples/hello_world_google_adk_mcp_http.pyi +5 -0
- aip_agents/examples/hello_world_google_adk_mcp_http_stream.py +40 -0
- aip_agents/examples/hello_world_google_adk_mcp_http_stream.pyi +5 -0
- aip_agents/examples/hello_world_google_adk_mcp_sse.py +44 -0
- aip_agents/examples/hello_world_google_adk_mcp_sse.pyi +5 -0
- aip_agents/examples/hello_world_google_adk_mcp_sse_stream.py +48 -0
- aip_agents/examples/hello_world_google_adk_mcp_sse_stream.pyi +5 -0
- aip_agents/examples/hello_world_google_adk_mcp_stdio.py +44 -0
- aip_agents/examples/hello_world_google_adk_mcp_stdio.pyi +5 -0
- aip_agents/examples/hello_world_google_adk_mcp_stdio_stream.py +48 -0
- aip_agents/examples/hello_world_google_adk_mcp_stdio_stream.pyi +5 -0
- aip_agents/examples/hello_world_google_adk_stream.py +44 -0
- aip_agents/examples/hello_world_google_adk_stream.pyi +5 -0
- aip_agents/examples/hello_world_langchain.py +28 -0
- aip_agents/examples/hello_world_langchain.pyi +5 -0
- aip_agents/examples/hello_world_langchain_lm_invoker.py +15 -0
- aip_agents/examples/hello_world_langchain_lm_invoker.pyi +2 -0
- aip_agents/examples/hello_world_langchain_mcp_http.py +34 -0
- aip_agents/examples/hello_world_langchain_mcp_http.pyi +5 -0
- aip_agents/examples/hello_world_langchain_mcp_http_interactive.py +130 -0
- aip_agents/examples/hello_world_langchain_mcp_http_interactive.pyi +16 -0
- aip_agents/examples/hello_world_langchain_mcp_http_stream.py +42 -0
- aip_agents/examples/hello_world_langchain_mcp_http_stream.pyi +5 -0
- aip_agents/examples/hello_world_langchain_mcp_multi_server.py +155 -0
- aip_agents/examples/hello_world_langchain_mcp_multi_server.pyi +18 -0
- aip_agents/examples/hello_world_langchain_mcp_sse.py +34 -0
- aip_agents/examples/hello_world_langchain_mcp_sse.pyi +5 -0
- aip_agents/examples/hello_world_langchain_mcp_sse_stream.py +40 -0
- aip_agents/examples/hello_world_langchain_mcp_sse_stream.pyi +5 -0
- aip_agents/examples/hello_world_langchain_mcp_stdio.py +30 -0
- aip_agents/examples/hello_world_langchain_mcp_stdio.pyi +5 -0
- aip_agents/examples/hello_world_langchain_mcp_stdio_stream.py +41 -0
- aip_agents/examples/hello_world_langchain_mcp_stdio_stream.pyi +5 -0
- aip_agents/examples/hello_world_langchain_stream.py +36 -0
- aip_agents/examples/hello_world_langchain_stream.pyi +5 -0
- aip_agents/examples/hello_world_langchain_stream_lm_invoker.py +39 -0
- aip_agents/examples/hello_world_langchain_stream_lm_invoker.pyi +5 -0
- aip_agents/examples/hello_world_langflow_agent.py +163 -0
- aip_agents/examples/hello_world_langflow_agent.pyi +35 -0
- aip_agents/examples/hello_world_langgraph.py +39 -0
- aip_agents/examples/hello_world_langgraph.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_gl_connector_twitter.py +44 -0
- aip_agents/examples/hello_world_langgraph_gl_connector_twitter.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_mcp_http.py +31 -0
- aip_agents/examples/hello_world_langgraph_mcp_http.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_mcp_http_stream.py +34 -0
- aip_agents/examples/hello_world_langgraph_mcp_http_stream.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_mcp_sse.py +35 -0
- aip_agents/examples/hello_world_langgraph_mcp_sse.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_mcp_sse_stream.py +50 -0
- aip_agents/examples/hello_world_langgraph_mcp_sse_stream.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_mcp_stdio.py +35 -0
- aip_agents/examples/hello_world_langgraph_mcp_stdio.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_mcp_stdio_stream.py +50 -0
- aip_agents/examples/hello_world_langgraph_mcp_stdio_stream.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_stream.py +43 -0
- aip_agents/examples/hello_world_langgraph_stream.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_stream_lm_invoker.py +37 -0
- aip_agents/examples/hello_world_langgraph_stream_lm_invoker.pyi +5 -0
- aip_agents/examples/hello_world_model_switch_cli.py +210 -0
- aip_agents/examples/hello_world_model_switch_cli.pyi +30 -0
- aip_agents/examples/hello_world_multi_agent_adk.py +75 -0
- aip_agents/examples/hello_world_multi_agent_adk.pyi +6 -0
- aip_agents/examples/hello_world_multi_agent_langchain.py +54 -0
- aip_agents/examples/hello_world_multi_agent_langchain.pyi +5 -0
- aip_agents/examples/hello_world_multi_agent_langgraph.py +66 -0
- aip_agents/examples/hello_world_multi_agent_langgraph.pyi +5 -0
- aip_agents/examples/hello_world_multi_agent_langgraph_lm_invoker.py +69 -0
- aip_agents/examples/hello_world_multi_agent_langgraph_lm_invoker.pyi +5 -0
- aip_agents/examples/hello_world_pii_logger.py +21 -0
- aip_agents/examples/hello_world_pii_logger.pyi +5 -0
- aip_agents/examples/hello_world_sentry.py +133 -0
- aip_agents/examples/hello_world_sentry.pyi +21 -0
- aip_agents/examples/hello_world_step_limits.py +273 -0
- aip_agents/examples/hello_world_step_limits.pyi +17 -0
- aip_agents/examples/hello_world_stock_a2a_server.py +103 -0
- aip_agents/examples/hello_world_stock_a2a_server.pyi +17 -0
- aip_agents/examples/hello_world_tool_output_client.py +46 -0
- aip_agents/examples/hello_world_tool_output_client.pyi +5 -0
- aip_agents/examples/hello_world_tool_output_server.py +114 -0
- aip_agents/examples/hello_world_tool_output_server.pyi +19 -0
- aip_agents/examples/hitl_demo.py +724 -0
- aip_agents/examples/hitl_demo.pyi +67 -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_client.pyi +5 -0
- aip_agents/examples/pii_demo_langgraph_server.py +126 -0
- aip_agents/examples/pii_demo_langgraph_server.pyi +20 -0
- aip_agents/examples/pii_demo_multi_agent_client.py +80 -0
- aip_agents/examples/pii_demo_multi_agent_client.pyi +5 -0
- aip_agents/examples/pii_demo_multi_agent_server.py +247 -0
- aip_agents/examples/pii_demo_multi_agent_server.pyi +40 -0
- aip_agents/examples/todolist_planning_a2a_langchain_client.py +70 -0
- aip_agents/examples/todolist_planning_a2a_langchain_client.pyi +5 -0
- aip_agents/examples/todolist_planning_a2a_langgraph_server.py +88 -0
- aip_agents/examples/todolist_planning_a2a_langgraph_server.pyi +19 -0
- aip_agents/examples/tools/__init__.py +27 -0
- aip_agents/examples/tools/__init__.pyi +9 -0
- aip_agents/examples/tools/adk_arithmetic_tools.py +36 -0
- aip_agents/examples/tools/adk_arithmetic_tools.pyi +24 -0
- aip_agents/examples/tools/adk_weather_tool.py +60 -0
- aip_agents/examples/tools/adk_weather_tool.pyi +18 -0
- aip_agents/examples/tools/data_generator_tool.py +103 -0
- aip_agents/examples/tools/data_generator_tool.pyi +15 -0
- aip_agents/examples/tools/data_visualization_tool.py +312 -0
- aip_agents/examples/tools/data_visualization_tool.pyi +19 -0
- aip_agents/examples/tools/image_artifact_tool.py +136 -0
- aip_agents/examples/tools/image_artifact_tool.pyi +26 -0
- aip_agents/examples/tools/langchain_arithmetic_tools.py +26 -0
- aip_agents/examples/tools/langchain_arithmetic_tools.pyi +17 -0
- aip_agents/examples/tools/langchain_currency_exchange_tool.py +88 -0
- aip_agents/examples/tools/langchain_currency_exchange_tool.pyi +20 -0
- aip_agents/examples/tools/langchain_graph_artifact_tool.py +172 -0
- aip_agents/examples/tools/langchain_graph_artifact_tool.pyi +25 -0
- aip_agents/examples/tools/langchain_weather_tool.py +48 -0
- aip_agents/examples/tools/langchain_weather_tool.pyi +19 -0
- aip_agents/examples/tools/langgraph_streaming_tool.py +130 -0
- aip_agents/examples/tools/langgraph_streaming_tool.pyi +43 -0
- aip_agents/examples/tools/mock_retrieval_tool.py +56 -0
- aip_agents/examples/tools/mock_retrieval_tool.pyi +13 -0
- aip_agents/examples/tools/pii_demo_tools.py +189 -0
- aip_agents/examples/tools/pii_demo_tools.pyi +54 -0
- aip_agents/examples/tools/random_chart_tool.py +142 -0
- aip_agents/examples/tools/random_chart_tool.pyi +20 -0
- aip_agents/examples/tools/serper_tool.py +202 -0
- aip_agents/examples/tools/serper_tool.pyi +16 -0
- aip_agents/examples/tools/stock_tools.py +82 -0
- aip_agents/examples/tools/stock_tools.pyi +36 -0
- aip_agents/examples/tools/table_generator_tool.py +167 -0
- aip_agents/examples/tools/table_generator_tool.pyi +22 -0
- aip_agents/examples/tools/time_tool.py +82 -0
- aip_agents/examples/tools/time_tool.pyi +15 -0
- aip_agents/examples/tools/weather_forecast_tool.py +38 -0
- aip_agents/examples/tools/weather_forecast_tool.pyi +14 -0
- aip_agents/executor/agent_executor.py +473 -0
- aip_agents/executor/base.py +48 -0
- aip_agents/guardrails/__init__.py +83 -0
- aip_agents/guardrails/__init__.pyi +6 -0
- aip_agents/guardrails/engines/__init__.py +69 -0
- aip_agents/guardrails/engines/__init__.pyi +4 -0
- aip_agents/guardrails/engines/base.py +90 -0
- aip_agents/guardrails/engines/base.pyi +61 -0
- aip_agents/guardrails/engines/nemo.py +101 -0
- aip_agents/guardrails/engines/nemo.pyi +46 -0
- aip_agents/guardrails/engines/phrase_matcher.py +113 -0
- aip_agents/guardrails/engines/phrase_matcher.pyi +48 -0
- aip_agents/guardrails/exceptions.py +39 -0
- aip_agents/guardrails/exceptions.pyi +23 -0
- aip_agents/guardrails/manager.py +163 -0
- aip_agents/guardrails/manager.pyi +42 -0
- aip_agents/guardrails/middleware.py +199 -0
- aip_agents/guardrails/middleware.pyi +87 -0
- aip_agents/guardrails/schemas.py +63 -0
- aip_agents/guardrails/schemas.pyi +43 -0
- aip_agents/guardrails/utils.py +45 -0
- aip_agents/guardrails/utils.pyi +19 -0
- aip_agents/mcp/__init__.py +1 -0
- aip_agents/mcp/__init__.pyi +0 -0
- aip_agents/mcp/client/__init__.py +14 -0
- aip_agents/mcp/client/__init__.pyi +5 -0
- aip_agents/mcp/client/base_mcp_client.py +369 -0
- aip_agents/mcp/client/base_mcp_client.pyi +148 -0
- aip_agents/mcp/client/connection_manager.py +193 -0
- aip_agents/mcp/client/connection_manager.pyi +48 -0
- aip_agents/mcp/client/google_adk/__init__.py +11 -0
- aip_agents/mcp/client/google_adk/__init__.pyi +3 -0
- aip_agents/mcp/client/google_adk/client.py +381 -0
- aip_agents/mcp/client/google_adk/client.pyi +75 -0
- aip_agents/mcp/client/langchain/__init__.py +11 -0
- aip_agents/mcp/client/langchain/__init__.pyi +3 -0
- aip_agents/mcp/client/langchain/client.py +265 -0
- aip_agents/mcp/client/langchain/client.pyi +48 -0
- aip_agents/mcp/client/persistent_session.py +362 -0
- aip_agents/mcp/client/persistent_session.pyi +113 -0
- aip_agents/mcp/client/session_pool.py +351 -0
- aip_agents/mcp/client/session_pool.pyi +101 -0
- aip_agents/mcp/client/transports.py +228 -0
- aip_agents/mcp/client/transports.pyi +123 -0
- aip_agents/mcp/utils/__init__.py +7 -0
- aip_agents/mcp/utils/__init__.pyi +0 -0
- aip_agents/mcp/utils/config_validator.py +139 -0
- aip_agents/mcp/utils/config_validator.pyi +82 -0
- aip_agents/memory/__init__.py +14 -0
- aip_agents/memory/__init__.pyi +5 -0
- aip_agents/memory/adapters/__init__.py +10 -0
- aip_agents/memory/adapters/__init__.pyi +4 -0
- aip_agents/memory/adapters/base_adapter.py +717 -0
- aip_agents/memory/adapters/base_adapter.pyi +150 -0
- aip_agents/memory/adapters/mem0.py +84 -0
- aip_agents/memory/adapters/mem0.pyi +22 -0
- aip_agents/memory/base.py +84 -0
- aip_agents/memory/base.pyi +60 -0
- aip_agents/memory/constants.py +49 -0
- aip_agents/memory/constants.pyi +25 -0
- aip_agents/memory/factory.py +86 -0
- aip_agents/memory/factory.pyi +24 -0
- aip_agents/memory/guidance.py +20 -0
- aip_agents/memory/guidance.pyi +3 -0
- aip_agents/memory/simple_memory.py +47 -0
- aip_agents/memory/simple_memory.pyi +23 -0
- aip_agents/middleware/__init__.py +17 -0
- aip_agents/middleware/__init__.pyi +5 -0
- aip_agents/middleware/base.py +96 -0
- aip_agents/middleware/base.pyi +75 -0
- aip_agents/middleware/manager.py +150 -0
- aip_agents/middleware/manager.pyi +84 -0
- aip_agents/middleware/todolist.py +274 -0
- aip_agents/middleware/todolist.pyi +125 -0
- aip_agents/schema/__init__.py +69 -0
- aip_agents/schema/__init__.pyi +9 -0
- aip_agents/schema/a2a.py +56 -0
- aip_agents/schema/a2a.pyi +40 -0
- aip_agents/schema/agent.py +111 -0
- aip_agents/schema/agent.pyi +65 -0
- aip_agents/schema/hitl.py +157 -0
- aip_agents/schema/hitl.pyi +89 -0
- aip_agents/schema/langgraph.py +37 -0
- aip_agents/schema/langgraph.pyi +28 -0
- aip_agents/schema/model_id.py +97 -0
- aip_agents/schema/model_id.pyi +54 -0
- aip_agents/schema/step_limit.py +108 -0
- aip_agents/schema/step_limit.pyi +63 -0
- aip_agents/schema/storage.py +40 -0
- aip_agents/schema/storage.pyi +21 -0
- aip_agents/sentry/__init__.py +11 -0
- aip_agents/sentry/__init__.pyi +3 -0
- aip_agents/sentry/sentry.py +151 -0
- aip_agents/sentry/sentry.pyi +48 -0
- aip_agents/storage/__init__.py +41 -0
- aip_agents/storage/__init__.pyi +8 -0
- aip_agents/storage/base.py +85 -0
- aip_agents/storage/base.pyi +58 -0
- aip_agents/storage/clients/__init__.py +12 -0
- aip_agents/storage/clients/__init__.pyi +3 -0
- aip_agents/storage/clients/minio_client.py +318 -0
- aip_agents/storage/clients/minio_client.pyi +137 -0
- aip_agents/storage/config.py +62 -0
- aip_agents/storage/config.pyi +29 -0
- aip_agents/storage/providers/__init__.py +15 -0
- aip_agents/storage/providers/__init__.pyi +5 -0
- aip_agents/storage/providers/base.py +106 -0
- aip_agents/storage/providers/base.pyi +88 -0
- aip_agents/storage/providers/memory.py +114 -0
- aip_agents/storage/providers/memory.pyi +79 -0
- aip_agents/storage/providers/object_storage.py +214 -0
- aip_agents/storage/providers/object_storage.pyi +98 -0
- aip_agents/tools/__init__.py +53 -0
- aip_agents/tools/__init__.pyi +9 -0
- aip_agents/tools/browser_use/__init__.py +82 -0
- aip_agents/tools/browser_use/__init__.pyi +14 -0
- aip_agents/tools/browser_use/action_parser.py +103 -0
- aip_agents/tools/browser_use/action_parser.pyi +18 -0
- aip_agents/tools/browser_use/browser_use_tool.py +1112 -0
- aip_agents/tools/browser_use/browser_use_tool.pyi +50 -0
- aip_agents/tools/browser_use/llm_config.py +120 -0
- aip_agents/tools/browser_use/llm_config.pyi +52 -0
- aip_agents/tools/browser_use/minio_storage.py +198 -0
- aip_agents/tools/browser_use/minio_storage.pyi +109 -0
- aip_agents/tools/browser_use/schemas.py +119 -0
- aip_agents/tools/browser_use/schemas.pyi +32 -0
- aip_agents/tools/browser_use/session.py +76 -0
- aip_agents/tools/browser_use/session.pyi +4 -0
- aip_agents/tools/browser_use/session_errors.py +132 -0
- aip_agents/tools/browser_use/session_errors.pyi +53 -0
- aip_agents/tools/browser_use/steel_session_recording.py +317 -0
- aip_agents/tools/browser_use/steel_session_recording.pyi +63 -0
- aip_agents/tools/browser_use/streaming.py +813 -0
- aip_agents/tools/browser_use/streaming.pyi +81 -0
- aip_agents/tools/browser_use/structured_data_parser.py +257 -0
- aip_agents/tools/browser_use/structured_data_parser.pyi +86 -0
- aip_agents/tools/browser_use/structured_data_recovery.py +204 -0
- aip_agents/tools/browser_use/structured_data_recovery.pyi +43 -0
- aip_agents/tools/browser_use/types.py +78 -0
- aip_agents/tools/browser_use/types.pyi +45 -0
- aip_agents/tools/code_sandbox/__init__.py +26 -0
- aip_agents/tools/code_sandbox/__init__.pyi +3 -0
- aip_agents/tools/code_sandbox/constant.py +13 -0
- aip_agents/tools/code_sandbox/constant.pyi +4 -0
- aip_agents/tools/code_sandbox/e2b_cloud_sandbox_extended.py +306 -0
- aip_agents/tools/code_sandbox/e2b_cloud_sandbox_extended.pyi +102 -0
- aip_agents/tools/code_sandbox/e2b_sandbox_tool.py +411 -0
- aip_agents/tools/code_sandbox/e2b_sandbox_tool.pyi +29 -0
- aip_agents/tools/constants.py +177 -0
- aip_agents/tools/constants.pyi +138 -0
- aip_agents/tools/document_loader/__init__.py +44 -0
- aip_agents/tools/document_loader/__init__.pyi +7 -0
- aip_agents/tools/document_loader/base_reader.py +302 -0
- aip_agents/tools/document_loader/base_reader.pyi +75 -0
- aip_agents/tools/document_loader/docx_reader_tool.py +68 -0
- aip_agents/tools/document_loader/docx_reader_tool.pyi +10 -0
- aip_agents/tools/document_loader/excel_reader_tool.py +171 -0
- aip_agents/tools/document_loader/excel_reader_tool.pyi +26 -0
- aip_agents/tools/document_loader/pdf_reader_tool.py +79 -0
- aip_agents/tools/document_loader/pdf_reader_tool.pyi +11 -0
- aip_agents/tools/document_loader/pdf_splitter.py +169 -0
- aip_agents/tools/document_loader/pdf_splitter.pyi +18 -0
- aip_agents/tools/gl_connector/__init__.py +5 -0
- aip_agents/tools/gl_connector/__init__.pyi +3 -0
- aip_agents/tools/gl_connector/tool.py +383 -0
- aip_agents/tools/gl_connector/tool.pyi +74 -0
- aip_agents/tools/gl_connector_tools.py +119 -0
- aip_agents/tools/gl_connector_tools.pyi +39 -0
- aip_agents/tools/memory_search/__init__.py +22 -0
- aip_agents/tools/memory_search/__init__.pyi +5 -0
- aip_agents/tools/memory_search/base.py +200 -0
- aip_agents/tools/memory_search/base.pyi +69 -0
- aip_agents/tools/memory_search/mem0.py +258 -0
- aip_agents/tools/memory_search/mem0.pyi +19 -0
- aip_agents/tools/memory_search/schema.py +48 -0
- aip_agents/tools/memory_search/schema.pyi +15 -0
- aip_agents/tools/memory_search_tool.py +26 -0
- aip_agents/tools/memory_search_tool.pyi +3 -0
- aip_agents/tools/time_tool.py +117 -0
- aip_agents/tools/time_tool.pyi +16 -0
- aip_agents/tools/tool_config_injector.py +300 -0
- aip_agents/tools/tool_config_injector.pyi +26 -0
- aip_agents/tools/web_search/__init__.py +15 -0
- aip_agents/tools/web_search/__init__.pyi +3 -0
- aip_agents/tools/web_search/serper_tool.py +187 -0
- aip_agents/tools/web_search/serper_tool.pyi +19 -0
- aip_agents/types/__init__.py +70 -0
- aip_agents/types/__init__.pyi +36 -0
- aip_agents/types/a2a_events.py +13 -0
- aip_agents/types/a2a_events.pyi +3 -0
- aip_agents/utils/__init__.py +79 -0
- aip_agents/utils/__init__.pyi +11 -0
- aip_agents/utils/a2a_connector.py +1757 -0
- aip_agents/utils/a2a_connector.pyi +146 -0
- aip_agents/utils/artifact_helpers.py +502 -0
- aip_agents/utils/artifact_helpers.pyi +203 -0
- aip_agents/utils/constants.py +22 -0
- aip_agents/utils/constants.pyi +10 -0
- aip_agents/utils/datetime/__init__.py +34 -0
- aip_agents/utils/datetime/__init__.pyi +4 -0
- aip_agents/utils/datetime/normalization.py +231 -0
- aip_agents/utils/datetime/normalization.pyi +95 -0
- aip_agents/utils/datetime/timezone.py +206 -0
- aip_agents/utils/datetime/timezone.pyi +48 -0
- aip_agents/utils/env_loader.py +27 -0
- aip_agents/utils/env_loader.pyi +10 -0
- aip_agents/utils/event_handler_registry.py +58 -0
- aip_agents/utils/event_handler_registry.pyi +23 -0
- aip_agents/utils/file_prompt_utils.py +176 -0
- aip_agents/utils/file_prompt_utils.pyi +21 -0
- aip_agents/utils/final_response_builder.py +211 -0
- aip_agents/utils/final_response_builder.pyi +34 -0
- aip_agents/utils/formatter_llm_client.py +231 -0
- aip_agents/utils/formatter_llm_client.pyi +71 -0
- aip_agents/utils/langgraph/__init__.py +19 -0
- aip_agents/utils/langgraph/__init__.pyi +3 -0
- aip_agents/utils/langgraph/converter.py +128 -0
- aip_agents/utils/langgraph/converter.pyi +49 -0
- aip_agents/utils/langgraph/tool_managers/__init__.py +15 -0
- aip_agents/utils/langgraph/tool_managers/__init__.pyi +5 -0
- aip_agents/utils/langgraph/tool_managers/a2a_tool_manager.py +99 -0
- aip_agents/utils/langgraph/tool_managers/a2a_tool_manager.pyi +35 -0
- aip_agents/utils/langgraph/tool_managers/base_tool_manager.py +66 -0
- aip_agents/utils/langgraph/tool_managers/base_tool_manager.pyi +48 -0
- aip_agents/utils/langgraph/tool_managers/delegation_tool_manager.py +1071 -0
- aip_agents/utils/langgraph/tool_managers/delegation_tool_manager.pyi +56 -0
- aip_agents/utils/langgraph/tool_output_management.py +967 -0
- aip_agents/utils/langgraph/tool_output_management.pyi +292 -0
- aip_agents/utils/logger.py +195 -0
- aip_agents/utils/logger.pyi +60 -0
- aip_agents/utils/metadata/__init__.py +27 -0
- aip_agents/utils/metadata/__init__.pyi +5 -0
- aip_agents/utils/metadata/activity_metadata_helper.py +407 -0
- aip_agents/utils/metadata/activity_metadata_helper.pyi +25 -0
- aip_agents/utils/metadata/activity_narrative/__init__.py +35 -0
- aip_agents/utils/metadata/activity_narrative/__init__.pyi +7 -0
- aip_agents/utils/metadata/activity_narrative/builder.py +817 -0
- aip_agents/utils/metadata/activity_narrative/builder.pyi +35 -0
- aip_agents/utils/metadata/activity_narrative/constants.py +51 -0
- aip_agents/utils/metadata/activity_narrative/constants.pyi +10 -0
- aip_agents/utils/metadata/activity_narrative/context.py +49 -0
- aip_agents/utils/metadata/activity_narrative/context.pyi +32 -0
- aip_agents/utils/metadata/activity_narrative/formatters.py +230 -0
- aip_agents/utils/metadata/activity_narrative/formatters.pyi +48 -0
- aip_agents/utils/metadata/activity_narrative/utils.py +35 -0
- aip_agents/utils/metadata/activity_narrative/utils.pyi +12 -0
- aip_agents/utils/metadata/schemas/__init__.py +16 -0
- aip_agents/utils/metadata/schemas/__init__.pyi +4 -0
- aip_agents/utils/metadata/schemas/activity_schema.py +29 -0
- aip_agents/utils/metadata/schemas/activity_schema.pyi +18 -0
- aip_agents/utils/metadata/schemas/thinking_schema.py +31 -0
- aip_agents/utils/metadata/schemas/thinking_schema.pyi +20 -0
- aip_agents/utils/metadata/thinking_metadata_helper.py +38 -0
- aip_agents/utils/metadata/thinking_metadata_helper.pyi +4 -0
- aip_agents/utils/metadata_helper.py +358 -0
- aip_agents/utils/metadata_helper.pyi +117 -0
- aip_agents/utils/name_preprocessor/__init__.py +17 -0
- aip_agents/utils/name_preprocessor/__init__.pyi +6 -0
- aip_agents/utils/name_preprocessor/base_name_preprocessor.py +73 -0
- aip_agents/utils/name_preprocessor/base_name_preprocessor.pyi +52 -0
- aip_agents/utils/name_preprocessor/google_name_preprocessor.py +100 -0
- aip_agents/utils/name_preprocessor/google_name_preprocessor.pyi +38 -0
- aip_agents/utils/name_preprocessor/name_preprocessor.py +87 -0
- aip_agents/utils/name_preprocessor/name_preprocessor.pyi +41 -0
- aip_agents/utils/name_preprocessor/openai_name_preprocessor.py +48 -0
- aip_agents/utils/name_preprocessor/openai_name_preprocessor.pyi +34 -0
- aip_agents/utils/pii/__init__.py +25 -0
- aip_agents/utils/pii/__init__.pyi +5 -0
- aip_agents/utils/pii/pii_handler.py +397 -0
- aip_agents/utils/pii/pii_handler.pyi +96 -0
- aip_agents/utils/pii/pii_helper.py +207 -0
- aip_agents/utils/pii/pii_helper.pyi +78 -0
- aip_agents/utils/pii/uuid_deanonymizer_mapping.py +195 -0
- aip_agents/utils/pii/uuid_deanonymizer_mapping.pyi +73 -0
- aip_agents/utils/reference_helper.py +273 -0
- aip_agents/utils/reference_helper.pyi +81 -0
- aip_agents/utils/sse_chunk_transformer.py +831 -0
- aip_agents/utils/sse_chunk_transformer.pyi +166 -0
- aip_agents/utils/step_limit_manager.py +265 -0
- aip_agents/utils/step_limit_manager.pyi +112 -0
- aip_agents/utils/token_usage_helper.py +156 -0
- aip_agents/utils/token_usage_helper.pyi +60 -0
- aip_agents_binary-0.5.25b1.dist-info/METADATA +681 -0
- aip_agents_binary-0.5.25b1.dist-info/RECORD +566 -0
- aip_agents_binary-0.5.25b1.dist-info/WHEEL +5 -0
- aip_agents_binary-0.5.25b1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,817 @@
|
|
|
1
|
+
"""Activity narrative builder utilities for tool and delegate messaging.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import re
|
|
11
|
+
from collections.abc import Callable
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from gllm_inference.schema import Message
|
|
15
|
+
|
|
16
|
+
from aip_agents.schema.hitl import ApprovalDecisionType, HitlMetadata
|
|
17
|
+
from aip_agents.utils.formatter_llm_client import (
|
|
18
|
+
FormatterInvocationError,
|
|
19
|
+
FormatterInvokerUnavailableError,
|
|
20
|
+
get_formatter_llm_client,
|
|
21
|
+
)
|
|
22
|
+
from aip_agents.utils.logger import get_logger
|
|
23
|
+
from aip_agents.utils.metadata.activity_narrative.constants import (
|
|
24
|
+
DELEGATE_PREFIX,
|
|
25
|
+
HITL_DECISION_MESSAGES,
|
|
26
|
+
HITL_PENDING_DESCRIPTION,
|
|
27
|
+
HITL_PENDING_TITLE,
|
|
28
|
+
OUTPUT_EXCERPT_MAX_CHARS,
|
|
29
|
+
SYSTEM_PROMPT,
|
|
30
|
+
)
|
|
31
|
+
from aip_agents.utils.metadata.activity_narrative.context import ActivityContext, ActivityPhase
|
|
32
|
+
from aip_agents.utils.metadata.activity_narrative.formatters import (
|
|
33
|
+
ArgsFormatter,
|
|
34
|
+
OutputFormatter,
|
|
35
|
+
SensitiveInfoFilter,
|
|
36
|
+
)
|
|
37
|
+
from aip_agents.utils.metadata.activity_narrative.utils import _format_tool_or_subagent_name
|
|
38
|
+
|
|
39
|
+
logger = get_logger(__name__)
|
|
40
|
+
_formatter_llm_client = get_formatter_llm_client()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ActivityNarrativeBuilder:
|
|
44
|
+
"""Generate structured activity payloads via formatter LLM.
|
|
45
|
+
|
|
46
|
+
High-level flow:
|
|
47
|
+
1. Gather raw metadata about a tool/delegate event and normalize it into an ``ActivityContext``.
|
|
48
|
+
2. Sanitize arguments and outputs so no sensitive values reach downstream renderers or the formatter model.
|
|
49
|
+
3. Prompt the shared formatter with phase-specific instructions (e.g., describe intent on start, summarize results on end).
|
|
50
|
+
4. If the formatter responds with usable heading/body text, surface it; otherwise fall back to deterministic templates
|
|
51
|
+
built from the sanitized context.
|
|
52
|
+
|
|
53
|
+
This approach keeps SSE activity cards readable when the formatter is healthy while still providing sensible copy when
|
|
54
|
+
the formatter is unavailable or returns low-quality text.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
_PHASE_PROMPTS: dict[ActivityPhase, dict[str, str]] = {
|
|
58
|
+
ActivityPhase.TOOL_START: {
|
|
59
|
+
"heading": (
|
|
60
|
+
"Return heading_text describing the tool action such as 'Executing Time Tool' or "
|
|
61
|
+
"'Searching for flight status'. Keep it short, friendly, and avoid colon-separated detail."
|
|
62
|
+
),
|
|
63
|
+
"body": (
|
|
64
|
+
"Summarize what the tool is about to do using the arguments. "
|
|
65
|
+
"The first sentence must start with 'Trying to'. Use at most two sentences."
|
|
66
|
+
),
|
|
67
|
+
},
|
|
68
|
+
ActivityPhase.TOOL_END: {
|
|
69
|
+
"heading": (
|
|
70
|
+
"Return heading_text in the form '<subject> completed'. Do not add colons or extra punctuation."
|
|
71
|
+
),
|
|
72
|
+
"body": (
|
|
73
|
+
"Summarize the observed outcome using the outputs or errors. "
|
|
74
|
+
"Only mention errors when one is provided; never state that no errors occurred. "
|
|
75
|
+
"The first sentence must start with 'Reporting'. Keep it concise."
|
|
76
|
+
),
|
|
77
|
+
},
|
|
78
|
+
ActivityPhase.DELEGATE_START: {
|
|
79
|
+
"heading": (
|
|
80
|
+
"Return heading_text describing the delegated agent, e.g., 'Delegating to Research Agent'. "
|
|
81
|
+
"Keep it friendly and avoid punctuation beyond spaces."
|
|
82
|
+
),
|
|
83
|
+
"body": (
|
|
84
|
+
"Explain the work being handed off using the arguments or task description. "
|
|
85
|
+
"Start the first sentence with 'Investigating' or 'Researching' and describe the sub-agent's goal."
|
|
86
|
+
),
|
|
87
|
+
},
|
|
88
|
+
ActivityPhase.DELEGATE_END: {
|
|
89
|
+
"heading": (
|
|
90
|
+
"Return heading_text describing that the delegate finished, such as 'Compiling results from Research Agent'."
|
|
91
|
+
),
|
|
92
|
+
"body": (
|
|
93
|
+
"Summarize the delegate's outcome using the outputs. "
|
|
94
|
+
"Only mention errors when one is provided; never state that no errors occurred. "
|
|
95
|
+
"The first sentence should start with 'Reporting' or 'Returning'."
|
|
96
|
+
),
|
|
97
|
+
},
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
def __init__(self) -> None:
|
|
101
|
+
"""Initialize the activity narrative builder."""
|
|
102
|
+
self._filter = SensitiveInfoFilter()
|
|
103
|
+
self._args_formatter = ArgsFormatter()
|
|
104
|
+
self._output_formatter = OutputFormatter()
|
|
105
|
+
|
|
106
|
+
# ------------------------------------------------------------------
|
|
107
|
+
# Public API
|
|
108
|
+
# ------------------------------------------------------------------
|
|
109
|
+
def build_payload(self, metadata: dict[str, Any] | None) -> dict[str, Any] | None:
|
|
110
|
+
"""Build enriched payload for the provided metadata.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
metadata: The metadata dictionary containing tool_info, hitl, and other context.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Dictionary payload with a rendered message, or None when not available.
|
|
117
|
+
"""
|
|
118
|
+
if not isinstance(metadata, dict):
|
|
119
|
+
logger.info("activity narrative skipping non-dict metadata (type=%s)", type(metadata))
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
step_id = metadata.get("step_id") if isinstance(metadata.get("step_id"), str) else None
|
|
123
|
+
|
|
124
|
+
context = self._build_context(metadata)
|
|
125
|
+
if context is None:
|
|
126
|
+
logger.info("activity narrative context unavailable; falling back to templates (step_id=%s)", step_id)
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
if context.phase in {ActivityPhase.HITL_PENDING, ActivityPhase.HITL_RESOLVED}:
|
|
130
|
+
message = self._build_hitl_message(context)
|
|
131
|
+
logger.info(
|
|
132
|
+
"activity narrative generated HITL message step_id=%s phase=%s subject=%s",
|
|
133
|
+
context.step_id,
|
|
134
|
+
context.phase.value,
|
|
135
|
+
context.subject_name,
|
|
136
|
+
)
|
|
137
|
+
return {"message": message}
|
|
138
|
+
|
|
139
|
+
heading_text, body_text = self._generate_llm_texts(context)
|
|
140
|
+
message = self._assemble_message(context, heading_text, body_text)
|
|
141
|
+
logger.info(
|
|
142
|
+
"activity narrative message ready step_id=%s phase=%s chars=%s",
|
|
143
|
+
context.step_id,
|
|
144
|
+
context.phase.value,
|
|
145
|
+
len(message),
|
|
146
|
+
)
|
|
147
|
+
return {"message": message}
|
|
148
|
+
|
|
149
|
+
# ------------------------------------------------------------------
|
|
150
|
+
# Context extraction
|
|
151
|
+
# ------------------------------------------------------------------
|
|
152
|
+
def _build_context(self, metadata: dict[str, Any]) -> ActivityContext | None:
|
|
153
|
+
"""Build ActivityContext from metadata dictionary.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
metadata: Metadata dictionary containing tool_info, hitl, and other context.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
ActivityContext instance with extracted and formatted information, or None if invalid.
|
|
160
|
+
"""
|
|
161
|
+
tool_info = self._ensure_dict(self._extract_value(metadata, "tool_info"))
|
|
162
|
+
hitl_payload = self._extract_value(metadata, "hitl")
|
|
163
|
+
hitl_metadata = self._parse_hitl_metadata(hitl_payload)
|
|
164
|
+
hitl_decision = self._to_decision(hitl_metadata)
|
|
165
|
+
|
|
166
|
+
phase = self._resolve_phase(tool_info, hitl_decision)
|
|
167
|
+
args = self._extract_args(tool_info)
|
|
168
|
+
output = self._extract_output(tool_info)
|
|
169
|
+
sanitizer = self._extract_activity_sanitizer(tool_info)
|
|
170
|
+
sanitized_args, sanitized_output = self._filter.sanitize(args, output, sanitizer)
|
|
171
|
+
sanitized_args = self._ensure_dict(sanitized_args)
|
|
172
|
+
|
|
173
|
+
arguments_excerpt = self._args_formatter.format(sanitized_args)
|
|
174
|
+
output_excerpt = self._output_formatter.format(sanitized_output)
|
|
175
|
+
error_excerpt = self._extract_error_excerpt(tool_info, metadata)
|
|
176
|
+
is_delegate = self._is_delegate_tool(tool_info)
|
|
177
|
+
subject_name = self._resolve_subject_name(tool_info, is_delegate)
|
|
178
|
+
agent_name = self._extract_agent_name(metadata)
|
|
179
|
+
step_id_value = self._extract_value(metadata, "step_id")
|
|
180
|
+
step_id = step_id_value if isinstance(step_id_value, str) and step_id_value.strip() else None
|
|
181
|
+
default_heading = self._default_heading(phase, subject_name, is_delegate)
|
|
182
|
+
|
|
183
|
+
context = ActivityContext(
|
|
184
|
+
phase=phase,
|
|
185
|
+
agent_name=agent_name,
|
|
186
|
+
step_id=step_id,
|
|
187
|
+
subject_name=subject_name,
|
|
188
|
+
sanitized_args=sanitized_args,
|
|
189
|
+
sanitized_output=sanitized_output,
|
|
190
|
+
arguments_excerpt=arguments_excerpt,
|
|
191
|
+
output_excerpt=output_excerpt,
|
|
192
|
+
error_excerpt=error_excerpt,
|
|
193
|
+
is_delegate=is_delegate,
|
|
194
|
+
hitl_metadata=hitl_metadata,
|
|
195
|
+
hitl_decision=hitl_decision,
|
|
196
|
+
default_heading=default_heading,
|
|
197
|
+
)
|
|
198
|
+
return context
|
|
199
|
+
|
|
200
|
+
def _extract_value(self, metadata: dict[str, Any], key: str) -> Any:
|
|
201
|
+
"""Extract value from metadata by key name or enum value.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
metadata: Metadata dictionary.
|
|
205
|
+
key: Key name or enum value to look up.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
Extracted value, or None if not found.
|
|
209
|
+
"""
|
|
210
|
+
if key in metadata:
|
|
211
|
+
return metadata[key]
|
|
212
|
+
for meta_key, value in metadata.items():
|
|
213
|
+
if getattr(meta_key, "value", None) == key:
|
|
214
|
+
return value
|
|
215
|
+
return None
|
|
216
|
+
|
|
217
|
+
def _ensure_dict(self, value: Any) -> dict[str, Any] | None:
|
|
218
|
+
"""Ensure value is a dictionary.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
value: Value to check.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
Value if it's a dict, None otherwise.
|
|
225
|
+
"""
|
|
226
|
+
return value if isinstance(value, dict) else None
|
|
227
|
+
|
|
228
|
+
def _resolve_phase(
|
|
229
|
+
self, tool_info: dict[str, Any] | None, hitl_decision: ApprovalDecisionType | None
|
|
230
|
+
) -> ActivityPhase:
|
|
231
|
+
"""Determine activity phase from tool info and HITL decision.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
tool_info: Tool information dictionary.
|
|
235
|
+
hitl_decision: HITL approval decision type.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
ActivityPhase enum value indicating the current phase.
|
|
239
|
+
"""
|
|
240
|
+
if hitl_decision == ApprovalDecisionType.PENDING:
|
|
241
|
+
return ActivityPhase.HITL_PENDING
|
|
242
|
+
if hitl_decision and hitl_decision != ApprovalDecisionType.PENDING:
|
|
243
|
+
return ActivityPhase.HITL_RESOLVED
|
|
244
|
+
|
|
245
|
+
has_output = isinstance(tool_info, dict) and "output" in tool_info
|
|
246
|
+
is_delegate = self._is_delegate_tool(tool_info)
|
|
247
|
+
if is_delegate:
|
|
248
|
+
return ActivityPhase.DELEGATE_END if has_output else ActivityPhase.DELEGATE_START
|
|
249
|
+
return ActivityPhase.TOOL_END if has_output else ActivityPhase.TOOL_START
|
|
250
|
+
|
|
251
|
+
def _is_delegate_tool(self, tool_info: dict[str, Any] | None) -> bool:
|
|
252
|
+
"""Check if tool is a delegation tool.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
tool_info: Tool information dictionary.
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
True if tool is a delegation tool, False otherwise.
|
|
259
|
+
"""
|
|
260
|
+
if not isinstance(tool_info, dict):
|
|
261
|
+
return False
|
|
262
|
+
tool_instance = tool_info.get("tool_instance")
|
|
263
|
+
metadata = getattr(tool_instance, "metadata", None)
|
|
264
|
+
if isinstance(metadata, dict) and metadata.get("is_delegation_tool"):
|
|
265
|
+
return True
|
|
266
|
+
names = self._collect_tool_names(tool_info)
|
|
267
|
+
return any(isinstance(name, str) and name.startswith(DELEGATE_PREFIX) for name in names)
|
|
268
|
+
|
|
269
|
+
def _extract_args(self, tool_info: dict[str, Any] | None) -> dict[str, Any] | None:
|
|
270
|
+
"""Extract tool arguments from tool info.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
tool_info: Tool information dictionary.
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
Arguments dictionary, or None if not found.
|
|
277
|
+
"""
|
|
278
|
+
if not isinstance(tool_info, dict):
|
|
279
|
+
return None
|
|
280
|
+
if isinstance(tool_info.get("args"), dict):
|
|
281
|
+
return tool_info["args"]
|
|
282
|
+
calls = tool_info.get("tool_calls")
|
|
283
|
+
if isinstance(calls, list):
|
|
284
|
+
for call in calls:
|
|
285
|
+
if isinstance(call, dict) and isinstance(call.get("args"), dict):
|
|
286
|
+
return call["args"]
|
|
287
|
+
return None
|
|
288
|
+
|
|
289
|
+
def _extract_output(self, tool_info: dict[str, Any] | None) -> Any:
|
|
290
|
+
"""Extract tool output from tool info.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
tool_info: Tool information dictionary.
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
Tool output value, or None if not found.
|
|
297
|
+
"""
|
|
298
|
+
if not isinstance(tool_info, dict):
|
|
299
|
+
return None
|
|
300
|
+
return tool_info.get("output")
|
|
301
|
+
|
|
302
|
+
def _extract_activity_sanitizer(
|
|
303
|
+
self, tool_info: dict[str, Any] | None
|
|
304
|
+
) -> Callable[[dict[str, Any] | None, Any | None], dict[str, Any]] | None:
|
|
305
|
+
"""Extract activity sanitizer function from tool instance.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
tool_info: Tool information dictionary.
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
Sanitizer callable if found, None otherwise.
|
|
312
|
+
"""
|
|
313
|
+
if not isinstance(tool_info, dict):
|
|
314
|
+
return None
|
|
315
|
+
tool_instance = tool_info.get("tool_instance")
|
|
316
|
+
sanitizer = getattr(tool_instance, "activity_sanitizer", None)
|
|
317
|
+
return sanitizer if callable(sanitizer) else None
|
|
318
|
+
|
|
319
|
+
def _extract_agent_name(self, metadata: dict[str, Any]) -> str | None:
|
|
320
|
+
"""Extract and prettify agent name from metadata.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
metadata: Metadata dictionary.
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
Prettified agent name, or None if not found.
|
|
327
|
+
"""
|
|
328
|
+
candidate: str | None = None
|
|
329
|
+
for key in ("agent_display_name", "agent_label", "agent_name", "agent_id", "memory_user_id"):
|
|
330
|
+
value = self._extract_value(metadata, key)
|
|
331
|
+
if isinstance(value, str) and value.strip():
|
|
332
|
+
candidate = value.strip()
|
|
333
|
+
if key in ("agent_display_name", "agent_label"):
|
|
334
|
+
break
|
|
335
|
+
if not candidate:
|
|
336
|
+
return None
|
|
337
|
+
return _format_tool_or_subagent_name(candidate)
|
|
338
|
+
|
|
339
|
+
def _extract_error_excerpt(self, tool_info: dict[str, Any] | None, metadata: dict[str, Any]) -> str | None:
|
|
340
|
+
"""Extract error message excerpt from tool info or metadata.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
tool_info: Tool information dictionary.
|
|
344
|
+
metadata: Metadata dictionary.
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
Error message string, or None if no error found.
|
|
348
|
+
"""
|
|
349
|
+
error_payload = None
|
|
350
|
+
if isinstance(tool_info, dict):
|
|
351
|
+
error_payload = tool_info.get("error")
|
|
352
|
+
if not error_payload:
|
|
353
|
+
error_payload = self._extract_value(metadata, "error")
|
|
354
|
+
if isinstance(error_payload, dict):
|
|
355
|
+
message = error_payload.get("message") or error_payload.get("detail")
|
|
356
|
+
if isinstance(message, str) and message.strip():
|
|
357
|
+
return message.strip()
|
|
358
|
+
try:
|
|
359
|
+
return json.dumps(error_payload, ensure_ascii=False)
|
|
360
|
+
except Exception:
|
|
361
|
+
return str(error_payload)
|
|
362
|
+
if isinstance(error_payload, str) and error_payload.strip():
|
|
363
|
+
return error_payload.strip()
|
|
364
|
+
return None
|
|
365
|
+
|
|
366
|
+
def _resolve_subject_name(self, tool_info: dict[str, Any] | None, is_delegate: bool) -> str | None:
|
|
367
|
+
"""Resolve subject name (tool or agent) from tool info.
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
tool_info: Tool information dictionary.
|
|
371
|
+
is_delegate: Whether this is a delegation tool.
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
Formatted subject name, or None if not found.
|
|
375
|
+
"""
|
|
376
|
+
if not isinstance(tool_info, dict):
|
|
377
|
+
return None
|
|
378
|
+
tool_instance = tool_info.get("tool_instance")
|
|
379
|
+
metadata = getattr(tool_instance, "metadata", None)
|
|
380
|
+
if isinstance(metadata, dict):
|
|
381
|
+
delegated = metadata.get("delegated_agent_name")
|
|
382
|
+
if isinstance(delegated, str) and delegated.strip():
|
|
383
|
+
return delegated
|
|
384
|
+
names = self._collect_tool_names(tool_info)
|
|
385
|
+
if not names:
|
|
386
|
+
return None
|
|
387
|
+
formatted = (
|
|
388
|
+
[_format_tool_or_subagent_name(name, remove_delegate_prefix=True) for name in names]
|
|
389
|
+
if is_delegate
|
|
390
|
+
else [_format_tool_or_subagent_name(name) for name in names]
|
|
391
|
+
)
|
|
392
|
+
if len(formatted) == 1:
|
|
393
|
+
return formatted[0]
|
|
394
|
+
return ", ".join(formatted)
|
|
395
|
+
|
|
396
|
+
def _collect_tool_names(self, tool_info: dict[str, Any]) -> list[str]:
|
|
397
|
+
"""Collect tool names from tool info.
|
|
398
|
+
|
|
399
|
+
Args:
|
|
400
|
+
tool_info: Tool information dictionary.
|
|
401
|
+
|
|
402
|
+
Returns:
|
|
403
|
+
List of tool names found.
|
|
404
|
+
"""
|
|
405
|
+
names: list[str] = []
|
|
406
|
+
if "name" in tool_info and isinstance(tool_info["name"], str):
|
|
407
|
+
names.append(tool_info["name"])
|
|
408
|
+
calls = tool_info.get("tool_calls")
|
|
409
|
+
if isinstance(calls, list):
|
|
410
|
+
for call in calls:
|
|
411
|
+
if isinstance(call, dict) and isinstance(call.get("name"), str):
|
|
412
|
+
names.append(call["name"])
|
|
413
|
+
return names
|
|
414
|
+
|
|
415
|
+
def _default_heading(self, phase: ActivityPhase, subject_name: str | None, is_delegate: bool) -> str:
|
|
416
|
+
"""Return a deterministic heading used when the LLM omits one.
|
|
417
|
+
|
|
418
|
+
Args:
|
|
419
|
+
phase: Current activity phase indicating start/end/delegate state.
|
|
420
|
+
subject_name: Friendly tool or delegate name, when available.
|
|
421
|
+
is_delegate: Whether the subject represents a delegated agent.
|
|
422
|
+
|
|
423
|
+
Returns:
|
|
424
|
+
Heading string normalized for markdown display.
|
|
425
|
+
"""
|
|
426
|
+
subject = subject_name or ("delegated task" if is_delegate else "agent task")
|
|
427
|
+
subject = subject.strip().rstrip(".")
|
|
428
|
+
templates: dict[ActivityPhase, str] = {
|
|
429
|
+
ActivityPhase.TOOL_START: "Executing {subject}",
|
|
430
|
+
ActivityPhase.TOOL_END: "{subject} completed",
|
|
431
|
+
ActivityPhase.DELEGATE_START: "Delegating to {subject}",
|
|
432
|
+
ActivityPhase.DELEGATE_END: "Compiling results from {subject}",
|
|
433
|
+
}
|
|
434
|
+
template = templates.get(phase)
|
|
435
|
+
if template:
|
|
436
|
+
heading = template.format(subject=subject)
|
|
437
|
+
elif subject_name:
|
|
438
|
+
heading = subject_name.strip()
|
|
439
|
+
else:
|
|
440
|
+
heading = "Delegated task" if is_delegate else "Agent update"
|
|
441
|
+
heading = re.sub(r"\s+", " ", heading).strip()
|
|
442
|
+
return heading.rstrip(":.")
|
|
443
|
+
|
|
444
|
+
def _parse_hitl_metadata(self, payload: Any) -> HitlMetadata | None:
|
|
445
|
+
"""Parse HITL metadata from payload.
|
|
446
|
+
|
|
447
|
+
Args:
|
|
448
|
+
payload: HITL metadata payload (dict, HitlMetadata, or other).
|
|
449
|
+
|
|
450
|
+
Returns:
|
|
451
|
+
HitlMetadata instance if valid, None otherwise.
|
|
452
|
+
"""
|
|
453
|
+
if isinstance(payload, HitlMetadata):
|
|
454
|
+
return payload
|
|
455
|
+
if isinstance(payload, dict):
|
|
456
|
+
try:
|
|
457
|
+
return HitlMetadata.model_validate(payload) # type: ignore[attr-defined]
|
|
458
|
+
except Exception:
|
|
459
|
+
try:
|
|
460
|
+
return HitlMetadata(**payload)
|
|
461
|
+
except Exception:
|
|
462
|
+
return None
|
|
463
|
+
return None
|
|
464
|
+
|
|
465
|
+
def _to_decision(self, metadata: HitlMetadata | None) -> ApprovalDecisionType | None:
|
|
466
|
+
"""Convert HITL metadata decision to ApprovalDecisionType.
|
|
467
|
+
|
|
468
|
+
Args:
|
|
469
|
+
metadata: HITL metadata instance.
|
|
470
|
+
|
|
471
|
+
Returns:
|
|
472
|
+
ApprovalDecisionType enum value, or None if invalid.
|
|
473
|
+
"""
|
|
474
|
+
if metadata and metadata.decision:
|
|
475
|
+
try:
|
|
476
|
+
return ApprovalDecisionType(metadata.decision)
|
|
477
|
+
except Exception:
|
|
478
|
+
return None
|
|
479
|
+
return None
|
|
480
|
+
|
|
481
|
+
def _build_context_summary(self, context: ActivityContext) -> str | None:
|
|
482
|
+
"""Build summary from context excerpts or sanitized data.
|
|
483
|
+
|
|
484
|
+
Args:
|
|
485
|
+
context: Activity context.
|
|
486
|
+
|
|
487
|
+
Returns:
|
|
488
|
+
Summary string, or None if no data available.
|
|
489
|
+
"""
|
|
490
|
+
if context.arguments_excerpt:
|
|
491
|
+
return context.arguments_excerpt
|
|
492
|
+
if context.output_excerpt:
|
|
493
|
+
return context.output_excerpt
|
|
494
|
+
if context.sanitized_args:
|
|
495
|
+
return self._truncate_json(context.sanitized_args)
|
|
496
|
+
if context.sanitized_output:
|
|
497
|
+
return self._truncate_json(context.sanitized_output)
|
|
498
|
+
return None
|
|
499
|
+
|
|
500
|
+
def _truncate_json(self, value: Any, limit: int = OUTPUT_EXCERPT_MAX_CHARS) -> str | None:
|
|
501
|
+
"""Truncate JSON-serialized value to limit with ellipsis.
|
|
502
|
+
|
|
503
|
+
Args:
|
|
504
|
+
value: Value to serialize and truncate.
|
|
505
|
+
limit: Maximum length (default 400).
|
|
506
|
+
|
|
507
|
+
Returns:
|
|
508
|
+
Truncated JSON string, or None if empty.
|
|
509
|
+
"""
|
|
510
|
+
try:
|
|
511
|
+
serialized = json.dumps(value, ensure_ascii=False)
|
|
512
|
+
except Exception:
|
|
513
|
+
serialized = str(value)
|
|
514
|
+
serialized = serialized.strip()
|
|
515
|
+
if not serialized:
|
|
516
|
+
return None
|
|
517
|
+
if len(serialized) <= limit:
|
|
518
|
+
return serialized
|
|
519
|
+
return serialized[: limit - 1].rstrip() + "…"
|
|
520
|
+
|
|
521
|
+
def _build_hitl_message(self, context: ActivityContext) -> str:
|
|
522
|
+
"""Build HITL message based on phase and decision.
|
|
523
|
+
|
|
524
|
+
Args:
|
|
525
|
+
context: Activity context with HITL information.
|
|
526
|
+
|
|
527
|
+
Returns:
|
|
528
|
+
Formatted HITL message string.
|
|
529
|
+
"""
|
|
530
|
+
if context.phase == ActivityPhase.HITL_PENDING:
|
|
531
|
+
detail = HITL_PENDING_DESCRIPTION
|
|
532
|
+
summary = context.subject_name or self._build_context_summary(context)
|
|
533
|
+
if summary:
|
|
534
|
+
detail = f"{detail} Request: {summary}."
|
|
535
|
+
return f"{HITL_PENDING_TITLE}\n\n{detail}"
|
|
536
|
+
|
|
537
|
+
title, desc = HITL_DECISION_MESSAGES.get(
|
|
538
|
+
context.hitl_decision or ApprovalDecisionType.SKIPPED,
|
|
539
|
+
HITL_DECISION_MESSAGES[ApprovalDecisionType.SKIPPED],
|
|
540
|
+
)
|
|
541
|
+
message = f"{title}\n\n{desc}"
|
|
542
|
+
extra = self._build_context_summary(context)
|
|
543
|
+
if extra:
|
|
544
|
+
message = f"{message}\n\nContext: {extra}"
|
|
545
|
+
return message
|
|
546
|
+
|
|
547
|
+
def _invoke_formatter(self, context: ActivityContext) -> Any | None:
|
|
548
|
+
"""Invoke the formatter LLM and return the raw response.
|
|
549
|
+
|
|
550
|
+
Args:
|
|
551
|
+
context: Prepared activity context containing prompt payload inputs.
|
|
552
|
+
|
|
553
|
+
Returns:
|
|
554
|
+
Formatter client response object or None when the invoker is unavailable.
|
|
555
|
+
"""
|
|
556
|
+
prompt_payload = self._build_prompt_payload(context)
|
|
557
|
+
user_prompt = self._build_user_prompt(prompt_payload)
|
|
558
|
+
messages = [
|
|
559
|
+
Message.system(SYSTEM_PROMPT),
|
|
560
|
+
Message.user(user_prompt),
|
|
561
|
+
]
|
|
562
|
+
try:
|
|
563
|
+
raw_response = _formatter_llm_client.invoke_blocking(messages=messages)
|
|
564
|
+
return raw_response
|
|
565
|
+
except FormatterInvokerUnavailableError:
|
|
566
|
+
logger.warning(
|
|
567
|
+
"activity narrative formatter unavailable; skipping narrative (step_id=%s phase=%s subject=%s)",
|
|
568
|
+
context.step_id,
|
|
569
|
+
context.phase.value,
|
|
570
|
+
context.subject_name,
|
|
571
|
+
)
|
|
572
|
+
return None
|
|
573
|
+
except FormatterInvocationError as exc: # pragma: no cover - defensive
|
|
574
|
+
logger.warning(
|
|
575
|
+
"activity narrative LLM invocation failed step_id=%s phase=%s: %s",
|
|
576
|
+
context.step_id,
|
|
577
|
+
context.phase.value,
|
|
578
|
+
exc,
|
|
579
|
+
)
|
|
580
|
+
return None
|
|
581
|
+
|
|
582
|
+
def _generate_llm_texts(self, context: ActivityContext) -> tuple[str | None, str]:
|
|
583
|
+
"""Return heading/body text strings produced by the formatter model.
|
|
584
|
+
|
|
585
|
+
Args:
|
|
586
|
+
context: Fully constructed activity context for the event.
|
|
587
|
+
|
|
588
|
+
Returns:
|
|
589
|
+
A tuple of (heading_text, body_text) where heading_text may be None when
|
|
590
|
+
the formatter is unavailable and body_text always contains a fallback.
|
|
591
|
+
"""
|
|
592
|
+
fallback_body = self._build_context_summary(context) or "No additional detail provided."
|
|
593
|
+
raw_response = self._invoke_formatter(context)
|
|
594
|
+
if raw_response is None:
|
|
595
|
+
logger.info("activity narrative LLM unavailable step_id=%s; using fallback", context.step_id)
|
|
596
|
+
return None, fallback_body
|
|
597
|
+
|
|
598
|
+
response_text = self._extract_response_text(raw_response)
|
|
599
|
+
logger.debug(
|
|
600
|
+
"activity narrative LLM raw response step_id=%s preview=%s",
|
|
601
|
+
context.step_id,
|
|
602
|
+
response_text[:160] + ("…" if len(response_text) > 160 else ""),
|
|
603
|
+
)
|
|
604
|
+
heading_text, body_text = self._extract_payload_texts(response_text, context)
|
|
605
|
+
|
|
606
|
+
if not body_text:
|
|
607
|
+
body_text = self._clean_llm_body(response_text)
|
|
608
|
+
if not body_text:
|
|
609
|
+
body_text = fallback_body
|
|
610
|
+
return heading_text, body_text
|
|
611
|
+
|
|
612
|
+
def _extract_payload_texts(self, response_text: str, context: ActivityContext) -> tuple[str | None, str | None]:
|
|
613
|
+
"""Extract heading/body strings from an embedded JSON block.
|
|
614
|
+
|
|
615
|
+
Args:
|
|
616
|
+
response_text: Raw formatter response serialized as text.
|
|
617
|
+
context: Activity context used for logging.
|
|
618
|
+
|
|
619
|
+
Returns:
|
|
620
|
+
Tuple of sanitized heading and body text strings, or (None, None) when parsing fails.
|
|
621
|
+
"""
|
|
622
|
+
payload = self._parse_embedded_json(response_text)
|
|
623
|
+
if not isinstance(payload, dict):
|
|
624
|
+
return None, None
|
|
625
|
+
heading_text = self._sanitize_heading_text(payload.get("heading_text"))
|
|
626
|
+
body_candidate = payload.get("body_text")
|
|
627
|
+
body_text = self._clean_llm_body(body_candidate) if body_candidate is not None else None
|
|
628
|
+
heading_present = bool(heading_text)
|
|
629
|
+
body_present = bool(body_text)
|
|
630
|
+
logger.debug(
|
|
631
|
+
"activity narrative parsed JSON step_id=%s heading_present=%s body_present=%s",
|
|
632
|
+
context.step_id,
|
|
633
|
+
heading_present,
|
|
634
|
+
body_present,
|
|
635
|
+
)
|
|
636
|
+
return heading_text, body_text
|
|
637
|
+
|
|
638
|
+
def _parse_embedded_json(self, response_text: str) -> dict[str, Any] | None:
|
|
639
|
+
"""Return a dict parsed from the first {...} block in the response text.
|
|
640
|
+
|
|
641
|
+
Args:
|
|
642
|
+
response_text: Formatter output that may contain an embedded JSON payload.
|
|
643
|
+
|
|
644
|
+
Returns:
|
|
645
|
+
Parsed dictionary if JSON is found and valid, otherwise None.
|
|
646
|
+
"""
|
|
647
|
+
start = response_text.find("{")
|
|
648
|
+
end = response_text.rfind("}")
|
|
649
|
+
if start == -1 or end == -1 or end <= start:
|
|
650
|
+
return None
|
|
651
|
+
candidate = response_text[start : end + 1]
|
|
652
|
+
try:
|
|
653
|
+
parsed_payload = json.loads(candidate)
|
|
654
|
+
except Exception:
|
|
655
|
+
return None
|
|
656
|
+
return parsed_payload if isinstance(parsed_payload, dict) else None
|
|
657
|
+
|
|
658
|
+
def _build_prompt_payload(self, context: ActivityContext) -> dict[str, Any]:
|
|
659
|
+
"""Build prompt payload for LLM invocation.
|
|
660
|
+
|
|
661
|
+
Args:
|
|
662
|
+
context: Activity context.
|
|
663
|
+
|
|
664
|
+
Returns:
|
|
665
|
+
Prompt payload dictionary.
|
|
666
|
+
"""
|
|
667
|
+
payload = {
|
|
668
|
+
"phase": context.phase.value,
|
|
669
|
+
"agent": context.agent_name,
|
|
670
|
+
"subject": context.subject_name,
|
|
671
|
+
"arguments": context.sanitized_args,
|
|
672
|
+
"output": context.sanitized_output,
|
|
673
|
+
"arguments_excerpt": context.arguments_excerpt,
|
|
674
|
+
"output_excerpt": context.output_excerpt,
|
|
675
|
+
"error": context.error_excerpt,
|
|
676
|
+
"is_delegate": context.is_delegate,
|
|
677
|
+
}
|
|
678
|
+
if context.hitl_metadata:
|
|
679
|
+
payload["hitl"] = context.hitl_metadata.model_dump(mode="json", exclude_none=True)
|
|
680
|
+
instructions = self._phase_prompt(context.phase)
|
|
681
|
+
return {
|
|
682
|
+
"context": payload,
|
|
683
|
+
"heading_instruction": instructions["heading"],
|
|
684
|
+
"body_instruction": instructions["body"],
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
def _phase_prompt(self, phase: ActivityPhase) -> dict[str, str]:
|
|
688
|
+
"""Return heading/body instructions for the given phase.
|
|
689
|
+
|
|
690
|
+
Args:
|
|
691
|
+
phase: Activity phase to build prompt instructions for.
|
|
692
|
+
|
|
693
|
+
Returns:
|
|
694
|
+
Dictionary with "heading" and "body" instruction strings.
|
|
695
|
+
"""
|
|
696
|
+
return self._PHASE_PROMPTS.get(
|
|
697
|
+
phase,
|
|
698
|
+
{
|
|
699
|
+
"heading": "Return heading_text describing this update in five words or fewer.",
|
|
700
|
+
"body": "Return body_text summarizing the activity in one or two sentences.",
|
|
701
|
+
},
|
|
702
|
+
)
|
|
703
|
+
|
|
704
|
+
def _build_user_prompt(self, payload: dict[str, Any]) -> str:
|
|
705
|
+
"""Compose the user-facing prompt string for the formatter model.
|
|
706
|
+
|
|
707
|
+
Args:
|
|
708
|
+
payload: Prompt payload containing context plus heading/body instructions.
|
|
709
|
+
|
|
710
|
+
Returns:
|
|
711
|
+
Fully formatted string that will be sent as the user message.
|
|
712
|
+
"""
|
|
713
|
+
heading_instruction = payload.get("heading_instruction") or "Provide heading_text describing this update."
|
|
714
|
+
body_instruction = payload.get("body_instruction") or "Provide sentence_text summarizing this update."
|
|
715
|
+
context_payload = payload.get("context") or {}
|
|
716
|
+
arguments_excerpt = context_payload.get("arguments_excerpt") or "Not provided."
|
|
717
|
+
output_excerpt = context_payload.get("output_excerpt") or "Not provided."
|
|
718
|
+
context_json = json.dumps(context_payload, ensure_ascii=False)
|
|
719
|
+
return (
|
|
720
|
+
"Return valid JSON with keys 'heading_text' and 'body_text'. "
|
|
721
|
+
"Do not include any other keys.\n"
|
|
722
|
+
f"Heading instruction: {heading_instruction}\n"
|
|
723
|
+
f"Body instruction: {body_instruction}\n"
|
|
724
|
+
"Constraints: start each text with a verb phrase, avoid 'I' or 'We'.\n"
|
|
725
|
+
f"Arguments excerpt: {arguments_excerpt}\n"
|
|
726
|
+
f"Output excerpt: {output_excerpt}\n"
|
|
727
|
+
f"Context JSON: {context_json}"
|
|
728
|
+
)
|
|
729
|
+
|
|
730
|
+
def _clean_llm_body(self, response: Any) -> str | None:
|
|
731
|
+
"""Normalize the LLM response (or snippet) into a plain body string.
|
|
732
|
+
|
|
733
|
+
Args:
|
|
734
|
+
response: Formatter response object, dictionary, or string snippet.
|
|
735
|
+
|
|
736
|
+
Returns:
|
|
737
|
+
Cleaned body text or None when the response lacks usable content.
|
|
738
|
+
"""
|
|
739
|
+
text = self._extract_response_text(response)
|
|
740
|
+
if not text:
|
|
741
|
+
return None
|
|
742
|
+
lines = [line.strip() for line in text.splitlines() if line.strip()]
|
|
743
|
+
body_lines: list[str] = []
|
|
744
|
+
for line in lines:
|
|
745
|
+
if line.startswith("#"):
|
|
746
|
+
continue
|
|
747
|
+
if line.startswith("**") and line.endswith("**"):
|
|
748
|
+
continue
|
|
749
|
+
body_lines.append(line)
|
|
750
|
+
body = "\n".join(body_lines).strip()
|
|
751
|
+
return body or None
|
|
752
|
+
|
|
753
|
+
def _sanitize_heading_text(self, heading: Any | None) -> str | None:
|
|
754
|
+
"""Normalize heading text extracted from the formatter JSON payload.
|
|
755
|
+
|
|
756
|
+
Args:
|
|
757
|
+
heading: Raw heading value returned by the formatter.
|
|
758
|
+
|
|
759
|
+
Returns:
|
|
760
|
+
Sanitized heading string or None when the value is unusable.
|
|
761
|
+
"""
|
|
762
|
+
if not isinstance(heading, str):
|
|
763
|
+
return None
|
|
764
|
+
text = heading.replace("\n", " ").strip()
|
|
765
|
+
text = re.sub(r"\s+", " ", text)
|
|
766
|
+
return text.rstrip(" .:;-")
|
|
767
|
+
|
|
768
|
+
def _extract_response_text(self, response: Any) -> str:
|
|
769
|
+
"""Extract a usable text payload from various LLM client response shapes.
|
|
770
|
+
|
|
771
|
+
Args:
|
|
772
|
+
response: Formatter response object/dict/string produced by the client.
|
|
773
|
+
|
|
774
|
+
Returns:
|
|
775
|
+
Stripped string representation suitable for downstream parsing.
|
|
776
|
+
"""
|
|
777
|
+
if hasattr(response, "output_text"):
|
|
778
|
+
text = getattr(response, "output_text")
|
|
779
|
+
elif isinstance(response, dict) and isinstance(response.get("output_text"), str):
|
|
780
|
+
text = response["output_text"]
|
|
781
|
+
else:
|
|
782
|
+
text = response if isinstance(response, str) else str(response or "")
|
|
783
|
+
return text.strip()
|
|
784
|
+
|
|
785
|
+
def _assemble_message(
|
|
786
|
+
self,
|
|
787
|
+
context: ActivityContext,
|
|
788
|
+
heading_text: str | None = None,
|
|
789
|
+
body_text: str | None = None,
|
|
790
|
+
) -> str:
|
|
791
|
+
"""Combine heading/body strings with deterministic fallbacks.
|
|
792
|
+
|
|
793
|
+
Args:
|
|
794
|
+
context: Activity context associated with the event.
|
|
795
|
+
heading_text: Optional heading supplied by the formatter.
|
|
796
|
+
body_text: Optional body text supplied by the formatter.
|
|
797
|
+
|
|
798
|
+
Returns:
|
|
799
|
+
Markdown-formatted message ready for dashboards.
|
|
800
|
+
"""
|
|
801
|
+
heading = (
|
|
802
|
+
heading_text
|
|
803
|
+
or context.default_heading
|
|
804
|
+
or self._default_heading(context.phase, context.subject_name, context.is_delegate)
|
|
805
|
+
)
|
|
806
|
+
if heading:
|
|
807
|
+
heading = heading.strip()
|
|
808
|
+
else:
|
|
809
|
+
heading = "Delegated task"
|
|
810
|
+
if not context.is_delegate:
|
|
811
|
+
heading = "Agent update"
|
|
812
|
+
heading = re.sub(r"\s+", " ", heading).rstrip(".")
|
|
813
|
+
|
|
814
|
+
body = body_text.strip() if body_text else (self._build_context_summary(context) or "")
|
|
815
|
+
if not body:
|
|
816
|
+
body = "No additional detail provided."
|
|
817
|
+
return f"**{heading}**\n\n{body}"
|