aip-agents-binary 0.6.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of aip-agents-binary might be problematic. Click here for more details.
- aip_agents/__init__.py +65 -0
- aip_agents/__init__.pyi +19 -0
- aip_agents/a2a/__init__.py +19 -0
- aip_agents/a2a/__init__.pyi +3 -0
- aip_agents/a2a/server/__init__.py +10 -0
- aip_agents/a2a/server/__init__.pyi +4 -0
- aip_agents/a2a/server/base_executor.py +1086 -0
- aip_agents/a2a/server/base_executor.pyi +73 -0
- aip_agents/a2a/server/google_adk_executor.py +198 -0
- aip_agents/a2a/server/google_adk_executor.pyi +51 -0
- aip_agents/a2a/server/langflow_executor.py +180 -0
- aip_agents/a2a/server/langflow_executor.pyi +43 -0
- aip_agents/a2a/server/langgraph_executor.py +270 -0
- aip_agents/a2a/server/langgraph_executor.pyi +47 -0
- aip_agents/a2a/types.py +232 -0
- aip_agents/a2a/types.pyi +132 -0
- aip_agents/agent/__init__.py +27 -0
- aip_agents/agent/__init__.pyi +9 -0
- aip_agents/agent/base_agent.py +970 -0
- aip_agents/agent/base_agent.pyi +221 -0
- aip_agents/agent/base_langgraph_agent.py +3037 -0
- aip_agents/agent/base_langgraph_agent.pyi +233 -0
- aip_agents/agent/google_adk_agent.py +926 -0
- aip_agents/agent/google_adk_agent.pyi +141 -0
- aip_agents/agent/google_adk_constants.py +6 -0
- aip_agents/agent/google_adk_constants.pyi +3 -0
- aip_agents/agent/hitl/__init__.py +24 -0
- aip_agents/agent/hitl/__init__.pyi +6 -0
- aip_agents/agent/hitl/config.py +28 -0
- aip_agents/agent/hitl/config.pyi +15 -0
- aip_agents/agent/hitl/langgraph_hitl_mixin.py +515 -0
- aip_agents/agent/hitl/langgraph_hitl_mixin.pyi +42 -0
- aip_agents/agent/hitl/manager.py +532 -0
- aip_agents/agent/hitl/manager.pyi +200 -0
- aip_agents/agent/hitl/models.py +18 -0
- aip_agents/agent/hitl/models.pyi +3 -0
- aip_agents/agent/hitl/prompt/__init__.py +9 -0
- aip_agents/agent/hitl/prompt/__init__.pyi +4 -0
- aip_agents/agent/hitl/prompt/base.py +42 -0
- aip_agents/agent/hitl/prompt/base.pyi +24 -0
- aip_agents/agent/hitl/prompt/deferred.py +73 -0
- aip_agents/agent/hitl/prompt/deferred.pyi +30 -0
- aip_agents/agent/hitl/registry.py +149 -0
- aip_agents/agent/hitl/registry.pyi +101 -0
- aip_agents/agent/interface.py +138 -0
- aip_agents/agent/interface.pyi +81 -0
- aip_agents/agent/interfaces.py +65 -0
- aip_agents/agent/interfaces.pyi +44 -0
- aip_agents/agent/langflow_agent.py +464 -0
- aip_agents/agent/langflow_agent.pyi +133 -0
- aip_agents/agent/langgraph_memory_enhancer_agent.py +767 -0
- aip_agents/agent/langgraph_memory_enhancer_agent.pyi +50 -0
- aip_agents/agent/langgraph_react_agent.py +2856 -0
- aip_agents/agent/langgraph_react_agent.pyi +170 -0
- aip_agents/agent/system_instruction_context.py +34 -0
- aip_agents/agent/system_instruction_context.pyi +13 -0
- aip_agents/clients/__init__.py +10 -0
- aip_agents/clients/__init__.pyi +4 -0
- aip_agents/clients/langflow/__init__.py +10 -0
- aip_agents/clients/langflow/__init__.pyi +4 -0
- aip_agents/clients/langflow/client.py +477 -0
- aip_agents/clients/langflow/client.pyi +140 -0
- aip_agents/clients/langflow/types.py +18 -0
- aip_agents/clients/langflow/types.pyi +7 -0
- aip_agents/constants.py +23 -0
- aip_agents/constants.pyi +7 -0
- aip_agents/credentials/manager.py +132 -0
- aip_agents/examples/__init__.py +5 -0
- aip_agents/examples/__init__.pyi +0 -0
- aip_agents/examples/compare_streaming_client.py +783 -0
- aip_agents/examples/compare_streaming_client.pyi +48 -0
- aip_agents/examples/compare_streaming_server.py +142 -0
- aip_agents/examples/compare_streaming_server.pyi +18 -0
- aip_agents/examples/hello_world_a2a_google_adk_client.py +49 -0
- aip_agents/examples/hello_world_a2a_google_adk_client.pyi +9 -0
- aip_agents/examples/hello_world_a2a_google_adk_client_agent.py +48 -0
- aip_agents/examples/hello_world_a2a_google_adk_client_agent.pyi +9 -0
- aip_agents/examples/hello_world_a2a_google_adk_client_streaming.py +60 -0
- aip_agents/examples/hello_world_a2a_google_adk_client_streaming.pyi +9 -0
- aip_agents/examples/hello_world_a2a_google_adk_server.py +79 -0
- aip_agents/examples/hello_world_a2a_google_adk_server.pyi +15 -0
- aip_agents/examples/hello_world_a2a_langchain_client.py +39 -0
- aip_agents/examples/hello_world_a2a_langchain_client.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langchain_client_agent.py +39 -0
- aip_agents/examples/hello_world_a2a_langchain_client_agent.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langchain_client_lm_invoker.py +37 -0
- aip_agents/examples/hello_world_a2a_langchain_client_lm_invoker.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langchain_client_streaming.py +41 -0
- aip_agents/examples/hello_world_a2a_langchain_client_streaming.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langchain_reference_client_streaming.py +60 -0
- aip_agents/examples/hello_world_a2a_langchain_reference_client_streaming.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langchain_reference_server.py +105 -0
- aip_agents/examples/hello_world_a2a_langchain_reference_server.pyi +15 -0
- aip_agents/examples/hello_world_a2a_langchain_server.py +79 -0
- aip_agents/examples/hello_world_a2a_langchain_server.pyi +15 -0
- aip_agents/examples/hello_world_a2a_langchain_server_lm_invoker.py +78 -0
- aip_agents/examples/hello_world_a2a_langchain_server_lm_invoker.pyi +15 -0
- aip_agents/examples/hello_world_a2a_langflow_client.py +83 -0
- aip_agents/examples/hello_world_a2a_langflow_client.pyi +9 -0
- aip_agents/examples/hello_world_a2a_langflow_server.py +82 -0
- aip_agents/examples/hello_world_a2a_langflow_server.pyi +14 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_client.py +73 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_client.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_client_streaming.py +76 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_client_streaming.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_server.py +92 -0
- aip_agents/examples/hello_world_a2a_langgraph_artifact_server.pyi +16 -0
- aip_agents/examples/hello_world_a2a_langgraph_client.py +54 -0
- aip_agents/examples/hello_world_a2a_langgraph_client.pyi +9 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_agent.py +54 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_agent.pyi +9 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_agent_lm_invoker.py +32 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_agent_lm_invoker.pyi +2 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming.py +50 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming.pyi +9 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming_lm_invoker.py +44 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming_lm_invoker.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming_tool_streaming.py +92 -0
- aip_agents/examples/hello_world_a2a_langgraph_client_streaming_tool_streaming.pyi +5 -0
- aip_agents/examples/hello_world_a2a_langgraph_server.py +84 -0
- aip_agents/examples/hello_world_a2a_langgraph_server.pyi +14 -0
- aip_agents/examples/hello_world_a2a_langgraph_server_lm_invoker.py +79 -0
- aip_agents/examples/hello_world_a2a_langgraph_server_lm_invoker.pyi +15 -0
- aip_agents/examples/hello_world_a2a_langgraph_server_tool_streaming.py +132 -0
- aip_agents/examples/hello_world_a2a_langgraph_server_tool_streaming.pyi +15 -0
- aip_agents/examples/hello_world_a2a_mcp_langgraph.py +196 -0
- aip_agents/examples/hello_world_a2a_mcp_langgraph.pyi +48 -0
- aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_client.py +244 -0
- aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_client.pyi +48 -0
- aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_server.py +251 -0
- aip_agents/examples/hello_world_a2a_three_level_agent_hierarchy_server.pyi +45 -0
- aip_agents/examples/hello_world_a2a_with_metadata_langchain_client.py +57 -0
- aip_agents/examples/hello_world_a2a_with_metadata_langchain_client.pyi +5 -0
- aip_agents/examples/hello_world_a2a_with_metadata_langchain_server_lm_invoker.py +80 -0
- aip_agents/examples/hello_world_a2a_with_metadata_langchain_server_lm_invoker.pyi +15 -0
- aip_agents/examples/hello_world_google_adk.py +41 -0
- aip_agents/examples/hello_world_google_adk.pyi +5 -0
- aip_agents/examples/hello_world_google_adk_mcp_http.py +34 -0
- aip_agents/examples/hello_world_google_adk_mcp_http.pyi +5 -0
- aip_agents/examples/hello_world_google_adk_mcp_http_stream.py +40 -0
- aip_agents/examples/hello_world_google_adk_mcp_http_stream.pyi +5 -0
- aip_agents/examples/hello_world_google_adk_mcp_sse.py +44 -0
- aip_agents/examples/hello_world_google_adk_mcp_sse.pyi +5 -0
- aip_agents/examples/hello_world_google_adk_mcp_sse_stream.py +48 -0
- aip_agents/examples/hello_world_google_adk_mcp_sse_stream.pyi +5 -0
- aip_agents/examples/hello_world_google_adk_mcp_stdio.py +44 -0
- aip_agents/examples/hello_world_google_adk_mcp_stdio.pyi +5 -0
- aip_agents/examples/hello_world_google_adk_mcp_stdio_stream.py +48 -0
- aip_agents/examples/hello_world_google_adk_mcp_stdio_stream.pyi +5 -0
- aip_agents/examples/hello_world_google_adk_stream.py +44 -0
- aip_agents/examples/hello_world_google_adk_stream.pyi +5 -0
- aip_agents/examples/hello_world_langchain.py +28 -0
- aip_agents/examples/hello_world_langchain.pyi +5 -0
- aip_agents/examples/hello_world_langchain_lm_invoker.py +15 -0
- aip_agents/examples/hello_world_langchain_lm_invoker.pyi +2 -0
- aip_agents/examples/hello_world_langchain_mcp_http.py +34 -0
- aip_agents/examples/hello_world_langchain_mcp_http.pyi +5 -0
- aip_agents/examples/hello_world_langchain_mcp_http_interactive.py +130 -0
- aip_agents/examples/hello_world_langchain_mcp_http_interactive.pyi +16 -0
- aip_agents/examples/hello_world_langchain_mcp_http_stream.py +42 -0
- aip_agents/examples/hello_world_langchain_mcp_http_stream.pyi +5 -0
- aip_agents/examples/hello_world_langchain_mcp_multi_server.py +155 -0
- aip_agents/examples/hello_world_langchain_mcp_multi_server.pyi +18 -0
- aip_agents/examples/hello_world_langchain_mcp_sse.py +34 -0
- aip_agents/examples/hello_world_langchain_mcp_sse.pyi +5 -0
- aip_agents/examples/hello_world_langchain_mcp_sse_stream.py +40 -0
- aip_agents/examples/hello_world_langchain_mcp_sse_stream.pyi +5 -0
- aip_agents/examples/hello_world_langchain_mcp_stdio.py +30 -0
- aip_agents/examples/hello_world_langchain_mcp_stdio.pyi +5 -0
- aip_agents/examples/hello_world_langchain_mcp_stdio_stream.py +41 -0
- aip_agents/examples/hello_world_langchain_mcp_stdio_stream.pyi +5 -0
- aip_agents/examples/hello_world_langchain_stream.py +36 -0
- aip_agents/examples/hello_world_langchain_stream.pyi +5 -0
- aip_agents/examples/hello_world_langchain_stream_lm_invoker.py +39 -0
- aip_agents/examples/hello_world_langchain_stream_lm_invoker.pyi +5 -0
- aip_agents/examples/hello_world_langflow_agent.py +163 -0
- aip_agents/examples/hello_world_langflow_agent.pyi +35 -0
- aip_agents/examples/hello_world_langgraph.py +39 -0
- aip_agents/examples/hello_world_langgraph.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_gl_connector_twitter.py +44 -0
- aip_agents/examples/hello_world_langgraph_gl_connector_twitter.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_mcp_http.py +31 -0
- aip_agents/examples/hello_world_langgraph_mcp_http.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_mcp_http_stream.py +34 -0
- aip_agents/examples/hello_world_langgraph_mcp_http_stream.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_mcp_sse.py +35 -0
- aip_agents/examples/hello_world_langgraph_mcp_sse.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_mcp_sse_stream.py +50 -0
- aip_agents/examples/hello_world_langgraph_mcp_sse_stream.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_mcp_stdio.py +35 -0
- aip_agents/examples/hello_world_langgraph_mcp_stdio.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_mcp_stdio_stream.py +50 -0
- aip_agents/examples/hello_world_langgraph_mcp_stdio_stream.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_stream.py +43 -0
- aip_agents/examples/hello_world_langgraph_stream.pyi +5 -0
- aip_agents/examples/hello_world_langgraph_stream_lm_invoker.py +37 -0
- aip_agents/examples/hello_world_langgraph_stream_lm_invoker.pyi +5 -0
- aip_agents/examples/hello_world_model_switch_cli.py +210 -0
- aip_agents/examples/hello_world_model_switch_cli.pyi +30 -0
- aip_agents/examples/hello_world_multi_agent_adk.py +75 -0
- aip_agents/examples/hello_world_multi_agent_adk.pyi +6 -0
- aip_agents/examples/hello_world_multi_agent_langchain.py +54 -0
- aip_agents/examples/hello_world_multi_agent_langchain.pyi +5 -0
- aip_agents/examples/hello_world_multi_agent_langgraph.py +66 -0
- aip_agents/examples/hello_world_multi_agent_langgraph.pyi +5 -0
- aip_agents/examples/hello_world_multi_agent_langgraph_lm_invoker.py +69 -0
- aip_agents/examples/hello_world_multi_agent_langgraph_lm_invoker.pyi +5 -0
- aip_agents/examples/hello_world_pii_logger.py +21 -0
- aip_agents/examples/hello_world_pii_logger.pyi +5 -0
- aip_agents/examples/hello_world_ptc.py +49 -0
- aip_agents/examples/hello_world_ptc.pyi +5 -0
- aip_agents/examples/hello_world_sentry.py +133 -0
- aip_agents/examples/hello_world_sentry.pyi +21 -0
- aip_agents/examples/hello_world_step_limits.py +273 -0
- aip_agents/examples/hello_world_step_limits.pyi +17 -0
- aip_agents/examples/hello_world_stock_a2a_server.py +103 -0
- aip_agents/examples/hello_world_stock_a2a_server.pyi +17 -0
- aip_agents/examples/hello_world_tool_output_client.py +55 -0
- aip_agents/examples/hello_world_tool_output_client.pyi +5 -0
- aip_agents/examples/hello_world_tool_output_server.py +114 -0
- aip_agents/examples/hello_world_tool_output_server.pyi +19 -0
- aip_agents/examples/hitl_demo.py +724 -0
- aip_agents/examples/hitl_demo.pyi +67 -0
- aip_agents/examples/mcp_configs/configs.py +63 -0
- aip_agents/examples/mcp_servers/common.py +76 -0
- aip_agents/examples/mcp_servers/mcp_name.py +29 -0
- aip_agents/examples/mcp_servers/mcp_server_http.py +19 -0
- aip_agents/examples/mcp_servers/mcp_server_sse.py +19 -0
- aip_agents/examples/mcp_servers/mcp_server_stdio.py +19 -0
- aip_agents/examples/mcp_servers/mcp_time.py +10 -0
- aip_agents/examples/pii_demo_langgraph_client.py +69 -0
- aip_agents/examples/pii_demo_langgraph_client.pyi +5 -0
- aip_agents/examples/pii_demo_langgraph_server.py +126 -0
- aip_agents/examples/pii_demo_langgraph_server.pyi +20 -0
- aip_agents/examples/pii_demo_multi_agent_client.py +80 -0
- aip_agents/examples/pii_demo_multi_agent_client.pyi +5 -0
- aip_agents/examples/pii_demo_multi_agent_server.py +247 -0
- aip_agents/examples/pii_demo_multi_agent_server.pyi +40 -0
- aip_agents/examples/todolist_planning_a2a_langchain_client.py +70 -0
- aip_agents/examples/todolist_planning_a2a_langchain_client.pyi +5 -0
- aip_agents/examples/todolist_planning_a2a_langgraph_server.py +88 -0
- aip_agents/examples/todolist_planning_a2a_langgraph_server.pyi +19 -0
- aip_agents/examples/tools/__init__.py +27 -0
- aip_agents/examples/tools/__init__.pyi +9 -0
- aip_agents/examples/tools/adk_arithmetic_tools.py +36 -0
- aip_agents/examples/tools/adk_arithmetic_tools.pyi +24 -0
- aip_agents/examples/tools/adk_weather_tool.py +60 -0
- aip_agents/examples/tools/adk_weather_tool.pyi +18 -0
- aip_agents/examples/tools/data_generator_tool.py +103 -0
- aip_agents/examples/tools/data_generator_tool.pyi +15 -0
- aip_agents/examples/tools/data_visualization_tool.py +312 -0
- aip_agents/examples/tools/data_visualization_tool.pyi +19 -0
- aip_agents/examples/tools/image_artifact_tool.py +136 -0
- aip_agents/examples/tools/image_artifact_tool.pyi +26 -0
- aip_agents/examples/tools/langchain_arithmetic_tools.py +26 -0
- aip_agents/examples/tools/langchain_arithmetic_tools.pyi +17 -0
- aip_agents/examples/tools/langchain_currency_exchange_tool.py +88 -0
- aip_agents/examples/tools/langchain_currency_exchange_tool.pyi +20 -0
- aip_agents/examples/tools/langchain_graph_artifact_tool.py +172 -0
- aip_agents/examples/tools/langchain_graph_artifact_tool.pyi +25 -0
- aip_agents/examples/tools/langchain_weather_tool.py +48 -0
- aip_agents/examples/tools/langchain_weather_tool.pyi +19 -0
- aip_agents/examples/tools/langgraph_streaming_tool.py +130 -0
- aip_agents/examples/tools/langgraph_streaming_tool.pyi +43 -0
- aip_agents/examples/tools/mock_retrieval_tool.py +56 -0
- aip_agents/examples/tools/mock_retrieval_tool.pyi +13 -0
- aip_agents/examples/tools/pii_demo_tools.py +189 -0
- aip_agents/examples/tools/pii_demo_tools.pyi +54 -0
- aip_agents/examples/tools/random_chart_tool.py +142 -0
- aip_agents/examples/tools/random_chart_tool.pyi +20 -0
- aip_agents/examples/tools/serper_tool.py +202 -0
- aip_agents/examples/tools/serper_tool.pyi +16 -0
- aip_agents/examples/tools/stock_tools.py +82 -0
- aip_agents/examples/tools/stock_tools.pyi +36 -0
- aip_agents/examples/tools/table_generator_tool.py +167 -0
- aip_agents/examples/tools/table_generator_tool.pyi +22 -0
- aip_agents/examples/tools/time_tool.py +82 -0
- aip_agents/examples/tools/time_tool.pyi +15 -0
- aip_agents/examples/tools/weather_forecast_tool.py +38 -0
- aip_agents/examples/tools/weather_forecast_tool.pyi +14 -0
- aip_agents/executor/agent_executor.py +473 -0
- aip_agents/executor/base.py +48 -0
- aip_agents/guardrails/__init__.py +83 -0
- aip_agents/guardrails/__init__.pyi +6 -0
- aip_agents/guardrails/engines/__init__.py +69 -0
- aip_agents/guardrails/engines/__init__.pyi +4 -0
- aip_agents/guardrails/engines/base.py +90 -0
- aip_agents/guardrails/engines/base.pyi +61 -0
- aip_agents/guardrails/engines/nemo.py +101 -0
- aip_agents/guardrails/engines/nemo.pyi +46 -0
- aip_agents/guardrails/engines/phrase_matcher.py +113 -0
- aip_agents/guardrails/engines/phrase_matcher.pyi +48 -0
- aip_agents/guardrails/exceptions.py +39 -0
- aip_agents/guardrails/exceptions.pyi +23 -0
- aip_agents/guardrails/manager.py +163 -0
- aip_agents/guardrails/manager.pyi +42 -0
- aip_agents/guardrails/middleware.py +199 -0
- aip_agents/guardrails/middleware.pyi +87 -0
- aip_agents/guardrails/schemas.py +63 -0
- aip_agents/guardrails/schemas.pyi +43 -0
- aip_agents/guardrails/utils.py +45 -0
- aip_agents/guardrails/utils.pyi +19 -0
- aip_agents/mcp/__init__.py +1 -0
- aip_agents/mcp/__init__.pyi +0 -0
- aip_agents/mcp/client/__init__.py +14 -0
- aip_agents/mcp/client/__init__.pyi +5 -0
- aip_agents/mcp/client/base_mcp_client.py +369 -0
- aip_agents/mcp/client/base_mcp_client.pyi +148 -0
- aip_agents/mcp/client/connection_manager.py +228 -0
- aip_agents/mcp/client/connection_manager.pyi +51 -0
- aip_agents/mcp/client/google_adk/__init__.py +11 -0
- aip_agents/mcp/client/google_adk/__init__.pyi +3 -0
- aip_agents/mcp/client/google_adk/client.py +381 -0
- aip_agents/mcp/client/google_adk/client.pyi +75 -0
- aip_agents/mcp/client/langchain/__init__.py +11 -0
- aip_agents/mcp/client/langchain/__init__.pyi +3 -0
- aip_agents/mcp/client/langchain/client.py +265 -0
- aip_agents/mcp/client/langchain/client.pyi +48 -0
- aip_agents/mcp/client/persistent_session.py +612 -0
- aip_agents/mcp/client/persistent_session.pyi +122 -0
- aip_agents/mcp/client/session_pool.py +351 -0
- aip_agents/mcp/client/session_pool.pyi +101 -0
- aip_agents/mcp/client/transports.py +263 -0
- aip_agents/mcp/client/transports.pyi +132 -0
- aip_agents/mcp/utils/__init__.py +7 -0
- aip_agents/mcp/utils/__init__.pyi +0 -0
- aip_agents/mcp/utils/config_validator.py +139 -0
- aip_agents/mcp/utils/config_validator.pyi +82 -0
- aip_agents/memory/__init__.py +14 -0
- aip_agents/memory/__init__.pyi +5 -0
- aip_agents/memory/adapters/__init__.py +10 -0
- aip_agents/memory/adapters/__init__.pyi +4 -0
- aip_agents/memory/adapters/base_adapter.py +811 -0
- aip_agents/memory/adapters/base_adapter.pyi +176 -0
- aip_agents/memory/adapters/mem0.py +84 -0
- aip_agents/memory/adapters/mem0.pyi +22 -0
- aip_agents/memory/base.py +84 -0
- aip_agents/memory/base.pyi +60 -0
- aip_agents/memory/constants.py +49 -0
- aip_agents/memory/constants.pyi +25 -0
- aip_agents/memory/factory.py +86 -0
- aip_agents/memory/factory.pyi +24 -0
- aip_agents/memory/guidance.py +20 -0
- aip_agents/memory/guidance.pyi +3 -0
- aip_agents/memory/simple_memory.py +47 -0
- aip_agents/memory/simple_memory.pyi +23 -0
- aip_agents/middleware/__init__.py +17 -0
- aip_agents/middleware/__init__.pyi +5 -0
- aip_agents/middleware/base.py +96 -0
- aip_agents/middleware/base.pyi +75 -0
- aip_agents/middleware/manager.py +150 -0
- aip_agents/middleware/manager.pyi +84 -0
- aip_agents/middleware/todolist.py +274 -0
- aip_agents/middleware/todolist.pyi +125 -0
- aip_agents/ptc/__init__.py +48 -0
- aip_agents/ptc/__init__.pyi +10 -0
- aip_agents/ptc/doc_gen.py +122 -0
- aip_agents/ptc/doc_gen.pyi +40 -0
- aip_agents/ptc/exceptions.py +39 -0
- aip_agents/ptc/exceptions.pyi +22 -0
- aip_agents/ptc/executor.py +143 -0
- aip_agents/ptc/executor.pyi +73 -0
- aip_agents/ptc/mcp/__init__.py +45 -0
- aip_agents/ptc/mcp/__init__.pyi +7 -0
- aip_agents/ptc/mcp/sandbox_bridge.py +668 -0
- aip_agents/ptc/mcp/sandbox_bridge.pyi +47 -0
- aip_agents/ptc/mcp/templates/__init__.py +1 -0
- aip_agents/ptc/mcp/templates/__init__.pyi +0 -0
- aip_agents/ptc/mcp/templates/mcp_client.py.template +239 -0
- aip_agents/ptc/naming.py +184 -0
- aip_agents/ptc/naming.pyi +76 -0
- aip_agents/ptc/payload.py +26 -0
- aip_agents/ptc/payload.pyi +15 -0
- aip_agents/ptc/prompt_builder.py +571 -0
- aip_agents/ptc/prompt_builder.pyi +55 -0
- aip_agents/ptc/ptc_helper.py +16 -0
- aip_agents/ptc/ptc_helper.pyi +1 -0
- aip_agents/ptc/sandbox_bridge.py +58 -0
- aip_agents/ptc/sandbox_bridge.pyi +25 -0
- aip_agents/ptc/template_utils.py +33 -0
- aip_agents/ptc/template_utils.pyi +13 -0
- aip_agents/ptc/templates/__init__.py +1 -0
- aip_agents/ptc/templates/__init__.pyi +0 -0
- aip_agents/ptc/templates/ptc_helper.py.template +134 -0
- aip_agents/sandbox/__init__.py +43 -0
- aip_agents/sandbox/__init__.pyi +5 -0
- aip_agents/sandbox/defaults.py +9 -0
- aip_agents/sandbox/defaults.pyi +2 -0
- aip_agents/sandbox/e2b_runtime.py +267 -0
- aip_agents/sandbox/e2b_runtime.pyi +51 -0
- aip_agents/sandbox/template_builder.py +131 -0
- aip_agents/sandbox/template_builder.pyi +36 -0
- aip_agents/sandbox/types.py +24 -0
- aip_agents/sandbox/types.pyi +14 -0
- aip_agents/sandbox/validation.py +50 -0
- aip_agents/sandbox/validation.pyi +20 -0
- aip_agents/schema/__init__.py +69 -0
- aip_agents/schema/__init__.pyi +9 -0
- aip_agents/schema/a2a.py +56 -0
- aip_agents/schema/a2a.pyi +40 -0
- aip_agents/schema/agent.py +111 -0
- aip_agents/schema/agent.pyi +65 -0
- aip_agents/schema/hitl.py +157 -0
- aip_agents/schema/hitl.pyi +89 -0
- aip_agents/schema/langgraph.py +37 -0
- aip_agents/schema/langgraph.pyi +28 -0
- aip_agents/schema/model_id.py +97 -0
- aip_agents/schema/model_id.pyi +54 -0
- aip_agents/schema/step_limit.py +108 -0
- aip_agents/schema/step_limit.pyi +63 -0
- aip_agents/schema/storage.py +40 -0
- aip_agents/schema/storage.pyi +21 -0
- aip_agents/sentry/__init__.py +11 -0
- aip_agents/sentry/__init__.pyi +3 -0
- aip_agents/sentry/sentry.py +151 -0
- aip_agents/sentry/sentry.pyi +48 -0
- aip_agents/storage/__init__.py +41 -0
- aip_agents/storage/__init__.pyi +8 -0
- aip_agents/storage/base.py +85 -0
- aip_agents/storage/base.pyi +58 -0
- aip_agents/storage/clients/__init__.py +12 -0
- aip_agents/storage/clients/__init__.pyi +3 -0
- aip_agents/storage/clients/minio_client.py +318 -0
- aip_agents/storage/clients/minio_client.pyi +137 -0
- aip_agents/storage/config.py +62 -0
- aip_agents/storage/config.pyi +29 -0
- aip_agents/storage/providers/__init__.py +15 -0
- aip_agents/storage/providers/__init__.pyi +5 -0
- aip_agents/storage/providers/base.py +106 -0
- aip_agents/storage/providers/base.pyi +88 -0
- aip_agents/storage/providers/memory.py +114 -0
- aip_agents/storage/providers/memory.pyi +79 -0
- aip_agents/storage/providers/object_storage.py +214 -0
- aip_agents/storage/providers/object_storage.pyi +98 -0
- aip_agents/tools/__init__.py +64 -0
- aip_agents/tools/__init__.pyi +11 -0
- aip_agents/tools/browser_use/__init__.py +82 -0
- aip_agents/tools/browser_use/__init__.pyi +14 -0
- aip_agents/tools/browser_use/action_parser.py +103 -0
- aip_agents/tools/browser_use/action_parser.pyi +18 -0
- aip_agents/tools/browser_use/browser_use_tool.py +1120 -0
- aip_agents/tools/browser_use/browser_use_tool.pyi +50 -0
- aip_agents/tools/browser_use/llm_config.py +120 -0
- aip_agents/tools/browser_use/llm_config.pyi +52 -0
- aip_agents/tools/browser_use/minio_storage.py +198 -0
- aip_agents/tools/browser_use/minio_storage.pyi +109 -0
- aip_agents/tools/browser_use/schemas.py +119 -0
- aip_agents/tools/browser_use/schemas.pyi +32 -0
- aip_agents/tools/browser_use/session.py +76 -0
- aip_agents/tools/browser_use/session.pyi +4 -0
- aip_agents/tools/browser_use/session_errors.py +132 -0
- aip_agents/tools/browser_use/session_errors.pyi +53 -0
- aip_agents/tools/browser_use/steel_session_recording.py +317 -0
- aip_agents/tools/browser_use/steel_session_recording.pyi +63 -0
- aip_agents/tools/browser_use/streaming.py +815 -0
- aip_agents/tools/browser_use/streaming.pyi +81 -0
- aip_agents/tools/browser_use/structured_data_parser.py +257 -0
- aip_agents/tools/browser_use/structured_data_parser.pyi +86 -0
- aip_agents/tools/browser_use/structured_data_recovery.py +204 -0
- aip_agents/tools/browser_use/structured_data_recovery.pyi +43 -0
- aip_agents/tools/browser_use/types.py +78 -0
- aip_agents/tools/browser_use/types.pyi +45 -0
- aip_agents/tools/code_sandbox/__init__.py +26 -0
- aip_agents/tools/code_sandbox/__init__.pyi +3 -0
- aip_agents/tools/code_sandbox/constant.py +13 -0
- aip_agents/tools/code_sandbox/constant.pyi +4 -0
- aip_agents/tools/code_sandbox/e2b_cloud_sandbox_extended.py +306 -0
- aip_agents/tools/code_sandbox/e2b_cloud_sandbox_extended.pyi +102 -0
- aip_agents/tools/code_sandbox/e2b_sandbox_tool.py +411 -0
- aip_agents/tools/code_sandbox/e2b_sandbox_tool.pyi +29 -0
- aip_agents/tools/constants.py +177 -0
- aip_agents/tools/constants.pyi +138 -0
- aip_agents/tools/date_range_tool.py +554 -0
- aip_agents/tools/date_range_tool.pyi +21 -0
- aip_agents/tools/document_loader/__init__.py +44 -0
- aip_agents/tools/document_loader/__init__.pyi +7 -0
- aip_agents/tools/document_loader/base_reader.py +302 -0
- aip_agents/tools/document_loader/base_reader.pyi +75 -0
- aip_agents/tools/document_loader/docx_reader_tool.py +68 -0
- aip_agents/tools/document_loader/docx_reader_tool.pyi +10 -0
- aip_agents/tools/document_loader/excel_reader_tool.py +171 -0
- aip_agents/tools/document_loader/excel_reader_tool.pyi +26 -0
- aip_agents/tools/document_loader/pdf_reader_tool.py +79 -0
- aip_agents/tools/document_loader/pdf_reader_tool.pyi +11 -0
- aip_agents/tools/document_loader/pdf_splitter.py +169 -0
- aip_agents/tools/document_loader/pdf_splitter.pyi +18 -0
- aip_agents/tools/execute_ptc_code.py +308 -0
- aip_agents/tools/execute_ptc_code.pyi +90 -0
- aip_agents/tools/gl_connector/__init__.py +5 -0
- aip_agents/tools/gl_connector/__init__.pyi +3 -0
- aip_agents/tools/gl_connector/tool.py +383 -0
- aip_agents/tools/gl_connector/tool.pyi +74 -0
- aip_agents/tools/gl_connector_tools.py +119 -0
- aip_agents/tools/gl_connector_tools.pyi +39 -0
- aip_agents/tools/memory_search/__init__.py +29 -0
- aip_agents/tools/memory_search/__init__.pyi +5 -0
- aip_agents/tools/memory_search/base.py +200 -0
- aip_agents/tools/memory_search/base.pyi +69 -0
- aip_agents/tools/memory_search/mem0.py +365 -0
- aip_agents/tools/memory_search/mem0.pyi +29 -0
- aip_agents/tools/memory_search/schema.py +81 -0
- aip_agents/tools/memory_search/schema.pyi +25 -0
- aip_agents/tools/memory_search_tool.py +34 -0
- aip_agents/tools/memory_search_tool.pyi +3 -0
- aip_agents/tools/time_tool.py +117 -0
- aip_agents/tools/time_tool.pyi +16 -0
- aip_agents/tools/tool_config_injector.py +300 -0
- aip_agents/tools/tool_config_injector.pyi +26 -0
- aip_agents/tools/web_search/__init__.py +15 -0
- aip_agents/tools/web_search/__init__.pyi +3 -0
- aip_agents/tools/web_search/serper_tool.py +187 -0
- aip_agents/tools/web_search/serper_tool.pyi +19 -0
- aip_agents/types/__init__.py +70 -0
- aip_agents/types/__init__.pyi +36 -0
- aip_agents/types/a2a_events.py +13 -0
- aip_agents/types/a2a_events.pyi +3 -0
- aip_agents/utils/__init__.py +79 -0
- aip_agents/utils/__init__.pyi +11 -0
- aip_agents/utils/a2a_connector.py +1757 -0
- aip_agents/utils/a2a_connector.pyi +146 -0
- aip_agents/utils/artifact_helpers.py +502 -0
- aip_agents/utils/artifact_helpers.pyi +203 -0
- aip_agents/utils/constants.py +22 -0
- aip_agents/utils/constants.pyi +10 -0
- aip_agents/utils/datetime/__init__.py +34 -0
- aip_agents/utils/datetime/__init__.pyi +4 -0
- aip_agents/utils/datetime/normalization.py +231 -0
- aip_agents/utils/datetime/normalization.pyi +95 -0
- aip_agents/utils/datetime/timezone.py +206 -0
- aip_agents/utils/datetime/timezone.pyi +48 -0
- aip_agents/utils/env_loader.py +27 -0
- aip_agents/utils/env_loader.pyi +10 -0
- aip_agents/utils/event_handler_registry.py +58 -0
- aip_agents/utils/event_handler_registry.pyi +23 -0
- aip_agents/utils/file_prompt_utils.py +176 -0
- aip_agents/utils/file_prompt_utils.pyi +21 -0
- aip_agents/utils/final_response_builder.py +211 -0
- aip_agents/utils/final_response_builder.pyi +34 -0
- aip_agents/utils/formatter_llm_client.py +231 -0
- aip_agents/utils/formatter_llm_client.pyi +71 -0
- aip_agents/utils/langgraph/__init__.py +19 -0
- aip_agents/utils/langgraph/__init__.pyi +3 -0
- aip_agents/utils/langgraph/converter.py +128 -0
- aip_agents/utils/langgraph/converter.pyi +49 -0
- aip_agents/utils/langgraph/tool_managers/__init__.py +15 -0
- aip_agents/utils/langgraph/tool_managers/__init__.pyi +5 -0
- aip_agents/utils/langgraph/tool_managers/a2a_tool_manager.py +99 -0
- aip_agents/utils/langgraph/tool_managers/a2a_tool_manager.pyi +35 -0
- aip_agents/utils/langgraph/tool_managers/base_tool_manager.py +66 -0
- aip_agents/utils/langgraph/tool_managers/base_tool_manager.pyi +48 -0
- aip_agents/utils/langgraph/tool_managers/delegation_tool_manager.py +1096 -0
- aip_agents/utils/langgraph/tool_managers/delegation_tool_manager.pyi +56 -0
- aip_agents/utils/langgraph/tool_output_management.py +1047 -0
- aip_agents/utils/langgraph/tool_output_management.pyi +329 -0
- aip_agents/utils/logger.py +195 -0
- aip_agents/utils/logger.pyi +60 -0
- aip_agents/utils/metadata/__init__.py +27 -0
- aip_agents/utils/metadata/__init__.pyi +5 -0
- aip_agents/utils/metadata/activity_metadata_helper.py +407 -0
- aip_agents/utils/metadata/activity_metadata_helper.pyi +25 -0
- aip_agents/utils/metadata/activity_narrative/__init__.py +35 -0
- aip_agents/utils/metadata/activity_narrative/__init__.pyi +7 -0
- aip_agents/utils/metadata/activity_narrative/builder.py +817 -0
- aip_agents/utils/metadata/activity_narrative/builder.pyi +35 -0
- aip_agents/utils/metadata/activity_narrative/constants.py +51 -0
- aip_agents/utils/metadata/activity_narrative/constants.pyi +10 -0
- aip_agents/utils/metadata/activity_narrative/context.py +49 -0
- aip_agents/utils/metadata/activity_narrative/context.pyi +32 -0
- aip_agents/utils/metadata/activity_narrative/formatters.py +230 -0
- aip_agents/utils/metadata/activity_narrative/formatters.pyi +48 -0
- aip_agents/utils/metadata/activity_narrative/utils.py +35 -0
- aip_agents/utils/metadata/activity_narrative/utils.pyi +12 -0
- aip_agents/utils/metadata/schemas/__init__.py +16 -0
- aip_agents/utils/metadata/schemas/__init__.pyi +4 -0
- aip_agents/utils/metadata/schemas/activity_schema.py +29 -0
- aip_agents/utils/metadata/schemas/activity_schema.pyi +18 -0
- aip_agents/utils/metadata/schemas/thinking_schema.py +31 -0
- aip_agents/utils/metadata/schemas/thinking_schema.pyi +20 -0
- aip_agents/utils/metadata/thinking_metadata_helper.py +38 -0
- aip_agents/utils/metadata/thinking_metadata_helper.pyi +4 -0
- aip_agents/utils/metadata_helper.py +358 -0
- aip_agents/utils/metadata_helper.pyi +117 -0
- aip_agents/utils/name_preprocessor/__init__.py +17 -0
- aip_agents/utils/name_preprocessor/__init__.pyi +6 -0
- aip_agents/utils/name_preprocessor/base_name_preprocessor.py +73 -0
- aip_agents/utils/name_preprocessor/base_name_preprocessor.pyi +52 -0
- aip_agents/utils/name_preprocessor/google_name_preprocessor.py +100 -0
- aip_agents/utils/name_preprocessor/google_name_preprocessor.pyi +38 -0
- aip_agents/utils/name_preprocessor/name_preprocessor.py +87 -0
- aip_agents/utils/name_preprocessor/name_preprocessor.pyi +41 -0
- aip_agents/utils/name_preprocessor/openai_name_preprocessor.py +48 -0
- aip_agents/utils/name_preprocessor/openai_name_preprocessor.pyi +34 -0
- aip_agents/utils/pii/__init__.py +25 -0
- aip_agents/utils/pii/__init__.pyi +5 -0
- aip_agents/utils/pii/pii_handler.py +397 -0
- aip_agents/utils/pii/pii_handler.pyi +96 -0
- aip_agents/utils/pii/pii_helper.py +207 -0
- aip_agents/utils/pii/pii_helper.pyi +78 -0
- aip_agents/utils/pii/uuid_deanonymizer_mapping.py +195 -0
- aip_agents/utils/pii/uuid_deanonymizer_mapping.pyi +73 -0
- aip_agents/utils/reference_helper.py +273 -0
- aip_agents/utils/reference_helper.pyi +81 -0
- aip_agents/utils/sse_chunk_transformer.py +831 -0
- aip_agents/utils/sse_chunk_transformer.pyi +166 -0
- aip_agents/utils/step_limit_manager.py +265 -0
- aip_agents/utils/step_limit_manager.pyi +112 -0
- aip_agents/utils/token_usage_helper.py +156 -0
- aip_agents/utils/token_usage_helper.pyi +60 -0
- aip_agents_binary-0.6.4.dist-info/METADATA +673 -0
- aip_agents_binary-0.6.4.dist-info/RECORD +612 -0
- aip_agents_binary-0.6.4.dist-info/WHEEL +5 -0
- aip_agents_binary-0.6.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,724 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Interactive HITL (Human-in-the-Loop) Approval Demo.
|
|
3
|
+
|
|
4
|
+
This demo creates a recruitment-focused LangGraph agent that requires human approval for
|
|
5
|
+
critical steps in a candidate workflow.
|
|
6
|
+
You'll be prompted to approve/reject tool calls in real-time.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python -m aip_agents.examples.hitl_demo
|
|
10
|
+
|
|
11
|
+
Requirements:
|
|
12
|
+
- OPENAI_API_KEY in environment variables or .env file (auto-loaded)
|
|
13
|
+
- Internet connection for LLM API calls
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import asyncio
|
|
17
|
+
import contextlib
|
|
18
|
+
import json
|
|
19
|
+
import logging
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from datetime import datetime
|
|
22
|
+
from http import HTTPStatus
|
|
23
|
+
|
|
24
|
+
import httpx
|
|
25
|
+
import uvicorn
|
|
26
|
+
from langchain_core.tools import tool
|
|
27
|
+
from starlette.applications import Starlette
|
|
28
|
+
from starlette.requests import Request
|
|
29
|
+
from starlette.responses import JSONResponse
|
|
30
|
+
from starlette.routing import Route
|
|
31
|
+
|
|
32
|
+
from aip_agents.agent import LangGraphReactAgent
|
|
33
|
+
from aip_agents.schema.hitl import ApprovalRequest
|
|
34
|
+
from aip_agents.utils.env_loader import load_local_env
|
|
35
|
+
from aip_agents.utils.logger import get_logger
|
|
36
|
+
|
|
37
|
+
# Load environment variables for local development
|
|
38
|
+
load_local_env()
|
|
39
|
+
|
|
40
|
+
# Get logger instance for this demo
|
|
41
|
+
logger = get_logger("aip_agents.examples.hitl_demo", logging.CRITICAL)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@tool
|
|
45
|
+
def check_candidate_inbox(candidate_email: str) -> str:
|
|
46
|
+
"""Retrieve the latest email from a candidate (safe tool).
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
candidate_email (str): The email address of the candidate.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
str: The latest email content from the candidate.
|
|
53
|
+
"""
|
|
54
|
+
profile = CANDIDATE_PROFILES.get(candidate_email.lower())
|
|
55
|
+
if profile and isinstance(profile.get("inbox"), str):
|
|
56
|
+
return profile["inbox"]
|
|
57
|
+
return f"Email from {candidate_email}: Thank you for the update. Looking forward to next steps."
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@tool
|
|
61
|
+
def validate_candidate(candidate_name: str, role: str, score: int) -> str:
|
|
62
|
+
"""Record the candidate decision in the applicant tracking system.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
candidate_name (str): The name of the candidate.
|
|
66
|
+
role (str): The role the candidate is being evaluated for.
|
|
67
|
+
score (int): The evaluation score for the candidate.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
str: The validation result with recommendation and notes.
|
|
71
|
+
"""
|
|
72
|
+
profile = NAME_INDEX.get(candidate_name.lower())
|
|
73
|
+
if profile is None:
|
|
74
|
+
return (
|
|
75
|
+
f"Candidate {candidate_name} evaluated for {role}. Final assessment score: {score}. "
|
|
76
|
+
"Recommendation data not found; manual review required."
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
recommendation = profile.get("recommendation", "pending")
|
|
80
|
+
summary = profile.get("notes", "No additional notes provided.")
|
|
81
|
+
actual_score = profile.get("score", score)
|
|
82
|
+
if recommendation == "approved":
|
|
83
|
+
status_text = "ATS recommendation: move forward"
|
|
84
|
+
elif recommendation == "rejected":
|
|
85
|
+
status_text = "ATS recommendation: do not proceed"
|
|
86
|
+
else:
|
|
87
|
+
status_text = "ATS recommendation: pending hiring committee review"
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
f"Candidate {profile['name']} evaluated for {role}. Final assessment score: {actual_score}. "
|
|
91
|
+
f"{summary} {status_text}."
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@tool
|
|
96
|
+
def send_candidate_email(candidate_email: str, subject: str, body: str) -> str:
|
|
97
|
+
"""Send an email update to the candidate.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
candidate_email (str): The email address of the candidate.
|
|
101
|
+
subject (str): The subject line of the email.
|
|
102
|
+
body (str): The body content of the email.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
str: Confirmation message that the email was sent.
|
|
106
|
+
"""
|
|
107
|
+
profile = CANDIDATE_PROFILES.get(candidate_email.lower())
|
|
108
|
+
if profile is not None:
|
|
109
|
+
profile["last_email_subject"] = subject
|
|
110
|
+
profile["last_email_body"] = body
|
|
111
|
+
return f"Email sent to {candidate_email} with subject '{subject}'"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
CANDIDATE_PROFILES: dict[str, dict[str, str | int | bool]] = {
|
|
115
|
+
"jane.doe@example.com": {
|
|
116
|
+
"email": "jane.doe@example.com",
|
|
117
|
+
"name": "Jane Doe",
|
|
118
|
+
"role": "Senior Backend Engineer",
|
|
119
|
+
"score": 87,
|
|
120
|
+
"recommendation": "approved",
|
|
121
|
+
"inbox": (
|
|
122
|
+
"Hi team, thanks for the update! I'm excited about the opportunity and available for a call next week."
|
|
123
|
+
),
|
|
124
|
+
"notes": "Strong performance in system design and coding exercises.",
|
|
125
|
+
"offer_subject": "Offer Confirmation ā Senior Backend Engineer",
|
|
126
|
+
"pending_subject": "Interview Update ā Senior Backend Engineer",
|
|
127
|
+
"rejection_subject": "Application Update ā Senior Backend Engineer",
|
|
128
|
+
},
|
|
129
|
+
"sam.lee@example.com": {
|
|
130
|
+
"email": "sam.lee@example.com",
|
|
131
|
+
"name": "Sam Lee",
|
|
132
|
+
"role": "Senior Backend Engineer",
|
|
133
|
+
"score": 68,
|
|
134
|
+
"recommendation": "rejected",
|
|
135
|
+
"inbox": (
|
|
136
|
+
"Hello recruiter, I appreciate the opportunity. Please let me know if you need any "
|
|
137
|
+
"additional information from my end."
|
|
138
|
+
),
|
|
139
|
+
"notes": "Great collaboration skills but struggled with distributed systems questions.",
|
|
140
|
+
"offer_subject": "Offer Confirmation ā Senior Backend Engineer",
|
|
141
|
+
"pending_subject": "Interview Update ā Senior Backend Engineer",
|
|
142
|
+
"rejection_subject": "Application Update ā Senior Backend Engineer",
|
|
143
|
+
},
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
CANDIDATE_SEQUENCE: list[dict[str, str | int | bool]] = [
|
|
147
|
+
CANDIDATE_PROFILES["jane.doe@example.com"],
|
|
148
|
+
CANDIDATE_PROFILES["sam.lee@example.com"],
|
|
149
|
+
]
|
|
150
|
+
|
|
151
|
+
NAME_INDEX = {profile["name"].lower(): profile for profile in CANDIDATE_SEQUENCE}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _normalize_timeout_decision(decision: str | None) -> str:
|
|
155
|
+
"""Treat timeout (or missing) decisions as skips for downstream handling.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
decision (str | None): The decision to normalize, or None if timed out.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
str: The normalized decision ("approved", "rejected", or "skipped").
|
|
162
|
+
"""
|
|
163
|
+
if decision in {None, "timeout"}:
|
|
164
|
+
return "skipped"
|
|
165
|
+
return decision
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _summarize_outcome(
|
|
169
|
+
name: str,
|
|
170
|
+
validation: str,
|
|
171
|
+
email: str,
|
|
172
|
+
*,
|
|
173
|
+
validation_timeout: bool,
|
|
174
|
+
email_timeout: bool,
|
|
175
|
+
) -> str:
|
|
176
|
+
if validation_timeout:
|
|
177
|
+
validation_text = f"left {name}'s hiring decision pending (timed out)"
|
|
178
|
+
elif validation == "approved":
|
|
179
|
+
validation_text = f"approved {name}'s hiring decision"
|
|
180
|
+
elif validation == "rejected":
|
|
181
|
+
validation_text = f"rejected {name}'s hiring decision"
|
|
182
|
+
else:
|
|
183
|
+
validation_text = f"left {name}'s hiring decision pending"
|
|
184
|
+
|
|
185
|
+
if email_timeout:
|
|
186
|
+
email_text = "no email update was sent (skipped due to timeout)"
|
|
187
|
+
elif email == "approved":
|
|
188
|
+
email_text = "an email update was sent"
|
|
189
|
+
elif email == "skipped":
|
|
190
|
+
email_text = "no email update was sent yet"
|
|
191
|
+
else:
|
|
192
|
+
email_text = f"email outcome recorded as '{email}'"
|
|
193
|
+
|
|
194
|
+
return f" - You {validation_text}; {email_text}."
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _print_intro() -> None:
|
|
198
|
+
print("š HITL Approval Demo")
|
|
199
|
+
print("This demo requires real LLM API access and will prompt for human input.")
|
|
200
|
+
print(
|
|
201
|
+
"Scenario: recruitment coordinator processing candidate updates."
|
|
202
|
+
" Steps: check candidate inbox (safe), validate candidate (approval), send candidate email (approval)."
|
|
203
|
+
)
|
|
204
|
+
print("This demo walks through two candidates: one recommended to move forward and one declined.")
|
|
205
|
+
print(
|
|
206
|
+
"Commands are shown for each step (e.g., a/r or send/cancel). "
|
|
207
|
+
"You can append optional comments like 'send looks good'."
|
|
208
|
+
)
|
|
209
|
+
print()
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _format_json_block(raw: str) -> str:
|
|
213
|
+
try:
|
|
214
|
+
parsed = json.loads(raw)
|
|
215
|
+
except (TypeError, ValueError):
|
|
216
|
+
return raw
|
|
217
|
+
return json.dumps(parsed, indent=2, ensure_ascii=False)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _format_timeout(request: ApprovalRequest) -> str:
|
|
221
|
+
if request.timeout_at and isinstance(request.timeout_at, datetime):
|
|
222
|
+
return request.timeout_at.isoformat()
|
|
223
|
+
return "n/a"
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _print_multiline(label: str, content: str) -> None:
|
|
227
|
+
print(f"{label}:")
|
|
228
|
+
if not content.strip():
|
|
229
|
+
print(" (none)")
|
|
230
|
+
return
|
|
231
|
+
for line in content.splitlines():
|
|
232
|
+
print(f" {line}")
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _print_command_help(tool_name: str) -> None:
|
|
236
|
+
print("Commands:")
|
|
237
|
+
if tool_name == "validate_candidate":
|
|
238
|
+
options = [
|
|
239
|
+
("a", "approve"),
|
|
240
|
+
("r", "reject"),
|
|
241
|
+
]
|
|
242
|
+
elif tool_name == "send_candidate_email":
|
|
243
|
+
options = [
|
|
244
|
+
("send", "send email"),
|
|
245
|
+
("cancel", "cancel email"),
|
|
246
|
+
]
|
|
247
|
+
else:
|
|
248
|
+
options = [
|
|
249
|
+
("a", "approve"),
|
|
250
|
+
("s", "skip"),
|
|
251
|
+
("r", "reject"),
|
|
252
|
+
]
|
|
253
|
+
|
|
254
|
+
for key, description in options:
|
|
255
|
+
print(f" [{key}] {description}")
|
|
256
|
+
print(" (optional comment after command, e.g. 'send looks good')")
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _decision_mapping(tool_name: str) -> tuple[dict[str, str], str]:
|
|
260
|
+
if tool_name == "validate_candidate":
|
|
261
|
+
return (
|
|
262
|
+
{
|
|
263
|
+
"a": "approved",
|
|
264
|
+
"approve": "approved",
|
|
265
|
+
"approved": "approved",
|
|
266
|
+
"r": "rejected",
|
|
267
|
+
"reject": "rejected",
|
|
268
|
+
"rejected": "rejected",
|
|
269
|
+
},
|
|
270
|
+
"Please enter a or r.",
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
if tool_name == "send_candidate_email":
|
|
274
|
+
return (
|
|
275
|
+
{
|
|
276
|
+
"send": "approved",
|
|
277
|
+
"s": "approved",
|
|
278
|
+
"approved": "approved",
|
|
279
|
+
"cancel": "skipped",
|
|
280
|
+
"c": "skipped",
|
|
281
|
+
"skip": "skipped",
|
|
282
|
+
},
|
|
283
|
+
"Please enter send or cancel.",
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
return (
|
|
287
|
+
{
|
|
288
|
+
"a": "approved",
|
|
289
|
+
"approve": "approved",
|
|
290
|
+
"approved": "approved",
|
|
291
|
+
"s": "skipped",
|
|
292
|
+
"skip": "skipped",
|
|
293
|
+
"skipped": "skipped",
|
|
294
|
+
"r": "rejected",
|
|
295
|
+
"reject": "rejected",
|
|
296
|
+
"rejected": "rejected",
|
|
297
|
+
},
|
|
298
|
+
"Please enter a, s, or r.",
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _print_pending_request(request: ApprovalRequest) -> None:
|
|
303
|
+
border = "ā" * 70
|
|
304
|
+
print(f"\n{border}")
|
|
305
|
+
print("š Approval Required")
|
|
306
|
+
print(border)
|
|
307
|
+
print(f"Tool : {request.tool_name}")
|
|
308
|
+
print(f"Request ID : {request.request_id}")
|
|
309
|
+
print(f"Timeout At : {_format_timeout(request)}")
|
|
310
|
+
|
|
311
|
+
arguments_block = _format_json_block(request.arguments_preview)
|
|
312
|
+
_print_multiline("Arguments", arguments_block)
|
|
313
|
+
|
|
314
|
+
context_text = json.dumps(request.context, indent=2, ensure_ascii=False) if request.context else ""
|
|
315
|
+
_print_multiline("Context", context_text)
|
|
316
|
+
|
|
317
|
+
print()
|
|
318
|
+
_print_command_help(request.tool_name)
|
|
319
|
+
print(border)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def _build_demo_agent() -> "LangGraphReactAgent":
|
|
323
|
+
return LangGraphReactAgent(
|
|
324
|
+
name="HITL Demo Agent",
|
|
325
|
+
instruction=(
|
|
326
|
+
"You are a recruitment coordinator preparing a candidate update. "
|
|
327
|
+
"When asked for the latest message from a candidate, call check_candidate_inbox with their email. "
|
|
328
|
+
"When directed to record the hiring decision, you must call validate_candidate "
|
|
329
|
+
"before taking any other action. "
|
|
330
|
+
"If the validation is rejected you must halt the workflow and inform the user; if it is skipped, "
|
|
331
|
+
"you may continue but clarify in follow-up actions that the decision remains pending "
|
|
332
|
+
"and do not attempt to validate again unless explicitly asked. "
|
|
333
|
+
"Use send_candidate_email to notify the candidate once the decision status is settled."
|
|
334
|
+
),
|
|
335
|
+
model="openai/gpt-4.1",
|
|
336
|
+
tools=[
|
|
337
|
+
check_candidate_inbox,
|
|
338
|
+
validate_candidate,
|
|
339
|
+
send_candidate_email,
|
|
340
|
+
],
|
|
341
|
+
tool_configs={
|
|
342
|
+
"tool_configs": {
|
|
343
|
+
"send_candidate_email": {"hitl": {"timeout_seconds": 30}},
|
|
344
|
+
"validate_candidate": {"hitl": {"timeout_seconds": 10}},
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def _candidate_inbox_query(profile: dict[str, str | int | bool]) -> str:
|
|
351
|
+
return (
|
|
352
|
+
f"Check the candidate inbox for {profile['email']} using check_candidate_inbox and "
|
|
353
|
+
"summarise key information they provided."
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def _validate_candidate_query(profile: dict[str, str | int | bool]) -> str:
|
|
358
|
+
return (
|
|
359
|
+
f"Validate {profile['name']} for the {profile['role']} role using validate_candidate "
|
|
360
|
+
f"with a final score of {profile['score']}. Highlight the main strengths noted during interviews."
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def _candidate_email_query(
|
|
365
|
+
profile: dict[str, str | int | bool],
|
|
366
|
+
validation_status: str | None,
|
|
367
|
+
) -> str:
|
|
368
|
+
if validation_status == "approved":
|
|
369
|
+
subject = profile.get("offer_subject", "Offer Confirmation")
|
|
370
|
+
return (
|
|
371
|
+
"You must notify the candidate of the offer. "
|
|
372
|
+
f"Call send_candidate_email to send an offer confirmation to {profile['email']} with subject '{subject}'. "
|
|
373
|
+
"Do not call validate_candidate again; the decision has already been recorded."
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
if validation_status in {"skipped", "timeout", None}:
|
|
377
|
+
subject = profile.get("pending_subject", "Interview Update")
|
|
378
|
+
return (
|
|
379
|
+
"The decision is still pending. "
|
|
380
|
+
f"Call send_candidate_email to provide an update to {profile['email']} with subject '{subject}'. "
|
|
381
|
+
"Do not attempt to re-run validate_candidate; simply let the candidate know the decision is pending."
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
subject = profile.get("rejection_subject", "Application Update")
|
|
385
|
+
return (
|
|
386
|
+
"The candidate has been declined. "
|
|
387
|
+
f"Call send_candidate_email to send a polite rejection email to {profile['email']} with subject '{subject}'. "
|
|
388
|
+
"Do not call validate_candidate again during this follow-up."
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def _create_server_app(agent: "LangGraphReactAgent") -> tuple[Starlette, asyncio.Queue[ApprovalRequest]]:
|
|
393
|
+
pending_queue: asyncio.Queue[ApprovalRequest] = asyncio.Queue()
|
|
394
|
+
|
|
395
|
+
def notifier(request: ApprovalRequest) -> None:
|
|
396
|
+
try:
|
|
397
|
+
loop = asyncio.get_running_loop()
|
|
398
|
+
loop.call_soon_threadsafe(pending_queue.put_nowait, request)
|
|
399
|
+
except RuntimeError:
|
|
400
|
+
pending_queue.put_nowait(request)
|
|
401
|
+
|
|
402
|
+
agent.register_hitl_notifier(notifier)
|
|
403
|
+
_ = agent.hitl_manager
|
|
404
|
+
|
|
405
|
+
async def run_agent(request: Request) -> JSONResponse:
|
|
406
|
+
payload = await request.json()
|
|
407
|
+
message = payload.get("message", "")
|
|
408
|
+
result = await agent.arun(message, recursion_limit=5)
|
|
409
|
+
output = result.get("output")
|
|
410
|
+
serialized_state = repr(result.get("full_final_state"))
|
|
411
|
+
return JSONResponse({"output": output, "state": serialized_state})
|
|
412
|
+
|
|
413
|
+
async def hitl_decision(request: Request) -> JSONResponse:
|
|
414
|
+
payload = await request.json()
|
|
415
|
+
request_id = payload.get("request_id")
|
|
416
|
+
decision = payload.get("decision")
|
|
417
|
+
operator_input = payload.get("operator_input", "")
|
|
418
|
+
try:
|
|
419
|
+
agent.hitl_manager.resolve_pending_request(request_id, decision, operator_input=operator_input)
|
|
420
|
+
return JSONResponse({"status": "ok"})
|
|
421
|
+
except KeyError as exc:
|
|
422
|
+
return JSONResponse({"error": str(exc)}, status_code=404)
|
|
423
|
+
except ValueError as exc:
|
|
424
|
+
return JSONResponse({"error": str(exc)}, status_code=400)
|
|
425
|
+
|
|
426
|
+
routes = [
|
|
427
|
+
Route("/agent/run", run_agent, methods=["POST"]),
|
|
428
|
+
Route("/hitl/decision", hitl_decision, methods=["POST"]),
|
|
429
|
+
]
|
|
430
|
+
|
|
431
|
+
app = Starlette(routes=routes)
|
|
432
|
+
return app, pending_queue
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
class _ServerContext:
|
|
436
|
+
def __init__(self, app: Starlette, host: str, port: int) -> None:
|
|
437
|
+
config = uvicorn.Config(app, host=host, port=port, log_level="warning")
|
|
438
|
+
self._server = uvicorn.Server(config)
|
|
439
|
+
self._server.install_signal_handlers = False
|
|
440
|
+
self._task: asyncio.Task | None = None
|
|
441
|
+
|
|
442
|
+
async def __aenter__(self) -> "_ServerContext":
|
|
443
|
+
self._task = asyncio.create_task(self._server.serve())
|
|
444
|
+
while not self._server.started:
|
|
445
|
+
await asyncio.sleep(0.05)
|
|
446
|
+
return self
|
|
447
|
+
|
|
448
|
+
async def __aexit__(self, exc_type, exc, _tb) -> None:
|
|
449
|
+
self._server.should_exit = True
|
|
450
|
+
if self._task:
|
|
451
|
+
await self._task
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def _drain_queue(queue: asyncio.Queue[ApprovalRequest]) -> None:
|
|
455
|
+
try:
|
|
456
|
+
while True:
|
|
457
|
+
queue.get_nowait()
|
|
458
|
+
except asyncio.QueueEmpty:
|
|
459
|
+
pass
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
async def _invoke_run_endpoint(
|
|
463
|
+
http_client: httpx.AsyncClient,
|
|
464
|
+
host: str,
|
|
465
|
+
port: int,
|
|
466
|
+
message: str,
|
|
467
|
+
) -> dict[str, str]:
|
|
468
|
+
response = await http_client.post(
|
|
469
|
+
f"http://{host}:{port}/agent/run",
|
|
470
|
+
json={"message": message},
|
|
471
|
+
timeout=None,
|
|
472
|
+
)
|
|
473
|
+
response.raise_for_status()
|
|
474
|
+
return response.json()
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
async def _prompt_and_send_decision(
|
|
478
|
+
http_client: httpx.AsyncClient,
|
|
479
|
+
host: str,
|
|
480
|
+
port: int,
|
|
481
|
+
request: ApprovalRequest,
|
|
482
|
+
) -> str:
|
|
483
|
+
_print_pending_request(request)
|
|
484
|
+
mapping, invalid_message = _decision_mapping(request.tool_name)
|
|
485
|
+
|
|
486
|
+
while True:
|
|
487
|
+
user_input = await asyncio.to_thread(input, "> ")
|
|
488
|
+
stripped = user_input.strip()
|
|
489
|
+
if not stripped:
|
|
490
|
+
print(invalid_message)
|
|
491
|
+
continue
|
|
492
|
+
|
|
493
|
+
first_token, *rest = stripped.split(maxsplit=1)
|
|
494
|
+
token = first_token.lower()
|
|
495
|
+
decision = mapping.get(token)
|
|
496
|
+
|
|
497
|
+
if decision is None:
|
|
498
|
+
print(invalid_message)
|
|
499
|
+
continue
|
|
500
|
+
|
|
501
|
+
resp = await http_client.post(
|
|
502
|
+
f"http://{host}:{port}/hitl/decision",
|
|
503
|
+
json={
|
|
504
|
+
"request_id": request.request_id,
|
|
505
|
+
"decision": decision,
|
|
506
|
+
"operator_input": stripped,
|
|
507
|
+
},
|
|
508
|
+
timeout=None,
|
|
509
|
+
)
|
|
510
|
+
if resp.status_code != HTTPStatus.OK:
|
|
511
|
+
if resp.status_code in {HTTPStatus.NOT_FOUND, HTTPStatus.GONE}:
|
|
512
|
+
print(f"ā±ļø Request {request.request_id} expired (status {resp.status_code}). Skipping this action.")
|
|
513
|
+
return "timeout"
|
|
514
|
+
|
|
515
|
+
print(f"Failed to submit decision ({resp.status_code}): {resp.text}")
|
|
516
|
+
continue
|
|
517
|
+
|
|
518
|
+
comment = rest[0] if rest else ""
|
|
519
|
+
base_msg = f"Submitted '{decision}' for request {request.request_id}"
|
|
520
|
+
if comment:
|
|
521
|
+
base_msg += f" with comment: {comment}"
|
|
522
|
+
print(base_msg + ". Waiting for agent...\n")
|
|
523
|
+
return decision
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
@dataclass
|
|
527
|
+
class RunContext:
|
|
528
|
+
"""Context object containing runtime dependencies for the HITL demo server."""
|
|
529
|
+
|
|
530
|
+
http_client: httpx.AsyncClient
|
|
531
|
+
host: str
|
|
532
|
+
port: int
|
|
533
|
+
pending_queue: asyncio.Queue[ApprovalRequest]
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
async def _run_step(
|
|
537
|
+
context: RunContext,
|
|
538
|
+
message: str,
|
|
539
|
+
*,
|
|
540
|
+
step_name: str,
|
|
541
|
+
allowed_tools: set[str] | None = None,
|
|
542
|
+
) -> tuple[str, dict[str, str]]:
|
|
543
|
+
print(f"\nāā {step_name} āā")
|
|
544
|
+
|
|
545
|
+
_drain_queue(context.pending_queue)
|
|
546
|
+
|
|
547
|
+
run_task = asyncio.create_task(_invoke_run_endpoint(context.http_client, context.host, context.port, message))
|
|
548
|
+
queue_task = asyncio.create_task(context.pending_queue.get())
|
|
549
|
+
decisions: dict[str, str] = {}
|
|
550
|
+
|
|
551
|
+
try:
|
|
552
|
+
while True:
|
|
553
|
+
done, _ = await asyncio.wait({run_task, queue_task}, return_when=asyncio.FIRST_COMPLETED)
|
|
554
|
+
|
|
555
|
+
if run_task in done:
|
|
556
|
+
result = run_task.result()
|
|
557
|
+
queue_task.cancel()
|
|
558
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
559
|
+
await queue_task
|
|
560
|
+
|
|
561
|
+
output = result.get("output", "No output")
|
|
562
|
+
if output:
|
|
563
|
+
print("š¤ AI response:")
|
|
564
|
+
print(output)
|
|
565
|
+
else:
|
|
566
|
+
print("š¤ AI response: (no message returned)")
|
|
567
|
+
if decisions:
|
|
568
|
+
print("š HITL decisions during this step:")
|
|
569
|
+
for tool, decision in decisions.items():
|
|
570
|
+
print(f" - {tool}: {decision}")
|
|
571
|
+
return output, decisions
|
|
572
|
+
|
|
573
|
+
if queue_task in done:
|
|
574
|
+
request = queue_task.result()
|
|
575
|
+
|
|
576
|
+
if allowed_tools is not None and request.tool_name not in allowed_tools:
|
|
577
|
+
print(f"āļø Auto-skipping unexpected tool '{request.tool_name}' during {step_name}.")
|
|
578
|
+
await context.http_client.post(
|
|
579
|
+
f"http://{context.host}:{context.port}/hitl/decision",
|
|
580
|
+
json={
|
|
581
|
+
"request_id": request.request_id,
|
|
582
|
+
"decision": "skipped",
|
|
583
|
+
"operator_input": "AUTO_SKIP_UNEXPECTED_TOOL",
|
|
584
|
+
},
|
|
585
|
+
timeout=None,
|
|
586
|
+
)
|
|
587
|
+
else:
|
|
588
|
+
decision = await _prompt_and_send_decision(context.http_client, context.host, context.port, request)
|
|
589
|
+
decisions[request.tool_name] = decision
|
|
590
|
+
|
|
591
|
+
queue_task = asyncio.create_task(context.pending_queue.get())
|
|
592
|
+
|
|
593
|
+
except Exception as exc: # noqa: BLE001
|
|
594
|
+
run_task.cancel()
|
|
595
|
+
queue_task.cancel()
|
|
596
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
597
|
+
await run_task
|
|
598
|
+
await queue_task
|
|
599
|
+
_handle_run_error(exc)
|
|
600
|
+
return "", {}
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
async def _run_workflow(context: RunContext) -> None:
|
|
604
|
+
summaries: list[dict[str, str | bool]] = []
|
|
605
|
+
|
|
606
|
+
print("š¤ Workflow starting.")
|
|
607
|
+
print(" Step 1 (candidate inbox) runs automatically without approval.")
|
|
608
|
+
print(
|
|
609
|
+
" Steps 2 and 3 will prompt you for HITL decisions; use the keys listed in the command panel "
|
|
610
|
+
"(e.g., a/r or send/cancel)."
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
for profile in CANDIDATE_SEQUENCE:
|
|
614
|
+
name = profile["name"]
|
|
615
|
+
email = profile["email"]
|
|
616
|
+
|
|
617
|
+
print("\n==================================================")
|
|
618
|
+
print(f"Processing candidate: {name} ({email}) ā {profile['role']}")
|
|
619
|
+
print("==================================================")
|
|
620
|
+
recommendation = profile.get("recommendation", "pending")
|
|
621
|
+
score = profile.get("score", "n/a")
|
|
622
|
+
print(f"ATS recommendation: {recommendation} (score: {score})")
|
|
623
|
+
|
|
624
|
+
await _run_step(
|
|
625
|
+
context,
|
|
626
|
+
_candidate_inbox_query(profile),
|
|
627
|
+
step_name=f"{name}: Inbox review (no approval)",
|
|
628
|
+
allowed_tools=None,
|
|
629
|
+
)
|
|
630
|
+
|
|
631
|
+
_, validation_decisions = await _run_step(
|
|
632
|
+
context,
|
|
633
|
+
_validate_candidate_query(profile),
|
|
634
|
+
step_name=f"{name}: Validation (approval required)",
|
|
635
|
+
allowed_tools={"validate_candidate"},
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
raw_validation_status = validation_decisions.get("validate_candidate")
|
|
639
|
+
validation_status = _normalize_timeout_decision(raw_validation_status)
|
|
640
|
+
|
|
641
|
+
if raw_validation_status == "timeout":
|
|
642
|
+
print("ā±ļø Validation timed out ā treating as skipped; the decision remains pending.")
|
|
643
|
+
elif validation_status == "rejected":
|
|
644
|
+
print("ā Validation rejected ā the candidate will be notified of the decision.")
|
|
645
|
+
elif validation_status == "skipped":
|
|
646
|
+
print("ā ļø Validation skipped ā notification will indicate the decision is still pending.")
|
|
647
|
+
else:
|
|
648
|
+
print("ā
Candidate validation approved ā proceeding to send offer confirmation.")
|
|
649
|
+
|
|
650
|
+
_, email_decisions = await _run_step(
|
|
651
|
+
context,
|
|
652
|
+
_candidate_email_query(profile, validation_status),
|
|
653
|
+
step_name=f"{name}: Candidate email (approval required)",
|
|
654
|
+
allowed_tools={"send_candidate_email"},
|
|
655
|
+
)
|
|
656
|
+
|
|
657
|
+
raw_email_status = email_decisions.get("send_candidate_email")
|
|
658
|
+
email_status = _normalize_timeout_decision(raw_email_status)
|
|
659
|
+
|
|
660
|
+
if raw_email_status == "timeout":
|
|
661
|
+
print("ā±ļø Email send timed out ā treating as skipped; no notification was sent.")
|
|
662
|
+
|
|
663
|
+
summaries.append(
|
|
664
|
+
{
|
|
665
|
+
"name": name,
|
|
666
|
+
"validation": validation_status,
|
|
667
|
+
"validation_timeout": raw_validation_status == "timeout",
|
|
668
|
+
"email_decision": email_status,
|
|
669
|
+
"email_timeout": raw_email_status == "timeout",
|
|
670
|
+
}
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
summary_messages: list[str] = [
|
|
674
|
+
_summarize_outcome(
|
|
675
|
+
name=summary["name"],
|
|
676
|
+
validation=summary["validation"],
|
|
677
|
+
email=summary["email_decision"],
|
|
678
|
+
validation_timeout=bool(summary.get("validation_timeout", False)),
|
|
679
|
+
email_timeout=bool(summary.get("email_timeout", False)),
|
|
680
|
+
)
|
|
681
|
+
for summary in summaries
|
|
682
|
+
]
|
|
683
|
+
|
|
684
|
+
print("\nš What happened:")
|
|
685
|
+
for message in summary_messages:
|
|
686
|
+
print(message)
|
|
687
|
+
|
|
688
|
+
print("\nš Workflow completed successfully!")
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
def _handle_run_error(error: Exception) -> None:
|
|
692
|
+
message = str(error)
|
|
693
|
+
if "api_key" in message.lower():
|
|
694
|
+
print("\nā Error: OpenAI API key not found!")
|
|
695
|
+
print("Make sure OPENAI_API_KEY is set in your environment or .env file.")
|
|
696
|
+
print("The demo automatically loads from .env files, so create one with:")
|
|
697
|
+
print(" echo 'OPENAI_API_KEY=your-key-here' > .env")
|
|
698
|
+
else:
|
|
699
|
+
print(f"\nā Error: {error}")
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
async def main():
|
|
703
|
+
"""Interactive HITL approval demo."""
|
|
704
|
+
_print_intro()
|
|
705
|
+
agent = _build_demo_agent()
|
|
706
|
+
app, pending_queue = _create_server_app(agent)
|
|
707
|
+
host, port = "127.0.0.1", 8787
|
|
708
|
+
|
|
709
|
+
async with _ServerContext(app, host, port):
|
|
710
|
+
print(f"š„ļø HITL API listening on http://{host}:{port}")
|
|
711
|
+
print(' POST /agent/run {"message": ...}')
|
|
712
|
+
print(' POST /hitl/decision {"request_id": ..., "decision": ...}')
|
|
713
|
+
async with httpx.AsyncClient(timeout=None) as http_client:
|
|
714
|
+
context = RunContext(
|
|
715
|
+
http_client=http_client,
|
|
716
|
+
host=host,
|
|
717
|
+
port=port,
|
|
718
|
+
pending_queue=pending_queue,
|
|
719
|
+
)
|
|
720
|
+
await _run_workflow(context)
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
if __name__ == "__main__":
|
|
724
|
+
asyncio.run(main())
|