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,29 @@
|
|
|
1
|
+
"""Public API for PII-related utilities.
|
|
2
|
+
|
|
3
|
+
This subpackage groups all helpers for anonymizing/deanonymizing PII used by
|
|
4
|
+
agents and tools.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from aip_agents.utils.pii.pii_handler import ToolPIIHandler
|
|
8
|
+
from aip_agents.utils.pii.pii_helper import (
|
|
9
|
+
PII_AGENT_PROMPT_ENV_VAR,
|
|
10
|
+
add_pii_mappings,
|
|
11
|
+
anonymize_final_response_content,
|
|
12
|
+
deanonymize_final_response_content,
|
|
13
|
+
extract_pii_mapping_from_agent_response,
|
|
14
|
+
get_pii_agent_prompt,
|
|
15
|
+
normalize_enable_pii,
|
|
16
|
+
)
|
|
17
|
+
from aip_agents.utils.pii.uuid_deanonymizer_mapping import UUIDDeanonymizerMapping
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"PII_AGENT_PROMPT_ENV_VAR",
|
|
21
|
+
"ToolPIIHandler",
|
|
22
|
+
"add_pii_mappings",
|
|
23
|
+
"anonymize_final_response_content",
|
|
24
|
+
"deanonymize_final_response_content",
|
|
25
|
+
"extract_pii_mapping_from_agent_response",
|
|
26
|
+
"get_pii_agent_prompt",
|
|
27
|
+
"normalize_enable_pii",
|
|
28
|
+
"UUIDDeanonymizerMapping",
|
|
29
|
+
]
|
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
"""PII handler for masking/demasking at the tool calling level.
|
|
2
|
+
|
|
3
|
+
This module provides the ToolPIIHandler class for handling PII operations
|
|
4
|
+
during tool execution in LangGraph agents. Tag replacement works with the
|
|
5
|
+
mapping supplied by the runner, while advanced NER-powered detection is only
|
|
6
|
+
enabled when NER_API_URL and NER_API_KEY environment variables are set.
|
|
7
|
+
|
|
8
|
+
Authors:
|
|
9
|
+
Fachriza Adhiatma (fachriza.d.adhiatma@gdplabs.id)
|
|
10
|
+
|
|
11
|
+
References:
|
|
12
|
+
1. https://gdplabs.gitbook.io/sdk/tutorials/security-and-privacy/pii-masking#anonymizer-with-ner
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import socket
|
|
17
|
+
from enum import StrEnum
|
|
18
|
+
from typing import Any
|
|
19
|
+
from urllib.parse import urlparse, urlunparse
|
|
20
|
+
|
|
21
|
+
import requests
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
from gllm_privacy.pii_detector import TextAnalyzer, TextAnonymizer
|
|
25
|
+
from gllm_privacy.pii_detector.anonymizer import Operation
|
|
26
|
+
from gllm_privacy.pii_detector.constants import GLLM_PRIVACY_ENTITIES, Entities
|
|
27
|
+
from gllm_privacy.pii_detector.recognizer.gdplabs_ner_api_remote_recognizer import (
|
|
28
|
+
GDPLabsNerApiRemoteRecognizer,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
_HAS_GLLM_PRIVACY = True
|
|
32
|
+
except ImportError: # pragma: no cover
|
|
33
|
+
TextAnalyzer = Any # type: ignore[assignment]
|
|
34
|
+
TextAnonymizer = Any # type: ignore[assignment]
|
|
35
|
+
GDPLabsNerApiRemoteRecognizer = Any # type: ignore[assignment]
|
|
36
|
+
|
|
37
|
+
class _Operation(StrEnum):
|
|
38
|
+
ANONYMIZE = "ANONYMIZE"
|
|
39
|
+
DEANONYMIZE = "DEANONYMIZE"
|
|
40
|
+
|
|
41
|
+
Operation = _Operation
|
|
42
|
+
|
|
43
|
+
GLLM_PRIVACY_ENTITIES = [] # type: ignore[assignment]
|
|
44
|
+
Entities = None # type: ignore[assignment]
|
|
45
|
+
_HAS_GLLM_PRIVACY = False
|
|
46
|
+
|
|
47
|
+
from aip_agents.utils.logger import get_logger
|
|
48
|
+
from aip_agents.utils.pii.uuid_deanonymizer_mapping import UUIDDeanonymizerMapping
|
|
49
|
+
|
|
50
|
+
logger = get_logger(__name__)
|
|
51
|
+
|
|
52
|
+
NER_API_URL_ENV_VAR = "NER_API_URL"
|
|
53
|
+
NER_API_KEY_ENV_VAR = "NER_API_KEY"
|
|
54
|
+
NER_API_TIMEOUT_ENV_VAR = "NER_API_TIMEOUT"
|
|
55
|
+
NER_API_TIMEOUT_DEFAULT = 10
|
|
56
|
+
if _HAS_GLLM_PRIVACY:
|
|
57
|
+
EXCLUDED_ENTITIES = [Entities.URL.value]
|
|
58
|
+
DEFAULT_SUPPORTED_ENTITIES = [entity for entity in GLLM_PRIVACY_ENTITIES if entity not in EXCLUDED_ENTITIES]
|
|
59
|
+
else:
|
|
60
|
+
EXCLUDED_ENTITIES = []
|
|
61
|
+
DEFAULT_SUPPORTED_ENTITIES = []
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _is_ner_reachable(host: str, port: int, ner_api_url: str, timeout: int) -> tuple[bool, str | None]:
|
|
65
|
+
"""Check NER server reachability via two-stage probe: TCP + HTTP health check.
|
|
66
|
+
|
|
67
|
+
Performs two-stage probe to validate NER server health:
|
|
68
|
+
Stage 1: TCP connectivity to host:port (quick check for unreachable servers)
|
|
69
|
+
Stage 2: HTTP GET request to server root to detect unhealthy HTTP services
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
host: NER server hostname or IP address
|
|
73
|
+
port: NER server port
|
|
74
|
+
ner_api_url: Full NER API URL (used to construct root URL for health check)
|
|
75
|
+
timeout: Connection timeout in seconds (applies to both TCP and HTTP)
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Tuple of (reachable, failure_reason):
|
|
79
|
+
- (True, None) if both TCP and HTTP checks succeed
|
|
80
|
+
- (False, reason_str) on any failure with specific failure context
|
|
81
|
+
|
|
82
|
+
Note:
|
|
83
|
+
- Checks the server root path (e.g., https://ner.glair.ai/) instead of the
|
|
84
|
+
API endpoint, since most APIs only accept POST to their endpoints.
|
|
85
|
+
- 2xx-4xx responses indicate the server is healthy and reachable.
|
|
86
|
+
- 5xx responses indicate server errors; the server is unhealthy.
|
|
87
|
+
- Timeouts or connection errors are treated as unreachable.
|
|
88
|
+
"""
|
|
89
|
+
# Stage 1: TCP connectivity
|
|
90
|
+
sock = None
|
|
91
|
+
try:
|
|
92
|
+
sock = socket.create_connection((host, port), timeout=timeout)
|
|
93
|
+
except TimeoutError:
|
|
94
|
+
return False, "TCP connection timeout" # pragma: no cover
|
|
95
|
+
except (ConnectionRefusedError, ConnectionResetError):
|
|
96
|
+
return False, "TCP connection refused"
|
|
97
|
+
except OSError as e:
|
|
98
|
+
return False, f"TCP connection failed: {type(e).__name__}"
|
|
99
|
+
finally:
|
|
100
|
+
if sock:
|
|
101
|
+
try:
|
|
102
|
+
sock.close()
|
|
103
|
+
except OSError: # pragma: no cover
|
|
104
|
+
pass # pragma: no cover
|
|
105
|
+
|
|
106
|
+
# Stage 2: HTTP health check (GET to server root, treating any non-5xx response as healthy)
|
|
107
|
+
try:
|
|
108
|
+
# Build root URL (e.g., from "https://ner.glair.ai/analyze" to "https://ner.glair.ai/")
|
|
109
|
+
parsed = urlparse(ner_api_url)
|
|
110
|
+
root_url = urlunparse((parsed.scheme, parsed.netloc, "/", "", "", ""))
|
|
111
|
+
|
|
112
|
+
response = requests.get(root_url, timeout=timeout, allow_redirects=True)
|
|
113
|
+
# Consider any response < 500 as healthy (endpoint exists and server is running)
|
|
114
|
+
# Only 5xx indicates server is unhealthy
|
|
115
|
+
if response.status_code < 500:
|
|
116
|
+
return True, None
|
|
117
|
+
return False, f"HTTP {response.status_code} (server error)"
|
|
118
|
+
except requests.Timeout:
|
|
119
|
+
return False, "HTTP request timeout"
|
|
120
|
+
except requests.ConnectionError as e: # pragma: no cover
|
|
121
|
+
return False, f"HTTP connection failed: {type(e).__name__}" # pragma: no cover
|
|
122
|
+
except requests.RequestException as e: # pragma: no cover
|
|
123
|
+
return False, f"HTTP request failed: {type(e).__name__}" # pragma: no cover
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class ToolPIIHandler:
|
|
127
|
+
"""Handles PII masking/demasking for tool calling.
|
|
128
|
+
|
|
129
|
+
Tag replacement based on runner-provided mappings always works. Optional
|
|
130
|
+
NER-powered masking/de-masking is only enabled when NER_API_URL and
|
|
131
|
+
NER_API_KEY environment variables are set.
|
|
132
|
+
|
|
133
|
+
Attributes:
|
|
134
|
+
flat_pii_mapping: Flat mapping from runner service (tag → value)
|
|
135
|
+
text_analyzer: GLLM Privacy TextAnalyzer instance
|
|
136
|
+
text_anonymizer: GLLM Privacy TextAnonymizer instance
|
|
137
|
+
enable_ner: Whether NER is enabled
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
def __init__(
|
|
141
|
+
self,
|
|
142
|
+
pii_mapping: dict[str, str] | None = None,
|
|
143
|
+
ner_api_url: str | None = None,
|
|
144
|
+
ner_api_key: str | None = None,
|
|
145
|
+
ner_api_timeout: int = NER_API_TIMEOUT_DEFAULT,
|
|
146
|
+
) -> None:
|
|
147
|
+
"""Initialize PII handler (private - use create_if_enabled() instead).
|
|
148
|
+
|
|
149
|
+
Initializes GLLM Privacy components (TextAnalyzer, TextAnonymizer) if NER credentials
|
|
150
|
+
are provided. Creates dual recognizers for Indonesian and English languages.
|
|
151
|
+
Pre-loads any existing PII mappings into the anonymizer's internal state.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
pii_mapping: Existing PII mapping from runner service (flat format: tag -> value)
|
|
155
|
+
ner_api_url: NER API endpoint URL
|
|
156
|
+
ner_api_key: NER API authentication key
|
|
157
|
+
ner_api_timeout: Timeout in seconds for NER API requests (default: 10)
|
|
158
|
+
|
|
159
|
+
Raises:
|
|
160
|
+
ValueError: If ner_api_timeout is not a positive integer
|
|
161
|
+
"""
|
|
162
|
+
if ner_api_timeout <= 0:
|
|
163
|
+
raise ValueError(f"{NER_API_TIMEOUT_ENV_VAR} must be a positive integer, got {ner_api_timeout}.")
|
|
164
|
+
|
|
165
|
+
self.flat_pii_mapping: dict[str, str] = pii_mapping or {}
|
|
166
|
+
self.enable_ner: bool = bool(ner_api_url and ner_api_key)
|
|
167
|
+
self.text_analyzer: TextAnalyzer | None = None
|
|
168
|
+
self.text_anonymizer: TextAnonymizer | None = None
|
|
169
|
+
|
|
170
|
+
if self.enable_ner and not _HAS_GLLM_PRIVACY:
|
|
171
|
+
logger.warning(
|
|
172
|
+
"NER is configured (NER_API_URL/NER_API_KEY present) but optional dependency 'gllm-privacy' "
|
|
173
|
+
"is not installed. Continuing with NER disabled."
|
|
174
|
+
)
|
|
175
|
+
self.enable_ner = False
|
|
176
|
+
|
|
177
|
+
if self.enable_ner:
|
|
178
|
+
self._initialize_ner(ner_api_url, ner_api_key, ner_api_timeout)
|
|
179
|
+
|
|
180
|
+
def _initialize_ner(self, ner_api_url: str, ner_api_key: str, ner_api_timeout: int) -> None:
|
|
181
|
+
"""Initialize NER components after health probe.
|
|
182
|
+
|
|
183
|
+
Performs TCP+HTTP health check, then constructs GLLM Privacy recognizers
|
|
184
|
+
if server is reachable. Sets enable_ner=False and logs warning on probe failure.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
ner_api_url: Full NER API URL
|
|
188
|
+
ner_api_key: NER API authentication key
|
|
189
|
+
ner_api_timeout: Request timeout in seconds
|
|
190
|
+
"""
|
|
191
|
+
# Perform two-stage probe (TCP + HTTP) to check NER server reachability
|
|
192
|
+
parsed_url = urlparse(ner_api_url)
|
|
193
|
+
host = str(parsed_url.hostname or "")
|
|
194
|
+
port = parsed_url.port or (443 if parsed_url.scheme == "https" else 80)
|
|
195
|
+
|
|
196
|
+
# Validate host before calling probe to avoid wasting resources on malformed URLs
|
|
197
|
+
if not host:
|
|
198
|
+
logger.warning("NER server unreachable: malformed URL, disabling NER. Continuing with mapping-only mode.")
|
|
199
|
+
self.enable_ner = False
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
reachable, failure_reason = _is_ner_reachable(host, port, ner_api_url, ner_api_timeout)
|
|
203
|
+
if not reachable:
|
|
204
|
+
reason_msg = f": {failure_reason}" if failure_reason else ""
|
|
205
|
+
logger.warning(
|
|
206
|
+
f"NER server unreachable at {host}:{port}{reason_msg}, disabling NER. Continuing with mapping-only mode."
|
|
207
|
+
)
|
|
208
|
+
self.enable_ner = False
|
|
209
|
+
return
|
|
210
|
+
|
|
211
|
+
# Construct recognizers and text processing components
|
|
212
|
+
try:
|
|
213
|
+
headers = {"X-Api-Key": ner_api_key}
|
|
214
|
+
|
|
215
|
+
id_recognizer = GDPLabsNerApiRemoteRecognizer(
|
|
216
|
+
api_url=ner_api_url,
|
|
217
|
+
supported_language="id",
|
|
218
|
+
api_headers=headers,
|
|
219
|
+
api_timeout=ner_api_timeout,
|
|
220
|
+
)
|
|
221
|
+
en_recognizer = GDPLabsNerApiRemoteRecognizer(
|
|
222
|
+
api_url=ner_api_url,
|
|
223
|
+
supported_language="en",
|
|
224
|
+
api_headers=headers,
|
|
225
|
+
api_timeout=ner_api_timeout,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
self.text_analyzer = TextAnalyzer(additional_recognizers=[id_recognizer, en_recognizer])
|
|
229
|
+
|
|
230
|
+
# Initialize with UUID-based deanonymizer mapping
|
|
231
|
+
uuid_mapping = UUIDDeanonymizerMapping(uuid_length=8)
|
|
232
|
+
self.text_anonymizer = TextAnonymizer(
|
|
233
|
+
text_analyzer=self.text_analyzer,
|
|
234
|
+
deanonymizer_mapping=uuid_mapping,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
if self.flat_pii_mapping:
|
|
238
|
+
gllm_mapping = self._convert_flat_to_gllm_format(self.flat_pii_mapping)
|
|
239
|
+
self.text_anonymizer._deanonymizer_mapping.update(gllm_mapping, use_uuid_suffix=False)
|
|
240
|
+
|
|
241
|
+
except Exception as e: # noqa: BLE001 # pragma: no cover
|
|
242
|
+
logger.warning(f"Failed to initialize GLLM Privacy components: {e}") # pragma: no cover
|
|
243
|
+
self.enable_ner = False # pragma: no cover
|
|
244
|
+
|
|
245
|
+
@classmethod
|
|
246
|
+
def create_if_enabled(cls, pii_mapping: dict[str, str] | None = None) -> "ToolPIIHandler | None":
|
|
247
|
+
"""Create ToolPIIHandler when mappings or NER configuration exist.
|
|
248
|
+
|
|
249
|
+
Reads NER_API_URL, NER_API_KEY, and optionally NER_API_TIMEOUT from environment variables.
|
|
250
|
+
NER_API_TIMEOUT must be a positive integer; defaults to 10 seconds if not specified.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
pii_mapping: Existing PII mapping from runner service
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
ToolPIIHandler instance when mapping or NER config is available, None otherwise
|
|
257
|
+
|
|
258
|
+
Raises:
|
|
259
|
+
ValueError: If NER_API_TIMEOUT is set but cannot be parsed as a positive integer
|
|
260
|
+
"""
|
|
261
|
+
ner_api_url = os.getenv(NER_API_URL_ENV_VAR)
|
|
262
|
+
ner_api_key = os.getenv(NER_API_KEY_ENV_VAR)
|
|
263
|
+
ner_api_timeout_str = os.getenv(NER_API_TIMEOUT_ENV_VAR)
|
|
264
|
+
|
|
265
|
+
if ner_api_timeout_str:
|
|
266
|
+
try:
|
|
267
|
+
ner_api_timeout = int(ner_api_timeout_str)
|
|
268
|
+
except ValueError as e:
|
|
269
|
+
raise ValueError(
|
|
270
|
+
f"Invalid value for {NER_API_TIMEOUT_ENV_VAR}: '{ner_api_timeout_str}' is not an integer."
|
|
271
|
+
) from e
|
|
272
|
+
else:
|
|
273
|
+
ner_api_timeout = NER_API_TIMEOUT_DEFAULT
|
|
274
|
+
|
|
275
|
+
if ner_api_url and ner_api_key:
|
|
276
|
+
return cls(pii_mapping, ner_api_url, ner_api_key, ner_api_timeout)
|
|
277
|
+
|
|
278
|
+
if pii_mapping:
|
|
279
|
+
return cls(pii_mapping)
|
|
280
|
+
|
|
281
|
+
return None
|
|
282
|
+
|
|
283
|
+
@classmethod
|
|
284
|
+
def create_mapping_only(cls, pii_mapping: dict[str, str] | None = None) -> "ToolPIIHandler | None":
|
|
285
|
+
"""Create ToolPIIHandler in mapping-only mode (no NER).
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
pii_mapping: Existing PII mapping from runner service
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
ToolPIIHandler instance when mapping exists, None otherwise
|
|
292
|
+
"""
|
|
293
|
+
if not pii_mapping:
|
|
294
|
+
return None
|
|
295
|
+
|
|
296
|
+
return cls(pii_mapping)
|
|
297
|
+
|
|
298
|
+
def deanonymize_tool_args(self, args: dict[str, Any]) -> dict[str, Any]:
|
|
299
|
+
"""Replace PII tags in tool arguments with real values.
|
|
300
|
+
|
|
301
|
+
Recursively processes dictionaries, lists, and strings to replace all PII tags
|
|
302
|
+
(e.g., '<EMAIL_1>') with their corresponding real values from flat_pii_mapping.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
args: Tool arguments that may contain PII tags
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
Arguments with tags replaced by real values
|
|
309
|
+
"""
|
|
310
|
+
return self._process_value(args, operation=Operation.DEANONYMIZE)
|
|
311
|
+
|
|
312
|
+
def anonymize(self, output: Any) -> tuple[Any, dict[str, str]]:
|
|
313
|
+
"""Mask PII values in any string or dict input.
|
|
314
|
+
|
|
315
|
+
Handles string and dictionary inputs. For strings, uses two-phase anonymization:
|
|
316
|
+
first masks known PII, then detects new PII via NER. For dictionaries, recursively
|
|
317
|
+
processes all string values. Returns updated mapping with any newly discovered PII.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
output: Input that may contain PII values (string, dict, or other)
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
Tuple of (anonymized_output, updated_flat_pii_mapping)
|
|
324
|
+
"""
|
|
325
|
+
if isinstance(output, str):
|
|
326
|
+
anonymized, updated_mapping = self._anonymize_text(output)
|
|
327
|
+
return anonymized, updated_mapping
|
|
328
|
+
elif isinstance(output, dict):
|
|
329
|
+
anonymized, updated_mapping = self._anonymize_dict(output)
|
|
330
|
+
return anonymized, updated_mapping
|
|
331
|
+
else:
|
|
332
|
+
# For non-string, non-dict outputs, return as-is
|
|
333
|
+
return output, self.flat_pii_mapping
|
|
334
|
+
|
|
335
|
+
def anonymize_tool_output(self, output: Any) -> tuple[Any, dict[str, str]]:
|
|
336
|
+
"""Backward-compatible alias for anonymize()."""
|
|
337
|
+
return self.anonymize(output)
|
|
338
|
+
|
|
339
|
+
def anonymize_command_update(self, update: dict[str, Any]) -> dict[str, Any]:
|
|
340
|
+
"""Anonymize user-visible values in a ``Command.update`` dict.
|
|
341
|
+
|
|
342
|
+
Recursively masks PII in strings under ``result``/``metadata``/``messages``/other
|
|
343
|
+
state, while preserving binary artifact payload fields (``data``/``bytes``/
|
|
344
|
+
``base64``) inside the ``artifacts`` subtree and leaving ``pii_mapping`` untouched
|
|
345
|
+
as the raw tag->value channel.
|
|
346
|
+
"""
|
|
347
|
+
return self._anonymize_command_value("", update, in_artifact=False)
|
|
348
|
+
|
|
349
|
+
def _anonymize_command_value(self, key: str, value: Any, *, in_artifact: bool) -> Any:
|
|
350
|
+
"""Recursively anonymize one Command.update value (see anonymize_command_update)."""
|
|
351
|
+
if key == "pii_mapping":
|
|
352
|
+
return value
|
|
353
|
+
if in_artifact and key in {"data", "bytes", "base64"}:
|
|
354
|
+
return value
|
|
355
|
+
if isinstance(value, str):
|
|
356
|
+
return self.anonymize(value)[0]
|
|
357
|
+
if isinstance(value, dict):
|
|
358
|
+
return {
|
|
359
|
+
child_key: self._anonymize_command_value(str(child_key), child_value, in_artifact=in_artifact)
|
|
360
|
+
for child_key, child_value in value.items()
|
|
361
|
+
}
|
|
362
|
+
if isinstance(value, list):
|
|
363
|
+
child_in_artifact = key == "artifacts"
|
|
364
|
+
return [self._anonymize_command_value(key, item, in_artifact=child_in_artifact) for item in value]
|
|
365
|
+
return value
|
|
366
|
+
|
|
367
|
+
@staticmethod
|
|
368
|
+
def _convert_flat_to_gllm_format(
|
|
369
|
+
flat_mapping: dict[str, str],
|
|
370
|
+
) -> dict[str, dict[str, str]]:
|
|
371
|
+
"""Convert flat PII mapping to GLLM Privacy nested format.
|
|
372
|
+
|
|
373
|
+
Transforms flat format {'<PERSON_1>': 'Alice'} to nested format
|
|
374
|
+
{'PERSON': {'<PERSON_1>': 'Alice'}} required by GLLM Privacy's internal state.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
flat_mapping: Flat mapping (tag → value)
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
Nested mapping organized by entity type
|
|
381
|
+
"""
|
|
382
|
+
gllm_mapping: dict[str, dict[str, str]] = {}
|
|
383
|
+
|
|
384
|
+
for tag, value in flat_mapping.items():
|
|
385
|
+
tag_content = tag.strip("<>")
|
|
386
|
+
parts = tag_content.rsplit("_", 1)
|
|
387
|
+
entity_type = parts[0] if parts else "UNKNOWN"
|
|
388
|
+
|
|
389
|
+
if entity_type not in gllm_mapping:
|
|
390
|
+
gllm_mapping[entity_type] = {}
|
|
391
|
+
|
|
392
|
+
gllm_mapping[entity_type][tag] = value
|
|
393
|
+
|
|
394
|
+
return gllm_mapping
|
|
395
|
+
|
|
396
|
+
@staticmethod
|
|
397
|
+
def _convert_gllm_to_flat_format(
|
|
398
|
+
gllm_mapping: dict[str, dict[str, str]],
|
|
399
|
+
) -> dict[str, str]:
|
|
400
|
+
"""Convert GLLM Privacy nested format back to flat format.
|
|
401
|
+
|
|
402
|
+
Inverse of _convert_flat_to_gllm_format. Flattens nested structure
|
|
403
|
+
{'PERSON': {'<PERSON_1>': 'Alice'}} back to {'<PERSON_1>': 'Alice'}.
|
|
404
|
+
|
|
405
|
+
Args:
|
|
406
|
+
gllm_mapping: Nested mapping from GLLM Privacy
|
|
407
|
+
|
|
408
|
+
Returns:
|
|
409
|
+
Flat mapping (tag → value)
|
|
410
|
+
"""
|
|
411
|
+
flat_mapping: dict[str, str] = {}
|
|
412
|
+
|
|
413
|
+
for entity_type, tags_dict in gllm_mapping.items():
|
|
414
|
+
for tag, value in tags_dict.items():
|
|
415
|
+
flat_mapping[tag] = value
|
|
416
|
+
|
|
417
|
+
return flat_mapping
|
|
418
|
+
|
|
419
|
+
def _deanonymize_text(self, text: str) -> str:
|
|
420
|
+
"""Deanonymize a single text string by replacing PII tags with real values.
|
|
421
|
+
|
|
422
|
+
Uses GLLM Privacy's TextAnonymizer.deanonymize() when NER is enabled,
|
|
423
|
+
otherwise falls back to simple string replacement. GLLM Privacy's deanonymize()
|
|
424
|
+
doesn't require entities parameter as they're configured during TextAnalyzer init.
|
|
425
|
+
|
|
426
|
+
Args:
|
|
427
|
+
text: Text containing PII tags (e.g., '<EMAIL_1>', '<PERSON_1>')
|
|
428
|
+
|
|
429
|
+
Returns:
|
|
430
|
+
Text with PII tags replaced by their real values from flat_pii_mapping
|
|
431
|
+
"""
|
|
432
|
+
if self.enable_ner and self.text_anonymizer:
|
|
433
|
+
try:
|
|
434
|
+
restored_text = self.text_anonymizer.deanonymize(text=text)
|
|
435
|
+
return restored_text
|
|
436
|
+
except Exception as e:
|
|
437
|
+
logger.warning(f"GLLM Privacy deanonymization failed: {e}, falling back to simple replacement")
|
|
438
|
+
return self._replace_tags_in_text(text)
|
|
439
|
+
else:
|
|
440
|
+
return self._replace_tags_in_text(text)
|
|
441
|
+
|
|
442
|
+
def _anonymize_text(self, text: str) -> tuple[str, dict[str, str]]:
|
|
443
|
+
"""Anonymize PII in text using a two-phase approach.
|
|
444
|
+
|
|
445
|
+
Phase 1: Masks known PII values using existing flat_pii_mapping (simple replacement).
|
|
446
|
+
Phase 2: Uses NER (if enabled) to detect and mask NEW PII values not in mapping.
|
|
447
|
+
Phase 3: Extracts newly discovered PII from GLLM Privacy's internal state.
|
|
448
|
+
Phase 4-5: Converts and merges new mappings into flat_pii_mapping.
|
|
449
|
+
|
|
450
|
+
Args:
|
|
451
|
+
text: Text that may contain real PII values to be masked
|
|
452
|
+
|
|
453
|
+
Returns:
|
|
454
|
+
Tuple of (anonymized_text, updated_flat_pii_mapping including new discoveries)
|
|
455
|
+
"""
|
|
456
|
+
anonymized = self._mask_with_existing_mapping(text)
|
|
457
|
+
|
|
458
|
+
if self.enable_ner and self.text_anonymizer:
|
|
459
|
+
try:
|
|
460
|
+
result = self.text_anonymizer.anonymize(text=anonymized, entities=DEFAULT_SUPPORTED_ENTITIES)
|
|
461
|
+
if hasattr(result, "text"):
|
|
462
|
+
anonymized = result.text
|
|
463
|
+
else:
|
|
464
|
+
anonymized = result
|
|
465
|
+
|
|
466
|
+
gllm_mapping = self.text_anonymizer.deanonymizer_mapping
|
|
467
|
+
if gllm_mapping:
|
|
468
|
+
new_flat_mapping = self._convert_gllm_to_flat_format(gllm_mapping)
|
|
469
|
+
self.flat_pii_mapping.update(new_flat_mapping)
|
|
470
|
+
except Exception as e:
|
|
471
|
+
logger.warning(f"GLLM Privacy anonymization failed: {e}, continuing with masked text")
|
|
472
|
+
|
|
473
|
+
return anonymized, self.flat_pii_mapping
|
|
474
|
+
|
|
475
|
+
def _anonymize_dict(self, data: dict[str, Any]) -> tuple[dict[str, Any], dict[str, str]]:
|
|
476
|
+
"""Anonymize PII in dictionary recursively.
|
|
477
|
+
|
|
478
|
+
Processes string values with _anonymize_text(), recursively handles nested dicts,
|
|
479
|
+
and processes list items. Non-string/dict/list values are preserved as-is.
|
|
480
|
+
|
|
481
|
+
Args:
|
|
482
|
+
data: Dictionary that may contain PII values
|
|
483
|
+
|
|
484
|
+
Returns:
|
|
485
|
+
Tuple of (anonymized_dict, updated_mapping)
|
|
486
|
+
"""
|
|
487
|
+
anonymized = {}
|
|
488
|
+
|
|
489
|
+
for key, value in data.items():
|
|
490
|
+
if isinstance(value, str):
|
|
491
|
+
anonymized_value, _ = self._anonymize_text(value)
|
|
492
|
+
anonymized[key] = anonymized_value
|
|
493
|
+
elif isinstance(value, dict):
|
|
494
|
+
anonymized[key], _ = self._anonymize_dict(value)
|
|
495
|
+
elif isinstance(value, list):
|
|
496
|
+
anonymized[key] = [self._anonymize_text(item)[0] if isinstance(item, str) else item for item in value]
|
|
497
|
+
else:
|
|
498
|
+
anonymized[key] = value
|
|
499
|
+
|
|
500
|
+
return anonymized, self.flat_pii_mapping
|
|
501
|
+
|
|
502
|
+
def _mask_with_existing_mapping(self, text: str) -> str:
|
|
503
|
+
"""Mask PII using existing flat mapping.
|
|
504
|
+
|
|
505
|
+
Iterates through mapping in reverse order of value length to handle overlapping
|
|
506
|
+
PII values correctly (longer values are replaced first).
|
|
507
|
+
|
|
508
|
+
Args:
|
|
509
|
+
text: Text to mask
|
|
510
|
+
|
|
511
|
+
Returns:
|
|
512
|
+
Text with known PII values replaced by tags
|
|
513
|
+
"""
|
|
514
|
+
masked = text
|
|
515
|
+
for tag, value in sorted(self.flat_pii_mapping.items(), key=lambda x: len(x[1]), reverse=True):
|
|
516
|
+
if value in masked:
|
|
517
|
+
masked = masked.replace(value, tag)
|
|
518
|
+
return masked
|
|
519
|
+
|
|
520
|
+
def _replace_tags_in_text(self, text: str) -> str:
|
|
521
|
+
"""Replace PII tags with real values in text using flat mapping.
|
|
522
|
+
|
|
523
|
+
Args:
|
|
524
|
+
text: Text containing PII tags
|
|
525
|
+
|
|
526
|
+
Returns:
|
|
527
|
+
Text with tags replaced by values
|
|
528
|
+
"""
|
|
529
|
+
replaced = text
|
|
530
|
+
for tag, value in self.flat_pii_mapping.items():
|
|
531
|
+
if tag in replaced:
|
|
532
|
+
replaced = replaced.replace(tag, value)
|
|
533
|
+
return replaced
|
|
534
|
+
|
|
535
|
+
def _process_value(self, value: Any, operation: Operation) -> Any:
|
|
536
|
+
"""Process a value recursively based on operation type.
|
|
537
|
+
|
|
538
|
+
Args:
|
|
539
|
+
value: Value to process
|
|
540
|
+
operation: Operation enum value (ANONYMIZE or DEANONYMIZE)
|
|
541
|
+
|
|
542
|
+
Returns:
|
|
543
|
+
Processed value
|
|
544
|
+
"""
|
|
545
|
+
if isinstance(value, str):
|
|
546
|
+
if operation == Operation.DEANONYMIZE:
|
|
547
|
+
return self._deanonymize_text(value)
|
|
548
|
+
else:
|
|
549
|
+
return self._anonymize_text(value)[0]
|
|
550
|
+
elif isinstance(value, dict):
|
|
551
|
+
return {k: self._process_value(v, operation) for k, v in value.items()}
|
|
552
|
+
elif isinstance(value, list):
|
|
553
|
+
return [self._process_value(item, operation) for item in value]
|
|
554
|
+
else:
|
|
555
|
+
return value
|