aip-agents-binary 0.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aip_agents/__init__.py +66 -0
- aip_agents/a2a/__init__.py +19 -0
- aip_agents/a2a/server/__init__.py +10 -0
- aip_agents/a2a/server/base_executor.py +1125 -0
- aip_agents/a2a/server/google_adk_executor.py +198 -0
- aip_agents/a2a/server/langflow_executor.py +180 -0
- aip_agents/a2a/server/langgraph_executor.py +270 -0
- aip_agents/a2a/types.py +232 -0
- aip_agents/a2ui/__init__.py +26 -0
- aip_agents/a2ui/catalog_provider.py +109 -0
- aip_agents/a2ui/examples/0.8/booking_form.json +1 -0
- aip_agents/a2ui/examples/0.8/confirmation.json +1 -0
- aip_agents/a2ui/examples/0.8/single_column_list.json +1 -0
- aip_agents/a2ui/examples/0.9/booking_form.json +1 -0
- aip_agents/a2ui/examples/0.9/confirmation.json +1 -0
- aip_agents/a2ui/examples/0.9/single_column_list.json +1 -0
- aip_agents/a2ui/middleware.py +754 -0
- aip_agents/a2ui/models.py +153 -0
- aip_agents/a2ui/public_url.py +138 -0
- aip_agents/a2ui/stream_filter.py +67 -0
- aip_agents/agent/__init__.py +72 -0
- aip_agents/agent/base_agent.py +1437 -0
- aip_agents/agent/base_langgraph_agent.py +4048 -0
- aip_agents/agent/google_adk_agent.py +932 -0
- aip_agents/agent/google_adk_constants.py +11 -0
- aip_agents/agent/graph_runtime.py +185 -0
- aip_agents/agent/hitl/__init__.py +24 -0
- aip_agents/agent/hitl/config.py +28 -0
- aip_agents/agent/hitl/langgraph_hitl_mixin.py +515 -0
- aip_agents/agent/hitl/manager.py +532 -0
- aip_agents/agent/hitl/models.py +18 -0
- aip_agents/agent/hitl/prompt/__init__.py +9 -0
- aip_agents/agent/hitl/prompt/base.py +42 -0
- aip_agents/agent/hitl/prompt/deferred.py +73 -0
- aip_agents/agent/hitl/registry.py +149 -0
- aip_agents/agent/interface.py +138 -0
- aip_agents/agent/interfaces.py +65 -0
- aip_agents/agent/langflow_agent.py +464 -0
- aip_agents/agent/langgraph_react_agent.py +3741 -0
- aip_agents/agent/model_invocation_adapter.py +346 -0
- aip_agents/agent/system_instruction_context.py +34 -0
- aip_agents/agent/tool_execution_engine.py +587 -0
- aip_agents/audio_interface/__init__.py +30 -0
- aip_agents/audio_interface/audio_agent_adapter.py +254 -0
- aip_agents/audio_interface/config.py +146 -0
- aip_agents/audio_interface/errors.py +15 -0
- aip_agents/audio_interface/livekit_audio_session.py +504 -0
- aip_agents/audio_interface/livekit_realtime_audio_session.py +142 -0
- aip_agents/audio_interface/session_factory.py +40 -0
- aip_agents/clients/__init__.py +10 -0
- aip_agents/clients/langflow/__init__.py +10 -0
- aip_agents/clients/langflow/client.py +477 -0
- aip_agents/clients/langflow/types.py +18 -0
- aip_agents/common/__init__.py +5 -0
- aip_agents/common/callable_introspection.py +17 -0
- aip_agents/constants.py +23 -0
- aip_agents/context/__init__.py +5 -0
- aip_agents/context/budget.py +237 -0
- aip_agents/context/compaction.py +339 -0
- aip_agents/context/metadata.py +169 -0
- aip_agents/context/offload.py +449 -0
- aip_agents/context/preparation.py +196 -0
- aip_agents/context/prompt.py +61 -0
- aip_agents/context/runtime.py +149 -0
- aip_agents/context/session_recall.py +69 -0
- aip_agents/context/state.py +48 -0
- aip_agents/context/utils.py +44 -0
- aip_agents/credentials/manager.py +132 -0
- aip_agents/examples/__init__.py +5 -0
- aip_agents/examples/audio_livekit_session.py +116 -0
- aip_agents/examples/compare_streaming_client.py +783 -0
- aip_agents/examples/compare_streaming_server.py +141 -0
- aip_agents/examples/gl_connectors_smoke.py +148 -0
- aip_agents/examples/hello_world_a2a_google_adk_client.py +49 -0
- aip_agents/examples/hello_world_a2a_google_adk_client_agent.py +48 -0
- aip_agents/examples/hello_world_a2a_google_adk_client_streaming.py +60 -0
- aip_agents/examples/hello_world_a2a_google_adk_server.py +78 -0
- aip_agents/examples/hello_world_a2a_langchain_client.py +39 -0
- aip_agents/examples/hello_world_a2a_langchain_client_agent.py +39 -0
- aip_agents/examples/hello_world_a2a_langchain_client_lm_invoker.py +37 -0
- aip_agents/examples/hello_world_a2a_langchain_client_streaming.py +41 -0
- aip_agents/examples/hello_world_a2a_langchain_reference_client_streaming.py +60 -0
- aip_agents/examples/hello_world_a2a_langchain_reference_server.py +104 -0
- aip_agents/examples/hello_world_a2a_langchain_server.py +78 -0
- aip_agents/examples/hello_world_a2a_langchain_server_lm_invoker.py +77 -0
- aip_agents/examples/hello_world_a2a_langflow_client.py +83 -0
- aip_agents/examples/hello_world_a2a_langflow_server.py +80 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_client.py +73 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_client_streaming.py +76 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_server.py +91 -0
- aip_agents/examples/hello_world_a2a_langgraph_client.py +54 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_agent.py +54 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_agent_lm_invoker.py +32 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming.py +50 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming_lm_invoker.py +44 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming_tool_streaming.py +92 -0
- aip_agents/examples/hello_world_a2a_langgraph_server.py +83 -0
- aip_agents/examples/hello_world_a2a_langgraph_server_lm_invoker.py +78 -0
- aip_agents/examples/hello_world_a2a_langgraph_server_tool_streaming.py +131 -0
- aip_agents/examples/hello_world_a2a_mcp_langgraph.py +196 -0
- aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_client.py +244 -0
- aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_server.py +250 -0
- aip_agents/examples/hello_world_a2a_with_metadata_langchain_client.py +57 -0
- aip_agents/examples/hello_world_a2a_with_metadata_langchain_server_lm_invoker.py +79 -0
- aip_agents/examples/hello_world_filesystem.py +162 -0
- aip_agents/examples/hello_world_google_adk.py +41 -0
- aip_agents/examples/hello_world_google_adk_mcp_http.py +34 -0
- aip_agents/examples/hello_world_google_adk_mcp_http_stream.py +40 -0
- aip_agents/examples/hello_world_google_adk_mcp_sse.py +44 -0
- aip_agents/examples/hello_world_google_adk_mcp_sse_stream.py +48 -0
- aip_agents/examples/hello_world_google_adk_mcp_stdio.py +44 -0
- aip_agents/examples/hello_world_google_adk_mcp_stdio_stream.py +48 -0
- aip_agents/examples/hello_world_google_adk_stream.py +44 -0
- aip_agents/examples/hello_world_langchain.py +28 -0
- aip_agents/examples/hello_world_langchain_lm_invoker.py +15 -0
- aip_agents/examples/hello_world_langchain_mcp_http.py +34 -0
- aip_agents/examples/hello_world_langchain_mcp_http_interactive.py +130 -0
- aip_agents/examples/hello_world_langchain_mcp_http_stream.py +42 -0
- aip_agents/examples/hello_world_langchain_mcp_multi_server.py +155 -0
- aip_agents/examples/hello_world_langchain_mcp_sse.py +34 -0
- aip_agents/examples/hello_world_langchain_mcp_sse_stream.py +40 -0
- aip_agents/examples/hello_world_langchain_mcp_stdio.py +30 -0
- aip_agents/examples/hello_world_langchain_mcp_stdio_stream.py +41 -0
- aip_agents/examples/hello_world_langchain_stream.py +36 -0
- aip_agents/examples/hello_world_langchain_stream_lm_invoker.py +39 -0
- aip_agents/examples/hello_world_langflow_agent.py +163 -0
- aip_agents/examples/hello_world_langgraph.py +39 -0
- aip_agents/examples/hello_world_langgraph_gl_connector_twitter.py +44 -0
- aip_agents/examples/hello_world_langgraph_mcp_http.py +31 -0
- aip_agents/examples/hello_world_langgraph_mcp_http_stream.py +34 -0
- aip_agents/examples/hello_world_langgraph_mcp_sse.py +35 -0
- aip_agents/examples/hello_world_langgraph_mcp_sse_stream.py +50 -0
- aip_agents/examples/hello_world_langgraph_mcp_stdio.py +35 -0
- aip_agents/examples/hello_world_langgraph_mcp_stdio_stream.py +50 -0
- aip_agents/examples/hello_world_langgraph_skills.py +62 -0
- aip_agents/examples/hello_world_langgraph_stream.py +43 -0
- aip_agents/examples/hello_world_langgraph_stream_lm_invoker.py +37 -0
- aip_agents/examples/hello_world_model_switch_cli.py +210 -0
- aip_agents/examples/hello_world_multi_agent_adk.py +75 -0
- aip_agents/examples/hello_world_multi_agent_langchain.py +54 -0
- aip_agents/examples/hello_world_multi_agent_langgraph.py +66 -0
- aip_agents/examples/hello_world_multi_agent_langgraph_lm_invoker.py +69 -0
- aip_agents/examples/hello_world_multi_agent_tool_output_client.py +57 -0
- aip_agents/examples/hello_world_multi_agent_tool_output_server.py +131 -0
- aip_agents/examples/hello_world_pii_logger.py +21 -0
- aip_agents/examples/hello_world_ptc.py +49 -0
- aip_agents/examples/hello_world_ptc_custom_tools.py +83 -0
- aip_agents/examples/hello_world_sentry.py +133 -0
- aip_agents/examples/hello_world_step_limits.py +273 -0
- aip_agents/examples/hello_world_stock_a2a_server.py +100 -0
- aip_agents/examples/hello_world_tool_output_client.py +55 -0
- aip_agents/examples/hello_world_tool_output_server.py +113 -0
- aip_agents/examples/hitl_demo.py +724 -0
- aip_agents/examples/mcp_configs/configs.py +63 -0
- aip_agents/examples/mcp_servers/common.py +76 -0
- aip_agents/examples/mcp_servers/mcp_name.py +29 -0
- aip_agents/examples/mcp_servers/mcp_server_http.py +19 -0
- aip_agents/examples/mcp_servers/mcp_server_sse.py +19 -0
- aip_agents/examples/mcp_servers/mcp_server_stdio.py +19 -0
- aip_agents/examples/mcp_servers/mcp_time.py +10 -0
- aip_agents/examples/pii_demo_langgraph_client.py +69 -0
- aip_agents/examples/pii_demo_langgraph_server.py +125 -0
- aip_agents/examples/pii_demo_multi_agent_client.py +80 -0
- aip_agents/examples/pii_demo_multi_agent_server.py +246 -0
- aip_agents/examples/todolist_planning_a2a_langchain_client.py +70 -0
- aip_agents/examples/todolist_planning_a2a_langgraph_server.py +87 -0
- aip_agents/examples/tools/__init__.py +27 -0
- aip_agents/examples/tools/adk_arithmetic_tools.py +36 -0
- aip_agents/examples/tools/adk_weather_tool.py +60 -0
- aip_agents/examples/tools/data_generator_tool.py +103 -0
- aip_agents/examples/tools/data_visualization_tool.py +312 -0
- aip_agents/examples/tools/image_artifact_tool.py +136 -0
- aip_agents/examples/tools/langchain_arithmetic_tools.py +26 -0
- aip_agents/examples/tools/langchain_currency_exchange_tool.py +88 -0
- aip_agents/examples/tools/langchain_graph_artifact_tool.py +172 -0
- aip_agents/examples/tools/langchain_weather_tool.py +48 -0
- aip_agents/examples/tools/langgraph_streaming_tool.py +130 -0
- aip_agents/examples/tools/mock_retrieval_tool.py +56 -0
- aip_agents/examples/tools/multiply_tool.py +43 -0
- aip_agents/examples/tools/pii_demo_tools.py +189 -0
- aip_agents/examples/tools/random_chart_tool.py +142 -0
- aip_agents/examples/tools/serper_tool.py +202 -0
- aip_agents/examples/tools/stock_tools.py +82 -0
- aip_agents/examples/tools/table_generator_tool.py +167 -0
- aip_agents/examples/tools/time_tool.py +82 -0
- aip_agents/examples/tools/weather_forecast_tool.py +38 -0
- aip_agents/guardrails/__init__.py +83 -0
- aip_agents/guardrails/engines/__init__.py +69 -0
- aip_agents/guardrails/engines/base.py +90 -0
- aip_agents/guardrails/engines/nemo.py +101 -0
- aip_agents/guardrails/engines/phrase_matcher.py +113 -0
- aip_agents/guardrails/exceptions.py +39 -0
- aip_agents/guardrails/manager.py +163 -0
- aip_agents/guardrails/middleware.py +199 -0
- aip_agents/guardrails/schemas.py +63 -0
- aip_agents/guardrails/utils.py +45 -0
- aip_agents/integration/__init__.py +43 -0
- aip_agents/integration/agent.py +58 -0
- aip_agents/integration/guardrails.py +52 -0
- aip_agents/integration/hitl.py +71 -0
- aip_agents/integration/ptc.py +95 -0
- aip_agents/integration/skills.py +37 -0
- aip_agents/integration/storage.py +81 -0
- aip_agents/integration/version.py +103 -0
- aip_agents/mcp/__init__.py +1 -0
- aip_agents/mcp/client/__init__.py +60 -0
- aip_agents/mcp/client/base_mcp_client.py +377 -0
- aip_agents/mcp/client/connection_manager.py +231 -0
- aip_agents/mcp/client/errors.py +119 -0
- aip_agents/mcp/client/google_adk/__init__.py +45 -0
- aip_agents/mcp/client/google_adk/client.py +383 -0
- aip_agents/mcp/client/langchain/__init__.py +11 -0
- aip_agents/mcp/client/langchain/client.py +265 -0
- aip_agents/mcp/client/persistent_session.py +704 -0
- aip_agents/mcp/client/session_pool.py +358 -0
- aip_agents/mcp/client/transports.py +288 -0
- aip_agents/mcp/utils/__init__.py +7 -0
- aip_agents/mcp/utils/config_validator.py +139 -0
- aip_agents/memory/__init__.py +14 -0
- aip_agents/memory/adapters/__init__.py +10 -0
- aip_agents/memory/adapters/base_adapter.py +815 -0
- aip_agents/memory/adapters/mem0.py +286 -0
- aip_agents/memory/base.py +84 -0
- aip_agents/memory/constants.py +49 -0
- aip_agents/memory/factory.py +86 -0
- aip_agents/memory/guidance.py +20 -0
- aip_agents/memory/simple_memory.py +47 -0
- aip_agents/middleware/__init__.py +65 -0
- aip_agents/middleware/backends/__init__.py +72 -0
- aip_agents/middleware/backends/in_memory.py +578 -0
- aip_agents/middleware/backends/local_disk.py +1203 -0
- aip_agents/middleware/backends/protocol.py +353 -0
- aip_agents/middleware/backends/sandbox.py +1431 -0
- aip_agents/middleware/backends/sandbox_files.py +113 -0
- aip_agents/middleware/backends/utils.py +293 -0
- aip_agents/middleware/base.py +239 -0
- aip_agents/middleware/filesystem.py +690 -0
- aip_agents/middleware/manager.py +291 -0
- aip_agents/middleware/memory.py +532 -0
- aip_agents/middleware/pii_middleware.py +192 -0
- aip_agents/middleware/schema.py +19 -0
- aip_agents/middleware/skills.py +872 -0
- aip_agents/middleware/task_delegation.py +1656 -0
- aip_agents/middleware/todolist.py +276 -0
- aip_agents/middleware/tools/__init__.py +23 -0
- aip_agents/middleware/tools/edit_file.py +70 -0
- aip_agents/middleware/tools/execute.py +428 -0
- aip_agents/middleware/tools/grep_file.py +79 -0
- aip_agents/middleware/tools/ls.py +71 -0
- aip_agents/middleware/tools/read_file.py +239 -0
- aip_agents/middleware/tools/skill_load.py +193 -0
- aip_agents/middleware/tools/skill_resource.py +338 -0
- aip_agents/middleware/tools/write_file.py +114 -0
- aip_agents/middleware/vision.py +322 -0
- aip_agents/ptc/__init__.py +87 -0
- aip_agents/ptc/custom_tools.py +511 -0
- aip_agents/ptc/custom_tools_payload.py +499 -0
- aip_agents/ptc/custom_tools_templates/__init__.py +1 -0
- aip_agents/ptc/custom_tools_templates/custom_build_function.py.template +24 -0
- aip_agents/ptc/custom_tools_templates/custom_init.py.template +26 -0
- aip_agents/ptc/custom_tools_templates/custom_invoke.py.template +149 -0
- aip_agents/ptc/custom_tools_templates/custom_registry.py.template +112 -0
- aip_agents/ptc/custom_tools_templates/custom_sources_init.py.template +7 -0
- aip_agents/ptc/custom_tools_templates/custom_wrapper.py.template +87 -0
- aip_agents/ptc/doc_gen.py +122 -0
- aip_agents/ptc/exceptions.py +57 -0
- aip_agents/ptc/executor.py +627 -0
- aip_agents/ptc/mcp/__init__.py +45 -0
- aip_agents/ptc/mcp/sandbox_bridge.py +679 -0
- aip_agents/ptc/mcp/templates/__init__.py +1 -0
- aip_agents/ptc/mcp/templates/mcp_client.py.template +240 -0
- aip_agents/ptc/naming.py +430 -0
- aip_agents/ptc/payload.py +26 -0
- aip_agents/ptc/prompt_builder.py +674 -0
- aip_agents/ptc/ptc_helper.py +16 -0
- aip_agents/ptc/sandbox_bridge.py +297 -0
- aip_agents/ptc/template_utils.py +33 -0
- aip_agents/ptc/templates/__init__.py +1 -0
- aip_agents/ptc/templates/ptc_helper.py.template +134 -0
- aip_agents/ptc/tool_def_helpers.py +101 -0
- aip_agents/ptc/tool_enrichment.py +163 -0
- aip_agents/resilience/__init__.py +10 -0
- aip_agents/resilience/base.py +46 -0
- aip_agents/resilience/tool.py +1661 -0
- aip_agents/sandbox/__init__.py +62 -0
- aip_agents/sandbox/defaults.py +220 -0
- aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-1-datasaur.Dockerfile +120 -0
- aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-1.Dockerfile +100 -0
- aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-2-datasaur.Dockerfile +143 -0
- aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-2.Dockerfile +119 -0
- aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-3-datasaur.Dockerfile +173 -0
- aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-3.Dockerfile +130 -0
- aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-4-runner-managed.Dockerfile +164 -0
- aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-4-runner-managed.patch_js_context.py +41 -0
- aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2-4-runner-managed.requirements.txt +29 -0
- aip_agents/sandbox/dockerfiles/aip-agents-sandbox-v2.Dockerfile +87 -0
- aip_agents/sandbox/e2b_runtime.py +27 -0
- aip_agents/sandbox/image_registry.py +98 -0
- aip_agents/sandbox/providers/__init__.py +76 -0
- aip_agents/sandbox/providers/base.py +392 -0
- aip_agents/sandbox/providers/bedrock.py +28 -0
- aip_agents/sandbox/providers/e2b.py +230 -0
- aip_agents/sandbox/providers/opensandbox.py +163 -0
- aip_agents/sandbox/sandbox_runtime.py +967 -0
- aip_agents/sandbox/template_builder.py +240 -0
- aip_agents/sandbox/types.py +141 -0
- aip_agents/sandbox/validation.py +50 -0
- aip_agents/schema/__init__.py +106 -0
- aip_agents/schema/a2a.py +58 -0
- aip_agents/schema/agent.py +117 -0
- aip_agents/schema/artifacts.py +75 -0
- aip_agents/schema/attachments.py +425 -0
- aip_agents/schema/hitl.py +157 -0
- aip_agents/schema/langgraph.py +47 -0
- aip_agents/schema/model_id.py +103 -0
- aip_agents/schema/model_usage.py +50 -0
- aip_agents/schema/runtime_metadata.py +78 -0
- aip_agents/schema/step_limit.py +108 -0
- aip_agents/schema/storage.py +40 -0
- aip_agents/schema/tool_payloads.py +68 -0
- aip_agents/sentry/__init__.py +11 -0
- aip_agents/sentry/sentry.py +267 -0
- aip_agents/skills/__init__.py +30 -0
- aip_agents/skills/errors.py +110 -0
- aip_agents/skills/installer.py +479 -0
- aip_agents/skills/models.py +769 -0
- aip_agents/skills/stager.py +224 -0
- aip_agents/skills/validation.py +53 -0
- aip_agents/storage/__init__.py +41 -0
- aip_agents/storage/base.py +85 -0
- aip_agents/storage/clients/__init__.py +12 -0
- aip_agents/storage/clients/minio_client.py +318 -0
- aip_agents/storage/config.py +62 -0
- aip_agents/storage/providers/__init__.py +15 -0
- aip_agents/storage/providers/base.py +106 -0
- aip_agents/storage/providers/memory.py +114 -0
- aip_agents/storage/providers/object_storage.py +214 -0
- aip_agents/tools/__init__.py +65 -0
- aip_agents/tools/agent_schedule_tool.py +392 -0
- aip_agents/tools/browser_use/__init__.py +83 -0
- aip_agents/tools/browser_use/action_parser.py +104 -0
- aip_agents/tools/browser_use/browser_use_tool.py +1118 -0
- aip_agents/tools/browser_use/llm_config.py +120 -0
- aip_agents/tools/browser_use/minio_storage.py +203 -0
- aip_agents/tools/browser_use/schemas.py +121 -0
- aip_agents/tools/browser_use/session.py +13 -0
- aip_agents/tools/browser_use/session_errors.py +132 -0
- aip_agents/tools/browser_use/steel_session_recording.py +321 -0
- aip_agents/tools/browser_use/streaming.py +263 -0
- aip_agents/tools/browser_use/structured_data_parser.py +257 -0
- aip_agents/tools/browser_use/structured_data_recovery.py +204 -0
- aip_agents/tools/browser_use/types.py +37 -0
- aip_agents/tools/code_sandbox/__init__.py +48 -0
- aip_agents/tools/code_sandbox/code_sandbox_tool.py +350 -0
- aip_agents/tools/code_sandbox/constant.py +13 -0
- aip_agents/tools/code_sandbox/e2b_cloud_sandbox_extended.py +346 -0
- aip_agents/tools/code_sandbox/e2b_sandbox_tool.py +65 -0
- aip_agents/tools/computer_use/__init__.py +75 -0
- aip_agents/tools/computer_use/computer_use_tool.py +910 -0
- aip_agents/tools/computer_use/schemas.py +78 -0
- aip_agents/tools/computer_use/streaming.py +198 -0
- aip_agents/tools/computer_use/types.py +32 -0
- aip_agents/tools/constants.py +177 -0
- aip_agents/tools/date_range_tool.py +554 -0
- aip_agents/tools/document_loader/__init__.py +44 -0
- aip_agents/tools/document_loader/base_reader.py +302 -0
- aip_agents/tools/document_loader/docx_reader_tool.py +68 -0
- aip_agents/tools/document_loader/excel_reader_tool.py +171 -0
- aip_agents/tools/document_loader/pdf_reader_tool.py +79 -0
- aip_agents/tools/document_loader/pdf_splitter.py +169 -0
- aip_agents/tools/execute_ptc_code.py +582 -0
- aip_agents/tools/gl_connector/__init__.py +5 -0
- aip_agents/tools/gl_connector/tool.py +518 -0
- aip_agents/tools/gl_connector_tools.py +170 -0
- aip_agents/tools/memory_search/__init__.py +29 -0
- aip_agents/tools/memory_search/base.py +213 -0
- aip_agents/tools/memory_search/mem0.py +424 -0
- aip_agents/tools/memory_search/schema.py +81 -0
- aip_agents/tools/memory_search_tool.py +34 -0
- aip_agents/tools/streaming_common.py +577 -0
- aip_agents/tools/time_tool.py +117 -0
- aip_agents/tools/tool_config_injector.py +300 -0
- aip_agents/tools/web_search/__init__.py +15 -0
- aip_agents/tools/web_search/serper_tool.py +187 -0
- aip_agents/types/__init__.py +73 -0
- aip_agents/types/a2a_events.py +13 -0
- aip_agents/utils/__init__.py +91 -0
- aip_agents/utils/a2a_connector.py +1758 -0
- aip_agents/utils/artifact_helpers.py +542 -0
- aip_agents/utils/artifacts/__init__.py +20 -0
- aip_agents/utils/artifacts/tracker.py +367 -0
- aip_agents/utils/attachment_helpers.py +178 -0
- aip_agents/utils/constants.py +22 -0
- aip_agents/utils/datetime/__init__.py +34 -0
- aip_agents/utils/datetime/normalization.py +231 -0
- aip_agents/utils/datetime/timezone.py +206 -0
- aip_agents/utils/env_loader.py +27 -0
- aip_agents/utils/event_handler_registry.py +58 -0
- aip_agents/utils/file_prompt_utils.py +276 -0
- aip_agents/utils/file_watchers/__init__.py +33 -0
- aip_agents/utils/file_watchers/base.py +46 -0
- aip_agents/utils/file_watchers/e2b.py +128 -0
- aip_agents/utils/file_watchers/factory.py +39 -0
- aip_agents/utils/file_watchers/ls_diff.py +88 -0
- aip_agents/utils/file_watchers/sandbox.py +66 -0
- aip_agents/utils/file_watchers/watchdog.py +97 -0
- aip_agents/utils/final_response_builder.py +211 -0
- aip_agents/utils/formatter_llm_client.py +258 -0
- aip_agents/utils/langgraph/__init__.py +19 -0
- aip_agents/utils/langgraph/converter.py +399 -0
- aip_agents/utils/langgraph/delegation_context.py +22 -0
- aip_agents/utils/langgraph/tool_managers/__init__.py +15 -0
- aip_agents/utils/langgraph/tool_managers/a2a_tool_manager.py +99 -0
- aip_agents/utils/langgraph/tool_managers/base_tool_manager.py +66 -0
- aip_agents/utils/langgraph/tool_managers/delegation_tool_manager.py +1329 -0
- aip_agents/utils/langgraph/tool_output_management.py +1047 -0
- aip_agents/utils/logger.py +195 -0
- aip_agents/utils/metadata/__init__.py +27 -0
- aip_agents/utils/metadata/activity_metadata_helper.py +407 -0
- aip_agents/utils/metadata/activity_narrative/__init__.py +35 -0
- aip_agents/utils/metadata/activity_narrative/builder.py +822 -0
- aip_agents/utils/metadata/activity_narrative/constants.py +51 -0
- aip_agents/utils/metadata/activity_narrative/context.py +49 -0
- aip_agents/utils/metadata/activity_narrative/formatters.py +230 -0
- aip_agents/utils/metadata/activity_narrative/utils.py +35 -0
- aip_agents/utils/metadata/schemas/__init__.py +16 -0
- aip_agents/utils/metadata/schemas/activity_schema.py +29 -0
- aip_agents/utils/metadata/schemas/thinking_schema.py +31 -0
- aip_agents/utils/metadata/thinking_metadata_helper.py +38 -0
- aip_agents/utils/metadata_helper.py +363 -0
- aip_agents/utils/name_preprocessor/__init__.py +17 -0
- aip_agents/utils/name_preprocessor/base_name_preprocessor.py +73 -0
- aip_agents/utils/name_preprocessor/google_name_preprocessor.py +100 -0
- aip_agents/utils/name_preprocessor/name_preprocessor.py +87 -0
- aip_agents/utils/name_preprocessor/openai_name_preprocessor.py +48 -0
- aip_agents/utils/pii/__init__.py +29 -0
- aip_agents/utils/pii/pii_handler.py +555 -0
- aip_agents/utils/pii/pii_helper.py +225 -0
- aip_agents/utils/pii/uuid_deanonymizer_mapping.py +195 -0
- aip_agents/utils/reference_helper.py +273 -0
- aip_agents/utils/sse_chunk_transformer.py +836 -0
- aip_agents/utils/step_limit_manager.py +265 -0
- aip_agents/utils/token_usage_helper.py +531 -0
- aip_agents_binary-0.0.0.dist-info/METADATA +825 -0
- aip_agents_binary-0.0.0.dist-info/RECORD +447 -0
- aip_agents_binary-0.0.0.dist-info/WHEEL +5 -0
- aip_agents_binary-0.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"""Context budget and compaction decision helpers.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from collections.abc import Sequence
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import Any, Literal
|
|
12
|
+
|
|
13
|
+
from aip_agents.context.state import SummaryInsertionPoint
|
|
14
|
+
|
|
15
|
+
_CHARS_PER_TOKEN = 4
|
|
16
|
+
_BASE_TOKENS_PER_MESSAGE = 4
|
|
17
|
+
_IMAGE_PART_TOKEN_ESTIMATE = 256
|
|
18
|
+
|
|
19
|
+
CompactionReason = Literal[
|
|
20
|
+
"below_threshold",
|
|
21
|
+
"proactive_threshold",
|
|
22
|
+
"fallback_threshold",
|
|
23
|
+
"manual_focus",
|
|
24
|
+
"provider_overflow",
|
|
25
|
+
"anti_thrash_skip",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class ContextBudgetConfig:
|
|
31
|
+
"""Configuration for context budget decisions.
|
|
32
|
+
|
|
33
|
+
Attributes:
|
|
34
|
+
threshold_ratio: Fraction of the model context that triggers proactive compaction.
|
|
35
|
+
fallback_threshold_tokens: Absolute threshold when the model context window is unknown.
|
|
36
|
+
min_tail_tokens: Minimum recent-token budget preserved after compaction.
|
|
37
|
+
target_tail_ratio: Fraction of estimated prompt tokens preserved as recent tail.
|
|
38
|
+
min_head_messages: Number of initial messages always preserved.
|
|
39
|
+
anti_thrash_min_savings_ratio: Minimum useful savings before repeated compaction.
|
|
40
|
+
max_retry_after_overflow: Runtime retry cap consumed by overflow handling.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
threshold_ratio: float = 0.85
|
|
44
|
+
fallback_threshold_tokens: int = 170_000
|
|
45
|
+
min_tail_tokens: int = 6_000
|
|
46
|
+
target_tail_ratio: float = 0.10
|
|
47
|
+
min_head_messages: int = 2
|
|
48
|
+
anti_thrash_min_savings_ratio: float = 0.10
|
|
49
|
+
max_retry_after_overflow: int = 1
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True)
|
|
53
|
+
class CompactionDecision:
|
|
54
|
+
"""Result of pure compaction decision-making.
|
|
55
|
+
|
|
56
|
+
Attributes:
|
|
57
|
+
should_compact: Whether the current input should enter the compaction path.
|
|
58
|
+
reason: Stable reason code for logging, tests, and retry behavior.
|
|
59
|
+
estimated_prompt_tokens: Estimated prompt size before compaction.
|
|
60
|
+
model_context_tokens: Model context window used for thresholding, if known.
|
|
61
|
+
threshold_tokens: Effective token threshold used by this decision.
|
|
62
|
+
protected_head_indexes: Initial message indexes excluded from replacement.
|
|
63
|
+
protected_tail_start_index: First recent-tail message index to preserve.
|
|
64
|
+
focus_topic: Optional user-provided focus topic for manual compaction.
|
|
65
|
+
summary_insertion_point: Replaceable range where a summary can be inserted.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
should_compact: bool
|
|
69
|
+
reason: CompactionReason
|
|
70
|
+
estimated_prompt_tokens: int
|
|
71
|
+
model_context_tokens: int | None
|
|
72
|
+
threshold_tokens: int
|
|
73
|
+
protected_head_indexes: tuple[int, ...]
|
|
74
|
+
protected_tail_start_index: int
|
|
75
|
+
focus_topic: str | None = None
|
|
76
|
+
summary_insertion_point: SummaryInsertionPoint | None = None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def estimate_message_token_counts(messages: Sequence[Any]) -> list[int]:
|
|
80
|
+
"""Estimate token counts per message with conservative heuristics."""
|
|
81
|
+
return [_estimate_single_message_tokens(message) for message in messages]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def estimate_prompt_tokens(messages: Sequence[Any]) -> int:
|
|
85
|
+
"""Estimate total prompt tokens from message sequence."""
|
|
86
|
+
return sum(estimate_message_token_counts(messages))
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def compute_protected_regions(
|
|
90
|
+
token_counts: Sequence[int],
|
|
91
|
+
config: ContextBudgetConfig | None = None,
|
|
92
|
+
) -> tuple[tuple[int, ...], int]:
|
|
93
|
+
"""Compute protected head indexes and token-budget tail start index."""
|
|
94
|
+
cfg = config or ContextBudgetConfig()
|
|
95
|
+
if not token_counts:
|
|
96
|
+
return (), 0
|
|
97
|
+
|
|
98
|
+
message_count = len(token_counts)
|
|
99
|
+
head_count = min(cfg.min_head_messages, message_count)
|
|
100
|
+
protected_head_indexes = tuple(range(head_count))
|
|
101
|
+
|
|
102
|
+
total_tokens = sum(token_counts)
|
|
103
|
+
tail_target_tokens = max(cfg.min_tail_tokens, int(total_tokens * cfg.target_tail_ratio))
|
|
104
|
+
|
|
105
|
+
running = 0
|
|
106
|
+
tail_start = message_count
|
|
107
|
+
for index in range(message_count - 1, -1, -1):
|
|
108
|
+
running += token_counts[index]
|
|
109
|
+
tail_start = index
|
|
110
|
+
if running >= tail_target_tokens:
|
|
111
|
+
break
|
|
112
|
+
|
|
113
|
+
if tail_start < head_count:
|
|
114
|
+
tail_start = head_count
|
|
115
|
+
|
|
116
|
+
return protected_head_indexes, tail_start
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def classify_provider_overflow_error(error: Exception | str) -> bool:
|
|
120
|
+
"""Return whether an error appears to be a context-window overflow."""
|
|
121
|
+
text = str(error).lower()
|
|
122
|
+
patterns = (
|
|
123
|
+
"context window",
|
|
124
|
+
"maximum context length",
|
|
125
|
+
"too many tokens",
|
|
126
|
+
"prompt is too long",
|
|
127
|
+
"context_length_exceeded",
|
|
128
|
+
"input length exceeds",
|
|
129
|
+
"token limit",
|
|
130
|
+
"context limit",
|
|
131
|
+
)
|
|
132
|
+
return any(pattern in text for pattern in patterns)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def decide_compaction(
|
|
136
|
+
messages: Sequence[Any],
|
|
137
|
+
*,
|
|
138
|
+
config: ContextBudgetConfig | None = None,
|
|
139
|
+
model_context_tokens: int | None = None,
|
|
140
|
+
focus_topic: str | None = None,
|
|
141
|
+
provider_overflow: bool = False,
|
|
142
|
+
recent_savings_ratio: float | None = None,
|
|
143
|
+
) -> CompactionDecision:
|
|
144
|
+
"""Return a pure compaction decision from budget inputs.
|
|
145
|
+
|
|
146
|
+
This function does not call models and does not mutate runtime state.
|
|
147
|
+
"""
|
|
148
|
+
cfg = config or ContextBudgetConfig()
|
|
149
|
+
token_counts = estimate_message_token_counts(messages)
|
|
150
|
+
estimated_tokens = sum(token_counts)
|
|
151
|
+
threshold_tokens = (
|
|
152
|
+
max(1, int(model_context_tokens * cfg.threshold_ratio))
|
|
153
|
+
if model_context_tokens is not None
|
|
154
|
+
else cfg.fallback_threshold_tokens
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
protected_head_indexes, protected_tail_start_index = compute_protected_regions(token_counts, config=cfg)
|
|
158
|
+
replace_start_index = len(protected_head_indexes)
|
|
159
|
+
replace_end_index = protected_tail_start_index - 1
|
|
160
|
+
insertion_point = None
|
|
161
|
+
if replace_start_index <= replace_end_index:
|
|
162
|
+
insertion_point = SummaryInsertionPoint(
|
|
163
|
+
protected_head_indexes=protected_head_indexes,
|
|
164
|
+
protected_tail_start_index=protected_tail_start_index,
|
|
165
|
+
replace_start_index=replace_start_index,
|
|
166
|
+
replace_end_index=replace_end_index,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
reason: CompactionReason
|
|
170
|
+
should_compact = False
|
|
171
|
+
|
|
172
|
+
normalized_focus_topic = (focus_topic or "").strip() or None
|
|
173
|
+
if normalized_focus_topic is not None:
|
|
174
|
+
reason = "manual_focus"
|
|
175
|
+
should_compact = True
|
|
176
|
+
elif provider_overflow:
|
|
177
|
+
reason = "provider_overflow"
|
|
178
|
+
should_compact = True
|
|
179
|
+
elif estimated_tokens > threshold_tokens:
|
|
180
|
+
reason = "proactive_threshold" if model_context_tokens is not None else "fallback_threshold"
|
|
181
|
+
should_compact = True
|
|
182
|
+
elif recent_savings_ratio is not None and recent_savings_ratio < cfg.anti_thrash_min_savings_ratio:
|
|
183
|
+
reason = "anti_thrash_skip"
|
|
184
|
+
should_compact = False
|
|
185
|
+
else:
|
|
186
|
+
reason = "below_threshold"
|
|
187
|
+
|
|
188
|
+
return CompactionDecision(
|
|
189
|
+
should_compact=should_compact,
|
|
190
|
+
reason=reason,
|
|
191
|
+
estimated_prompt_tokens=estimated_tokens,
|
|
192
|
+
model_context_tokens=model_context_tokens,
|
|
193
|
+
threshold_tokens=threshold_tokens,
|
|
194
|
+
protected_head_indexes=protected_head_indexes,
|
|
195
|
+
protected_tail_start_index=protected_tail_start_index,
|
|
196
|
+
focus_topic=normalized_focus_topic,
|
|
197
|
+
summary_insertion_point=insertion_point,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _estimate_single_message_tokens(message: Any) -> int:
|
|
202
|
+
if isinstance(message, str):
|
|
203
|
+
content = message
|
|
204
|
+
elif isinstance(message, dict) and "content" in message:
|
|
205
|
+
content = message["content"]
|
|
206
|
+
else:
|
|
207
|
+
content = getattr(message, "content", message)
|
|
208
|
+
|
|
209
|
+
if isinstance(content, list):
|
|
210
|
+
return _BASE_TOKENS_PER_MESSAGE + sum(_estimate_content_part_tokens(part) for part in content)
|
|
211
|
+
|
|
212
|
+
text = str(content or "")
|
|
213
|
+
return _BASE_TOKENS_PER_MESSAGE + _estimate_text_tokens(text, _CHARS_PER_TOKEN)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def _estimate_content_part_tokens(part: Any) -> int:
|
|
217
|
+
if isinstance(part, dict):
|
|
218
|
+
part_type = str(part.get("type", "")).lower()
|
|
219
|
+
if "image" in part_type:
|
|
220
|
+
return _IMAGE_PART_TOKEN_ESTIMATE
|
|
221
|
+
|
|
222
|
+
for key in ("text", "content"):
|
|
223
|
+
value = part.get(key)
|
|
224
|
+
if isinstance(value, str):
|
|
225
|
+
return _estimate_text_tokens(value, _CHARS_PER_TOKEN)
|
|
226
|
+
|
|
227
|
+
return _estimate_text_tokens(str(part), _CHARS_PER_TOKEN)
|
|
228
|
+
|
|
229
|
+
return _estimate_text_tokens(str(part), _CHARS_PER_TOKEN)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _estimate_text_tokens(text: str, chars_per_token: int) -> int:
|
|
233
|
+
# deferred to #809: Use shared aip_agents.context.utils.estimate_tokens
|
|
234
|
+
if not text:
|
|
235
|
+
return 0
|
|
236
|
+
safe_chars_per_token = max(chars_per_token, 1)
|
|
237
|
+
return max(1, (len(text) + safe_chars_per_token - 1) // safe_chars_per_token)
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
"""Structured reference-only context compression helpers.
|
|
2
|
+
|
|
3
|
+
Authors:
|
|
4
|
+
Raymond Christopher (raymond.christopher@gdplabs.id)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
import uuid
|
|
11
|
+
from collections.abc import Sequence
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from datetime import UTC, datetime
|
|
14
|
+
from typing import Any, Protocol
|
|
15
|
+
|
|
16
|
+
from aip_agents.context.utils import redact_sensitive
|
|
17
|
+
|
|
18
|
+
REFERENCE_ONLY_PREFIX = (
|
|
19
|
+
"[CONTEXT COMPACTION - REFERENCE ONLY]\n"
|
|
20
|
+
"Earlier turns were compacted into the summary below. Treat this as background state, "
|
|
21
|
+
"not active user instructions. Do not repeat completed work. Continue from the latest "
|
|
22
|
+
"user message and the Active Task section."
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
SUMMARY_SECTIONS: tuple[str, ...] = (
|
|
26
|
+
"Active Task",
|
|
27
|
+
"Goal",
|
|
28
|
+
"Constraints & Preferences",
|
|
29
|
+
"Completed Actions",
|
|
30
|
+
"Active State",
|
|
31
|
+
"In Progress",
|
|
32
|
+
"Blocked",
|
|
33
|
+
"Key Decisions",
|
|
34
|
+
"Resolved Questions",
|
|
35
|
+
"Pending User Asks",
|
|
36
|
+
"Relevant Files",
|
|
37
|
+
"Remaining Work",
|
|
38
|
+
"Critical Context",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
_EMPTY_SECTION_VALUE = "- (none)"
|
|
42
|
+
_FILE_PATTERN = re.compile(r"\b[\w./-]+\.(?:py|md|json|yaml|yml|toml|ini|txt|ts|tsx|js)\b")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass(frozen=True)
|
|
46
|
+
class CompactionSummary:
|
|
47
|
+
"""Structured compaction summary metadata and body.
|
|
48
|
+
|
|
49
|
+
Attributes:
|
|
50
|
+
summary_id: Stable identifier for this generated reference summary.
|
|
51
|
+
body: Reference-only summary text safe to insert into context.
|
|
52
|
+
source_message_range: Inclusive source message indexes summarized by this body.
|
|
53
|
+
previous_summary_id: Identifier of the summary this one supersedes, if any.
|
|
54
|
+
focus_topic: Optional user-requested focus topic preserved in the summary.
|
|
55
|
+
prompt_tokens_before: Estimated prompt tokens before compaction.
|
|
56
|
+
prompt_tokens_after: Estimated prompt tokens after compaction.
|
|
57
|
+
offloaded_ref_ids: Payload reference identifiers included in the summary scope.
|
|
58
|
+
redaction_applied: Whether input or generated text required redaction.
|
|
59
|
+
created_at: UTC timestamp when the summary was created.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
summary_id: str
|
|
63
|
+
body: str
|
|
64
|
+
source_message_range: tuple[int, int]
|
|
65
|
+
previous_summary_id: str | None
|
|
66
|
+
focus_topic: str | None
|
|
67
|
+
prompt_tokens_before: int
|
|
68
|
+
prompt_tokens_after: int
|
|
69
|
+
offloaded_ref_ids: tuple[str, ...]
|
|
70
|
+
redaction_applied: bool
|
|
71
|
+
created_at: datetime
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass(frozen=True)
|
|
75
|
+
class CompressionResult:
|
|
76
|
+
"""Outcome of a compaction summary generation attempt.
|
|
77
|
+
|
|
78
|
+
Attributes:
|
|
79
|
+
summary: Structured summary produced by the compressor.
|
|
80
|
+
used_fallback: Whether deterministic fallback summarization was used.
|
|
81
|
+
error: Safe error text when auxiliary summary generation failed.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
summary: CompactionSummary
|
|
85
|
+
used_fallback: bool
|
|
86
|
+
error: str | None = None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class SummarizerClient(Protocol):
|
|
90
|
+
"""Minimal abstraction for auxiliary summary generation."""
|
|
91
|
+
|
|
92
|
+
def summarize(self, prompt: str) -> str:
|
|
93
|
+
"""Return summary text from a redacted prompt input."""
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class ContextCompressor:
|
|
97
|
+
"""Generate structured reference-only summaries from prior conversation turns."""
|
|
98
|
+
|
|
99
|
+
def __init__(self, summarizer: SummarizerClient | None = None) -> None:
|
|
100
|
+
"""Initialize compressor with optional auxiliary summarizer client."""
|
|
101
|
+
self._summarizer = summarizer
|
|
102
|
+
|
|
103
|
+
def compress(
|
|
104
|
+
self,
|
|
105
|
+
messages: Sequence[Any],
|
|
106
|
+
*,
|
|
107
|
+
previous_summary: CompactionSummary | None = None,
|
|
108
|
+
focus_topic: str | None = None,
|
|
109
|
+
offloaded_ref_ids: Sequence[str] = (),
|
|
110
|
+
source_message_range: tuple[int, int] | None = None,
|
|
111
|
+
prompt_tokens_before: int = 0,
|
|
112
|
+
prompt_tokens_after: int = 0,
|
|
113
|
+
) -> CompressionResult:
|
|
114
|
+
"""Create a structured summary with safe fallback behavior.
|
|
115
|
+
|
|
116
|
+
Usage::
|
|
117
|
+
|
|
118
|
+
from aip_agents.context.compaction import ContextCompressor
|
|
119
|
+
|
|
120
|
+
compressor = ContextCompressor() # uses fallback summarization
|
|
121
|
+
result = compressor.compress(messages)
|
|
122
|
+
# result.summary.body contains the structured summary
|
|
123
|
+
# result.used_fallback is True when no external summarizer was set
|
|
124
|
+
"""
|
|
125
|
+
redacted_message_texts, message_roles, input_redacted = _prepare_redacted_inputs(messages, previous_summary)
|
|
126
|
+
generated, used_fallback, generation_error, output_redacted = self._generate_summary_text(
|
|
127
|
+
redacted_message_texts
|
|
128
|
+
)
|
|
129
|
+
input_redacted = input_redacted or output_redacted
|
|
130
|
+
|
|
131
|
+
sections = _extract_sections(
|
|
132
|
+
messages=redacted_message_texts,
|
|
133
|
+
message_roles=message_roles,
|
|
134
|
+
generated_summary=generated,
|
|
135
|
+
focus_topic=focus_topic,
|
|
136
|
+
previous_summary=previous_summary,
|
|
137
|
+
)
|
|
138
|
+
body = _render_summary_body(sections)
|
|
139
|
+
|
|
140
|
+
summary_id = f"summary_{uuid.uuid4().hex[:12]}"
|
|
141
|
+
message_range = _resolve_message_range(messages, source_message_range)
|
|
142
|
+
summary = CompactionSummary(
|
|
143
|
+
summary_id=summary_id,
|
|
144
|
+
body=body,
|
|
145
|
+
source_message_range=message_range,
|
|
146
|
+
previous_summary_id=previous_summary.summary_id if previous_summary else None,
|
|
147
|
+
focus_topic=(focus_topic or "").strip() or None,
|
|
148
|
+
prompt_tokens_before=prompt_tokens_before,
|
|
149
|
+
prompt_tokens_after=prompt_tokens_after,
|
|
150
|
+
offloaded_ref_ids=tuple(offloaded_ref_ids),
|
|
151
|
+
redaction_applied=input_redacted,
|
|
152
|
+
created_at=datetime.now(UTC),
|
|
153
|
+
)
|
|
154
|
+
return CompressionResult(summary=summary, used_fallback=used_fallback, error=generation_error)
|
|
155
|
+
|
|
156
|
+
def _generate_summary_text(self, redacted_message_texts: Sequence[str]) -> tuple[str, bool, str | None, bool]:
|
|
157
|
+
redacted_input = "\n".join(redacted_message_texts)
|
|
158
|
+
if self._summarizer is None:
|
|
159
|
+
return "", True, None, False
|
|
160
|
+
try:
|
|
161
|
+
generated_raw = self._summarizer.summarize(redacted_input)
|
|
162
|
+
generated, output_redacted = redact_sensitive(str(generated_raw))
|
|
163
|
+
return generated, False, None, output_redacted
|
|
164
|
+
except Exception as error: # pragma: no cover - behavior exercised by tests
|
|
165
|
+
return "", True, str(error), False
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _extract_sections(
|
|
169
|
+
*,
|
|
170
|
+
messages: Sequence[str],
|
|
171
|
+
message_roles: Sequence[str] = (),
|
|
172
|
+
generated_summary: str,
|
|
173
|
+
focus_topic: str | None,
|
|
174
|
+
previous_summary: CompactionSummary | None,
|
|
175
|
+
) -> dict[str, str]:
|
|
176
|
+
"""Extract section content from messages and optional generated summary."""
|
|
177
|
+
latest_user = _latest_user_message(messages, message_roles)
|
|
178
|
+
decision_lines = _collect_lines(messages, keywords=("decision", "decided", "chose"))
|
|
179
|
+
blocker_lines = _collect_lines(messages, keywords=("block", "blocked", "waiting", "dependency"))
|
|
180
|
+
done_lines = _collect_lines(messages, keywords=("done", "completed", "implemented", "finished"))
|
|
181
|
+
pending_lines = _collect_lines(messages, keywords=("todo", "next", "remaining", "pending"))
|
|
182
|
+
question_lines = _collect_lines(messages, keywords=("?", "question", "clarify"))
|
|
183
|
+
file_hits = sorted({match for text in messages for match in _FILE_PATTERN.findall(text)})
|
|
184
|
+
|
|
185
|
+
sections: dict[str, str] = dict.fromkeys(SUMMARY_SECTIONS, _EMPTY_SECTION_VALUE)
|
|
186
|
+
sections["Active Task"] = f"- {latest_user.strip()[:500]}" if latest_user else _EMPTY_SECTION_VALUE
|
|
187
|
+
sections["Goal"] = f"- Focus on {focus_topic}" if focus_topic else "- Continue current requested scope"
|
|
188
|
+
sections["Constraints & Preferences"] = "- Preserve prior constraints and avoid re-running completed work"
|
|
189
|
+
sections["Completed Actions"] = _format_lines(done_lines)
|
|
190
|
+
sections["Active State"] = "- Compacted reference summary prepared"
|
|
191
|
+
sections["In Progress"] = _format_lines(pending_lines)
|
|
192
|
+
sections["Blocked"] = _format_lines(blocker_lines)
|
|
193
|
+
sections["Key Decisions"] = _format_lines(decision_lines)
|
|
194
|
+
sections["Resolved Questions"] = _EMPTY_SECTION_VALUE
|
|
195
|
+
sections["Pending User Asks"] = _format_lines(question_lines)
|
|
196
|
+
sections["Relevant Files"] = _format_lines([f"`{path}`" for path in file_hits])
|
|
197
|
+
sections["Remaining Work"] = _format_lines(pending_lines)
|
|
198
|
+
|
|
199
|
+
_merge_generated_sections(sections, generated_summary)
|
|
200
|
+
|
|
201
|
+
critical_lines: list[str] = []
|
|
202
|
+
if focus_topic:
|
|
203
|
+
critical_lines.append(f"Focus topic: {focus_topic}")
|
|
204
|
+
if previous_summary is not None:
|
|
205
|
+
critical_lines.append(
|
|
206
|
+
f"Previous summary id: {previous_summary.summary_id}"
|
|
207
|
+
if previous_summary is not None
|
|
208
|
+
else "Previous summary id: none"
|
|
209
|
+
)
|
|
210
|
+
if generated_summary:
|
|
211
|
+
critical_lines.append(generated_summary[:1_000])
|
|
212
|
+
sections["Critical Context"] = _format_lines(critical_lines)
|
|
213
|
+
|
|
214
|
+
return sections
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _collect_lines(messages: Sequence[str], *, keywords: Sequence[str]) -> list[str]:
|
|
218
|
+
matched: list[str] = []
|
|
219
|
+
keyword_set = tuple(keyword.lower() for keyword in keywords)
|
|
220
|
+
for text in messages:
|
|
221
|
+
for line in text.splitlines():
|
|
222
|
+
lowered = line.lower()
|
|
223
|
+
if any(keyword in lowered for keyword in keyword_set):
|
|
224
|
+
cleaned = line.strip()
|
|
225
|
+
if cleaned and cleaned not in matched:
|
|
226
|
+
matched.append(cleaned)
|
|
227
|
+
return matched
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _render_summary_body(sections: dict[str, str]) -> str:
|
|
231
|
+
lines = [REFERENCE_ONLY_PREFIX, ""]
|
|
232
|
+
for section in SUMMARY_SECTIONS:
|
|
233
|
+
lines.append(f"## {section}")
|
|
234
|
+
lines.append(sections.get(section, _EMPTY_SECTION_VALUE))
|
|
235
|
+
lines.append("")
|
|
236
|
+
return "\n".join(lines).strip()
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _prepare_redacted_inputs(
|
|
240
|
+
messages: Sequence[Any], previous_summary: CompactionSummary | None
|
|
241
|
+
) -> tuple[list[str], list[str], bool]:
|
|
242
|
+
redacted_message_texts: list[str] = []
|
|
243
|
+
message_roles: list[str] = []
|
|
244
|
+
input_redacted = False
|
|
245
|
+
for message in messages:
|
|
246
|
+
redacted_text, redacted = redact_sensitive(_message_to_text(message))
|
|
247
|
+
redacted_message_texts.append(redacted_text)
|
|
248
|
+
input_redacted = input_redacted or redacted
|
|
249
|
+
message_roles.append(str(message.get("role", "")) if isinstance(message, dict) else "")
|
|
250
|
+
|
|
251
|
+
if previous_summary is not None:
|
|
252
|
+
previous_body, prev_redacted = redact_sensitive(previous_summary.body)
|
|
253
|
+
redacted_message_texts = [previous_body] + redacted_message_texts
|
|
254
|
+
message_roles = [""] + message_roles
|
|
255
|
+
input_redacted = input_redacted or prev_redacted
|
|
256
|
+
|
|
257
|
+
return redacted_message_texts, message_roles, input_redacted
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _resolve_message_range(messages: Sequence[Any], source_message_range: tuple[int, int] | None) -> tuple[int, int]:
|
|
261
|
+
if source_message_range is not None:
|
|
262
|
+
return source_message_range
|
|
263
|
+
if messages:
|
|
264
|
+
return (0, len(messages) - 1)
|
|
265
|
+
return (-1, -1)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _merge_generated_sections(sections: dict[str, str], generated_summary: str) -> None:
|
|
269
|
+
if not generated_summary:
|
|
270
|
+
return
|
|
271
|
+
parsed = _parse_generated_sections(generated_summary)
|
|
272
|
+
for section in SUMMARY_SECTIONS:
|
|
273
|
+
content = parsed.get(section)
|
|
274
|
+
if content:
|
|
275
|
+
sections[section] = content
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _parse_generated_sections(generated_summary: str) -> dict[str, str]:
|
|
279
|
+
"""Parse a generated summary for structured sections matching SUMMARY_SECTIONS."""
|
|
280
|
+
parsed: dict[str, str] = {}
|
|
281
|
+
current_section: str | None = None
|
|
282
|
+
current_lines: list[str] = []
|
|
283
|
+
for line in generated_summary.splitlines():
|
|
284
|
+
heading_match = re.match(r"^##\s+(.+)", line.strip())
|
|
285
|
+
if heading_match:
|
|
286
|
+
_flush_parsed_section(parsed, current_section, current_lines)
|
|
287
|
+
heading = heading_match.group(1).strip()
|
|
288
|
+
current_section = heading if heading in SUMMARY_SECTIONS else None
|
|
289
|
+
current_lines = []
|
|
290
|
+
elif current_section:
|
|
291
|
+
stripped = line.strip()
|
|
292
|
+
if stripped:
|
|
293
|
+
cleaned = stripped.removeprefix("- ").strip()
|
|
294
|
+
current_lines.append(cleaned or stripped)
|
|
295
|
+
_flush_parsed_section(parsed, current_section, current_lines)
|
|
296
|
+
return parsed
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _flush_parsed_section(parsed: dict[str, str], current_section: str | None, current_lines: list[str]) -> None:
|
|
300
|
+
if not current_section or not current_lines:
|
|
301
|
+
return
|
|
302
|
+
content = _format_lines(current_lines)
|
|
303
|
+
if content != _EMPTY_SECTION_VALUE:
|
|
304
|
+
parsed[current_section] = content
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _latest_user_message(messages: Sequence[str], message_roles: Sequence[str] = ()) -> str:
|
|
308
|
+
"""Return the latest user/human message text from the given sequence."""
|
|
309
|
+
if message_roles:
|
|
310
|
+
for text, role in zip(reversed(messages), reversed(message_roles)):
|
|
311
|
+
if role.lower() in {"user", "human"} and text.strip():
|
|
312
|
+
return text.strip()
|
|
313
|
+
for text in reversed(messages):
|
|
314
|
+
if text.strip():
|
|
315
|
+
return text.strip()
|
|
316
|
+
return ""
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _format_lines(lines: Sequence[str]) -> str:
|
|
320
|
+
if not lines:
|
|
321
|
+
return _EMPTY_SECTION_VALUE
|
|
322
|
+
unique = []
|
|
323
|
+
for line in lines:
|
|
324
|
+
normalized = line.strip()
|
|
325
|
+
if normalized and normalized not in unique:
|
|
326
|
+
unique.append(normalized)
|
|
327
|
+
if not unique:
|
|
328
|
+
return _EMPTY_SECTION_VALUE
|
|
329
|
+
return "\n".join(f"- {line}" for line in unique)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def _message_to_text(message: Any) -> str:
|
|
333
|
+
if isinstance(message, str):
|
|
334
|
+
return message
|
|
335
|
+
if isinstance(message, dict):
|
|
336
|
+
content = message.get("content")
|
|
337
|
+
return str(content or "")
|
|
338
|
+
content = getattr(message, "content", "")
|
|
339
|
+
return str(content or "")
|