nvidia-nat 1.2.0rc5__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.
- aiq/agent/__init__.py +0 -0
- aiq/agent/base.py +239 -0
- aiq/agent/dual_node.py +67 -0
- aiq/agent/react_agent/__init__.py +0 -0
- aiq/agent/react_agent/agent.py +355 -0
- aiq/agent/react_agent/output_parser.py +104 -0
- aiq/agent/react_agent/prompt.py +41 -0
- aiq/agent/react_agent/register.py +149 -0
- aiq/agent/reasoning_agent/__init__.py +0 -0
- aiq/agent/reasoning_agent/reasoning_agent.py +225 -0
- aiq/agent/register.py +23 -0
- aiq/agent/rewoo_agent/__init__.py +0 -0
- aiq/agent/rewoo_agent/agent.py +411 -0
- aiq/agent/rewoo_agent/prompt.py +108 -0
- aiq/agent/rewoo_agent/register.py +158 -0
- aiq/agent/tool_calling_agent/__init__.py +0 -0
- aiq/agent/tool_calling_agent/agent.py +119 -0
- aiq/agent/tool_calling_agent/register.py +106 -0
- aiq/authentication/__init__.py +14 -0
- aiq/authentication/api_key/__init__.py +14 -0
- aiq/authentication/api_key/api_key_auth_provider.py +96 -0
- aiq/authentication/api_key/api_key_auth_provider_config.py +124 -0
- aiq/authentication/api_key/register.py +26 -0
- aiq/authentication/exceptions/__init__.py +14 -0
- aiq/authentication/exceptions/api_key_exceptions.py +38 -0
- aiq/authentication/http_basic_auth/__init__.py +0 -0
- aiq/authentication/http_basic_auth/http_basic_auth_provider.py +81 -0
- aiq/authentication/http_basic_auth/register.py +30 -0
- aiq/authentication/interfaces.py +93 -0
- aiq/authentication/oauth2/__init__.py +14 -0
- aiq/authentication/oauth2/oauth2_auth_code_flow_provider.py +107 -0
- aiq/authentication/oauth2/oauth2_auth_code_flow_provider_config.py +39 -0
- aiq/authentication/oauth2/register.py +25 -0
- aiq/authentication/register.py +21 -0
- aiq/builder/__init__.py +0 -0
- aiq/builder/builder.py +285 -0
- aiq/builder/component_utils.py +316 -0
- aiq/builder/context.py +264 -0
- aiq/builder/embedder.py +24 -0
- aiq/builder/eval_builder.py +161 -0
- aiq/builder/evaluator.py +29 -0
- aiq/builder/framework_enum.py +24 -0
- aiq/builder/front_end.py +73 -0
- aiq/builder/function.py +344 -0
- aiq/builder/function_base.py +380 -0
- aiq/builder/function_info.py +627 -0
- aiq/builder/intermediate_step_manager.py +174 -0
- aiq/builder/llm.py +25 -0
- aiq/builder/retriever.py +25 -0
- aiq/builder/user_interaction_manager.py +74 -0
- aiq/builder/workflow.py +148 -0
- aiq/builder/workflow_builder.py +1117 -0
- aiq/cli/__init__.py +14 -0
- aiq/cli/cli_utils/__init__.py +0 -0
- aiq/cli/cli_utils/config_override.py +231 -0
- aiq/cli/cli_utils/validation.py +37 -0
- aiq/cli/commands/__init__.py +0 -0
- aiq/cli/commands/configure/__init__.py +0 -0
- aiq/cli/commands/configure/channel/__init__.py +0 -0
- aiq/cli/commands/configure/channel/add.py +28 -0
- aiq/cli/commands/configure/channel/channel.py +36 -0
- aiq/cli/commands/configure/channel/remove.py +30 -0
- aiq/cli/commands/configure/channel/update.py +30 -0
- aiq/cli/commands/configure/configure.py +33 -0
- aiq/cli/commands/evaluate.py +139 -0
- aiq/cli/commands/info/__init__.py +14 -0
- aiq/cli/commands/info/info.py +39 -0
- aiq/cli/commands/info/list_channels.py +32 -0
- aiq/cli/commands/info/list_components.py +129 -0
- aiq/cli/commands/info/list_mcp.py +213 -0
- aiq/cli/commands/registry/__init__.py +14 -0
- aiq/cli/commands/registry/publish.py +88 -0
- aiq/cli/commands/registry/pull.py +118 -0
- aiq/cli/commands/registry/registry.py +38 -0
- aiq/cli/commands/registry/remove.py +108 -0
- aiq/cli/commands/registry/search.py +155 -0
- aiq/cli/commands/sizing/__init__.py +14 -0
- aiq/cli/commands/sizing/calc.py +297 -0
- aiq/cli/commands/sizing/sizing.py +27 -0
- aiq/cli/commands/start.py +246 -0
- aiq/cli/commands/uninstall.py +81 -0
- aiq/cli/commands/validate.py +47 -0
- aiq/cli/commands/workflow/__init__.py +14 -0
- aiq/cli/commands/workflow/templates/__init__.py.j2 +0 -0
- aiq/cli/commands/workflow/templates/config.yml.j2 +16 -0
- aiq/cli/commands/workflow/templates/pyproject.toml.j2 +22 -0
- aiq/cli/commands/workflow/templates/register.py.j2 +5 -0
- aiq/cli/commands/workflow/templates/workflow.py.j2 +36 -0
- aiq/cli/commands/workflow/workflow.py +37 -0
- aiq/cli/commands/workflow/workflow_commands.py +313 -0
- aiq/cli/entrypoint.py +135 -0
- aiq/cli/main.py +44 -0
- aiq/cli/register_workflow.py +488 -0
- aiq/cli/type_registry.py +1000 -0
- aiq/data_models/__init__.py +14 -0
- aiq/data_models/api_server.py +694 -0
- aiq/data_models/authentication.py +231 -0
- aiq/data_models/common.py +171 -0
- aiq/data_models/component.py +54 -0
- aiq/data_models/component_ref.py +168 -0
- aiq/data_models/config.py +406 -0
- aiq/data_models/dataset_handler.py +123 -0
- aiq/data_models/discovery_metadata.py +335 -0
- aiq/data_models/embedder.py +27 -0
- aiq/data_models/evaluate.py +127 -0
- aiq/data_models/evaluator.py +26 -0
- aiq/data_models/front_end.py +26 -0
- aiq/data_models/function.py +30 -0
- aiq/data_models/function_dependencies.py +72 -0
- aiq/data_models/interactive.py +246 -0
- aiq/data_models/intermediate_step.py +302 -0
- aiq/data_models/invocation_node.py +38 -0
- aiq/data_models/llm.py +27 -0
- aiq/data_models/logging.py +26 -0
- aiq/data_models/memory.py +27 -0
- aiq/data_models/object_store.py +44 -0
- aiq/data_models/profiler.py +54 -0
- aiq/data_models/registry_handler.py +26 -0
- aiq/data_models/retriever.py +30 -0
- aiq/data_models/retry_mixin.py +35 -0
- aiq/data_models/span.py +187 -0
- aiq/data_models/step_adaptor.py +64 -0
- aiq/data_models/streaming.py +33 -0
- aiq/data_models/swe_bench_model.py +54 -0
- aiq/data_models/telemetry_exporter.py +26 -0
- aiq/data_models/ttc_strategy.py +30 -0
- aiq/embedder/__init__.py +0 -0
- aiq/embedder/langchain_client.py +41 -0
- aiq/embedder/nim_embedder.py +59 -0
- aiq/embedder/openai_embedder.py +43 -0
- aiq/embedder/register.py +24 -0
- aiq/eval/__init__.py +14 -0
- aiq/eval/config.py +60 -0
- aiq/eval/dataset_handler/__init__.py +0 -0
- aiq/eval/dataset_handler/dataset_downloader.py +106 -0
- aiq/eval/dataset_handler/dataset_filter.py +52 -0
- aiq/eval/dataset_handler/dataset_handler.py +254 -0
- aiq/eval/evaluate.py +506 -0
- aiq/eval/evaluator/__init__.py +14 -0
- aiq/eval/evaluator/base_evaluator.py +73 -0
- aiq/eval/evaluator/evaluator_model.py +45 -0
- aiq/eval/intermediate_step_adapter.py +99 -0
- aiq/eval/rag_evaluator/__init__.py +0 -0
- aiq/eval/rag_evaluator/evaluate.py +178 -0
- aiq/eval/rag_evaluator/register.py +143 -0
- aiq/eval/register.py +23 -0
- aiq/eval/remote_workflow.py +133 -0
- aiq/eval/runners/__init__.py +14 -0
- aiq/eval/runners/config.py +39 -0
- aiq/eval/runners/multi_eval_runner.py +54 -0
- aiq/eval/runtime_event_subscriber.py +52 -0
- aiq/eval/swe_bench_evaluator/__init__.py +0 -0
- aiq/eval/swe_bench_evaluator/evaluate.py +215 -0
- aiq/eval/swe_bench_evaluator/register.py +36 -0
- aiq/eval/trajectory_evaluator/__init__.py +0 -0
- aiq/eval/trajectory_evaluator/evaluate.py +75 -0
- aiq/eval/trajectory_evaluator/register.py +40 -0
- aiq/eval/tunable_rag_evaluator/__init__.py +0 -0
- aiq/eval/tunable_rag_evaluator/evaluate.py +245 -0
- aiq/eval/tunable_rag_evaluator/register.py +52 -0
- aiq/eval/usage_stats.py +41 -0
- aiq/eval/utils/__init__.py +0 -0
- aiq/eval/utils/output_uploader.py +140 -0
- aiq/eval/utils/tqdm_position_registry.py +40 -0
- aiq/eval/utils/weave_eval.py +184 -0
- aiq/experimental/__init__.py +0 -0
- aiq/experimental/decorators/__init__.py +0 -0
- aiq/experimental/decorators/experimental_warning_decorator.py +130 -0
- aiq/experimental/test_time_compute/__init__.py +0 -0
- aiq/experimental/test_time_compute/editing/__init__.py +0 -0
- aiq/experimental/test_time_compute/editing/iterative_plan_refinement_editor.py +147 -0
- aiq/experimental/test_time_compute/editing/llm_as_a_judge_editor.py +204 -0
- aiq/experimental/test_time_compute/editing/motivation_aware_summarization.py +107 -0
- aiq/experimental/test_time_compute/functions/__init__.py +0 -0
- aiq/experimental/test_time_compute/functions/execute_score_select_function.py +105 -0
- aiq/experimental/test_time_compute/functions/its_tool_orchestration_function.py +205 -0
- aiq/experimental/test_time_compute/functions/its_tool_wrapper_function.py +146 -0
- aiq/experimental/test_time_compute/functions/plan_select_execute_function.py +224 -0
- aiq/experimental/test_time_compute/models/__init__.py +0 -0
- aiq/experimental/test_time_compute/models/editor_config.py +132 -0
- aiq/experimental/test_time_compute/models/scoring_config.py +112 -0
- aiq/experimental/test_time_compute/models/search_config.py +120 -0
- aiq/experimental/test_time_compute/models/selection_config.py +154 -0
- aiq/experimental/test_time_compute/models/stage_enums.py +43 -0
- aiq/experimental/test_time_compute/models/strategy_base.py +66 -0
- aiq/experimental/test_time_compute/models/tool_use_config.py +41 -0
- aiq/experimental/test_time_compute/models/ttc_item.py +48 -0
- aiq/experimental/test_time_compute/register.py +36 -0
- aiq/experimental/test_time_compute/scoring/__init__.py +0 -0
- aiq/experimental/test_time_compute/scoring/llm_based_agent_scorer.py +168 -0
- aiq/experimental/test_time_compute/scoring/llm_based_plan_scorer.py +168 -0
- aiq/experimental/test_time_compute/scoring/motivation_aware_scorer.py +111 -0
- aiq/experimental/test_time_compute/search/__init__.py +0 -0
- aiq/experimental/test_time_compute/search/multi_llm_planner.py +128 -0
- aiq/experimental/test_time_compute/search/multi_query_retrieval_search.py +122 -0
- aiq/experimental/test_time_compute/search/single_shot_multi_plan_planner.py +128 -0
- aiq/experimental/test_time_compute/selection/__init__.py +0 -0
- aiq/experimental/test_time_compute/selection/best_of_n_selector.py +63 -0
- aiq/experimental/test_time_compute/selection/llm_based_agent_output_selector.py +131 -0
- aiq/experimental/test_time_compute/selection/llm_based_output_merging_selector.py +159 -0
- aiq/experimental/test_time_compute/selection/llm_based_plan_selector.py +128 -0
- aiq/experimental/test_time_compute/selection/threshold_selector.py +58 -0
- aiq/front_ends/__init__.py +14 -0
- aiq/front_ends/console/__init__.py +14 -0
- aiq/front_ends/console/authentication_flow_handler.py +233 -0
- aiq/front_ends/console/console_front_end_config.py +32 -0
- aiq/front_ends/console/console_front_end_plugin.py +96 -0
- aiq/front_ends/console/register.py +25 -0
- aiq/front_ends/cron/__init__.py +14 -0
- aiq/front_ends/fastapi/__init__.py +14 -0
- aiq/front_ends/fastapi/auth_flow_handlers/__init__.py +0 -0
- aiq/front_ends/fastapi/auth_flow_handlers/http_flow_handler.py +27 -0
- aiq/front_ends/fastapi/auth_flow_handlers/websocket_flow_handler.py +107 -0
- aiq/front_ends/fastapi/fastapi_front_end_config.py +234 -0
- aiq/front_ends/fastapi/fastapi_front_end_controller.py +68 -0
- aiq/front_ends/fastapi/fastapi_front_end_plugin.py +116 -0
- aiq/front_ends/fastapi/fastapi_front_end_plugin_worker.py +1092 -0
- aiq/front_ends/fastapi/html_snippets/__init__.py +14 -0
- aiq/front_ends/fastapi/html_snippets/auth_code_grant_success.py +35 -0
- aiq/front_ends/fastapi/intermediate_steps_subscriber.py +80 -0
- aiq/front_ends/fastapi/job_store.py +183 -0
- aiq/front_ends/fastapi/main.py +72 -0
- aiq/front_ends/fastapi/message_handler.py +298 -0
- aiq/front_ends/fastapi/message_validator.py +345 -0
- aiq/front_ends/fastapi/register.py +25 -0
- aiq/front_ends/fastapi/response_helpers.py +195 -0
- aiq/front_ends/fastapi/step_adaptor.py +321 -0
- aiq/front_ends/mcp/__init__.py +14 -0
- aiq/front_ends/mcp/mcp_front_end_config.py +32 -0
- aiq/front_ends/mcp/mcp_front_end_plugin.py +93 -0
- aiq/front_ends/mcp/register.py +27 -0
- aiq/front_ends/mcp/tool_converter.py +242 -0
- aiq/front_ends/register.py +22 -0
- aiq/front_ends/simple_base/__init__.py +14 -0
- aiq/front_ends/simple_base/simple_front_end_plugin_base.py +54 -0
- aiq/llm/__init__.py +0 -0
- aiq/llm/aws_bedrock_llm.py +57 -0
- aiq/llm/nim_llm.py +46 -0
- aiq/llm/openai_llm.py +46 -0
- aiq/llm/register.py +23 -0
- aiq/llm/utils/__init__.py +14 -0
- aiq/llm/utils/env_config_value.py +94 -0
- aiq/llm/utils/error.py +17 -0
- aiq/memory/__init__.py +20 -0
- aiq/memory/interfaces.py +183 -0
- aiq/memory/models.py +112 -0
- aiq/meta/module_to_distro.json +3 -0
- aiq/meta/pypi.md +58 -0
- aiq/object_store/__init__.py +20 -0
- aiq/object_store/in_memory_object_store.py +76 -0
- aiq/object_store/interfaces.py +84 -0
- aiq/object_store/models.py +36 -0
- aiq/object_store/register.py +20 -0
- aiq/observability/__init__.py +14 -0
- aiq/observability/exporter/__init__.py +14 -0
- aiq/observability/exporter/base_exporter.py +449 -0
- aiq/observability/exporter/exporter.py +78 -0
- aiq/observability/exporter/file_exporter.py +33 -0
- aiq/observability/exporter/processing_exporter.py +322 -0
- aiq/observability/exporter/raw_exporter.py +52 -0
- aiq/observability/exporter/span_exporter.py +265 -0
- aiq/observability/exporter_manager.py +335 -0
- aiq/observability/mixin/__init__.py +14 -0
- aiq/observability/mixin/batch_config_mixin.py +26 -0
- aiq/observability/mixin/collector_config_mixin.py +23 -0
- aiq/observability/mixin/file_mixin.py +288 -0
- aiq/observability/mixin/file_mode.py +23 -0
- aiq/observability/mixin/resource_conflict_mixin.py +134 -0
- aiq/observability/mixin/serialize_mixin.py +61 -0
- aiq/observability/mixin/type_introspection_mixin.py +183 -0
- aiq/observability/processor/__init__.py +14 -0
- aiq/observability/processor/batching_processor.py +310 -0
- aiq/observability/processor/callback_processor.py +42 -0
- aiq/observability/processor/intermediate_step_serializer.py +28 -0
- aiq/observability/processor/processor.py +71 -0
- aiq/observability/register.py +96 -0
- aiq/observability/utils/__init__.py +14 -0
- aiq/observability/utils/dict_utils.py +236 -0
- aiq/observability/utils/time_utils.py +31 -0
- aiq/plugins/.namespace +1 -0
- aiq/profiler/__init__.py +0 -0
- aiq/profiler/calc/__init__.py +14 -0
- aiq/profiler/calc/calc_runner.py +627 -0
- aiq/profiler/calc/calculations.py +288 -0
- aiq/profiler/calc/data_models.py +188 -0
- aiq/profiler/calc/plot.py +345 -0
- aiq/profiler/callbacks/__init__.py +0 -0
- aiq/profiler/callbacks/agno_callback_handler.py +295 -0
- aiq/profiler/callbacks/base_callback_class.py +20 -0
- aiq/profiler/callbacks/langchain_callback_handler.py +290 -0
- aiq/profiler/callbacks/llama_index_callback_handler.py +205 -0
- aiq/profiler/callbacks/semantic_kernel_callback_handler.py +238 -0
- aiq/profiler/callbacks/token_usage_base_model.py +27 -0
- aiq/profiler/data_frame_row.py +51 -0
- aiq/profiler/data_models.py +24 -0
- aiq/profiler/decorators/__init__.py +0 -0
- aiq/profiler/decorators/framework_wrapper.py +131 -0
- aiq/profiler/decorators/function_tracking.py +254 -0
- aiq/profiler/forecasting/__init__.py +0 -0
- aiq/profiler/forecasting/config.py +18 -0
- aiq/profiler/forecasting/model_trainer.py +75 -0
- aiq/profiler/forecasting/models/__init__.py +22 -0
- aiq/profiler/forecasting/models/forecasting_base_model.py +40 -0
- aiq/profiler/forecasting/models/linear_model.py +196 -0
- aiq/profiler/forecasting/models/random_forest_regressor.py +268 -0
- aiq/profiler/inference_metrics_model.py +28 -0
- aiq/profiler/inference_optimization/__init__.py +0 -0
- aiq/profiler/inference_optimization/bottleneck_analysis/__init__.py +0 -0
- aiq/profiler/inference_optimization/bottleneck_analysis/nested_stack_analysis.py +460 -0
- aiq/profiler/inference_optimization/bottleneck_analysis/simple_stack_analysis.py +258 -0
- aiq/profiler/inference_optimization/data_models.py +386 -0
- aiq/profiler/inference_optimization/experimental/__init__.py +0 -0
- aiq/profiler/inference_optimization/experimental/concurrency_spike_analysis.py +468 -0
- aiq/profiler/inference_optimization/experimental/prefix_span_analysis.py +405 -0
- aiq/profiler/inference_optimization/llm_metrics.py +212 -0
- aiq/profiler/inference_optimization/prompt_caching.py +163 -0
- aiq/profiler/inference_optimization/token_uniqueness.py +107 -0
- aiq/profiler/inference_optimization/workflow_runtimes.py +72 -0
- aiq/profiler/intermediate_property_adapter.py +102 -0
- aiq/profiler/profile_runner.py +473 -0
- aiq/profiler/utils.py +184 -0
- aiq/registry_handlers/__init__.py +0 -0
- aiq/registry_handlers/local/__init__.py +0 -0
- aiq/registry_handlers/local/local_handler.py +176 -0
- aiq/registry_handlers/local/register_local.py +37 -0
- aiq/registry_handlers/metadata_factory.py +60 -0
- aiq/registry_handlers/package_utils.py +567 -0
- aiq/registry_handlers/pypi/__init__.py +0 -0
- aiq/registry_handlers/pypi/pypi_handler.py +251 -0
- aiq/registry_handlers/pypi/register_pypi.py +40 -0
- aiq/registry_handlers/register.py +21 -0
- aiq/registry_handlers/registry_handler_base.py +157 -0
- aiq/registry_handlers/rest/__init__.py +0 -0
- aiq/registry_handlers/rest/register_rest.py +56 -0
- aiq/registry_handlers/rest/rest_handler.py +237 -0
- aiq/registry_handlers/schemas/__init__.py +0 -0
- aiq/registry_handlers/schemas/headers.py +42 -0
- aiq/registry_handlers/schemas/package.py +68 -0
- aiq/registry_handlers/schemas/publish.py +63 -0
- aiq/registry_handlers/schemas/pull.py +82 -0
- aiq/registry_handlers/schemas/remove.py +36 -0
- aiq/registry_handlers/schemas/search.py +91 -0
- aiq/registry_handlers/schemas/status.py +47 -0
- aiq/retriever/__init__.py +0 -0
- aiq/retriever/interface.py +37 -0
- aiq/retriever/milvus/__init__.py +14 -0
- aiq/retriever/milvus/register.py +81 -0
- aiq/retriever/milvus/retriever.py +228 -0
- aiq/retriever/models.py +74 -0
- aiq/retriever/nemo_retriever/__init__.py +14 -0
- aiq/retriever/nemo_retriever/register.py +60 -0
- aiq/retriever/nemo_retriever/retriever.py +190 -0
- aiq/retriever/register.py +22 -0
- aiq/runtime/__init__.py +14 -0
- aiq/runtime/loader.py +215 -0
- aiq/runtime/runner.py +190 -0
- aiq/runtime/session.py +158 -0
- aiq/runtime/user_metadata.py +130 -0
- aiq/settings/__init__.py +0 -0
- aiq/settings/global_settings.py +318 -0
- aiq/test/.namespace +1 -0
- aiq/tool/__init__.py +0 -0
- aiq/tool/chat_completion.py +74 -0
- aiq/tool/code_execution/README.md +151 -0
- aiq/tool/code_execution/__init__.py +0 -0
- aiq/tool/code_execution/code_sandbox.py +267 -0
- aiq/tool/code_execution/local_sandbox/.gitignore +1 -0
- aiq/tool/code_execution/local_sandbox/Dockerfile.sandbox +60 -0
- aiq/tool/code_execution/local_sandbox/__init__.py +13 -0
- aiq/tool/code_execution/local_sandbox/local_sandbox_server.py +198 -0
- aiq/tool/code_execution/local_sandbox/sandbox.requirements.txt +6 -0
- aiq/tool/code_execution/local_sandbox/start_local_sandbox.sh +50 -0
- aiq/tool/code_execution/register.py +74 -0
- aiq/tool/code_execution/test_code_execution_sandbox.py +414 -0
- aiq/tool/code_execution/utils.py +100 -0
- aiq/tool/datetime_tools.py +42 -0
- aiq/tool/document_search.py +141 -0
- aiq/tool/github_tools/__init__.py +0 -0
- aiq/tool/github_tools/create_github_commit.py +133 -0
- aiq/tool/github_tools/create_github_issue.py +87 -0
- aiq/tool/github_tools/create_github_pr.py +106 -0
- aiq/tool/github_tools/get_github_file.py +106 -0
- aiq/tool/github_tools/get_github_issue.py +166 -0
- aiq/tool/github_tools/get_github_pr.py +256 -0
- aiq/tool/github_tools/update_github_issue.py +100 -0
- aiq/tool/mcp/__init__.py +14 -0
- aiq/tool/mcp/exceptions.py +142 -0
- aiq/tool/mcp/mcp_client.py +255 -0
- aiq/tool/mcp/mcp_tool.py +96 -0
- aiq/tool/memory_tools/__init__.py +0 -0
- aiq/tool/memory_tools/add_memory_tool.py +79 -0
- aiq/tool/memory_tools/delete_memory_tool.py +67 -0
- aiq/tool/memory_tools/get_memory_tool.py +72 -0
- aiq/tool/nvidia_rag.py +95 -0
- aiq/tool/register.py +38 -0
- aiq/tool/retriever.py +89 -0
- aiq/tool/server_tools.py +66 -0
- aiq/utils/__init__.py +0 -0
- aiq/utils/data_models/__init__.py +0 -0
- aiq/utils/data_models/schema_validator.py +58 -0
- aiq/utils/debugging_utils.py +43 -0
- aiq/utils/dump_distro_mapping.py +32 -0
- aiq/utils/exception_handlers/__init__.py +0 -0
- aiq/utils/exception_handlers/automatic_retries.py +289 -0
- aiq/utils/exception_handlers/mcp.py +211 -0
- aiq/utils/exception_handlers/schemas.py +114 -0
- aiq/utils/io/__init__.py +0 -0
- aiq/utils/io/model_processing.py +28 -0
- aiq/utils/io/yaml_tools.py +119 -0
- aiq/utils/log_utils.py +37 -0
- aiq/utils/metadata_utils.py +74 -0
- aiq/utils/optional_imports.py +142 -0
- aiq/utils/producer_consumer_queue.py +178 -0
- aiq/utils/reactive/__init__.py +0 -0
- aiq/utils/reactive/base/__init__.py +0 -0
- aiq/utils/reactive/base/observable_base.py +65 -0
- aiq/utils/reactive/base/observer_base.py +55 -0
- aiq/utils/reactive/base/subject_base.py +79 -0
- aiq/utils/reactive/observable.py +59 -0
- aiq/utils/reactive/observer.py +76 -0
- aiq/utils/reactive/subject.py +131 -0
- aiq/utils/reactive/subscription.py +49 -0
- aiq/utils/settings/__init__.py +0 -0
- aiq/utils/settings/global_settings.py +197 -0
- aiq/utils/string_utils.py +38 -0
- aiq/utils/type_converter.py +290 -0
- aiq/utils/type_utils.py +484 -0
- aiq/utils/url_utils.py +27 -0
- nvidia_nat-1.2.0rc5.dist-info/METADATA +363 -0
- nvidia_nat-1.2.0rc5.dist-info/RECORD +435 -0
- nvidia_nat-1.2.0rc5.dist-info/WHEEL +5 -0
- nvidia_nat-1.2.0rc5.dist-info/entry_points.txt +20 -0
- nvidia_nat-1.2.0rc5.dist-info/licenses/LICENSE-3rd-party.txt +3686 -0
- nvidia_nat-1.2.0rc5.dist-info/licenses/LICENSE.md +201 -0
- nvidia_nat-1.2.0rc5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
3
|
+
SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
you may not use this file except in compliance with the License.
|
|
7
|
+
You may obtain a copy of the License at
|
|
8
|
+
|
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
|
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
See the License for the specific language governing permissions and
|
|
15
|
+
limitations under the License.
|
|
16
|
+
-->
|
|
17
|
+
|
|
18
|
+
# Code Execution Sandbox
|
|
19
|
+
|
|
20
|
+
A secure, containerized Python code execution environment that allows safe execution of Python code with comprehensive error handling and debugging capabilities.
|
|
21
|
+
|
|
22
|
+
## Overview
|
|
23
|
+
|
|
24
|
+
The Code Execution Sandbox provides:
|
|
25
|
+
- **Secure code execution** in isolated Docker containers
|
|
26
|
+
- **Multiple input formats** including raw code, dictionary format, and markdown
|
|
27
|
+
- **Dependency management** with pre-installed libraries
|
|
28
|
+
- **Flexible configuration** with customizable timeouts and output limits
|
|
29
|
+
- **Robust debugging** with extensive logging and error reporting
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
### Step 1: Start the Sandbox Server
|
|
34
|
+
|
|
35
|
+
Navigate to the local sandbox directory and start the server:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
cd src/aiq/tool/code_execution/local_sandbox
|
|
39
|
+
./start_local_sandbox.sh
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The script will:
|
|
43
|
+
- Build the Docker image if it doesn't exist
|
|
44
|
+
- Start the sandbox server on port 6000
|
|
45
|
+
- Mount your working directory for file operations
|
|
46
|
+
|
|
47
|
+
#### Advanced Usage:
|
|
48
|
+
```bash
|
|
49
|
+
# Custom container name
|
|
50
|
+
./start_local_sandbox.sh my-sandbox
|
|
51
|
+
|
|
52
|
+
# Custom output directory
|
|
53
|
+
./start_local_sandbox.sh my-sandbox /path/to/output
|
|
54
|
+
|
|
55
|
+
# Using environment variable
|
|
56
|
+
export OUTPUT_DATA_PATH=/path/to/output
|
|
57
|
+
./start_local_sandbox.sh
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Step 2: Test the Installation
|
|
61
|
+
|
|
62
|
+
Run the comprehensive test suite to verify everything is working:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
cd src/aiq/tool/code_execution
|
|
66
|
+
pytest test_code_execution_sandbox.py
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Note: a running instance of a local sandbox is required.
|
|
70
|
+
|
|
71
|
+
## Using the Code Execution Tool
|
|
72
|
+
|
|
73
|
+
### Basic Usage
|
|
74
|
+
|
|
75
|
+
The sandbox accepts HTTP POST requests to `http://localhost:6000/execute` with JSON payloads:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
curl -X POST \
|
|
79
|
+
-H "Content-Type: application/json" \
|
|
80
|
+
-d '{
|
|
81
|
+
"generated_code": "print(\"Hello, World!\")",
|
|
82
|
+
"timeout": 30,
|
|
83
|
+
"language": "python"
|
|
84
|
+
}' \
|
|
85
|
+
http://localhost:6000/execute
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Supported Input Formats
|
|
89
|
+
|
|
90
|
+
#### 1. Raw Python Code
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"generated_code": "import numpy as np\nprint(np.array([1, 2, 3]))",
|
|
94
|
+
"timeout": 30,
|
|
95
|
+
"language": "python"
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
#### 2. Dictionary Format
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"generated_code": "{'generated_code': 'print(\"Hello from dict format\")'}",
|
|
103
|
+
"timeout": 30,
|
|
104
|
+
"language": "python"
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### 3. Markdown Code Blocks
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"generated_code": "```python\nprint('Hello from markdown')\n```",
|
|
112
|
+
"timeout": 30,
|
|
113
|
+
"language": "python"
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Response Format
|
|
118
|
+
|
|
119
|
+
The sandbox returns JSON responses with the following structure:
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"process_status": "completed|error|timeout",
|
|
124
|
+
"stdout": "Standard output content",
|
|
125
|
+
"stderr": "Standard error content"
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Configuration Options
|
|
130
|
+
|
|
131
|
+
### Sandbox Configuration
|
|
132
|
+
|
|
133
|
+
- **URI**: Default `http://127.0.0.1:6000`
|
|
134
|
+
- **Timeout**: Default 10 seconds (configurable)
|
|
135
|
+
- **Max Output Characters**: Default 1000 characters
|
|
136
|
+
- **Memory Limit**: 10GB (configurable in Docker)
|
|
137
|
+
- **Working Directory**: Mounted volume for file operations
|
|
138
|
+
|
|
139
|
+
### Environment Variables
|
|
140
|
+
|
|
141
|
+
- `OUTPUT_DATA_PATH`: Custom path for file operations
|
|
142
|
+
- `SANDBOX_HOST`: Custom sandbox host
|
|
143
|
+
- `SANDBOX_PORT`: Custom sandbox port
|
|
144
|
+
|
|
145
|
+
## Security Considerations
|
|
146
|
+
|
|
147
|
+
- **Isolated execution**: All code runs in Docker containers
|
|
148
|
+
- **Resource limits**: Memory and CPU limits prevent resource exhaustion
|
|
149
|
+
- **Network isolation**: Containers have limited network access
|
|
150
|
+
- **File system isolation**: Mounted volumes provide controlled file access
|
|
151
|
+
- **Process isolation**: Each execution runs in a separate process
|
|
File without changes
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# Copyright (c) 2024-2025, NVIDIA CORPORATION. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
import abc
|
|
16
|
+
import json
|
|
17
|
+
import logging
|
|
18
|
+
import textwrap
|
|
19
|
+
from typing import Any
|
|
20
|
+
from urllib.parse import urljoin
|
|
21
|
+
|
|
22
|
+
import requests
|
|
23
|
+
import requests.adapters
|
|
24
|
+
from pydantic import HttpUrl
|
|
25
|
+
|
|
26
|
+
from aiq.utils.type_utils import override
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__file__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Sandbox(abc.ABC):
|
|
32
|
+
"""Code execution sandbox.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
host: Optional[str] = '127.0.0.1' - Host of the sandbox server.
|
|
36
|
+
Can also be specified through NEMO_SKILLS_SANDBOX_HOST env var.
|
|
37
|
+
port: Optional[str] = '5000' - Port of the sandbox server.
|
|
38
|
+
Can also be specified through NEMO_SKILLS_SANDBOX_PORT env var.
|
|
39
|
+
ssh_server: Optional[str] = None - SSH server for tunneling requests.
|
|
40
|
+
Useful if server is running on slurm cluster to which there is an ssh access.
|
|
41
|
+
Can also be specified through NEMO_SKILLS_SSH_SERVER env var.
|
|
42
|
+
ssh_key_path: Optional[str] = None - Path to the ssh key for tunneling.
|
|
43
|
+
Can also be specified through NEMO_SKILLS_SSH_KEY_PATH env var.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
*,
|
|
49
|
+
uri: HttpUrl,
|
|
50
|
+
):
|
|
51
|
+
self.url: str = self._get_execute_url(uri)
|
|
52
|
+
session = requests.Session()
|
|
53
|
+
adapter = requests.adapters.HTTPAdapter(pool_maxsize=1500, pool_connections=1500, max_retries=3)
|
|
54
|
+
session.mount('http://', adapter)
|
|
55
|
+
session.mount('https://', adapter)
|
|
56
|
+
self.http_session: requests.Session = session
|
|
57
|
+
|
|
58
|
+
def _send_request(self, request: dict[str, Any], timeout_seconds: float) -> dict[str, str]:
|
|
59
|
+
output = self.http_session.post(
|
|
60
|
+
url=self.url,
|
|
61
|
+
data=json.dumps(request),
|
|
62
|
+
timeout=timeout_seconds,
|
|
63
|
+
headers={"Content-Type": "application/json"},
|
|
64
|
+
)
|
|
65
|
+
# retrying 502 errors
|
|
66
|
+
if output.status_code == 502:
|
|
67
|
+
raise requests.exceptions.Timeout
|
|
68
|
+
|
|
69
|
+
return self._parse_request_output(output)
|
|
70
|
+
|
|
71
|
+
@abc.abstractmethod
|
|
72
|
+
def _parse_request_output(self, output: requests.Response) -> dict[str, str]:
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
@abc.abstractmethod
|
|
76
|
+
def _get_execute_url(self, uri: HttpUrl) -> str:
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
@abc.abstractmethod
|
|
80
|
+
def _prepare_request(self, generated_code: str, timeout_seconds: float) -> dict[str, Any]:
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
async def execute_code(
|
|
84
|
+
self,
|
|
85
|
+
generated_code: str,
|
|
86
|
+
timeout_seconds: float = 10.0,
|
|
87
|
+
language: str = "python",
|
|
88
|
+
max_output_characters: int = 1000,
|
|
89
|
+
) -> dict[str, str]:
|
|
90
|
+
|
|
91
|
+
if language != "python":
|
|
92
|
+
raise ValueError(f"Language {language} not supported")
|
|
93
|
+
|
|
94
|
+
generated_code = generated_code.strip().strip("`")
|
|
95
|
+
code_to_execute = textwrap.dedent("""
|
|
96
|
+
import traceback
|
|
97
|
+
import json
|
|
98
|
+
import os
|
|
99
|
+
import warnings
|
|
100
|
+
import contextlib
|
|
101
|
+
import io
|
|
102
|
+
warnings.filterwarnings('ignore')
|
|
103
|
+
os.environ['OPENBLAS_NUM_THREADS'] = '16'
|
|
104
|
+
""").strip()
|
|
105
|
+
|
|
106
|
+
# Use json.dumps to properly escape the generated_code instead of repr()
|
|
107
|
+
escaped_code = json.dumps(generated_code)
|
|
108
|
+
code_to_execute += textwrap.dedent(f"""
|
|
109
|
+
|
|
110
|
+
generated_code = {escaped_code}
|
|
111
|
+
|
|
112
|
+
stdout = io.StringIO()
|
|
113
|
+
stderr = io.StringIO()
|
|
114
|
+
|
|
115
|
+
with contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr):
|
|
116
|
+
try:
|
|
117
|
+
exec(generated_code)
|
|
118
|
+
status = "completed"
|
|
119
|
+
except Exception:
|
|
120
|
+
status = "error"
|
|
121
|
+
stderr.write(traceback.format_exc())
|
|
122
|
+
stdout = stdout.getvalue()
|
|
123
|
+
stderr = stderr.getvalue()
|
|
124
|
+
if len(stdout) > {max_output_characters}:
|
|
125
|
+
stdout = stdout[:{max_output_characters}] + "<output cut>"
|
|
126
|
+
if len(stderr) > {max_output_characters}:
|
|
127
|
+
stderr = stderr[:{max_output_characters}] + "<output cut>"
|
|
128
|
+
if stdout:
|
|
129
|
+
stdout += "\\n"
|
|
130
|
+
if stderr:
|
|
131
|
+
stderr += "\\n"
|
|
132
|
+
output = {{"process_status": status, "stdout": stdout, "stderr": stderr}}
|
|
133
|
+
print(json.dumps(output))
|
|
134
|
+
""").strip()
|
|
135
|
+
request = self._prepare_request(code_to_execute, timeout_seconds)
|
|
136
|
+
try:
|
|
137
|
+
return self._send_request(request, timeout_seconds)
|
|
138
|
+
except requests.exceptions.Timeout:
|
|
139
|
+
return {"process_status": "timeout", "stdout": "", "stderr": "Timed out\n"}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class LocalSandbox(Sandbox):
|
|
143
|
+
"""Locally hosted sandbox."""
|
|
144
|
+
|
|
145
|
+
def __init__(self, *, uri: HttpUrl):
|
|
146
|
+
super().__init__(uri=uri)
|
|
147
|
+
|
|
148
|
+
@override
|
|
149
|
+
def _get_execute_url(self, uri: HttpUrl) -> str:
|
|
150
|
+
return urljoin(str(uri), "execute")
|
|
151
|
+
|
|
152
|
+
@override
|
|
153
|
+
def _parse_request_output(self, output: requests.Response) -> dict[str, str]:
|
|
154
|
+
try:
|
|
155
|
+
output_json = output.json()
|
|
156
|
+
assert isinstance(output_json, dict)
|
|
157
|
+
return output_json
|
|
158
|
+
except json.JSONDecodeError as e:
|
|
159
|
+
logger.exception("Error parsing output: %s. %s", output.text, e)
|
|
160
|
+
return {'process_status': 'error', 'stdout': '', 'stderr': f'Unknown error: {e} \"{output.text}\"'}
|
|
161
|
+
|
|
162
|
+
@override
|
|
163
|
+
def _prepare_request(self,
|
|
164
|
+
generated_code: str,
|
|
165
|
+
timeout_seconds: float,
|
|
166
|
+
language: str = "python",
|
|
167
|
+
**kwargs) -> dict[str, Any]:
|
|
168
|
+
request = {
|
|
169
|
+
"generated_code": generated_code,
|
|
170
|
+
"timeout": timeout_seconds,
|
|
171
|
+
"language": language,
|
|
172
|
+
}
|
|
173
|
+
return request
|
|
174
|
+
|
|
175
|
+
@override
|
|
176
|
+
async def execute_code(
|
|
177
|
+
self,
|
|
178
|
+
generated_code: str,
|
|
179
|
+
timeout_seconds: float = 10.0,
|
|
180
|
+
language: str = "python",
|
|
181
|
+
max_output_characters: int = 1000,
|
|
182
|
+
) -> dict[str, str]:
|
|
183
|
+
"""Override execute_code to bypass the wrapper logic and send user code directly to our server."""
|
|
184
|
+
|
|
185
|
+
logger.debug("Raw input generated_code: %s", generated_code)
|
|
186
|
+
|
|
187
|
+
# The input appears to be a string representation of a dictionary
|
|
188
|
+
# We need to parse it and extract the actual code
|
|
189
|
+
try:
|
|
190
|
+
# Try to evaluate the string as a Python literal (dictionary)
|
|
191
|
+
import ast
|
|
192
|
+
parsed_dict = ast.literal_eval(generated_code)
|
|
193
|
+
if isinstance(parsed_dict, dict) and 'generated_code' in parsed_dict:
|
|
194
|
+
actual_code = parsed_dict['generated_code']
|
|
195
|
+
assert isinstance(actual_code, str)
|
|
196
|
+
logger.debug("Extracted code from dict: %s...", actual_code[:100])
|
|
197
|
+
else:
|
|
198
|
+
# If it's not a dict or doesn't have the expected key, use as-is
|
|
199
|
+
actual_code = generated_code
|
|
200
|
+
logger.debug("Using code as-is: %s...", actual_code[:100])
|
|
201
|
+
except (ValueError, SyntaxError):
|
|
202
|
+
# If parsing fails, use the input as-is
|
|
203
|
+
actual_code = generated_code
|
|
204
|
+
logger.debug("Failed to parse, using as-is: %s...", actual_code[:100])
|
|
205
|
+
|
|
206
|
+
# Clean the actual code more carefully to avoid removing backticks that are part of Python code
|
|
207
|
+
# remove all leading/trailing whitespace -- strip()
|
|
208
|
+
# remove all leading/trailing backticks -- strip("`")
|
|
209
|
+
# may potentially start with python, so just trim from the front.
|
|
210
|
+
POTENTIAL_PREFIXES = ["python"]
|
|
211
|
+
actual_code = actual_code.strip().strip("`")
|
|
212
|
+
for prefix in POTENTIAL_PREFIXES:
|
|
213
|
+
if actual_code.startswith(prefix):
|
|
214
|
+
actual_code = actual_code[len(prefix):]
|
|
215
|
+
break
|
|
216
|
+
|
|
217
|
+
# Send the user's code directly to our server without any wrapper logic
|
|
218
|
+
# Our server already handles stdout/stderr capture and error handling
|
|
219
|
+
request = self._prepare_request(actual_code, timeout_seconds, language)
|
|
220
|
+
try:
|
|
221
|
+
return self._send_request(request, timeout_seconds)
|
|
222
|
+
except requests.exceptions.Timeout:
|
|
223
|
+
return {"process_status": "timeout", "stdout": "", "stderr": "Timed out\n"}
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class PistonSandbox(Sandbox):
|
|
227
|
+
"""Piston sandbox (https://github.com/engineer-man/piston)"""
|
|
228
|
+
|
|
229
|
+
@override
|
|
230
|
+
def _get_execute_url(self, uri: HttpUrl) -> str:
|
|
231
|
+
return urljoin(str(uri), "execute")
|
|
232
|
+
|
|
233
|
+
@override
|
|
234
|
+
def _parse_request_output(self, output: requests.Response) -> dict[str, str]:
|
|
235
|
+
output_json = output.json()
|
|
236
|
+
assert isinstance(output_json, dict)
|
|
237
|
+
assert 'run' in output_json
|
|
238
|
+
run_json = output_json['run']
|
|
239
|
+
assert isinstance(run_json, dict)
|
|
240
|
+
if run_json["code"] != 0:
|
|
241
|
+
return {'process_status': "error", 'stdout': run_json['stdout'], 'stderr': run_json['stderr']}
|
|
242
|
+
return {'process_status': "completed", 'stdout': run_json['stdout'], 'stderr': run_json['stderr']}
|
|
243
|
+
|
|
244
|
+
@override
|
|
245
|
+
def _prepare_request(self, generated_code: str, timeout_seconds: float, **kwargs) -> dict[str, Any]:
|
|
246
|
+
return {
|
|
247
|
+
"language": "py",
|
|
248
|
+
"version": "3.10.0",
|
|
249
|
+
"files": [{
|
|
250
|
+
"content": generated_code,
|
|
251
|
+
}],
|
|
252
|
+
"stdin": "",
|
|
253
|
+
"args": [],
|
|
254
|
+
"run_timeout": timeout_seconds * 1000.0, # milliseconds
|
|
255
|
+
"compile_memory_limit": -1,
|
|
256
|
+
"run_memory_limit": -1,
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def get_sandbox(sandbox_type: str = "local", **kwargs):
|
|
261
|
+
"""A helper function to make it easier to set sandbox through cmd."""
|
|
262
|
+
sandboxes = {
|
|
263
|
+
'local': LocalSandbox,
|
|
264
|
+
'piston': PistonSandbox,
|
|
265
|
+
}
|
|
266
|
+
sandbox_class = sandboxes[sandbox_type.lower()]
|
|
267
|
+
return sandbox_class(**kwargs)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
persistence_test.*
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Copyright (c) 2024-2025, NVIDIA CORPORATION. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
# Use the base image with Python 3.10 and Flask
|
|
16
|
+
FROM tiangolo/uwsgi-nginx-flask:python3.10
|
|
17
|
+
|
|
18
|
+
# Install dependencies required for Lean 4 and other tools
|
|
19
|
+
RUN apt-get update && \
|
|
20
|
+
apt-get install -y curl git && \
|
|
21
|
+
curl https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh -sSf | sh -s -- -y && \
|
|
22
|
+
/root/.elan/bin/elan toolchain install leanprover/lean4:v4.12.0 && \
|
|
23
|
+
/root/.elan/bin/elan default leanprover/lean4:v4.12.0 && \
|
|
24
|
+
/root/.elan/bin/elan self update
|
|
25
|
+
|
|
26
|
+
# Set environment variables to include Lean and elan/lake in the PATH
|
|
27
|
+
ENV PATH="/root/.elan/bin:$PATH"
|
|
28
|
+
|
|
29
|
+
# Create Lean project directory and initialize a new Lean project with Mathlib4
|
|
30
|
+
RUN mkdir -p /lean4 && cd /lean4 && \
|
|
31
|
+
/root/.elan/bin/lake new my_project && \
|
|
32
|
+
cd my_project && \
|
|
33
|
+
echo 'leanprover/lean4:v4.12.0' > lean-toolchain && \
|
|
34
|
+
echo 'require mathlib from git "https://github.com/leanprover-community/mathlib4" @ "v4.12.0"' >> lakefile.lean
|
|
35
|
+
|
|
36
|
+
# Download and cache Mathlib4 to avoid recompiling, then build the project
|
|
37
|
+
RUN cd /lean4/my_project && \
|
|
38
|
+
/root/.elan/bin/lake exe cache get && \
|
|
39
|
+
/root/.elan/bin/lake build
|
|
40
|
+
|
|
41
|
+
# Set environment variables to include Lean project path
|
|
42
|
+
ENV LEAN_PATH="/lean4/my_project"
|
|
43
|
+
ENV PATH="/lean4/my_project:$PATH"
|
|
44
|
+
|
|
45
|
+
# Set up application code and install Python dependencies
|
|
46
|
+
COPY sandbox.requirements.txt /app/requirements.txt
|
|
47
|
+
RUN pip install --no-cache-dir -r /app/requirements.txt
|
|
48
|
+
COPY local_sandbox_server.py /app/main.py
|
|
49
|
+
|
|
50
|
+
# Set the working directory to /app
|
|
51
|
+
WORKDIR /app
|
|
52
|
+
|
|
53
|
+
# Set Flask app environment variables and ports
|
|
54
|
+
ARG UWSGI_CHEAPER
|
|
55
|
+
ENV UWSGI_CHEAPER=$UWSGI_CHEAPER
|
|
56
|
+
|
|
57
|
+
ARG UWSGI_PROCESSES
|
|
58
|
+
ENV UWSGI_PROCESSES=$UWSGI_PROCESSES
|
|
59
|
+
|
|
60
|
+
ENV LISTEN_PORT=6000
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Copyright (c) 2024-2025, NVIDIA CORPORATION. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# Copyright (c) 2024-2025, NVIDIA CORPORATION. All rights reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import contextlib
|
|
18
|
+
import logging
|
|
19
|
+
import multiprocessing
|
|
20
|
+
import os
|
|
21
|
+
import resource
|
|
22
|
+
from enum import Enum
|
|
23
|
+
from io import StringIO
|
|
24
|
+
|
|
25
|
+
from flask import Flask
|
|
26
|
+
from flask import Request
|
|
27
|
+
from flask import Response
|
|
28
|
+
from flask import request
|
|
29
|
+
from pydantic import BaseModel
|
|
30
|
+
from pydantic import Field
|
|
31
|
+
|
|
32
|
+
app = Flask(__name__)
|
|
33
|
+
logger = logging.getLogger(__name__)
|
|
34
|
+
logger.setLevel(logging.WARNING)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class CodeExecutionStatus(str, Enum):
|
|
38
|
+
"""
|
|
39
|
+
Status of code execution.
|
|
40
|
+
"""
|
|
41
|
+
COMPLETED = "completed"
|
|
42
|
+
ERROR = "error"
|
|
43
|
+
TIMEOUT = "timeout"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class CodeExecutionResult(BaseModel):
|
|
47
|
+
"""
|
|
48
|
+
Result of code execution.
|
|
49
|
+
"""
|
|
50
|
+
process_status: CodeExecutionStatus = Field(default=CodeExecutionStatus.COMPLETED,
|
|
51
|
+
description="Status of the process")
|
|
52
|
+
stdout: str = Field(description="Standard output of the process")
|
|
53
|
+
stderr: str = Field(description="Standard error of the process")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class CodeExecutionResponse(Response):
|
|
57
|
+
"""
|
|
58
|
+
Response class that returns a JSON response with the given status code and result.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(self, status_code: int, result: CodeExecutionResult):
|
|
62
|
+
super().__init__(status=status_code, mimetype="application/json", response=result.model_dump_json())
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def with_error(cls, status_code: int, error_message: str) -> 'CodeExecutionResponse':
|
|
66
|
+
return cls(status_code,
|
|
67
|
+
CodeExecutionResult(process_status=CodeExecutionStatus.ERROR, stdout="", stderr=error_message))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@app.after_request
|
|
71
|
+
def add_hsts_header(response):
|
|
72
|
+
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
|
|
73
|
+
response.headers['X-Content-Type-Options'] = 'nosniff'
|
|
74
|
+
response.headers['X-Frame-Options'] = 'SAMEORIGIN'
|
|
75
|
+
response.headers['X-XSS-Protection'] = '1; mode=block'
|
|
76
|
+
|
|
77
|
+
return response
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def execute_python(generated_code: str, timeout: float) -> CodeExecutionResult:
|
|
81
|
+
"""
|
|
82
|
+
Execute Python code in a subprocess.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
generated_code: The code to execute
|
|
86
|
+
timeout: The timeout for the execution
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
CodeExecutionResult object containing the execution result
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
# running in a separate process to ensure any kind of crashes are properly handled
|
|
93
|
+
queue = multiprocessing.Queue()
|
|
94
|
+
process = multiprocessing.Process(target=execute_code_subprocess, args=(generated_code, queue))
|
|
95
|
+
|
|
96
|
+
process.start()
|
|
97
|
+
# wait until the process finishes or the timeout expires
|
|
98
|
+
process.join(timeout=timeout)
|
|
99
|
+
if process.exitcode is None:
|
|
100
|
+
process.kill()
|
|
101
|
+
return CodeExecutionResult(process_status=CodeExecutionStatus.TIMEOUT, stdout="", stderr="Timed out\n")
|
|
102
|
+
|
|
103
|
+
return queue.get()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# need to memory-limit to avoid common errors of allocating too much
|
|
107
|
+
# but this has to be done in a subprocess to not crush server itself
|
|
108
|
+
def execute_code_subprocess(generated_code: str, queue):
|
|
109
|
+
"""
|
|
110
|
+
Execute code in a subprocess.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
generated_code: The code to execute
|
|
114
|
+
queue: The queue to put the result in
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
logger.debug("execute_code_subprocess started, PID: %s", os.getpid())
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
limit = 1024 * 1024 * 1024 * 10 # 10gb - somehow with a smaller limit the server dies when numpy is used
|
|
121
|
+
resource.setrlimit(resource.RLIMIT_AS, (limit, limit))
|
|
122
|
+
resource.setrlimit(resource.RLIMIT_DATA, (limit, limit))
|
|
123
|
+
except Exception as e:
|
|
124
|
+
logger.error("Failed to set resource limits, PID: %s, error: %s", os.getpid(), e)
|
|
125
|
+
|
|
126
|
+
stdout_capture = StringIO()
|
|
127
|
+
stderr_capture = StringIO()
|
|
128
|
+
try:
|
|
129
|
+
with contextlib.redirect_stdout(stdout_capture), contextlib.redirect_stderr(stderr_capture):
|
|
130
|
+
exec(generated_code, {}) # pylint: disable=W0122
|
|
131
|
+
logger.debug("execute_code_subprocess finished, PID: %s", os.getpid())
|
|
132
|
+
queue.put(CodeExecutionResult(stdout=stdout_capture.getvalue(), stderr=stderr_capture.getvalue()))
|
|
133
|
+
except Exception as e:
|
|
134
|
+
import traceback
|
|
135
|
+
with contextlib.redirect_stderr(stderr_capture):
|
|
136
|
+
traceback.print_exc()
|
|
137
|
+
logger.debug("execute_code_subprocess failed, PID: %s, error: %s", os.getpid(), e)
|
|
138
|
+
queue.put(
|
|
139
|
+
CodeExecutionResult(process_status=CodeExecutionStatus.ERROR,
|
|
140
|
+
stdout=stdout_capture.getvalue(),
|
|
141
|
+
stderr=stderr_capture.getvalue()))
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def do_execute(request: Request) -> CodeExecutionResponse:
|
|
145
|
+
"""
|
|
146
|
+
Main function to handle execution requests.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
request: Request object containing the execution request
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
CodeExecutionResponse object containing the execution result
|
|
153
|
+
"""
|
|
154
|
+
try:
|
|
155
|
+
# Check if request has JSON data
|
|
156
|
+
if not request.is_json:
|
|
157
|
+
return CodeExecutionResponse.with_error(400, "Request must be JSON")
|
|
158
|
+
|
|
159
|
+
# Get JSON data safely
|
|
160
|
+
json_data = request.get_json(silent=True)
|
|
161
|
+
|
|
162
|
+
if json_data is None:
|
|
163
|
+
return CodeExecutionResponse.with_error(400, "Invalid JSON data")
|
|
164
|
+
|
|
165
|
+
# Check for required fields
|
|
166
|
+
if 'generated_code' not in json_data:
|
|
167
|
+
return CodeExecutionResponse.with_error(400, "Missing required field: generated_code")
|
|
168
|
+
|
|
169
|
+
if 'timeout' not in json_data:
|
|
170
|
+
return CodeExecutionResponse.with_error(400, "Missing required field: timeout")
|
|
171
|
+
|
|
172
|
+
if 'language' not in json_data:
|
|
173
|
+
return CodeExecutionResponse.with_error(400, "Missing required field: language")
|
|
174
|
+
|
|
175
|
+
generated_code: str | None = json_data.get('generated_code', None)
|
|
176
|
+
assert generated_code is not None
|
|
177
|
+
timeout: float | None = json_data.get('timeout', None)
|
|
178
|
+
assert timeout is not None
|
|
179
|
+
language: str | None = json_data.get('language', None)
|
|
180
|
+
assert language is not None
|
|
181
|
+
|
|
182
|
+
if language != 'python':
|
|
183
|
+
return CodeExecutionResponse.with_error(400, "Only python execution is supported")
|
|
184
|
+
|
|
185
|
+
return CodeExecutionResponse(200, execute_python(generated_code, timeout))
|
|
186
|
+
|
|
187
|
+
except Exception as e:
|
|
188
|
+
return CodeExecutionResponse.with_error(500, f"Server error: {str(e)}")
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# Main Flask endpoint to handle execution requests
|
|
192
|
+
@app.route("/execute", methods=["POST"])
|
|
193
|
+
def execute():
|
|
194
|
+
return do_execute(request)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
if __name__ == '__main__':
|
|
198
|
+
app.run(port=6000)
|