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,504 @@
|
|
|
1
|
+
"""LiveKit-based audio session (local-only).
|
|
2
|
+
|
|
3
|
+
This session:
|
|
4
|
+
- connects to a LiveKit server/room
|
|
5
|
+
- uses OpenAI STT/TTS via `livekit-plugins-openai` when enabled
|
|
6
|
+
- routes final user transcripts into an AIP agent
|
|
7
|
+
- speaks the agent reply back into the room
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import asyncio
|
|
13
|
+
import importlib
|
|
14
|
+
import importlib.util
|
|
15
|
+
import inspect
|
|
16
|
+
import os
|
|
17
|
+
import uuid
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from aip_agents.audio_interface.audio_agent_adapter import AIPAudioAgentAdapter
|
|
21
|
+
from aip_agents.audio_interface.config import AudioSessionConfig, LiveKitConfig
|
|
22
|
+
from aip_agents.audio_interface.errors import AudioConfigError, AudioSessionUnavailableError
|
|
23
|
+
from aip_agents.utils.logger import get_logger
|
|
24
|
+
|
|
25
|
+
logger = get_logger(__name__)
|
|
26
|
+
|
|
27
|
+
_INTERRUPTED_SPEECH_NOTE = (
|
|
28
|
+
"[aip-audio] NOTE: speech was interrupted. If you are not hearing TTS, mute your mic after speaking "
|
|
29
|
+
"(or reduce background noise)."
|
|
30
|
+
)
|
|
31
|
+
_INTERRUPTED_SPEECH_RETRY_DELAY_S = 0.35
|
|
32
|
+
_TRANSCRIPT_DEBOUNCE_S = 0.6
|
|
33
|
+
_TOOL_WAIT_MESSAGE_DEFAULT = "Please wait a moment while I check that."
|
|
34
|
+
_TOOL_WAIT_DELAY_S_DEFAULT = 0.0
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _ensure_livekit_available() -> None:
|
|
38
|
+
if importlib.util.find_spec("livekit") is None or importlib.util.find_spec("livekit.agents") is None:
|
|
39
|
+
raise AudioSessionUnavailableError(
|
|
40
|
+
"LiveKit audio provider is unavailable. Install with: "
|
|
41
|
+
"pip install 'aip-agents[audio]' (or 'poetry install -E audio' in dev)"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _ensure_livekit_openai_plugin_available() -> None:
|
|
46
|
+
if importlib.util.find_spec("livekit.plugins.openai") is None:
|
|
47
|
+
raise AudioSessionUnavailableError(
|
|
48
|
+
"OpenAI STT/TTS requires livekit-plugins-openai. Install with: "
|
|
49
|
+
"pip install 'aip-agents[audio]' (or 'poetry install -E audio' in dev)"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class LiveKitAudioSession:
|
|
54
|
+
"""Manage a LiveKit room connection and stream audio to/from an agent."""
|
|
55
|
+
|
|
56
|
+
def __init__(self, config: AudioSessionConfig) -> None:
|
|
57
|
+
"""Create a session for the given `AudioSessionConfig`."""
|
|
58
|
+
provider = (config.provider or "livekit").strip().lower()
|
|
59
|
+
if provider != "livekit":
|
|
60
|
+
raise AudioConfigError(
|
|
61
|
+
f"LiveKitAudioSession requires provider='livekit' (got provider={config.provider!r})"
|
|
62
|
+
)
|
|
63
|
+
self._config = config
|
|
64
|
+
self._session: Any | None = None
|
|
65
|
+
self._close_event = asyncio.Event()
|
|
66
|
+
self._room: Any | None = None
|
|
67
|
+
self._http_context_mod: Any | None = None
|
|
68
|
+
self._pending_tasks: set[asyncio.Task[Any]] = set()
|
|
69
|
+
self._adapter: AIPAudioAgentAdapter | None = None
|
|
70
|
+
self._room_name: str | None = None
|
|
71
|
+
self._pending_transcripts: list[str] = []
|
|
72
|
+
self._flush_task: asyncio.Task[Any] | None = None
|
|
73
|
+
# Serialize agent turns. This avoids concurrent calls into the agent/checkpointer
|
|
74
|
+
# which can corrupt conversation state and produce "generic" responses.
|
|
75
|
+
self._turn_lock = asyncio.Lock()
|
|
76
|
+
|
|
77
|
+
@staticmethod
|
|
78
|
+
def _debug_enabled() -> bool:
|
|
79
|
+
return os.environ.get("AIP_AUDIO_DEBUG") == "1"
|
|
80
|
+
|
|
81
|
+
def _resolve_livekit_config(self) -> LiveKitConfig:
|
|
82
|
+
cfg = self._config.provider_config
|
|
83
|
+
if isinstance(cfg, LiveKitConfig):
|
|
84
|
+
return cfg
|
|
85
|
+
if isinstance(cfg, dict):
|
|
86
|
+
url = cfg.get("url")
|
|
87
|
+
if not url:
|
|
88
|
+
raise AudioConfigError("livekit.url is required")
|
|
89
|
+
return LiveKitConfig(
|
|
90
|
+
url=str(url),
|
|
91
|
+
api_key=cfg.get("api_key"),
|
|
92
|
+
api_secret=cfg.get("api_secret"),
|
|
93
|
+
room_name=cfg.get("room_name"),
|
|
94
|
+
identity=cfg.get("identity"),
|
|
95
|
+
openai_stt_model=cfg.get("openai_stt_model"),
|
|
96
|
+
openai_tts_model=cfg.get("openai_tts_model"),
|
|
97
|
+
openai_voice=cfg.get("openai_voice"),
|
|
98
|
+
openai_use_realtime_stt=bool(cfg.get("openai_use_realtime_stt", True)),
|
|
99
|
+
openai_api_key=cfg.get("openai_api_key"),
|
|
100
|
+
openai_base_url=cfg.get("openai_base_url"),
|
|
101
|
+
)
|
|
102
|
+
raise AudioConfigError("provider_config must be LiveKitConfig or dict")
|
|
103
|
+
|
|
104
|
+
def _wants_openai_stt_tts(self, lk_cfg: LiveKitConfig) -> bool:
|
|
105
|
+
model_cfg = self._config.model
|
|
106
|
+
if model_cfg is not None and (model_cfg.provider or "").lower() == "openai":
|
|
107
|
+
return True
|
|
108
|
+
return bool(
|
|
109
|
+
lk_cfg.openai_stt_model
|
|
110
|
+
or lk_cfg.openai_tts_model
|
|
111
|
+
or lk_cfg.openai_voice
|
|
112
|
+
or lk_cfg.openai_api_key
|
|
113
|
+
or lk_cfg.openai_base_url
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def _build_openai_stt_tts(self, lk_cfg: LiveKitConfig) -> tuple[Any, Any]:
|
|
117
|
+
_ensure_livekit_openai_plugin_available()
|
|
118
|
+
openai_plugin = importlib.import_module("livekit.plugins.openai")
|
|
119
|
+
|
|
120
|
+
api_key = lk_cfg.resolve_openai_api_key()
|
|
121
|
+
base_url = lk_cfg.openai_base_url
|
|
122
|
+
model_cfg = self._config.model
|
|
123
|
+
|
|
124
|
+
stt_model = lk_cfg.openai_stt_model or "gpt-4o-transcribe"
|
|
125
|
+
tts_model = lk_cfg.openai_tts_model or (model_cfg.model if model_cfg else None) or "gpt-4o-mini-tts"
|
|
126
|
+
voice = lk_cfg.openai_voice or (model_cfg.voice if model_cfg else None) or "echo"
|
|
127
|
+
|
|
128
|
+
STT = getattr(openai_plugin, "STT")
|
|
129
|
+
TTS = getattr(openai_plugin, "TTS")
|
|
130
|
+
|
|
131
|
+
stt_obj = STT(
|
|
132
|
+
model=stt_model,
|
|
133
|
+
api_key=api_key,
|
|
134
|
+
base_url=base_url,
|
|
135
|
+
use_realtime=bool(lk_cfg.openai_use_realtime_stt),
|
|
136
|
+
)
|
|
137
|
+
tts_obj = TTS(
|
|
138
|
+
model=tts_model,
|
|
139
|
+
voice=voice,
|
|
140
|
+
api_key=api_key,
|
|
141
|
+
base_url=base_url,
|
|
142
|
+
)
|
|
143
|
+
return stt_obj, tts_obj
|
|
144
|
+
|
|
145
|
+
async def start(self, aip_agent: Any, *, instructions: str) -> None:
|
|
146
|
+
"""Connect to LiveKit and start the voice pipeline."""
|
|
147
|
+
# Reset close flag in case this instance is restarted.
|
|
148
|
+
self._close_event.clear()
|
|
149
|
+
_ensure_livekit_available()
|
|
150
|
+
|
|
151
|
+
lk_cfg = self._resolve_livekit_config()
|
|
152
|
+
rtc, room_options_cls, agent_cls, agent_session_cls, access_token_cls, video_grants_cls = (
|
|
153
|
+
self._resolve_livekit_runtime()
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
room_name, token = self._build_join_token(lk_cfg, access_token_cls, video_grants_cls)
|
|
157
|
+
|
|
158
|
+
room = rtc.Room()
|
|
159
|
+
await room.connect(lk_cfg.url, token)
|
|
160
|
+
self._room = room
|
|
161
|
+
|
|
162
|
+
session_kwargs = self._build_agent_session_kwargs(lk_cfg)
|
|
163
|
+
|
|
164
|
+
session: Any = agent_session_cls(**session_kwargs)
|
|
165
|
+
lk_agent: Any = agent_cls(instructions=instructions, llm=None)
|
|
166
|
+
|
|
167
|
+
# Initialize our adapter/handlers before starting the LiveKit session so that
|
|
168
|
+
# any early text input is routed to the AIP agent (instead of LiveKit's default
|
|
169
|
+
# text handler, which expects an internal LLM and will throw).
|
|
170
|
+
self._register_session_handlers(session=session, aip_agent=aip_agent, room_name=room_name)
|
|
171
|
+
|
|
172
|
+
# Route LiveKit Meet chat input into the AIP agent (and avoid LiveKit generating its own replies).
|
|
173
|
+
# This keeps text messages working even though we pass `llm=None` to LiveKit.
|
|
174
|
+
room_io = importlib.import_module("livekit.agents.voice.room_io")
|
|
175
|
+
text_input_options_cls = getattr(room_io, "TextInputOptions", None)
|
|
176
|
+
|
|
177
|
+
async def _text_input_cb(_: Any, ev: Any) -> None:
|
|
178
|
+
try:
|
|
179
|
+
session.interrupt()
|
|
180
|
+
except Exception:
|
|
181
|
+
# Best-effort; interruption is not critical.
|
|
182
|
+
pass
|
|
183
|
+
|
|
184
|
+
text = getattr(ev, "text", "") or ""
|
|
185
|
+
text = str(text).strip()
|
|
186
|
+
if text:
|
|
187
|
+
await self._handle_final_transcript(session=session, text=text)
|
|
188
|
+
|
|
189
|
+
await session.start(
|
|
190
|
+
lk_agent,
|
|
191
|
+
room=room,
|
|
192
|
+
room_options=room_options_cls(
|
|
193
|
+
text_input=(
|
|
194
|
+
text_input_options_cls(text_input_cb=_text_input_cb) if callable(text_input_options_cls) else False
|
|
195
|
+
),
|
|
196
|
+
audio_input=bool(self._config.io.input_enabled),
|
|
197
|
+
audio_output=bool(self._config.io.output_enabled),
|
|
198
|
+
),
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
self._session = session
|
|
202
|
+
# Handlers already registered above.
|
|
203
|
+
|
|
204
|
+
def _resolve_livekit_runtime(self) -> tuple[Any, Any, Any, Any, Any, Any]:
|
|
205
|
+
rtc = importlib.import_module("livekit.rtc")
|
|
206
|
+
agents_mod = importlib.import_module("livekit.agents")
|
|
207
|
+
room_io = importlib.import_module("livekit.agents.voice.room_io")
|
|
208
|
+
api_mod = importlib.import_module("livekit.api")
|
|
209
|
+
|
|
210
|
+
agent_cls = getattr(agents_mod, "Agent", None)
|
|
211
|
+
agent_session_cls = getattr(agents_mod, "AgentSession", None)
|
|
212
|
+
access_token_cls = getattr(api_mod, "AccessToken", None)
|
|
213
|
+
video_grants_cls = getattr(api_mod, "VideoGrants", None)
|
|
214
|
+
room_options_cls = getattr(room_io, "RoomOptions", None)
|
|
215
|
+
|
|
216
|
+
if (
|
|
217
|
+
agent_cls is None
|
|
218
|
+
or agent_session_cls is None
|
|
219
|
+
or access_token_cls is None
|
|
220
|
+
or video_grants_cls is None
|
|
221
|
+
or room_options_cls is None
|
|
222
|
+
):
|
|
223
|
+
raise AudioSessionUnavailableError("LiveKit packages installed but missing expected symbols")
|
|
224
|
+
|
|
225
|
+
return rtc, room_options_cls, agent_cls, agent_session_cls, access_token_cls, video_grants_cls
|
|
226
|
+
|
|
227
|
+
def _build_join_token(self, lk_cfg: LiveKitConfig, access_token_cls: Any, video_grants_cls: Any) -> tuple[str, str]:
|
|
228
|
+
api_key = lk_cfg.resolve_api_key()
|
|
229
|
+
api_secret = lk_cfg.resolve_api_secret()
|
|
230
|
+
|
|
231
|
+
room_name = lk_cfg.room_name or f"aip-audio-{uuid.uuid4().hex[:8]}"
|
|
232
|
+
identity = lk_cfg.identity or "aip-agent"
|
|
233
|
+
|
|
234
|
+
token = (
|
|
235
|
+
access_token_cls(api_key=api_key, api_secret=api_secret)
|
|
236
|
+
.with_identity(identity)
|
|
237
|
+
.with_grants(video_grants_cls(room_join=True, room=room_name, agent=True))
|
|
238
|
+
.to_jwt()
|
|
239
|
+
)
|
|
240
|
+
return room_name, token
|
|
241
|
+
|
|
242
|
+
def _build_agent_session_kwargs(self, lk_cfg: LiveKitConfig) -> dict[str, Any]:
|
|
243
|
+
session_kwargs: dict[str, Any] = {}
|
|
244
|
+
if not self._wants_openai_stt_tts(lk_cfg):
|
|
245
|
+
return session_kwargs
|
|
246
|
+
|
|
247
|
+
self._ensure_http_context_initialized()
|
|
248
|
+
stt_obj, tts_obj = self._build_openai_stt_tts(lk_cfg)
|
|
249
|
+
session_kwargs.update({"stt": stt_obj, "tts": tts_obj, "llm": None})
|
|
250
|
+
return session_kwargs
|
|
251
|
+
|
|
252
|
+
def _ensure_http_context_initialized(self) -> None:
|
|
253
|
+
# livekit-plugins-openai (realtime STT) uses livekit.agents.utils.http_context
|
|
254
|
+
# which is normally initialized by the worker/job runtime.
|
|
255
|
+
# Since we're running standalone, initialize it here.
|
|
256
|
+
http_context = importlib.import_module("livekit.agents.utils.http_context")
|
|
257
|
+
new_ctx = getattr(http_context, "_new_session_ctx", None)
|
|
258
|
+
if callable(new_ctx):
|
|
259
|
+
new_ctx()
|
|
260
|
+
self._http_context_mod = http_context
|
|
261
|
+
|
|
262
|
+
def _register_session_handlers(self, *, session: Any, aip_agent: Any, room_name: str) -> None:
|
|
263
|
+
self._adapter = AIPAudioAgentAdapter(aip_agent)
|
|
264
|
+
self._room_name = room_name
|
|
265
|
+
self._pending_transcripts = []
|
|
266
|
+
self._flush_task = None
|
|
267
|
+
|
|
268
|
+
@session.on("close")
|
|
269
|
+
def _on_close(_: Any) -> None:
|
|
270
|
+
self._close_event.set()
|
|
271
|
+
|
|
272
|
+
@session.on("user_input_transcribed")
|
|
273
|
+
def _on_transcript(ev: Any) -> None:
|
|
274
|
+
self._on_user_input_transcribed(session=session, ev=ev)
|
|
275
|
+
|
|
276
|
+
def _on_user_input_transcribed(self, *, session: Any, ev: Any) -> None:
|
|
277
|
+
if not getattr(ev, "is_final", False):
|
|
278
|
+
return
|
|
279
|
+
|
|
280
|
+
text = getattr(ev, "transcript", "") or ""
|
|
281
|
+
if not str(text).strip():
|
|
282
|
+
return
|
|
283
|
+
|
|
284
|
+
self._pending_transcripts.append(str(text).strip())
|
|
285
|
+
|
|
286
|
+
if self._flush_task is not None and not self._flush_task.done():
|
|
287
|
+
self._flush_task.cancel()
|
|
288
|
+
|
|
289
|
+
task = asyncio.create_task(self._flush_pending_transcripts(session=session))
|
|
290
|
+
self._flush_task = task
|
|
291
|
+
self._pending_tasks.add(task)
|
|
292
|
+
|
|
293
|
+
def _done(t: asyncio.Task[Any]) -> None:
|
|
294
|
+
self._pending_tasks.discard(t)
|
|
295
|
+
|
|
296
|
+
task.add_done_callback(_done)
|
|
297
|
+
|
|
298
|
+
async def _flush_pending_transcripts(self, *, session: Any) -> None:
|
|
299
|
+
"""Wait briefly for STT to finish segmenting, then send one combined turn to the agent."""
|
|
300
|
+
try:
|
|
301
|
+
await asyncio.sleep(_TRANSCRIPT_DEBOUNCE_S)
|
|
302
|
+
except asyncio.CancelledError:
|
|
303
|
+
# Cancellation is expected when multiple final transcripts arrive quickly.
|
|
304
|
+
# Re-raise to preserve cancellation semantics.
|
|
305
|
+
raise
|
|
306
|
+
except Exception:
|
|
307
|
+
logger.exception("Unexpected error in flush task")
|
|
308
|
+
return
|
|
309
|
+
|
|
310
|
+
combined = " ".join(self._pending_transcripts).strip()
|
|
311
|
+
self._pending_transcripts.clear()
|
|
312
|
+
if combined:
|
|
313
|
+
await self._handle_final_transcript(session=session, text=combined)
|
|
314
|
+
|
|
315
|
+
async def _handle_final_transcript(self, *, session: Any, text: str) -> None:
|
|
316
|
+
async with self._turn_lock:
|
|
317
|
+
room_name = self._room_name or "unknown"
|
|
318
|
+
debug = self._debug_enabled()
|
|
319
|
+
|
|
320
|
+
if debug:
|
|
321
|
+
print(f"[aip-audio] room={room_name} user: {text}")
|
|
322
|
+
print(f"[aip-audio] room={room_name} user_repr={text!r} len={len(text)}")
|
|
323
|
+
|
|
324
|
+
if self._adapter is None:
|
|
325
|
+
return
|
|
326
|
+
|
|
327
|
+
final_received = asyncio.Event()
|
|
328
|
+
wait_task: asyncio.Task[Any] | None = None
|
|
329
|
+
turn_started = False
|
|
330
|
+
wait_scheduled = False
|
|
331
|
+
|
|
332
|
+
async def _on_a2a_event(ev: dict[str, Any]) -> None:
|
|
333
|
+
# Add await to satisfy 'async' keyword usage and ensure cooperative scheduling
|
|
334
|
+
await asyncio.sleep(0)
|
|
335
|
+
nonlocal wait_task, turn_started, wait_scheduled
|
|
336
|
+
if self._is_status_update_event(ev) or self._is_tool_call_event(ev):
|
|
337
|
+
turn_started = True
|
|
338
|
+
if not wait_scheduled:
|
|
339
|
+
# Emit at most one wait message per turn.
|
|
340
|
+
wait_task = self._schedule_wait_message(session, final_received, debug)
|
|
341
|
+
wait_scheduled = wait_task is not None
|
|
342
|
+
elif turn_started and self._is_final_response_event(ev):
|
|
343
|
+
# Some streams may include historical final_response events before
|
|
344
|
+
# events belonging to the current turn. Ignore those stale finals.
|
|
345
|
+
final_received.set()
|
|
346
|
+
|
|
347
|
+
reply = await self._adapter.generate_reply(text, prefer_a2a_stream=True, on_a2a_event=_on_a2a_event)
|
|
348
|
+
|
|
349
|
+
final_received.set()
|
|
350
|
+
# Avoid an "identity check" (`is not None`) here; Sonar can incorrectly
|
|
351
|
+
# infer `wait_task` is always None due to the nonlocal assignment.
|
|
352
|
+
if wait_task and not wait_task.done():
|
|
353
|
+
wait_task.cancel()
|
|
354
|
+
|
|
355
|
+
if debug:
|
|
356
|
+
print(f"[aip-audio] room={room_name} agent: {reply}")
|
|
357
|
+
|
|
358
|
+
await self._say_reply_if_enabled(session=session, reply=reply, debug=debug)
|
|
359
|
+
|
|
360
|
+
def _is_tool_call_event(self, ev: dict[str, Any]) -> bool:
|
|
361
|
+
raw_type = ev.get("event_type")
|
|
362
|
+
# Ensure raw_type is not None and has 'value' before accessing it, or use str conversion
|
|
363
|
+
event_type = raw_type.value if hasattr(raw_type, "value") else str(raw_type)
|
|
364
|
+
return event_type == "tool_call"
|
|
365
|
+
|
|
366
|
+
def _is_final_response_event(self, ev: dict[str, Any]) -> bool:
|
|
367
|
+
raw_type = ev.get("event_type")
|
|
368
|
+
event_type = raw_type.value if hasattr(raw_type, "value") else str(raw_type)
|
|
369
|
+
return event_type == "final_response"
|
|
370
|
+
|
|
371
|
+
def _is_status_update_event(self, ev: dict[str, Any]) -> bool:
|
|
372
|
+
raw_type = ev.get("event_type")
|
|
373
|
+
event_type = raw_type.value if hasattr(raw_type, "value") else str(raw_type)
|
|
374
|
+
return event_type == "status_update"
|
|
375
|
+
|
|
376
|
+
def _schedule_wait_message(
|
|
377
|
+
self, session: Any, final_received: asyncio.Event, debug: bool
|
|
378
|
+
) -> asyncio.Task[Any] | None:
|
|
379
|
+
"""Schedule a background task to speak the wait message if the tool takes too long."""
|
|
380
|
+
wait_message = os.environ.get("AIP_AUDIO_TOOL_WAIT_MESSAGE", _TOOL_WAIT_MESSAGE_DEFAULT).strip()
|
|
381
|
+
if not wait_message:
|
|
382
|
+
return None
|
|
383
|
+
|
|
384
|
+
try:
|
|
385
|
+
delay_s = float(os.environ.get("AIP_AUDIO_TOOL_WAIT_DELAY_S", str(_TOOL_WAIT_DELAY_S_DEFAULT)))
|
|
386
|
+
except Exception:
|
|
387
|
+
delay_s = _TOOL_WAIT_DELAY_S_DEFAULT
|
|
388
|
+
|
|
389
|
+
if debug:
|
|
390
|
+
print(f"[aip-audio] scheduling tool-wait message after {delay_s:.2f}s")
|
|
391
|
+
|
|
392
|
+
async def _delayed_say() -> None:
|
|
393
|
+
if delay_s > 0:
|
|
394
|
+
await asyncio.sleep(delay_s)
|
|
395
|
+
if final_received.is_set():
|
|
396
|
+
if debug:
|
|
397
|
+
print("[aip-audio] tool-wait message suppressed (final already received)")
|
|
398
|
+
return
|
|
399
|
+
if debug:
|
|
400
|
+
print("[aip-audio] speaking tool-wait message")
|
|
401
|
+
await self._say_reply_if_enabled(session=session, reply=wait_message, debug=debug)
|
|
402
|
+
|
|
403
|
+
task = asyncio.create_task(_delayed_say())
|
|
404
|
+
self._pending_tasks.add(task)
|
|
405
|
+
task.add_done_callback(lambda t: self._pending_tasks.discard(t))
|
|
406
|
+
return task
|
|
407
|
+
|
|
408
|
+
async def _say_reply_if_enabled(self, *, session: Any, reply: str, debug: bool) -> None:
|
|
409
|
+
if not (self._config.io.output_enabled and reply):
|
|
410
|
+
return
|
|
411
|
+
|
|
412
|
+
handle = session.say(reply)
|
|
413
|
+
if not inspect.isawaitable(handle):
|
|
414
|
+
return
|
|
415
|
+
|
|
416
|
+
await handle
|
|
417
|
+
if debug:
|
|
418
|
+
await self._handle_interrupted_speech(handle, session, reply)
|
|
419
|
+
|
|
420
|
+
async def _handle_interrupted_speech(self, handle: Any, session: Any, reply: str) -> None:
|
|
421
|
+
"""Retry speaking once if interrupted immediately."""
|
|
422
|
+
if not bool(getattr(handle, "interrupted", False)):
|
|
423
|
+
return
|
|
424
|
+
|
|
425
|
+
print(_INTERRUPTED_SPEECH_NOTE)
|
|
426
|
+
# Best-effort retry once; VAD/echo can cause immediate interruption on first attempt.
|
|
427
|
+
await asyncio.sleep(_INTERRUPTED_SPEECH_RETRY_DELAY_S)
|
|
428
|
+
retry_handle = session.say(reply)
|
|
429
|
+
if inspect.isawaitable(retry_handle):
|
|
430
|
+
await retry_handle
|
|
431
|
+
if bool(getattr(retry_handle, "interrupted", False)):
|
|
432
|
+
print(_INTERRUPTED_SPEECH_NOTE)
|
|
433
|
+
|
|
434
|
+
async def stop(self) -> None:
|
|
435
|
+
"""Stop the session and release LiveKit resources."""
|
|
436
|
+
# Cancellation will naturally propagate; this wrapper exists mainly for API symmetry.
|
|
437
|
+
await self._stop_impl()
|
|
438
|
+
|
|
439
|
+
async def _stop_impl(self) -> None:
|
|
440
|
+
"""Best-effort cleanup implementation for `stop()`."""
|
|
441
|
+
await self._cancel_pending_tasks()
|
|
442
|
+
await self._close_session()
|
|
443
|
+
await self._disconnect_room()
|
|
444
|
+
await self._cleanup_http_context()
|
|
445
|
+
|
|
446
|
+
self._close_event.set()
|
|
447
|
+
|
|
448
|
+
# Clear per-session state
|
|
449
|
+
self._adapter = None
|
|
450
|
+
self._room_name = None
|
|
451
|
+
self._pending_transcripts.clear()
|
|
452
|
+
self._flush_task = None
|
|
453
|
+
|
|
454
|
+
async def _cancel_pending_tasks(self) -> None:
|
|
455
|
+
if not self._pending_tasks:
|
|
456
|
+
return
|
|
457
|
+
tasks = tuple(self._pending_tasks)
|
|
458
|
+
for task in tasks:
|
|
459
|
+
task.cancel()
|
|
460
|
+
try:
|
|
461
|
+
await asyncio.gather(*tasks, return_exceptions=True)
|
|
462
|
+
except asyncio.CancelledError:
|
|
463
|
+
raise
|
|
464
|
+
except Exception:
|
|
465
|
+
logger.warning("Error cancelling pending tasks", exc_info=True)
|
|
466
|
+
finally:
|
|
467
|
+
self._pending_tasks.clear()
|
|
468
|
+
|
|
469
|
+
async def _close_session(self) -> None:
|
|
470
|
+
if self._session is None:
|
|
471
|
+
return
|
|
472
|
+
try:
|
|
473
|
+
await self._session.aclose()
|
|
474
|
+
except asyncio.CancelledError:
|
|
475
|
+
raise
|
|
476
|
+
except Exception:
|
|
477
|
+
logger.warning("Error closing LiveKit session", exc_info=True)
|
|
478
|
+
finally:
|
|
479
|
+
self._session = None
|
|
480
|
+
|
|
481
|
+
async def _disconnect_room(self) -> None:
|
|
482
|
+
if self._room is None:
|
|
483
|
+
return
|
|
484
|
+
try:
|
|
485
|
+
await self._room.disconnect()
|
|
486
|
+
except asyncio.CancelledError:
|
|
487
|
+
raise
|
|
488
|
+
except Exception:
|
|
489
|
+
logger.warning("Error disconnecting LiveKit room", exc_info=True)
|
|
490
|
+
finally:
|
|
491
|
+
self._room = None
|
|
492
|
+
|
|
493
|
+
async def _cleanup_http_context(self) -> None:
|
|
494
|
+
try:
|
|
495
|
+
if self._http_context_mod is not None and hasattr(self._http_context_mod, "_close_http_ctx"):
|
|
496
|
+
await self._http_context_mod._close_http_ctx()
|
|
497
|
+
except Exception:
|
|
498
|
+
logger.warning("Error closing http context", exc_info=True)
|
|
499
|
+
finally:
|
|
500
|
+
self._http_context_mod = None
|
|
501
|
+
|
|
502
|
+
async def wait_closed(self) -> None:
|
|
503
|
+
"""Wait until the LiveKit session signals close."""
|
|
504
|
+
await self._close_event.wait()
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Adapter over gllm_inference LiveKitRealtimeSession.
|
|
2
|
+
|
|
3
|
+
This implementation ports the gllm-inference realtime session API to the local
|
|
4
|
+
audio session interface used by `aip_agents.audio_interface`.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import importlib
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from aip_agents.audio_interface.config import AudioSessionConfig, LiveKitConfig
|
|
14
|
+
from aip_agents.audio_interface.errors import AudioConfigError, AudioSessionUnavailableError
|
|
15
|
+
from aip_agents.utils.logger import get_logger
|
|
16
|
+
|
|
17
|
+
logger = get_logger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _resolve_gllm_livekit_realtime_cls() -> type[Any]:
|
|
21
|
+
"""Resolve gllm-inference LiveKitRealtimeSession and validate base interface."""
|
|
22
|
+
try:
|
|
23
|
+
realtime_mod = importlib.import_module("gllm_inference.realtime_session")
|
|
24
|
+
base_mod = importlib.import_module("gllm_inference.realtime_session.realtime_session")
|
|
25
|
+
except Exception as exc:
|
|
26
|
+
raise AudioSessionUnavailableError(
|
|
27
|
+
"gllm-inference realtime_session modules are unavailable. "
|
|
28
|
+
"Install/upgrade gllm-inference with livekit support."
|
|
29
|
+
) from exc
|
|
30
|
+
|
|
31
|
+
livekit_cls = getattr(realtime_mod, "LiveKitRealtimeSession", None)
|
|
32
|
+
base_cls = getattr(base_mod, "BaseRealtimeSession", None)
|
|
33
|
+
|
|
34
|
+
if livekit_cls is None:
|
|
35
|
+
raise AudioSessionUnavailableError(
|
|
36
|
+
"Installed gllm-inference does not expose LiveKitRealtimeSession. "
|
|
37
|
+
"Upgrade gllm-inference to a version that includes "
|
|
38
|
+
"`gllm_inference.realtime_session.LiveKitRealtimeSession`."
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
if base_cls is not None and not issubclass(livekit_cls, base_cls):
|
|
42
|
+
raise AudioSessionUnavailableError("LiveKitRealtimeSession does not implement BaseRealtimeSession")
|
|
43
|
+
|
|
44
|
+
return livekit_cls
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class LiveKitRealtimeAudioSession:
|
|
48
|
+
"""Bridge class for provider=`livekit_realtime`."""
|
|
49
|
+
|
|
50
|
+
def __init__(self, config: AudioSessionConfig) -> None:
|
|
51
|
+
"""Initialize the LiveKit realtime audio session wrapper.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
config (AudioSessionConfig): Session configuration including provider and provider config.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
None: This initializer returns None.
|
|
58
|
+
"""
|
|
59
|
+
provider = (config.provider or "").strip().lower()
|
|
60
|
+
if provider != "livekit_realtime":
|
|
61
|
+
raise AudioConfigError(
|
|
62
|
+
f"LiveKitRealtimeAudioSession requires provider='livekit_realtime' "
|
|
63
|
+
f"(got provider={config.provider!r})"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
self._config = config
|
|
67
|
+
self._close_event = asyncio.Event()
|
|
68
|
+
self._session: Any | None = None
|
|
69
|
+
self._run_task: asyncio.Task[Any] | None = None
|
|
70
|
+
|
|
71
|
+
def _resolve_livekit_config(self) -> LiveKitConfig:
|
|
72
|
+
cfg = self._config.provider_config
|
|
73
|
+
if isinstance(cfg, LiveKitConfig):
|
|
74
|
+
return cfg
|
|
75
|
+
if isinstance(cfg, dict):
|
|
76
|
+
url = cfg.get("url")
|
|
77
|
+
if not url:
|
|
78
|
+
raise AudioConfigError("livekit_realtime.url is required")
|
|
79
|
+
return LiveKitConfig(
|
|
80
|
+
url=str(url),
|
|
81
|
+
api_key=cfg.get("api_key"),
|
|
82
|
+
api_secret=cfg.get("api_secret"),
|
|
83
|
+
room_name=cfg.get("room_name"),
|
|
84
|
+
identity=cfg.get("identity"),
|
|
85
|
+
)
|
|
86
|
+
raise AudioConfigError("provider_config must be LiveKitConfig or dict")
|
|
87
|
+
|
|
88
|
+
async def start(self, aip_agent: Any, *, instructions: str) -> None:
|
|
89
|
+
"""Start gllm-inference LiveKitRealtimeSession in a background task.
|
|
90
|
+
|
|
91
|
+
Note:
|
|
92
|
+
This provider adapts the transport/session layer only. The `aip_agent`
|
|
93
|
+
and `instructions` parameters are currently ignored by the underlying
|
|
94
|
+
gllm-inference LiveKit realtime session API.
|
|
95
|
+
"""
|
|
96
|
+
del aip_agent, instructions
|
|
97
|
+
|
|
98
|
+
if self._run_task is not None and not self._run_task.done():
|
|
99
|
+
raise AudioConfigError("LiveKitRealtimeAudioSession is already started")
|
|
100
|
+
|
|
101
|
+
lk_cfg = self._resolve_livekit_config()
|
|
102
|
+
session_cls = _resolve_gllm_livekit_realtime_cls()
|
|
103
|
+
self._close_event.clear()
|
|
104
|
+
|
|
105
|
+
self._session = session_cls(
|
|
106
|
+
url=lk_cfg.url,
|
|
107
|
+
api_key=lk_cfg.resolve_api_key(),
|
|
108
|
+
api_secret=lk_cfg.resolve_api_secret(),
|
|
109
|
+
room_name=lk_cfg.room_name,
|
|
110
|
+
identity=lk_cfg.identity,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
async def _run() -> None:
|
|
114
|
+
try:
|
|
115
|
+
await self._session.start()
|
|
116
|
+
finally:
|
|
117
|
+
self._close_event.set()
|
|
118
|
+
|
|
119
|
+
self._run_task = asyncio.create_task(_run())
|
|
120
|
+
await asyncio.sleep(0)
|
|
121
|
+
if self._run_task.done():
|
|
122
|
+
err = self._run_task.exception()
|
|
123
|
+
if err is not None:
|
|
124
|
+
raise err
|
|
125
|
+
|
|
126
|
+
async def stop(self) -> None:
|
|
127
|
+
"""Stop background run task."""
|
|
128
|
+
task = self._run_task
|
|
129
|
+
if task is None:
|
|
130
|
+
self._close_event.set()
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
if not task.done():
|
|
134
|
+
task.cancel()
|
|
135
|
+
await asyncio.gather(task, return_exceptions=True)
|
|
136
|
+
self._run_task = None
|
|
137
|
+
self._session = None
|
|
138
|
+
self._close_event.set()
|
|
139
|
+
|
|
140
|
+
async def wait_closed(self) -> None:
|
|
141
|
+
"""Wait for session termination."""
|
|
142
|
+
await self._close_event.wait()
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Factory for transport-specific audio sessions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Awaitable
|
|
6
|
+
from typing import Any, Protocol
|
|
7
|
+
|
|
8
|
+
from aip_agents.audio_interface.config import AudioSessionConfig
|
|
9
|
+
from aip_agents.audio_interface.errors import AudioConfigError
|
|
10
|
+
from aip_agents.audio_interface.livekit_audio_session import LiveKitAudioSession
|
|
11
|
+
from aip_agents.audio_interface.livekit_realtime_audio_session import LiveKitRealtimeAudioSession
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AudioSession(Protocol):
|
|
15
|
+
"""Minimal transport session contract for audio providers."""
|
|
16
|
+
|
|
17
|
+
def start(self, aip_agent: Any, *, instructions: str) -> Awaitable[None]:
|
|
18
|
+
"""Start the session."""
|
|
19
|
+
|
|
20
|
+
def stop(self) -> Awaitable[None]:
|
|
21
|
+
"""Stop the session and cleanup resources."""
|
|
22
|
+
|
|
23
|
+
def wait_closed(self) -> Awaitable[None]:
|
|
24
|
+
"""Wait until the remote session closes."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def create_audio_session(config: AudioSessionConfig) -> AudioSession:
|
|
28
|
+
"""Create an audio transport session for the configured provider.
|
|
29
|
+
|
|
30
|
+
This is the single entry point for "one interface, many implementations".
|
|
31
|
+
"""
|
|
32
|
+
provider = (config.provider or "livekit").strip().lower()
|
|
33
|
+
if provider == "livekit":
|
|
34
|
+
return LiveKitAudioSession(config)
|
|
35
|
+
if provider == "livekit_realtime":
|
|
36
|
+
return LiveKitRealtimeAudioSession(config)
|
|
37
|
+
|
|
38
|
+
raise AudioConfigError(
|
|
39
|
+
f"Unsupported audio provider '{config.provider}'. Supported providers: livekit, livekit_realtime"
|
|
40
|
+
)
|