aip-agents-binary 0.6.4__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.
Potentially problematic release.
This version of aip-agents-binary might be problematic. Click here for more details.
- 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 +3037 -0
- aip_agents/agent/base_langgraph_agent.pyi +233 -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 +767 -0
- aip_agents/agent/langgraph_memory_enhancer_agent.pyi +50 -0
- aip_agents/agent/langgraph_react_agent.py +2856 -0
- aip_agents/agent/langgraph_react_agent.pyi +170 -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/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_ptc.py +49 -0
- aip_agents/examples/hello_world_ptc.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 +55 -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 +228 -0
- aip_agents/mcp/client/connection_manager.pyi +51 -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 +612 -0
- aip_agents/mcp/client/persistent_session.pyi +122 -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 +263 -0
- aip_agents/mcp/client/transports.pyi +132 -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 +811 -0
- aip_agents/memory/adapters/base_adapter.pyi +176 -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/ptc/__init__.py +48 -0
- aip_agents/ptc/__init__.pyi +10 -0
- aip_agents/ptc/doc_gen.py +122 -0
- aip_agents/ptc/doc_gen.pyi +40 -0
- aip_agents/ptc/exceptions.py +39 -0
- aip_agents/ptc/exceptions.pyi +22 -0
- aip_agents/ptc/executor.py +143 -0
- aip_agents/ptc/executor.pyi +73 -0
- aip_agents/ptc/mcp/__init__.py +45 -0
- aip_agents/ptc/mcp/__init__.pyi +7 -0
- aip_agents/ptc/mcp/sandbox_bridge.py +668 -0
- aip_agents/ptc/mcp/sandbox_bridge.pyi +47 -0
- aip_agents/ptc/mcp/templates/__init__.py +1 -0
- aip_agents/ptc/mcp/templates/__init__.pyi +0 -0
- aip_agents/ptc/mcp/templates/mcp_client.py.template +239 -0
- aip_agents/ptc/naming.py +184 -0
- aip_agents/ptc/naming.pyi +76 -0
- aip_agents/ptc/payload.py +26 -0
- aip_agents/ptc/payload.pyi +15 -0
- aip_agents/ptc/prompt_builder.py +571 -0
- aip_agents/ptc/prompt_builder.pyi +55 -0
- aip_agents/ptc/ptc_helper.py +16 -0
- aip_agents/ptc/ptc_helper.pyi +1 -0
- aip_agents/ptc/sandbox_bridge.py +58 -0
- aip_agents/ptc/sandbox_bridge.pyi +25 -0
- aip_agents/ptc/template_utils.py +33 -0
- aip_agents/ptc/template_utils.pyi +13 -0
- aip_agents/ptc/templates/__init__.py +1 -0
- aip_agents/ptc/templates/__init__.pyi +0 -0
- aip_agents/ptc/templates/ptc_helper.py.template +134 -0
- aip_agents/sandbox/__init__.py +43 -0
- aip_agents/sandbox/__init__.pyi +5 -0
- aip_agents/sandbox/defaults.py +9 -0
- aip_agents/sandbox/defaults.pyi +2 -0
- aip_agents/sandbox/e2b_runtime.py +267 -0
- aip_agents/sandbox/e2b_runtime.pyi +51 -0
- aip_agents/sandbox/template_builder.py +131 -0
- aip_agents/sandbox/template_builder.pyi +36 -0
- aip_agents/sandbox/types.py +24 -0
- aip_agents/sandbox/types.pyi +14 -0
- aip_agents/sandbox/validation.py +50 -0
- aip_agents/sandbox/validation.pyi +20 -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 +64 -0
- aip_agents/tools/__init__.pyi +11 -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 +1120 -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 +815 -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/date_range_tool.py +554 -0
- aip_agents/tools/date_range_tool.pyi +21 -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/execute_ptc_code.py +308 -0
- aip_agents/tools/execute_ptc_code.pyi +90 -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 +29 -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 +365 -0
- aip_agents/tools/memory_search/mem0.pyi +29 -0
- aip_agents/tools/memory_search/schema.py +81 -0
- aip_agents/tools/memory_search/schema.pyi +25 -0
- aip_agents/tools/memory_search_tool.py +34 -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 +1096 -0
- aip_agents/utils/langgraph/tool_managers/delegation_tool_manager.pyi +56 -0
- aip_agents/utils/langgraph/tool_output_management.py +1047 -0
- aip_agents/utils/langgraph/tool_output_management.pyi +329 -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.6.4.dist-info/METADATA +673 -0
- aip_agents_binary-0.6.4.dist-info/RECORD +612 -0
- aip_agents_binary-0.6.4.dist-info/WHEEL +5 -0
- aip_agents_binary-0.6.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1047 @@
|
|
|
1
|
+
"""Production-ready tool output management system for LangGraph ReAct agents.
|
|
2
|
+
|
|
3
|
+
This module provides a comprehensive system for managing tool outputs in ReAct agents,
|
|
4
|
+
including secure reference resolution, lifecycle management, and hybrid storage patterns.
|
|
5
|
+
|
|
6
|
+
Key Features:
|
|
7
|
+
- Automatic and manual tool output storage with configurable lifecycle management
|
|
8
|
+
- Secure reference resolution with validation and sanitization
|
|
9
|
+
- LLM-friendly output summaries with data previews and tool context
|
|
10
|
+
- Production-ready error handling with specialized exceptions
|
|
11
|
+
- Memory management with automatic cleanup based on age and size limits
|
|
12
|
+
|
|
13
|
+
Authors:
|
|
14
|
+
Christian Trisno Sen Long Chen (christian.t.s.l.chen@gdplabs.id)
|
|
15
|
+
Fachriza Adhiatma (fachriza.d.adhiatma@gdplabs.id)
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
import re
|
|
20
|
+
import sys
|
|
21
|
+
import threading
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
from datetime import datetime, timedelta
|
|
24
|
+
from re import Pattern
|
|
25
|
+
from typing import Any
|
|
26
|
+
|
|
27
|
+
from aip_agents.storage.providers.base import BaseStorageProvider, StorageError
|
|
28
|
+
from aip_agents.storage.providers.memory import InMemoryStorageProvider
|
|
29
|
+
from aip_agents.utils.logger import get_logger
|
|
30
|
+
|
|
31
|
+
logger = get_logger(__name__)
|
|
32
|
+
|
|
33
|
+
# Constants for display formatting and reference resolution
|
|
34
|
+
STRING_TRUNCATION_LENGTH = 100
|
|
35
|
+
MAX_TOOL_ARGS_DISPLAY = 5
|
|
36
|
+
DATA_PREVIEW_TRUNCATION_LENGTH = 150
|
|
37
|
+
TOOL_OUTPUT_REFERENCE_PREFIX = "$tool_output."
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ToolReferenceError(Exception):
|
|
41
|
+
"""Specialized exception for tool output reference resolution errors.
|
|
42
|
+
|
|
43
|
+
This exception is raised when there are issues with resolving tool output references,
|
|
44
|
+
such as invalid reference syntax, missing outputs, or security violations.
|
|
45
|
+
|
|
46
|
+
Attributes:
|
|
47
|
+
reference: The original reference string that caused the error.
|
|
48
|
+
call_id: The call ID that was attempted to be resolved, if applicable.
|
|
49
|
+
details: Additional error details for debugging.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
message: str,
|
|
55
|
+
reference: str | None = None,
|
|
56
|
+
call_id: str | None = None,
|
|
57
|
+
details: dict[str, Any] | None = None,
|
|
58
|
+
):
|
|
59
|
+
"""Initialize a ToolReferenceError.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
message: Human-readable error message describing what went wrong.
|
|
63
|
+
reference: The original reference string that caused the error, if applicable.
|
|
64
|
+
call_id: The call ID that was attempted to be resolved, if applicable.
|
|
65
|
+
details: Additional error details for debugging, if applicable.
|
|
66
|
+
"""
|
|
67
|
+
super().__init__(message)
|
|
68
|
+
self.reference = reference
|
|
69
|
+
self.call_id = call_id
|
|
70
|
+
self.details = details or {}
|
|
71
|
+
|
|
72
|
+
def __str__(self) -> str:
|
|
73
|
+
"""Return a detailed string representation of the error.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
A formatted error message with context information.
|
|
77
|
+
"""
|
|
78
|
+
parts = [super().__str__()]
|
|
79
|
+
if self.reference:
|
|
80
|
+
parts.append(f"Reference: {self.reference}")
|
|
81
|
+
if self.call_id:
|
|
82
|
+
parts.append(f"Call ID: {self.call_id}")
|
|
83
|
+
if self.details:
|
|
84
|
+
parts.append(f"Details: {self.details}")
|
|
85
|
+
return " | ".join(parts)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass
|
|
89
|
+
class ToolOutputConfig:
|
|
90
|
+
"""Configuration for tool output management system.
|
|
91
|
+
|
|
92
|
+
This class defines the operational parameters for the tool output management
|
|
93
|
+
system, including storage limits, cleanup intervals, and lifecycle policies.
|
|
94
|
+
|
|
95
|
+
Attributes:
|
|
96
|
+
max_stored_outputs: Maximum number of tool outputs to store simultaneously.
|
|
97
|
+
When this limit is reached, oldest outputs are evicted. Defaults to 100.
|
|
98
|
+
max_age_minutes: Maximum age in minutes for stored outputs before they
|
|
99
|
+
become eligible for cleanup. Defaults to 60 minutes.
|
|
100
|
+
cleanup_interval: Number of tool calls between automatic cleanup operations.
|
|
101
|
+
Set to 0 to disable automatic cleanup. Defaults to 20.
|
|
102
|
+
storage_provider: Optional storage provider for persistent storage.
|
|
103
|
+
If None, uses in-memory storage (backward compatible). Defaults to None.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
max_stored_outputs: int = 100
|
|
107
|
+
max_age_minutes: int = 60
|
|
108
|
+
cleanup_interval: int = 20
|
|
109
|
+
storage_provider: BaseStorageProvider | None = None
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass
|
|
113
|
+
class ToolOutput:
|
|
114
|
+
"""Container for tool outputs with optional data payload.
|
|
115
|
+
|
|
116
|
+
This class represents tool output metadata and optionally the actual data.
|
|
117
|
+
When used as metadata only, the data field is None and must be retrieved
|
|
118
|
+
from storage. When loaded with data, it contains the complete output.
|
|
119
|
+
|
|
120
|
+
Attributes:
|
|
121
|
+
call_id: Unique identifier for this tool call, used for reference resolution.
|
|
122
|
+
tool_name: Name of the tool that produced this output.
|
|
123
|
+
timestamp: When this output was created and stored.
|
|
124
|
+
size_bytes: Approximate size of the stored data in bytes for memory management.
|
|
125
|
+
tool_args: Input arguments that were passed to the tool for this call.
|
|
126
|
+
data: Optional actual output data. None when used as metadata only.
|
|
127
|
+
data_description: Optional human-readable description of the data content,
|
|
128
|
+
typically provided by the tool itself.
|
|
129
|
+
tags: Optional list of tags for categorization and filtering.
|
|
130
|
+
agent_name: Name of the agent that created this output, for multi-agent context.
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
call_id: str
|
|
134
|
+
tool_name: str
|
|
135
|
+
timestamp: datetime
|
|
136
|
+
size_bytes: int
|
|
137
|
+
tool_args: dict[str, Any]
|
|
138
|
+
data: Any | None = None # None when metadata only, populated when retrieved
|
|
139
|
+
data_description: str | None = None
|
|
140
|
+
tags: list[str] | None = None
|
|
141
|
+
agent_name: str | None = None
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def is_metadata_only(self) -> bool:
|
|
145
|
+
"""Check if this instance contains only metadata without data."""
|
|
146
|
+
return self.data is None
|
|
147
|
+
|
|
148
|
+
def is_expired(self, max_age: timedelta) -> bool:
|
|
149
|
+
"""Check if this output has expired based on the given maximum age.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
max_age (timedelta): The maximum age allowed before expiration.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
bool: True if the output has expired, False otherwise.
|
|
156
|
+
"""
|
|
157
|
+
return datetime.now() - self.timestamp > max_age
|
|
158
|
+
|
|
159
|
+
def get_data_preview(
|
|
160
|
+
self, max_length: int = 200, storage_provider: BaseStorageProvider | None = None, thread_id: str | None = None
|
|
161
|
+
) -> str | None:
|
|
162
|
+
"""Get a truncated string representation of the stored data.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
max_length: Maximum length of the preview string.
|
|
166
|
+
storage_provider: Required only if data is not loaded.
|
|
167
|
+
thread_id: Thread ID required for proper storage key generation.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
A string representation of the data, truncated if necessary. None if data is not found.
|
|
171
|
+
"""
|
|
172
|
+
# If we have data, use it directly
|
|
173
|
+
if self.data is not None:
|
|
174
|
+
data_str = str(self.data)
|
|
175
|
+
if len(data_str) <= max_length:
|
|
176
|
+
return data_str
|
|
177
|
+
return data_str[:max_length] + "..."
|
|
178
|
+
|
|
179
|
+
if storage_provider is None or thread_id is None:
|
|
180
|
+
return None
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
# Use proper storage key format: thread_id:call_id
|
|
184
|
+
storage_key = f"{thread_id}:{self.call_id}"
|
|
185
|
+
actual_data = storage_provider.retrieve(storage_key)
|
|
186
|
+
data_str = str(actual_data)
|
|
187
|
+
if len(data_str) <= max_length:
|
|
188
|
+
return data_str
|
|
189
|
+
return data_str[:max_length] + "..."
|
|
190
|
+
except Exception:
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
def with_data(self, data: Any) -> "ToolOutput":
|
|
194
|
+
"""Create a new instance with data populated.
|
|
195
|
+
|
|
196
|
+
Returns a new ToolOutput instance with the same metadata but with
|
|
197
|
+
data field populated. Useful for converting metadata-only instances
|
|
198
|
+
to complete instances.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
data (Any): The actual output data to populate.
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
ToolOutput: A new instance with data populated.
|
|
205
|
+
"""
|
|
206
|
+
return ToolOutput(
|
|
207
|
+
call_id=self.call_id,
|
|
208
|
+
tool_name=self.tool_name,
|
|
209
|
+
timestamp=self.timestamp,
|
|
210
|
+
size_bytes=self.size_bytes,
|
|
211
|
+
tool_args=self.tool_args,
|
|
212
|
+
data=data,
|
|
213
|
+
data_description=self.data_description,
|
|
214
|
+
tags=self.tags,
|
|
215
|
+
agent_name=self.agent_name,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@dataclass
|
|
220
|
+
class StoreOutputParams:
|
|
221
|
+
"""Parameters for storing tool outputs.
|
|
222
|
+
|
|
223
|
+
Reduces the number of arguments passed to store_output method.
|
|
224
|
+
|
|
225
|
+
Attributes:
|
|
226
|
+
call_id: Unique identifier for this tool call.
|
|
227
|
+
tool_name: Name of the tool that produced the output.
|
|
228
|
+
data: The actual output data to store.
|
|
229
|
+
tool_args: Input arguments used for the tool call.
|
|
230
|
+
thread_id: Thread/conversation ID to organize outputs by conversation.
|
|
231
|
+
description: Optional human-readable description of the output.
|
|
232
|
+
tags: Optional list of tags for categorization.
|
|
233
|
+
agent_name: Name of the agent that created this output.
|
|
234
|
+
"""
|
|
235
|
+
|
|
236
|
+
call_id: str
|
|
237
|
+
tool_name: str
|
|
238
|
+
data: Any
|
|
239
|
+
tool_args: dict[str, Any]
|
|
240
|
+
thread_id: str
|
|
241
|
+
description: str | None = None
|
|
242
|
+
tags: list[str] | None = None
|
|
243
|
+
agent_name: str | None = None
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class ToolOutputManager:
|
|
247
|
+
"""Production-ready tool output manager with comprehensive lifecycle management.
|
|
248
|
+
|
|
249
|
+
This class provides centralized management of tool outputs including storage,
|
|
250
|
+
retrieval, lifecycle management, and LLM-friendly summarization. It handles
|
|
251
|
+
memory management through configurable cleanup policies and provides secure
|
|
252
|
+
access to stored outputs.
|
|
253
|
+
|
|
254
|
+
Key Features:
|
|
255
|
+
- Automatic and manual storage of tool outputs with metadata
|
|
256
|
+
- Configurable lifecycle management with age and size-based eviction
|
|
257
|
+
- LLM-friendly summary generation with data previews and context
|
|
258
|
+
- Memory management with size tracking and cleanup
|
|
259
|
+
- Thread-safe operations with proper error handling and locking
|
|
260
|
+
- Concurrent access support for multi-agent and parallel processing scenarios
|
|
261
|
+
|
|
262
|
+
Attributes:
|
|
263
|
+
config: Configuration object defining operational parameters.
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
def __init__(self, config: ToolOutputConfig):
|
|
267
|
+
"""Initialize the ToolOutputManager with the given configuration.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
config: Configuration object defining storage limits and cleanup policies.
|
|
271
|
+
"""
|
|
272
|
+
self.config = config
|
|
273
|
+
self._outputs: dict[str, dict[str, ToolOutput]] = {} # thread_id -> {call_id -> ToolOutput}
|
|
274
|
+
self._call_count = 0
|
|
275
|
+
self._total_size_bytes = 0
|
|
276
|
+
self._lock = threading.RLock() # Reentrant lock for nested operations
|
|
277
|
+
|
|
278
|
+
# Initialize storage provider (backward compatible)
|
|
279
|
+
self._storage_provider = config.storage_provider or InMemoryStorageProvider()
|
|
280
|
+
|
|
281
|
+
def _get_storage_key(self, thread_id: str, call_id: str) -> str:
|
|
282
|
+
"""Generate storage key for thread-scoped tool output storage.
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
thread_id: The thread/conversation ID.
|
|
286
|
+
call_id: The tool call ID.
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
Storage key in the format "thread_id:call_id".
|
|
290
|
+
"""
|
|
291
|
+
return f"{thread_id}:{call_id}"
|
|
292
|
+
|
|
293
|
+
def _is_external_storage(self) -> bool:
|
|
294
|
+
"""Check if the current storage provider is external (not in-memory).
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
True if using external storage, False if using in-memory storage.
|
|
298
|
+
"""
|
|
299
|
+
return not isinstance(self._storage_provider, InMemoryStorageProvider)
|
|
300
|
+
|
|
301
|
+
def store_output(
|
|
302
|
+
self,
|
|
303
|
+
params: StoreOutputParams,
|
|
304
|
+
) -> None:
|
|
305
|
+
"""Store a tool output with automatic cleanup and size management.
|
|
306
|
+
|
|
307
|
+
This method stores a tool output along with its metadata, automatically
|
|
308
|
+
handling size limits, cleanup, and memory management. If the same call_id
|
|
309
|
+
is used multiple times within the same thread, the previous output will be overwritten.
|
|
310
|
+
|
|
311
|
+
Thread-safe: This method uses internal locking to ensure safe concurrent access.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
params: StoreOutputParams containing all necessary parameters including thread_id.
|
|
315
|
+
|
|
316
|
+
Raises:
|
|
317
|
+
Exception: If storage operation fails for any reason.
|
|
318
|
+
"""
|
|
319
|
+
with self._lock:
|
|
320
|
+
try:
|
|
321
|
+
# Initialize thread storage if it doesn't exist
|
|
322
|
+
if params.thread_id not in self._outputs:
|
|
323
|
+
self._outputs[params.thread_id] = {}
|
|
324
|
+
|
|
325
|
+
if params.call_id in self._outputs[params.thread_id]:
|
|
326
|
+
logger.warning(
|
|
327
|
+
f"Overwriting existing tool output for call_id: {params.call_id} in thread: {params.thread_id}"
|
|
328
|
+
)
|
|
329
|
+
self._cleanup_single_output(params.call_id, params.thread_id)
|
|
330
|
+
|
|
331
|
+
# Ensure thread still exists after cleanup (in case it became empty)
|
|
332
|
+
if params.thread_id not in self._outputs:
|
|
333
|
+
self._outputs[params.thread_id] = {}
|
|
334
|
+
|
|
335
|
+
thread_outputs = self._outputs[params.thread_id]
|
|
336
|
+
|
|
337
|
+
size_bytes = self._calculate_size(params.data)
|
|
338
|
+
|
|
339
|
+
# Check if we need to evict oldest across all threads
|
|
340
|
+
total_outputs = sum(len(thread_outputs) for thread_outputs in self._outputs.values())
|
|
341
|
+
if total_outputs >= self.config.max_stored_outputs:
|
|
342
|
+
self._evict_oldest()
|
|
343
|
+
|
|
344
|
+
try:
|
|
345
|
+
# Use thread_id + call_id as storage key to avoid conflicts across threads
|
|
346
|
+
storage_key = self._get_storage_key(params.thread_id, params.call_id)
|
|
347
|
+
logger.debug(f"Storing data with key: {storage_key}")
|
|
348
|
+
self._storage_provider.store(storage_key, params.data)
|
|
349
|
+
|
|
350
|
+
logger.debug(
|
|
351
|
+
f"Stored in {type(self._storage_provider).__name__} for {params.call_id} "
|
|
352
|
+
f"(thread: {params.thread_id}): {size_bytes} bytes"
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
except StorageError as e:
|
|
356
|
+
logger.error(f"Storage error for {params.call_id} in thread {params.thread_id}: {e}")
|
|
357
|
+
raise
|
|
358
|
+
except Exception as e:
|
|
359
|
+
logger.error(
|
|
360
|
+
f"Unexpected error storing output for {params.call_id} in thread {params.thread_id}: {e}"
|
|
361
|
+
)
|
|
362
|
+
raise
|
|
363
|
+
|
|
364
|
+
# Store metadata only (actual data is in storage provider)
|
|
365
|
+
thread_outputs[params.call_id] = ToolOutput(
|
|
366
|
+
call_id=params.call_id,
|
|
367
|
+
tool_name=params.tool_name,
|
|
368
|
+
timestamp=datetime.now(),
|
|
369
|
+
size_bytes=size_bytes,
|
|
370
|
+
tool_args=params.tool_args,
|
|
371
|
+
data=None, # Metadata only - data stays in storage provider
|
|
372
|
+
data_description=params.description,
|
|
373
|
+
tags=params.tags,
|
|
374
|
+
agent_name=params.agent_name,
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
# Update size tracking and call count
|
|
378
|
+
self._total_size_bytes += size_bytes
|
|
379
|
+
self._call_count += 1
|
|
380
|
+
|
|
381
|
+
# Perform periodic cleanup if configured
|
|
382
|
+
if self.config.cleanup_interval > 0 and self._call_count % self.config.cleanup_interval == 0:
|
|
383
|
+
self._cleanup_expired()
|
|
384
|
+
|
|
385
|
+
storage_type = type(self._storage_provider).__name__
|
|
386
|
+
total_outputs_after = sum(len(thread_outputs) for thread_outputs in self._outputs.values())
|
|
387
|
+
logger.debug(
|
|
388
|
+
f"Stored output for {params.call_id} (thread: {params.thread_id}): {size_bytes} bytes "
|
|
389
|
+
f"in {storage_type}, total: {total_outputs_after} outputs ({self._total_size_bytes} bytes)"
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
except Exception as e:
|
|
393
|
+
logger.error(f"Failed to store tool output for {params.call_id} in thread {params.thread_id}: {e}")
|
|
394
|
+
raise
|
|
395
|
+
|
|
396
|
+
def get_output(self, call_id: str, thread_id: str) -> ToolOutput | None:
|
|
397
|
+
"""Retrieve a stored tool output by its call ID and thread ID.
|
|
398
|
+
|
|
399
|
+
Thread-safe: This method uses internal locking to ensure safe concurrent access.
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
call_id: The unique identifier for the tool call.
|
|
403
|
+
thread_id: The thread/conversation ID to search in.
|
|
404
|
+
|
|
405
|
+
Returns:
|
|
406
|
+
The ToolOutput object with data if found, None otherwise.
|
|
407
|
+
"""
|
|
408
|
+
with self._lock:
|
|
409
|
+
thread_outputs = self._outputs.get(thread_id, {})
|
|
410
|
+
if call_id not in thread_outputs:
|
|
411
|
+
logger.debug(f"Call ID {call_id} not found in thread {thread_id}")
|
|
412
|
+
return None
|
|
413
|
+
|
|
414
|
+
try:
|
|
415
|
+
# Get metadata
|
|
416
|
+
metadata = thread_outputs[call_id]
|
|
417
|
+
|
|
418
|
+
# Retrieve data using thread-specific storage key
|
|
419
|
+
storage_key = self._get_storage_key(thread_id, call_id)
|
|
420
|
+
logger.debug(f"Retrieving data with key: {storage_key}")
|
|
421
|
+
data = self._storage_provider.retrieve(storage_key)
|
|
422
|
+
return metadata.with_data(data)
|
|
423
|
+
|
|
424
|
+
except KeyError:
|
|
425
|
+
# Data missing from storage, clean up metadata
|
|
426
|
+
logger.warning(f"Data missing for {call_id} in thread {thread_id}, cleaning up metadata")
|
|
427
|
+
thread_outputs.pop(call_id, None)
|
|
428
|
+
return None
|
|
429
|
+
except StorageError as e:
|
|
430
|
+
logger.error(f"Storage error retrieving {call_id} from thread {thread_id}: {e}")
|
|
431
|
+
return None
|
|
432
|
+
except Exception as e:
|
|
433
|
+
logger.error(f"Unexpected error retrieving {call_id} from thread {thread_id}: {e}")
|
|
434
|
+
return None
|
|
435
|
+
|
|
436
|
+
def has_outputs(self, thread_id: str | None = None) -> bool:
|
|
437
|
+
"""Check if any outputs are currently stored.
|
|
438
|
+
|
|
439
|
+
Thread-safe: This method uses internal locking to ensure safe concurrent access.
|
|
440
|
+
|
|
441
|
+
Args:
|
|
442
|
+
thread_id: Optional thread ID to check for outputs in a specific thread.
|
|
443
|
+
If None, checks across all threads.
|
|
444
|
+
|
|
445
|
+
Returns:
|
|
446
|
+
True if there are stored outputs, False otherwise.
|
|
447
|
+
"""
|
|
448
|
+
with self._lock:
|
|
449
|
+
if thread_id:
|
|
450
|
+
return len(self._outputs.get(thread_id, {})) > 0
|
|
451
|
+
else:
|
|
452
|
+
return any(len(thread_outputs) > 0 for thread_outputs in self._outputs.values())
|
|
453
|
+
|
|
454
|
+
def generate_summary(self, thread_id: str, max_entries: int = 10) -> str:
|
|
455
|
+
"""Generate an LLM-friendly structured summary as JSON.
|
|
456
|
+
|
|
457
|
+
This method creates a comprehensive, structured summary of stored outputs that
|
|
458
|
+
can be easily parsed by LLMs and other systems. The JSON format provides rich
|
|
459
|
+
metadata and context about each tool output.
|
|
460
|
+
|
|
461
|
+
Thread-safe: This method uses internal locking to ensure safe concurrent access.
|
|
462
|
+
|
|
463
|
+
Args:
|
|
464
|
+
thread_id: Thread ID to generate summary for.
|
|
465
|
+
max_entries: Maximum number of entries to include in the summary.
|
|
466
|
+
Defaults to 10.
|
|
467
|
+
|
|
468
|
+
Returns:
|
|
469
|
+
A JSON string containing structured data about tool outputs. Always returns
|
|
470
|
+
valid JSON, even when no outputs are stored (empty entries list).
|
|
471
|
+
"""
|
|
472
|
+
return self._generate_json_summary(thread_id, max_entries)
|
|
473
|
+
|
|
474
|
+
def get_latest_reference(self, thread_id: str) -> str | None:
|
|
475
|
+
"""Return the most recent tool output reference for a thread.
|
|
476
|
+
|
|
477
|
+
Args:
|
|
478
|
+
thread_id: Thread ID to retrieve the latest output reference for.
|
|
479
|
+
|
|
480
|
+
Returns:
|
|
481
|
+
Latest tool output reference string or None when unavailable.
|
|
482
|
+
"""
|
|
483
|
+
try:
|
|
484
|
+
summary = json.loads(self.generate_summary(thread_id, max_entries=1))
|
|
485
|
+
except Exception as exc:
|
|
486
|
+
logger.debug("Failed to parse tool output summary: %s", exc)
|
|
487
|
+
return None
|
|
488
|
+
|
|
489
|
+
if not summary:
|
|
490
|
+
return None
|
|
491
|
+
latest = summary[0].get("reference")
|
|
492
|
+
return latest if isinstance(latest, str) and latest else None
|
|
493
|
+
|
|
494
|
+
def has_reference(self, value: Any) -> bool:
|
|
495
|
+
"""Check whether a value contains a tool output reference.
|
|
496
|
+
|
|
497
|
+
Args:
|
|
498
|
+
value: Value to inspect for tool output references.
|
|
499
|
+
|
|
500
|
+
Returns:
|
|
501
|
+
True if any tool output reference is present.
|
|
502
|
+
"""
|
|
503
|
+
if isinstance(value, str):
|
|
504
|
+
return value.startswith(TOOL_OUTPUT_REFERENCE_PREFIX)
|
|
505
|
+
if isinstance(value, dict):
|
|
506
|
+
return any(self.has_reference(item) for item in value.values())
|
|
507
|
+
if isinstance(value, list):
|
|
508
|
+
return any(self.has_reference(item) for item in value)
|
|
509
|
+
return False
|
|
510
|
+
|
|
511
|
+
def should_replace_with_reference(self, value: Any) -> bool:
|
|
512
|
+
"""Check whether a tool argument value should use a tool output reference.
|
|
513
|
+
|
|
514
|
+
Args:
|
|
515
|
+
value: Value to evaluate for replacement.
|
|
516
|
+
|
|
517
|
+
Returns:
|
|
518
|
+
True if the value should be replaced with a reference.
|
|
519
|
+
"""
|
|
520
|
+
if isinstance(value, dict | list | tuple):
|
|
521
|
+
return True
|
|
522
|
+
if isinstance(value, str):
|
|
523
|
+
return len(value) > DATA_PREVIEW_TRUNCATION_LENGTH
|
|
524
|
+
return False
|
|
525
|
+
|
|
526
|
+
def rewrite_args_with_latest_reference(self, args: dict[str, Any], thread_id: str) -> dict[str, Any]:
|
|
527
|
+
"""Rewrite tool args to use the latest tool output reference when appropriate.
|
|
528
|
+
|
|
529
|
+
Args:
|
|
530
|
+
args: Tool arguments to rewrite.
|
|
531
|
+
thread_id: Thread ID used for resolving stored outputs.
|
|
532
|
+
|
|
533
|
+
Returns:
|
|
534
|
+
Updated args dictionary with references substituted when needed.
|
|
535
|
+
"""
|
|
536
|
+
if not self.has_outputs(thread_id):
|
|
537
|
+
return args
|
|
538
|
+
if self.has_reference(args):
|
|
539
|
+
return args
|
|
540
|
+
|
|
541
|
+
latest_reference = self.get_latest_reference(thread_id)
|
|
542
|
+
if not latest_reference:
|
|
543
|
+
return args
|
|
544
|
+
|
|
545
|
+
updated_args = dict(args)
|
|
546
|
+
replaced_any = False
|
|
547
|
+
for key, value in args.items():
|
|
548
|
+
if self.should_replace_with_reference(value):
|
|
549
|
+
updated_args[key] = latest_reference
|
|
550
|
+
replaced_any = True
|
|
551
|
+
|
|
552
|
+
return updated_args if replaced_any else args
|
|
553
|
+
|
|
554
|
+
def _generate_json_summary(self, thread_id: str, max_entries: int) -> str:
|
|
555
|
+
"""Generate simplified JSON summary optimized for LLM prompts.
|
|
556
|
+
|
|
557
|
+
Args:
|
|
558
|
+
thread_id: Thread ID to generate summary for.
|
|
559
|
+
max_entries: Maximum number of entries to include in the summary.
|
|
560
|
+
Defaults to 10.
|
|
561
|
+
|
|
562
|
+
Returns:
|
|
563
|
+
A JSON string containing structured data about tool outputs. Always returns
|
|
564
|
+
valid JSON, even when no outputs are stored (empty entries list).
|
|
565
|
+
"""
|
|
566
|
+
with self._lock:
|
|
567
|
+
thread_outputs = self._outputs.get(thread_id, {})
|
|
568
|
+
|
|
569
|
+
sorted_outputs = sorted(
|
|
570
|
+
thread_outputs.values(),
|
|
571
|
+
key=lambda x: x.timestamp,
|
|
572
|
+
reverse=True,
|
|
573
|
+
)[:max_entries]
|
|
574
|
+
|
|
575
|
+
outputs = []
|
|
576
|
+
for output in sorted_outputs:
|
|
577
|
+
data_preview = output.get_data_preview(
|
|
578
|
+
DATA_PREVIEW_TRUNCATION_LENGTH, self._storage_provider, thread_id
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
entry = {
|
|
582
|
+
"reference": f"{TOOL_OUTPUT_REFERENCE_PREFIX}{output.call_id}",
|
|
583
|
+
"tool": output.tool_name,
|
|
584
|
+
}
|
|
585
|
+
if output.data_description:
|
|
586
|
+
entry["description"] = output.data_description
|
|
587
|
+
if output.agent_name:
|
|
588
|
+
entry["agent"] = output.agent_name
|
|
589
|
+
if data_preview:
|
|
590
|
+
entry["data_preview"] = data_preview
|
|
591
|
+
outputs.append(entry)
|
|
592
|
+
|
|
593
|
+
return json.dumps(outputs, indent=2)
|
|
594
|
+
|
|
595
|
+
def _format_tool_args(self, tool_args: dict[str, Any]) -> dict[str, Any]:
|
|
596
|
+
"""Format tool arguments with preview values for display in LLM context.
|
|
597
|
+
|
|
598
|
+
Creates a dictionary with truncated values that provides a preview of tool
|
|
599
|
+
arguments without overwhelming the LLM context. Limits to first 3 arguments
|
|
600
|
+
and recursively truncates large values within those arguments.
|
|
601
|
+
|
|
602
|
+
Args:
|
|
603
|
+
tool_args: Dictionary of tool arguments to format.
|
|
604
|
+
|
|
605
|
+
Returns:
|
|
606
|
+
Dictionary with same keys but truncated/preview values. If more than
|
|
607
|
+
3 arguments provided, includes "..." key with count of remaining args.
|
|
608
|
+
None if tool_args is empty.
|
|
609
|
+
|
|
610
|
+
Examples:
|
|
611
|
+
Simple arguments (no truncation):
|
|
612
|
+
>>> args = {"x": 5, "y": 10, "operation": "add"}
|
|
613
|
+
>>> manager._format_tool_args(args)
|
|
614
|
+
{'x': 5, 'y': 10, 'operation': 'add'}
|
|
615
|
+
|
|
616
|
+
Arguments with large data:
|
|
617
|
+
>>> args = {"data": list(range(100)), "format": "json"}
|
|
618
|
+
>>> manager._format_tool_args(args)
|
|
619
|
+
{'data': [0, 1, 2, '... and 97 more items'], 'format': 'json'}
|
|
620
|
+
|
|
621
|
+
Many arguments (limited to 3):
|
|
622
|
+
>>> args = {f"param_{i}": f"value_{i}" for i in range(5)}
|
|
623
|
+
>>> manager._format_tool_args(args)
|
|
624
|
+
{'param_0': 'value_0', 'param_1': 'value_1', 'param_2': 'value_2', '...': 'and 2 more args'}
|
|
625
|
+
|
|
626
|
+
Empty arguments:
|
|
627
|
+
>>> manager._format_tool_args({})
|
|
628
|
+
{}
|
|
629
|
+
|
|
630
|
+
Mixed argument types:
|
|
631
|
+
>>> args = {"query": ("very long search query that definitely exceeds the "
|
|
632
|
+
... "fifty character limit"), "limit": 100,
|
|
633
|
+
... "filters": {"status": "active", "type": ["user", "admin", "guest"]}}
|
|
634
|
+
>>> manager._format_tool_args(args)
|
|
635
|
+
{'query': 'very long search query that definitely ex...', 'limit': 100,
|
|
636
|
+
'filters': {'status': 'active', 'type': ['user', 'admin', 'guest']}}
|
|
637
|
+
"""
|
|
638
|
+
if not tool_args:
|
|
639
|
+
return {}
|
|
640
|
+
|
|
641
|
+
formatted_args = {}
|
|
642
|
+
|
|
643
|
+
# Show first 3 arguments with truncation for long values
|
|
644
|
+
for key, value in list(tool_args.items())[:MAX_TOOL_ARGS_DISPLAY]:
|
|
645
|
+
formatted_args[key] = self._truncate_value(value)
|
|
646
|
+
|
|
647
|
+
# Add indicator if there are more arguments
|
|
648
|
+
if len(tool_args) > MAX_TOOL_ARGS_DISPLAY:
|
|
649
|
+
formatted_args["..."] = f"and {len(tool_args) - MAX_TOOL_ARGS_DISPLAY} more args"
|
|
650
|
+
|
|
651
|
+
return formatted_args
|
|
652
|
+
|
|
653
|
+
def _truncate_string(self, value: str) -> str:
|
|
654
|
+
"""Truncate a string if it exceeds the maximum length.
|
|
655
|
+
|
|
656
|
+
Args:
|
|
657
|
+
value: The string to potentially truncate.
|
|
658
|
+
|
|
659
|
+
Returns:
|
|
660
|
+
The truncated string with "..." suffix if needed, otherwise the original string.
|
|
661
|
+
"""
|
|
662
|
+
if len(value) > STRING_TRUNCATION_LENGTH:
|
|
663
|
+
return value[: STRING_TRUNCATION_LENGTH - 3] + "..."
|
|
664
|
+
return value
|
|
665
|
+
|
|
666
|
+
def _truncate_collection(self, collection: list | tuple, item_type: str) -> list:
|
|
667
|
+
"""Truncate a collection (list or tuple) if it exceeds the maximum display size.
|
|
668
|
+
|
|
669
|
+
Args:
|
|
670
|
+
collection: The collection to truncate.
|
|
671
|
+
item_type: Type name for the collection items (used in truncation message).
|
|
672
|
+
|
|
673
|
+
Returns:
|
|
674
|
+
Truncated collection with recursive value truncation.
|
|
675
|
+
"""
|
|
676
|
+
if len(collection) > MAX_TOOL_ARGS_DISPLAY:
|
|
677
|
+
truncated_items = [self._truncate_value(item) for item in collection[:MAX_TOOL_ARGS_DISPLAY]]
|
|
678
|
+
truncated_items.append(f"... and {len(collection) - MAX_TOOL_ARGS_DISPLAY} more {item_type}")
|
|
679
|
+
return truncated_items
|
|
680
|
+
return [self._truncate_value(item) for item in collection]
|
|
681
|
+
|
|
682
|
+
def _truncate_dict(self, value: dict) -> dict:
|
|
683
|
+
"""Truncate a dictionary if it exceeds the maximum display size.
|
|
684
|
+
|
|
685
|
+
Args:
|
|
686
|
+
value: The dictionary to truncate.
|
|
687
|
+
|
|
688
|
+
Returns:
|
|
689
|
+
Truncated dictionary with recursive value truncation.
|
|
690
|
+
"""
|
|
691
|
+
if len(value) > MAX_TOOL_ARGS_DISPLAY:
|
|
692
|
+
truncated = {}
|
|
693
|
+
for key in list(value.keys())[:MAX_TOOL_ARGS_DISPLAY]:
|
|
694
|
+
truncated[key] = self._truncate_value(value[key])
|
|
695
|
+
truncated["..."] = f"and {len(value) - MAX_TOOL_ARGS_DISPLAY} more keys"
|
|
696
|
+
return truncated
|
|
697
|
+
return {key: self._truncate_value(val) for key, val in value.items()}
|
|
698
|
+
|
|
699
|
+
def _truncate_value(self, value: Any) -> Any:
|
|
700
|
+
"""Truncate a single value for preview display in LLM context.
|
|
701
|
+
|
|
702
|
+
Recursively truncates values to prevent overwhelming the LLM context with
|
|
703
|
+
large data structures. Uses configurable limits for strings (50 chars) and
|
|
704
|
+
collections (3 items/keys). Preserves data structure while limiting size.
|
|
705
|
+
|
|
706
|
+
Args:
|
|
707
|
+
value: The value to truncate. Can be any type (str, list, dict, etc.).
|
|
708
|
+
|
|
709
|
+
Returns:
|
|
710
|
+
Truncated version of the value maintaining the same type where possible.
|
|
711
|
+
Large strings get "..." suffix, large collections show first 3 items
|
|
712
|
+
with count of remaining items.
|
|
713
|
+
|
|
714
|
+
Examples:
|
|
715
|
+
String truncation:
|
|
716
|
+
>>> manager._truncate_value("This is a very long string that definitely "
|
|
717
|
+
... "exceeds the fifty character limit we have set")
|
|
718
|
+
'This is a very long string that definitely ex...'
|
|
719
|
+
|
|
720
|
+
>>> manager._truncate_value("short string")
|
|
721
|
+
'short string'
|
|
722
|
+
|
|
723
|
+
List truncation:
|
|
724
|
+
>>> manager._truncate_value([1, 2, 3, 4, 5, 6, 7])
|
|
725
|
+
[1, 2, 3, '... and 4 more items']
|
|
726
|
+
|
|
727
|
+
>>> manager._truncate_value([1, 2])
|
|
728
|
+
[1, 2]
|
|
729
|
+
|
|
730
|
+
Dictionary truncation:
|
|
731
|
+
>>> data = {"a": 1, "b": 2, "c": 3, "d": 4, "e": 5}
|
|
732
|
+
>>> manager._truncate_value(data)
|
|
733
|
+
{'a': 1, 'b': 2, 'c': 3, '...': 'and 2 more keys'}
|
|
734
|
+
|
|
735
|
+
>>> manager._truncate_value({"x": 1, "y": 2})
|
|
736
|
+
{'x': 1, 'y': 2}
|
|
737
|
+
|
|
738
|
+
Nested structure truncation:
|
|
739
|
+
>>> nested = {"data": [1, 2, 3, 4, 5], "meta": {"type": "test", "count": 100}}
|
|
740
|
+
>>> manager._truncate_value(nested)
|
|
741
|
+
{'data': [1, 2, 3, '... and 2 more items'], 'meta': {'type': 'test', 'count': 100}}
|
|
742
|
+
|
|
743
|
+
Non-collection types:
|
|
744
|
+
>>> manager._truncate_value(42)
|
|
745
|
+
42
|
|
746
|
+
|
|
747
|
+
>>> manager._truncate_value(None)
|
|
748
|
+
None
|
|
749
|
+
"""
|
|
750
|
+
if isinstance(value, str):
|
|
751
|
+
return self._truncate_string(value)
|
|
752
|
+
elif isinstance(value, list | tuple):
|
|
753
|
+
return self._truncate_collection(value, "items")
|
|
754
|
+
elif isinstance(value, dict):
|
|
755
|
+
return self._truncate_dict(value)
|
|
756
|
+
else:
|
|
757
|
+
# For other types, convert to string and truncate if needed
|
|
758
|
+
str_value = str(value)
|
|
759
|
+
if len(str_value) > STRING_TRUNCATION_LENGTH:
|
|
760
|
+
return str_value[: STRING_TRUNCATION_LENGTH - 3] + "..."
|
|
761
|
+
return value
|
|
762
|
+
|
|
763
|
+
def _cleanup_single_output(self, call_id: str, thread_id: str) -> None:
|
|
764
|
+
"""Clean up a single output from all storage.
|
|
765
|
+
|
|
766
|
+
Args:
|
|
767
|
+
call_id: The call ID to clean up
|
|
768
|
+
thread_id: The thread ID containing the output
|
|
769
|
+
"""
|
|
770
|
+
thread_outputs = self._outputs.get(thread_id, {})
|
|
771
|
+
if call_id in thread_outputs:
|
|
772
|
+
output = thread_outputs.pop(call_id)
|
|
773
|
+
self._total_size_bytes -= output.size_bytes
|
|
774
|
+
|
|
775
|
+
# Clean up empty thread
|
|
776
|
+
if not thread_outputs:
|
|
777
|
+
self._outputs.pop(thread_id, None)
|
|
778
|
+
|
|
779
|
+
try:
|
|
780
|
+
storage_key = self._get_storage_key(thread_id, call_id)
|
|
781
|
+
self._storage_provider.delete(storage_key)
|
|
782
|
+
except Exception as e:
|
|
783
|
+
logger.warning(f"Failed to delete {call_id} from thread {thread_id} storage: {e}")
|
|
784
|
+
|
|
785
|
+
def _cleanup_expired(self) -> None:
|
|
786
|
+
"""Remove expired outputs based on the configured maximum age.
|
|
787
|
+
|
|
788
|
+
This method is called periodically to remove outputs that have exceeded
|
|
789
|
+
the maximum age configured in the system. It helps prevent unbounded
|
|
790
|
+
memory growth in long-running agent sessions.
|
|
791
|
+
|
|
792
|
+
Note: This method assumes it's called within a lock context from store_output.
|
|
793
|
+
"""
|
|
794
|
+
max_age = timedelta(minutes=self.config.max_age_minutes)
|
|
795
|
+
expired_count = 0
|
|
796
|
+
|
|
797
|
+
# Iterate through all threads and find expired outputs
|
|
798
|
+
for thread_id, thread_outputs in list(self._outputs.items()):
|
|
799
|
+
expired_call_ids = [call_id for call_id, output in thread_outputs.items() if output.is_expired(max_age)]
|
|
800
|
+
|
|
801
|
+
for call_id in expired_call_ids:
|
|
802
|
+
self._cleanup_single_output(call_id, thread_id)
|
|
803
|
+
logger.debug(f"Cleaned up expired output: {call_id} from thread {thread_id}")
|
|
804
|
+
expired_count += 1
|
|
805
|
+
|
|
806
|
+
if expired_count > 0:
|
|
807
|
+
logger.debug(f"Cleaned up {expired_count} expired outputs across all threads")
|
|
808
|
+
|
|
809
|
+
def _evict_oldest(self) -> None:
|
|
810
|
+
"""Evict the oldest output to make room for a new one.
|
|
811
|
+
|
|
812
|
+
This method is called when the storage limit is reached and removes
|
|
813
|
+
the output with the earliest timestamp to maintain the size limit.
|
|
814
|
+
|
|
815
|
+
Note: This method assumes it's called within a lock context from store_output.
|
|
816
|
+
"""
|
|
817
|
+
oldest_output = None
|
|
818
|
+
oldest_thread_id = None
|
|
819
|
+
oldest_call_id = None
|
|
820
|
+
|
|
821
|
+
# Find the oldest output across all threads
|
|
822
|
+
for thread_id, thread_outputs in self._outputs.items():
|
|
823
|
+
for call_id, output in thread_outputs.items():
|
|
824
|
+
if oldest_output is None or output.timestamp < oldest_output.timestamp:
|
|
825
|
+
oldest_output = output
|
|
826
|
+
oldest_thread_id = thread_id
|
|
827
|
+
oldest_call_id = call_id
|
|
828
|
+
|
|
829
|
+
if oldest_output and oldest_thread_id and oldest_call_id:
|
|
830
|
+
# Clean up the oldest output
|
|
831
|
+
self._cleanup_single_output(oldest_call_id, oldest_thread_id)
|
|
832
|
+
logger.debug(f"Evicted oldest output: {oldest_call_id} from thread {oldest_thread_id}")
|
|
833
|
+
|
|
834
|
+
def _calculate_size(self, data: Any) -> int:
|
|
835
|
+
"""Calculate the approximate size of data in bytes for memory management.
|
|
836
|
+
|
|
837
|
+
This method provides a simple approximation of data size for memory
|
|
838
|
+
management purposes. It handles common data types efficiently without
|
|
839
|
+
the overhead of serialization libraries like pickle.
|
|
840
|
+
|
|
841
|
+
Args:
|
|
842
|
+
data: The data to calculate size for.
|
|
843
|
+
|
|
844
|
+
Returns:
|
|
845
|
+
Approximate size in bytes.
|
|
846
|
+
"""
|
|
847
|
+
try:
|
|
848
|
+
if isinstance(data, str):
|
|
849
|
+
return len(data.encode("utf-8"))
|
|
850
|
+
elif isinstance(data, dict | list):
|
|
851
|
+
return len(json.dumps(data, default=str).encode("utf-8"))
|
|
852
|
+
else:
|
|
853
|
+
return sys.getsizeof(str(data))
|
|
854
|
+
except Exception:
|
|
855
|
+
# Fallback for any JSON serialization issues
|
|
856
|
+
return sys.getsizeof(str(data))
|
|
857
|
+
|
|
858
|
+
def clear_all(self) -> None:
|
|
859
|
+
"""Clear all stored outputs from both metadata and storage.
|
|
860
|
+
|
|
861
|
+
Warning:
|
|
862
|
+
This operation is irreversible and will remove all stored tool outputs.
|
|
863
|
+
"""
|
|
864
|
+
# Clear storage
|
|
865
|
+
try:
|
|
866
|
+
self._storage_provider.clear()
|
|
867
|
+
except NotImplementedError:
|
|
868
|
+
# Fall back to individual deletes for storage providers that don't support clear
|
|
869
|
+
for thread_id, thread_outputs in self._outputs.items():
|
|
870
|
+
for call_id in thread_outputs.keys():
|
|
871
|
+
try:
|
|
872
|
+
storage_key = self._get_storage_key(thread_id, call_id)
|
|
873
|
+
self._storage_provider.delete(storage_key)
|
|
874
|
+
except Exception as e:
|
|
875
|
+
logger.warning(f"Failed to delete {call_id} from thread {thread_id} during clear_all: {e}")
|
|
876
|
+
except Exception as e:
|
|
877
|
+
logger.error(f"Failed to clear storage: {e}")
|
|
878
|
+
|
|
879
|
+
# Clear metadata
|
|
880
|
+
self._outputs.clear()
|
|
881
|
+
self._total_size_bytes = 0
|
|
882
|
+
|
|
883
|
+
logger.info("Cleared all stored outputs")
|
|
884
|
+
|
|
885
|
+
def get_storage_stats(self) -> dict[str, Any]:
|
|
886
|
+
"""Get storage statistics for monitoring and debugging.
|
|
887
|
+
|
|
888
|
+
Returns:
|
|
889
|
+
Dictionary containing storage statistics.
|
|
890
|
+
"""
|
|
891
|
+
# Count total outputs across all threads
|
|
892
|
+
total_outputs = sum(len(thread_outputs) for thread_outputs in self._outputs.values())
|
|
893
|
+
|
|
894
|
+
# All outputs use the same storage type, so calculate based on storage provider
|
|
895
|
+
external_count = total_outputs if self._is_external_storage() else 0
|
|
896
|
+
|
|
897
|
+
return {
|
|
898
|
+
"total_outputs": total_outputs,
|
|
899
|
+
"total_size_bytes": self._total_size_bytes,
|
|
900
|
+
"external_storage_count": external_count,
|
|
901
|
+
"memory_storage_count": total_outputs - external_count,
|
|
902
|
+
"storage_provider_type": type(self._storage_provider).__name__,
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
class ToolReferenceResolver:
|
|
907
|
+
"""Secure and efficient tool output reference resolution system.
|
|
908
|
+
|
|
909
|
+
This class handles the resolution of tool output references in a secure manner,
|
|
910
|
+
preventing injection attacks while providing simple and reliable access to
|
|
911
|
+
stored tool outputs. It uses a whitelist approach with regex validation to
|
|
912
|
+
ensure only safe references are processed.
|
|
913
|
+
|
|
914
|
+
Security Features:
|
|
915
|
+
- Strict regex pattern matching for reference syntax
|
|
916
|
+
- Whitelist-based validation to prevent injection attacks
|
|
917
|
+
- Fail-fast error handling with detailed error messages
|
|
918
|
+
- Input sanitization and validation at multiple levels
|
|
919
|
+
|
|
920
|
+
Supported Reference Syntax:
|
|
921
|
+
- $tool_output.<call_id> - Direct reference to a tool output by call ID
|
|
922
|
+
|
|
923
|
+
Attributes:
|
|
924
|
+
config: Configuration object for operational parameters.
|
|
925
|
+
"""
|
|
926
|
+
|
|
927
|
+
def __init__(self, config: ToolOutputConfig):
|
|
928
|
+
"""Initialize the ToolReferenceResolver with security configuration.
|
|
929
|
+
|
|
930
|
+
Args:
|
|
931
|
+
config: Configuration object defining operational parameters.
|
|
932
|
+
"""
|
|
933
|
+
self.config = config
|
|
934
|
+
|
|
935
|
+
# Compile regex pattern for performance
|
|
936
|
+
self._pattern: Pattern = re.compile(r"^\$tool_output\.([a-zA-Z0-9_-]{1,50})$")
|
|
937
|
+
|
|
938
|
+
def resolve_references(
|
|
939
|
+
self,
|
|
940
|
+
args: dict[str, Any],
|
|
941
|
+
manager: ToolOutputManager,
|
|
942
|
+
thread_id: str | None = None,
|
|
943
|
+
) -> dict[str, Any]:
|
|
944
|
+
"""Resolve all tool output references in the given arguments dictionary.
|
|
945
|
+
|
|
946
|
+
This method recursively processes a dictionary of tool arguments, finding
|
|
947
|
+
and resolving any tool output references. It supports nested dictionaries
|
|
948
|
+
and lists, providing comprehensive reference resolution.
|
|
949
|
+
|
|
950
|
+
Args:
|
|
951
|
+
args: Dictionary of tool arguments that may contain references.
|
|
952
|
+
manager: ToolOutputManager instance to resolve references against.
|
|
953
|
+
thread_id: Optional thread ID for context-aware resolution.
|
|
954
|
+
|
|
955
|
+
Returns:
|
|
956
|
+
New dictionary with all references resolved to their actual values.
|
|
957
|
+
|
|
958
|
+
Raises:
|
|
959
|
+
ToolReferenceError: If any reference is invalid or cannot be resolved.
|
|
960
|
+
"""
|
|
961
|
+
resolved = {}
|
|
962
|
+
|
|
963
|
+
for key, value in args.items():
|
|
964
|
+
resolved[key] = self._resolve_value(value, manager, thread_id)
|
|
965
|
+
|
|
966
|
+
return resolved
|
|
967
|
+
|
|
968
|
+
def _resolve_value(self, value: Any, manager: ToolOutputManager, thread_id: str | None = None) -> Any:
|
|
969
|
+
"""Resolve a single value that may be a reference, dict, list, or primitive.
|
|
970
|
+
|
|
971
|
+
Args:
|
|
972
|
+
value: The value to resolve.
|
|
973
|
+
manager: ToolOutputManager instance to resolve references against.
|
|
974
|
+
thread_id: Optional thread ID for context-aware resolution.
|
|
975
|
+
|
|
976
|
+
Returns:
|
|
977
|
+
The resolved value.
|
|
978
|
+
"""
|
|
979
|
+
if isinstance(value, str) and value.startswith(TOOL_OUTPUT_REFERENCE_PREFIX):
|
|
980
|
+
logger.debug(f"Resolved reference {value}")
|
|
981
|
+
return self._resolve_single_reference(value, manager, thread_id)
|
|
982
|
+
elif isinstance(value, dict):
|
|
983
|
+
return self.resolve_references(value, manager, thread_id)
|
|
984
|
+
elif isinstance(value, list):
|
|
985
|
+
return self._resolve_list(value, manager, thread_id)
|
|
986
|
+
else:
|
|
987
|
+
return value
|
|
988
|
+
|
|
989
|
+
def _resolve_list(self, items: list[Any], manager: ToolOutputManager, thread_id: str | None = None) -> list[Any]:
|
|
990
|
+
"""Resolve all items in a list that may contain references.
|
|
991
|
+
|
|
992
|
+
Args:
|
|
993
|
+
items: List of items to resolve.
|
|
994
|
+
manager: ToolOutputManager instance to resolve references against.
|
|
995
|
+
thread_id: Optional thread ID for context-aware resolution.
|
|
996
|
+
|
|
997
|
+
Returns:
|
|
998
|
+
List with all references resolved.
|
|
999
|
+
"""
|
|
1000
|
+
resolved_list = []
|
|
1001
|
+
for item in items:
|
|
1002
|
+
resolved_list.append(self._resolve_value(item, manager, thread_id))
|
|
1003
|
+
return resolved_list
|
|
1004
|
+
|
|
1005
|
+
def _resolve_single_reference(
|
|
1006
|
+
self,
|
|
1007
|
+
reference: str,
|
|
1008
|
+
manager: ToolOutputManager,
|
|
1009
|
+
thread_id: str | None = None,
|
|
1010
|
+
) -> Any:
|
|
1011
|
+
"""Resolve a single tool output reference with comprehensive validation.
|
|
1012
|
+
|
|
1013
|
+
This method handles the resolution of individual references with full
|
|
1014
|
+
security validation and error handling.
|
|
1015
|
+
|
|
1016
|
+
Args:
|
|
1017
|
+
reference: The reference string to resolve (e.g., "$tool_output.abc123").
|
|
1018
|
+
manager: ToolOutputManager instance to resolve against.
|
|
1019
|
+
thread_id: Optional thread ID for context-aware resolution.
|
|
1020
|
+
|
|
1021
|
+
Returns:
|
|
1022
|
+
The actual data stored for the referenced call ID.
|
|
1023
|
+
|
|
1024
|
+
Raises:
|
|
1025
|
+
ToolReferenceError: If the reference is invalid or cannot be resolved.
|
|
1026
|
+
"""
|
|
1027
|
+
# Validate reference format with regex
|
|
1028
|
+
match = self._pattern.match(reference)
|
|
1029
|
+
if not match:
|
|
1030
|
+
raise ToolReferenceError(
|
|
1031
|
+
f"Invalid reference format: {reference}. Expected: $tool_output.<call_id>",
|
|
1032
|
+
reference=reference,
|
|
1033
|
+
)
|
|
1034
|
+
|
|
1035
|
+
call_id = match.group(1)
|
|
1036
|
+
|
|
1037
|
+
# Retrieve the stored output with thread context
|
|
1038
|
+
stored_output = manager.get_output(call_id, thread_id)
|
|
1039
|
+
if not stored_output:
|
|
1040
|
+
context_msg = f" in thread {thread_id}" if thread_id else ""
|
|
1041
|
+
raise ToolReferenceError(
|
|
1042
|
+
f"Tool output not found for call ID: {call_id}{context_msg}",
|
|
1043
|
+
reference=reference,
|
|
1044
|
+
call_id=call_id,
|
|
1045
|
+
)
|
|
1046
|
+
|
|
1047
|
+
return stored_output.data
|