nvidia-nat 1.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aiq/__init__.py +66 -0
- nat/agent/__init__.py +0 -0
- nat/agent/base.py +256 -0
- nat/agent/dual_node.py +67 -0
- nat/agent/react_agent/__init__.py +0 -0
- nat/agent/react_agent/agent.py +363 -0
- nat/agent/react_agent/output_parser.py +104 -0
- nat/agent/react_agent/prompt.py +44 -0
- nat/agent/react_agent/register.py +149 -0
- nat/agent/reasoning_agent/__init__.py +0 -0
- nat/agent/reasoning_agent/reasoning_agent.py +225 -0
- nat/agent/register.py +23 -0
- nat/agent/rewoo_agent/__init__.py +0 -0
- nat/agent/rewoo_agent/agent.py +415 -0
- nat/agent/rewoo_agent/prompt.py +110 -0
- nat/agent/rewoo_agent/register.py +157 -0
- nat/agent/tool_calling_agent/__init__.py +0 -0
- nat/agent/tool_calling_agent/agent.py +119 -0
- nat/agent/tool_calling_agent/register.py +106 -0
- nat/authentication/__init__.py +14 -0
- nat/authentication/api_key/__init__.py +14 -0
- nat/authentication/api_key/api_key_auth_provider.py +96 -0
- nat/authentication/api_key/api_key_auth_provider_config.py +124 -0
- nat/authentication/api_key/register.py +26 -0
- nat/authentication/exceptions/__init__.py +14 -0
- nat/authentication/exceptions/api_key_exceptions.py +38 -0
- nat/authentication/http_basic_auth/__init__.py +0 -0
- nat/authentication/http_basic_auth/http_basic_auth_provider.py +81 -0
- nat/authentication/http_basic_auth/register.py +30 -0
- nat/authentication/interfaces.py +93 -0
- nat/authentication/oauth2/__init__.py +14 -0
- nat/authentication/oauth2/oauth2_auth_code_flow_provider.py +107 -0
- nat/authentication/oauth2/oauth2_auth_code_flow_provider_config.py +39 -0
- nat/authentication/oauth2/register.py +25 -0
- nat/authentication/register.py +21 -0
- nat/builder/__init__.py +0 -0
- nat/builder/builder.py +285 -0
- nat/builder/component_utils.py +316 -0
- nat/builder/context.py +270 -0
- nat/builder/embedder.py +24 -0
- nat/builder/eval_builder.py +161 -0
- nat/builder/evaluator.py +29 -0
- nat/builder/framework_enum.py +24 -0
- nat/builder/front_end.py +73 -0
- nat/builder/function.py +344 -0
- nat/builder/function_base.py +380 -0
- nat/builder/function_info.py +627 -0
- nat/builder/intermediate_step_manager.py +174 -0
- nat/builder/llm.py +25 -0
- nat/builder/retriever.py +25 -0
- nat/builder/user_interaction_manager.py +78 -0
- nat/builder/workflow.py +148 -0
- nat/builder/workflow_builder.py +1117 -0
- nat/cli/__init__.py +14 -0
- nat/cli/cli_utils/__init__.py +0 -0
- nat/cli/cli_utils/config_override.py +231 -0
- nat/cli/cli_utils/validation.py +37 -0
- nat/cli/commands/__init__.py +0 -0
- nat/cli/commands/configure/__init__.py +0 -0
- nat/cli/commands/configure/channel/__init__.py +0 -0
- nat/cli/commands/configure/channel/add.py +28 -0
- nat/cli/commands/configure/channel/channel.py +34 -0
- nat/cli/commands/configure/channel/remove.py +30 -0
- nat/cli/commands/configure/channel/update.py +30 -0
- nat/cli/commands/configure/configure.py +33 -0
- nat/cli/commands/evaluate.py +139 -0
- nat/cli/commands/info/__init__.py +14 -0
- nat/cli/commands/info/info.py +37 -0
- nat/cli/commands/info/list_channels.py +32 -0
- nat/cli/commands/info/list_components.py +129 -0
- nat/cli/commands/info/list_mcp.py +304 -0
- nat/cli/commands/registry/__init__.py +14 -0
- nat/cli/commands/registry/publish.py +88 -0
- nat/cli/commands/registry/pull.py +118 -0
- nat/cli/commands/registry/registry.py +36 -0
- nat/cli/commands/registry/remove.py +108 -0
- nat/cli/commands/registry/search.py +155 -0
- nat/cli/commands/sizing/__init__.py +14 -0
- nat/cli/commands/sizing/calc.py +297 -0
- nat/cli/commands/sizing/sizing.py +27 -0
- nat/cli/commands/start.py +246 -0
- nat/cli/commands/uninstall.py +81 -0
- nat/cli/commands/validate.py +47 -0
- nat/cli/commands/workflow/__init__.py +14 -0
- nat/cli/commands/workflow/templates/__init__.py.j2 +0 -0
- nat/cli/commands/workflow/templates/config.yml.j2 +16 -0
- nat/cli/commands/workflow/templates/pyproject.toml.j2 +22 -0
- nat/cli/commands/workflow/templates/register.py.j2 +5 -0
- nat/cli/commands/workflow/templates/workflow.py.j2 +36 -0
- nat/cli/commands/workflow/workflow.py +37 -0
- nat/cli/commands/workflow/workflow_commands.py +317 -0
- nat/cli/entrypoint.py +135 -0
- nat/cli/main.py +57 -0
- nat/cli/register_workflow.py +488 -0
- nat/cli/type_registry.py +1000 -0
- nat/data_models/__init__.py +14 -0
- nat/data_models/api_server.py +716 -0
- nat/data_models/authentication.py +231 -0
- nat/data_models/common.py +171 -0
- nat/data_models/component.py +58 -0
- nat/data_models/component_ref.py +168 -0
- nat/data_models/config.py +410 -0
- nat/data_models/dataset_handler.py +169 -0
- nat/data_models/discovery_metadata.py +305 -0
- nat/data_models/embedder.py +27 -0
- nat/data_models/evaluate.py +127 -0
- nat/data_models/evaluator.py +26 -0
- nat/data_models/front_end.py +26 -0
- nat/data_models/function.py +30 -0
- nat/data_models/function_dependencies.py +72 -0
- nat/data_models/interactive.py +246 -0
- nat/data_models/intermediate_step.py +302 -0
- nat/data_models/invocation_node.py +38 -0
- nat/data_models/llm.py +27 -0
- nat/data_models/logging.py +26 -0
- nat/data_models/memory.py +27 -0
- nat/data_models/object_store.py +44 -0
- nat/data_models/profiler.py +54 -0
- nat/data_models/registry_handler.py +26 -0
- nat/data_models/retriever.py +30 -0
- nat/data_models/retry_mixin.py +35 -0
- nat/data_models/span.py +190 -0
- nat/data_models/step_adaptor.py +64 -0
- nat/data_models/streaming.py +33 -0
- nat/data_models/swe_bench_model.py +54 -0
- nat/data_models/telemetry_exporter.py +26 -0
- nat/data_models/ttc_strategy.py +30 -0
- nat/embedder/__init__.py +0 -0
- nat/embedder/nim_embedder.py +59 -0
- nat/embedder/openai_embedder.py +43 -0
- nat/embedder/register.py +22 -0
- nat/eval/__init__.py +14 -0
- nat/eval/config.py +60 -0
- nat/eval/dataset_handler/__init__.py +0 -0
- nat/eval/dataset_handler/dataset_downloader.py +106 -0
- nat/eval/dataset_handler/dataset_filter.py +52 -0
- nat/eval/dataset_handler/dataset_handler.py +367 -0
- nat/eval/evaluate.py +510 -0
- nat/eval/evaluator/__init__.py +14 -0
- nat/eval/evaluator/base_evaluator.py +77 -0
- nat/eval/evaluator/evaluator_model.py +45 -0
- nat/eval/intermediate_step_adapter.py +99 -0
- nat/eval/rag_evaluator/__init__.py +0 -0
- nat/eval/rag_evaluator/evaluate.py +178 -0
- nat/eval/rag_evaluator/register.py +143 -0
- nat/eval/register.py +23 -0
- nat/eval/remote_workflow.py +133 -0
- nat/eval/runners/__init__.py +14 -0
- nat/eval/runners/config.py +39 -0
- nat/eval/runners/multi_eval_runner.py +54 -0
- nat/eval/runtime_event_subscriber.py +52 -0
- nat/eval/swe_bench_evaluator/__init__.py +0 -0
- nat/eval/swe_bench_evaluator/evaluate.py +215 -0
- nat/eval/swe_bench_evaluator/register.py +36 -0
- nat/eval/trajectory_evaluator/__init__.py +0 -0
- nat/eval/trajectory_evaluator/evaluate.py +75 -0
- nat/eval/trajectory_evaluator/register.py +40 -0
- nat/eval/tunable_rag_evaluator/__init__.py +0 -0
- nat/eval/tunable_rag_evaluator/evaluate.py +245 -0
- nat/eval/tunable_rag_evaluator/register.py +52 -0
- nat/eval/usage_stats.py +41 -0
- nat/eval/utils/__init__.py +0 -0
- nat/eval/utils/output_uploader.py +140 -0
- nat/eval/utils/tqdm_position_registry.py +40 -0
- nat/eval/utils/weave_eval.py +184 -0
- nat/experimental/__init__.py +0 -0
- nat/experimental/decorators/__init__.py +0 -0
- nat/experimental/decorators/experimental_warning_decorator.py +134 -0
- nat/experimental/test_time_compute/__init__.py +0 -0
- nat/experimental/test_time_compute/editing/__init__.py +0 -0
- nat/experimental/test_time_compute/editing/iterative_plan_refinement_editor.py +147 -0
- nat/experimental/test_time_compute/editing/llm_as_a_judge_editor.py +204 -0
- nat/experimental/test_time_compute/editing/motivation_aware_summarization.py +107 -0
- nat/experimental/test_time_compute/functions/__init__.py +0 -0
- nat/experimental/test_time_compute/functions/execute_score_select_function.py +105 -0
- nat/experimental/test_time_compute/functions/plan_select_execute_function.py +224 -0
- nat/experimental/test_time_compute/functions/ttc_tool_orchestration_function.py +205 -0
- nat/experimental/test_time_compute/functions/ttc_tool_wrapper_function.py +146 -0
- nat/experimental/test_time_compute/models/__init__.py +0 -0
- nat/experimental/test_time_compute/models/editor_config.py +132 -0
- nat/experimental/test_time_compute/models/scoring_config.py +112 -0
- nat/experimental/test_time_compute/models/search_config.py +120 -0
- nat/experimental/test_time_compute/models/selection_config.py +154 -0
- nat/experimental/test_time_compute/models/stage_enums.py +43 -0
- nat/experimental/test_time_compute/models/strategy_base.py +66 -0
- nat/experimental/test_time_compute/models/tool_use_config.py +41 -0
- nat/experimental/test_time_compute/models/ttc_item.py +48 -0
- nat/experimental/test_time_compute/register.py +36 -0
- nat/experimental/test_time_compute/scoring/__init__.py +0 -0
- nat/experimental/test_time_compute/scoring/llm_based_agent_scorer.py +168 -0
- nat/experimental/test_time_compute/scoring/llm_based_plan_scorer.py +168 -0
- nat/experimental/test_time_compute/scoring/motivation_aware_scorer.py +111 -0
- nat/experimental/test_time_compute/search/__init__.py +0 -0
- nat/experimental/test_time_compute/search/multi_llm_planner.py +128 -0
- nat/experimental/test_time_compute/search/multi_query_retrieval_search.py +122 -0
- nat/experimental/test_time_compute/search/single_shot_multi_plan_planner.py +128 -0
- nat/experimental/test_time_compute/selection/__init__.py +0 -0
- nat/experimental/test_time_compute/selection/best_of_n_selector.py +63 -0
- nat/experimental/test_time_compute/selection/llm_based_agent_output_selector.py +131 -0
- nat/experimental/test_time_compute/selection/llm_based_output_merging_selector.py +159 -0
- nat/experimental/test_time_compute/selection/llm_based_plan_selector.py +128 -0
- nat/experimental/test_time_compute/selection/threshold_selector.py +58 -0
- nat/front_ends/__init__.py +14 -0
- nat/front_ends/console/__init__.py +14 -0
- nat/front_ends/console/authentication_flow_handler.py +233 -0
- nat/front_ends/console/console_front_end_config.py +32 -0
- nat/front_ends/console/console_front_end_plugin.py +96 -0
- nat/front_ends/console/register.py +25 -0
- nat/front_ends/cron/__init__.py +14 -0
- nat/front_ends/fastapi/__init__.py +14 -0
- nat/front_ends/fastapi/auth_flow_handlers/__init__.py +0 -0
- nat/front_ends/fastapi/auth_flow_handlers/http_flow_handler.py +27 -0
- nat/front_ends/fastapi/auth_flow_handlers/websocket_flow_handler.py +107 -0
- nat/front_ends/fastapi/fastapi_front_end_config.py +241 -0
- nat/front_ends/fastapi/fastapi_front_end_controller.py +68 -0
- nat/front_ends/fastapi/fastapi_front_end_plugin.py +116 -0
- nat/front_ends/fastapi/fastapi_front_end_plugin_worker.py +1087 -0
- nat/front_ends/fastapi/html_snippets/__init__.py +14 -0
- nat/front_ends/fastapi/html_snippets/auth_code_grant_success.py +35 -0
- nat/front_ends/fastapi/intermediate_steps_subscriber.py +80 -0
- nat/front_ends/fastapi/job_store.py +183 -0
- nat/front_ends/fastapi/main.py +72 -0
- nat/front_ends/fastapi/message_handler.py +320 -0
- nat/front_ends/fastapi/message_validator.py +352 -0
- nat/front_ends/fastapi/register.py +25 -0
- nat/front_ends/fastapi/response_helpers.py +195 -0
- nat/front_ends/fastapi/step_adaptor.py +319 -0
- nat/front_ends/mcp/__init__.py +14 -0
- nat/front_ends/mcp/mcp_front_end_config.py +36 -0
- nat/front_ends/mcp/mcp_front_end_plugin.py +81 -0
- nat/front_ends/mcp/mcp_front_end_plugin_worker.py +143 -0
- nat/front_ends/mcp/register.py +27 -0
- nat/front_ends/mcp/tool_converter.py +241 -0
- nat/front_ends/register.py +22 -0
- nat/front_ends/simple_base/__init__.py +14 -0
- nat/front_ends/simple_base/simple_front_end_plugin_base.py +54 -0
- nat/llm/__init__.py +0 -0
- nat/llm/aws_bedrock_llm.py +57 -0
- nat/llm/nim_llm.py +46 -0
- nat/llm/openai_llm.py +46 -0
- nat/llm/register.py +23 -0
- nat/llm/utils/__init__.py +14 -0
- nat/llm/utils/env_config_value.py +94 -0
- nat/llm/utils/error.py +17 -0
- nat/memory/__init__.py +20 -0
- nat/memory/interfaces.py +183 -0
- nat/memory/models.py +112 -0
- nat/meta/pypi.md +58 -0
- nat/object_store/__init__.py +20 -0
- nat/object_store/in_memory_object_store.py +76 -0
- nat/object_store/interfaces.py +84 -0
- nat/object_store/models.py +38 -0
- nat/object_store/register.py +20 -0
- nat/observability/__init__.py +14 -0
- nat/observability/exporter/__init__.py +14 -0
- nat/observability/exporter/base_exporter.py +449 -0
- nat/observability/exporter/exporter.py +78 -0
- nat/observability/exporter/file_exporter.py +33 -0
- nat/observability/exporter/processing_exporter.py +322 -0
- nat/observability/exporter/raw_exporter.py +52 -0
- nat/observability/exporter/span_exporter.py +288 -0
- nat/observability/exporter_manager.py +335 -0
- nat/observability/mixin/__init__.py +14 -0
- nat/observability/mixin/batch_config_mixin.py +26 -0
- nat/observability/mixin/collector_config_mixin.py +23 -0
- nat/observability/mixin/file_mixin.py +288 -0
- nat/observability/mixin/file_mode.py +23 -0
- nat/observability/mixin/resource_conflict_mixin.py +134 -0
- nat/observability/mixin/serialize_mixin.py +61 -0
- nat/observability/mixin/type_introspection_mixin.py +183 -0
- nat/observability/processor/__init__.py +14 -0
- nat/observability/processor/batching_processor.py +310 -0
- nat/observability/processor/callback_processor.py +42 -0
- nat/observability/processor/intermediate_step_serializer.py +28 -0
- nat/observability/processor/processor.py +71 -0
- nat/observability/register.py +96 -0
- nat/observability/utils/__init__.py +14 -0
- nat/observability/utils/dict_utils.py +236 -0
- nat/observability/utils/time_utils.py +31 -0
- nat/plugins/.namespace +1 -0
- nat/profiler/__init__.py +0 -0
- nat/profiler/calc/__init__.py +14 -0
- nat/profiler/calc/calc_runner.py +627 -0
- nat/profiler/calc/calculations.py +288 -0
- nat/profiler/calc/data_models.py +188 -0
- nat/profiler/calc/plot.py +345 -0
- nat/profiler/callbacks/__init__.py +0 -0
- nat/profiler/callbacks/agno_callback_handler.py +295 -0
- nat/profiler/callbacks/base_callback_class.py +20 -0
- nat/profiler/callbacks/langchain_callback_handler.py +290 -0
- nat/profiler/callbacks/llama_index_callback_handler.py +205 -0
- nat/profiler/callbacks/semantic_kernel_callback_handler.py +238 -0
- nat/profiler/callbacks/token_usage_base_model.py +27 -0
- nat/profiler/data_frame_row.py +51 -0
- nat/profiler/data_models.py +24 -0
- nat/profiler/decorators/__init__.py +0 -0
- nat/profiler/decorators/framework_wrapper.py +131 -0
- nat/profiler/decorators/function_tracking.py +254 -0
- nat/profiler/forecasting/__init__.py +0 -0
- nat/profiler/forecasting/config.py +18 -0
- nat/profiler/forecasting/model_trainer.py +75 -0
- nat/profiler/forecasting/models/__init__.py +22 -0
- nat/profiler/forecasting/models/forecasting_base_model.py +40 -0
- nat/profiler/forecasting/models/linear_model.py +197 -0
- nat/profiler/forecasting/models/random_forest_regressor.py +269 -0
- nat/profiler/inference_metrics_model.py +28 -0
- nat/profiler/inference_optimization/__init__.py +0 -0
- nat/profiler/inference_optimization/bottleneck_analysis/__init__.py +0 -0
- nat/profiler/inference_optimization/bottleneck_analysis/nested_stack_analysis.py +460 -0
- nat/profiler/inference_optimization/bottleneck_analysis/simple_stack_analysis.py +258 -0
- nat/profiler/inference_optimization/data_models.py +386 -0
- nat/profiler/inference_optimization/experimental/__init__.py +0 -0
- nat/profiler/inference_optimization/experimental/concurrency_spike_analysis.py +468 -0
- nat/profiler/inference_optimization/experimental/prefix_span_analysis.py +405 -0
- nat/profiler/inference_optimization/llm_metrics.py +212 -0
- nat/profiler/inference_optimization/prompt_caching.py +163 -0
- nat/profiler/inference_optimization/token_uniqueness.py +107 -0
- nat/profiler/inference_optimization/workflow_runtimes.py +72 -0
- nat/profiler/intermediate_property_adapter.py +102 -0
- nat/profiler/profile_runner.py +473 -0
- nat/profiler/utils.py +184 -0
- nat/registry_handlers/__init__.py +0 -0
- nat/registry_handlers/local/__init__.py +0 -0
- nat/registry_handlers/local/local_handler.py +176 -0
- nat/registry_handlers/local/register_local.py +37 -0
- nat/registry_handlers/metadata_factory.py +60 -0
- nat/registry_handlers/package_utils.py +571 -0
- nat/registry_handlers/pypi/__init__.py +0 -0
- nat/registry_handlers/pypi/pypi_handler.py +251 -0
- nat/registry_handlers/pypi/register_pypi.py +40 -0
- nat/registry_handlers/register.py +21 -0
- nat/registry_handlers/registry_handler_base.py +157 -0
- nat/registry_handlers/rest/__init__.py +0 -0
- nat/registry_handlers/rest/register_rest.py +56 -0
- nat/registry_handlers/rest/rest_handler.py +237 -0
- nat/registry_handlers/schemas/__init__.py +0 -0
- nat/registry_handlers/schemas/headers.py +42 -0
- nat/registry_handlers/schemas/package.py +68 -0
- nat/registry_handlers/schemas/publish.py +68 -0
- nat/registry_handlers/schemas/pull.py +82 -0
- nat/registry_handlers/schemas/remove.py +36 -0
- nat/registry_handlers/schemas/search.py +91 -0
- nat/registry_handlers/schemas/status.py +47 -0
- nat/retriever/__init__.py +0 -0
- nat/retriever/interface.py +41 -0
- nat/retriever/milvus/__init__.py +14 -0
- nat/retriever/milvus/register.py +81 -0
- nat/retriever/milvus/retriever.py +228 -0
- nat/retriever/models.py +77 -0
- nat/retriever/nemo_retriever/__init__.py +14 -0
- nat/retriever/nemo_retriever/register.py +60 -0
- nat/retriever/nemo_retriever/retriever.py +190 -0
- nat/retriever/register.py +22 -0
- nat/runtime/__init__.py +14 -0
- nat/runtime/loader.py +220 -0
- nat/runtime/runner.py +195 -0
- nat/runtime/session.py +162 -0
- nat/runtime/user_metadata.py +130 -0
- nat/settings/__init__.py +0 -0
- nat/settings/global_settings.py +318 -0
- nat/test/.namespace +1 -0
- nat/tool/__init__.py +0 -0
- nat/tool/chat_completion.py +74 -0
- nat/tool/code_execution/README.md +151 -0
- nat/tool/code_execution/__init__.py +0 -0
- nat/tool/code_execution/code_sandbox.py +267 -0
- nat/tool/code_execution/local_sandbox/.gitignore +1 -0
- nat/tool/code_execution/local_sandbox/Dockerfile.sandbox +60 -0
- nat/tool/code_execution/local_sandbox/__init__.py +13 -0
- nat/tool/code_execution/local_sandbox/local_sandbox_server.py +198 -0
- nat/tool/code_execution/local_sandbox/sandbox.requirements.txt +6 -0
- nat/tool/code_execution/local_sandbox/start_local_sandbox.sh +50 -0
- nat/tool/code_execution/register.py +74 -0
- nat/tool/code_execution/test_code_execution_sandbox.py +414 -0
- nat/tool/code_execution/utils.py +100 -0
- nat/tool/datetime_tools.py +42 -0
- nat/tool/document_search.py +141 -0
- nat/tool/github_tools/__init__.py +0 -0
- nat/tool/github_tools/create_github_commit.py +133 -0
- nat/tool/github_tools/create_github_issue.py +87 -0
- nat/tool/github_tools/create_github_pr.py +106 -0
- nat/tool/github_tools/get_github_file.py +106 -0
- nat/tool/github_tools/get_github_issue.py +166 -0
- nat/tool/github_tools/get_github_pr.py +256 -0
- nat/tool/github_tools/update_github_issue.py +100 -0
- nat/tool/mcp/__init__.py +14 -0
- nat/tool/mcp/exceptions.py +142 -0
- nat/tool/mcp/mcp_client.py +255 -0
- nat/tool/mcp/mcp_tool.py +96 -0
- nat/tool/memory_tools/__init__.py +0 -0
- nat/tool/memory_tools/add_memory_tool.py +79 -0
- nat/tool/memory_tools/delete_memory_tool.py +67 -0
- nat/tool/memory_tools/get_memory_tool.py +72 -0
- nat/tool/nvidia_rag.py +95 -0
- nat/tool/register.py +38 -0
- nat/tool/retriever.py +94 -0
- nat/tool/server_tools.py +66 -0
- nat/utils/__init__.py +0 -0
- nat/utils/data_models/__init__.py +0 -0
- nat/utils/data_models/schema_validator.py +58 -0
- nat/utils/debugging_utils.py +43 -0
- nat/utils/dump_distro_mapping.py +32 -0
- nat/utils/exception_handlers/__init__.py +0 -0
- nat/utils/exception_handlers/automatic_retries.py +289 -0
- nat/utils/exception_handlers/mcp.py +211 -0
- nat/utils/exception_handlers/schemas.py +114 -0
- nat/utils/io/__init__.py +0 -0
- nat/utils/io/model_processing.py +28 -0
- nat/utils/io/yaml_tools.py +119 -0
- nat/utils/log_utils.py +37 -0
- nat/utils/metadata_utils.py +74 -0
- nat/utils/optional_imports.py +142 -0
- nat/utils/producer_consumer_queue.py +178 -0
- nat/utils/reactive/__init__.py +0 -0
- nat/utils/reactive/base/__init__.py +0 -0
- nat/utils/reactive/base/observable_base.py +65 -0
- nat/utils/reactive/base/observer_base.py +55 -0
- nat/utils/reactive/base/subject_base.py +79 -0
- nat/utils/reactive/observable.py +59 -0
- nat/utils/reactive/observer.py +76 -0
- nat/utils/reactive/subject.py +131 -0
- nat/utils/reactive/subscription.py +49 -0
- nat/utils/settings/__init__.py +0 -0
- nat/utils/settings/global_settings.py +197 -0
- nat/utils/string_utils.py +38 -0
- nat/utils/type_converter.py +290 -0
- nat/utils/type_utils.py +484 -0
- nat/utils/url_utils.py +27 -0
- nvidia_nat-1.2.0.dist-info/METADATA +365 -0
- nvidia_nat-1.2.0.dist-info/RECORD +435 -0
- nvidia_nat-1.2.0.dist-info/WHEEL +5 -0
- nvidia_nat-1.2.0.dist-info/entry_points.txt +21 -0
- nvidia_nat-1.2.0.dist-info/licenses/LICENSE-3rd-party.txt +5478 -0
- nvidia_nat-1.2.0.dist-info/licenses/LICENSE.md +201 -0
- nvidia_nat-1.2.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Copyright (c) 2024-2025, NVIDIA CORPORATION. All rights reserved.
|
|
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
|
+
# Usage: ./start_local_sandbox.sh [SANDBOX_NAME] [OUTPUT_DATA_PATH]
|
|
18
|
+
# NOTE: needs to run from the root of the repo!
|
|
19
|
+
|
|
20
|
+
DOCKER_COMMAND=${DOCKER_COMMAND:-"docker"}
|
|
21
|
+
SANDBOX_NAME=${1:-'local-sandbox'}
|
|
22
|
+
NUM_THREADS=10
|
|
23
|
+
|
|
24
|
+
# Get the output_data directory path for mounting
|
|
25
|
+
# Priority: command line argument > environment variable > default path (current directory)
|
|
26
|
+
OUTPUT_DATA_PATH=${2:-${OUTPUT_DATA_PATH:-$(pwd)}}
|
|
27
|
+
|
|
28
|
+
echo "Starting sandbox with container name: ${SANDBOX_NAME}"
|
|
29
|
+
echo "Mounting output_data directory: ${OUTPUT_DATA_PATH}"
|
|
30
|
+
|
|
31
|
+
# Verify the path exists before mounting, create if it doesn't
|
|
32
|
+
if [ ! -d "${OUTPUT_DATA_PATH}" ]; then
|
|
33
|
+
echo "Output data directory does not exist, creating: ${OUTPUT_DATA_PATH}"
|
|
34
|
+
mkdir -p "${OUTPUT_DATA_PATH}"
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
# Check if the Docker image already exists
|
|
38
|
+
if ! ${DOCKER_COMMAND} images ${SANDBOX_NAME} | grep -q "${SANDBOX_NAME}"; then
|
|
39
|
+
echo "Docker image not found locally. Building ${SANDBOX_NAME}..."
|
|
40
|
+
${DOCKER_COMMAND} build --tag=${SANDBOX_NAME} --build-arg="UWSGI_PROCESSES=$((${NUM_THREADS} * 10))" --build-arg="UWSGI_CHEAPER=${NUM_THREADS}" -f Dockerfile.sandbox .
|
|
41
|
+
else
|
|
42
|
+
echo "Using existing Docker image: ${SANDBOX_NAME}"
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# Mount the output_data directory directly so files created in container appear in the local directory
|
|
46
|
+
${DOCKER_COMMAND} run --rm --name=local-sandbox \
|
|
47
|
+
--network=host \
|
|
48
|
+
-v "${OUTPUT_DATA_PATH}:/workspace" \
|
|
49
|
+
-w /workspace \
|
|
50
|
+
${SANDBOX_NAME}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
from typing import Literal
|
|
18
|
+
|
|
19
|
+
from pydantic import BaseModel
|
|
20
|
+
from pydantic import Field
|
|
21
|
+
from pydantic import HttpUrl
|
|
22
|
+
|
|
23
|
+
from nat.builder.builder import Builder
|
|
24
|
+
from nat.builder.function_info import FunctionInfo
|
|
25
|
+
from nat.cli.register_workflow import register_function
|
|
26
|
+
from nat.data_models.function import FunctionBaseConfig
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class CodeExecutionToolConfig(FunctionBaseConfig, name="code_execution"):
|
|
32
|
+
"""
|
|
33
|
+
Tool for executing python code in a remotely hosted sandbox environment.
|
|
34
|
+
"""
|
|
35
|
+
uri: HttpUrl = Field(default=HttpUrl("http://127.0.0.1:6000"),
|
|
36
|
+
description="URI for the code execution sandbox server")
|
|
37
|
+
sandbox_type: Literal["local", "piston"] = Field(default="local", description="The type of code execution sandbox")
|
|
38
|
+
timeout: float = Field(default=10.0, description="Number of seconds to wait for a code execution request")
|
|
39
|
+
max_output_characters: int = Field(default=1000, description="Maximum number of characters that can be returned")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@register_function(config_type=CodeExecutionToolConfig)
|
|
43
|
+
async def code_execution_tool(config: CodeExecutionToolConfig, builder: Builder):
|
|
44
|
+
from nat.tool.code_execution.code_sandbox import get_sandbox
|
|
45
|
+
|
|
46
|
+
class CodeExecutionInputSchema(BaseModel):
|
|
47
|
+
generated_code: str = Field(description="String containing the code to be executed")
|
|
48
|
+
|
|
49
|
+
# Create sandbox without working_directory
|
|
50
|
+
sandbox_kwargs = {"uri": config.uri}
|
|
51
|
+
|
|
52
|
+
sandbox = get_sandbox(sandbox_type=config.sandbox_type, **sandbox_kwargs)
|
|
53
|
+
logger.info(f"[DEBUG] Created sandbox of type: {config.sandbox_type}")
|
|
54
|
+
|
|
55
|
+
async def _execute_code(generated_code: str) -> dict:
|
|
56
|
+
logger.info("Executing code in the sandbox at %s", config.uri)
|
|
57
|
+
try:
|
|
58
|
+
output = await sandbox.execute_code(
|
|
59
|
+
generated_code=generated_code,
|
|
60
|
+
language="python",
|
|
61
|
+
timeout_seconds=config.timeout,
|
|
62
|
+
max_output_characters=config.max_output_characters,
|
|
63
|
+
)
|
|
64
|
+
except Exception as e:
|
|
65
|
+
logger.exception("Error when executing code in the sandbox, %s", e)
|
|
66
|
+
return {"process_status": "error", "stdout": "", "stderr": str(e)}
|
|
67
|
+
return output
|
|
68
|
+
|
|
69
|
+
yield FunctionInfo.from_fn(
|
|
70
|
+
fn=_execute_code,
|
|
71
|
+
input_schema=CodeExecutionInputSchema,
|
|
72
|
+
description="""Executes the provied 'generated_code' in a python sandbox environment and returns
|
|
73
|
+
a dictionary containing stdout, stderr, and the execution status, as well as a session_id. The
|
|
74
|
+
session_id can be used to append to code that was previously executed.""")
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
"""
|
|
16
|
+
Test suite for Code Execution Sandbox using pytest.
|
|
17
|
+
|
|
18
|
+
This module provides comprehensive testing for the code execution sandbox service,
|
|
19
|
+
replacing the original bash script with a more maintainable Python implementation.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import os
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
import pytest
|
|
26
|
+
import requests
|
|
27
|
+
from requests.exceptions import ConnectionError
|
|
28
|
+
from requests.exceptions import RequestException
|
|
29
|
+
from requests.exceptions import Timeout
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TestCodeExecutionSandbox:
|
|
33
|
+
"""Test suite for the Code Execution Sandbox service."""
|
|
34
|
+
|
|
35
|
+
@pytest.fixture(scope="class")
|
|
36
|
+
def sandbox_config(self):
|
|
37
|
+
"""Configuration for sandbox testing."""
|
|
38
|
+
return {
|
|
39
|
+
"url": os.environ.get("SANDBOX_URL", "http://127.0.0.1:6000/execute"),
|
|
40
|
+
"timeout": int(os.environ.get("SANDBOX_TIMEOUT", "30")),
|
|
41
|
+
"connection_timeout": 5
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@pytest.fixture(scope="class", autouse=True)
|
|
45
|
+
def check_sandbox_running(self, sandbox_config):
|
|
46
|
+
"""Check if sandbox server is running before running tests."""
|
|
47
|
+
try:
|
|
48
|
+
_ = requests.get(sandbox_config["url"], timeout=sandbox_config["connection_timeout"])
|
|
49
|
+
print(f"✓ Sandbox server is running at {sandbox_config['url']}")
|
|
50
|
+
except (ConnectionError, Timeout, RequestException):
|
|
51
|
+
pytest.skip(
|
|
52
|
+
f"Sandbox server is not running at {sandbox_config['url']}. "
|
|
53
|
+
"Please start it with: cd src/nat/tool/code_execution/local_sandbox && ./start_local_sandbox.sh")
|
|
54
|
+
|
|
55
|
+
def execute_code(self, sandbox_config: dict[str, Any], code: str, language: str = "python") -> dict[str, Any]:
|
|
56
|
+
"""
|
|
57
|
+
Execute code in the sandbox and return the response.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
sandbox_config: Configuration dictionary
|
|
61
|
+
code: Code to execute
|
|
62
|
+
language: Programming language (default: python)
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
dictionary containing the response from the sandbox
|
|
66
|
+
"""
|
|
67
|
+
payload = {"generated_code": code, "timeout": sandbox_config["timeout"], "language": language}
|
|
68
|
+
|
|
69
|
+
response = requests.post(
|
|
70
|
+
sandbox_config["url"],
|
|
71
|
+
json=payload,
|
|
72
|
+
timeout=sandbox_config["timeout"] + 5 # Add buffer to request timeout
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Ensure we got a response
|
|
76
|
+
response.raise_for_status()
|
|
77
|
+
return response.json()
|
|
78
|
+
|
|
79
|
+
def test_simple_print(self, sandbox_config):
|
|
80
|
+
"""Test simple print statement execution."""
|
|
81
|
+
code = "print('Hello, World!')"
|
|
82
|
+
result = self.execute_code(sandbox_config, code)
|
|
83
|
+
|
|
84
|
+
assert result["process_status"] == "completed"
|
|
85
|
+
assert "Hello, World!" in result["stdout"]
|
|
86
|
+
assert result["stderr"] == ""
|
|
87
|
+
|
|
88
|
+
def test_basic_arithmetic(self, sandbox_config):
|
|
89
|
+
"""Test basic arithmetic operations."""
|
|
90
|
+
code = """
|
|
91
|
+
result = 2 + 3
|
|
92
|
+
print(f'Result: {result}')
|
|
93
|
+
"""
|
|
94
|
+
result = self.execute_code(sandbox_config, code)
|
|
95
|
+
|
|
96
|
+
assert result["process_status"] == "completed"
|
|
97
|
+
assert "Result: 5" in result["stdout"]
|
|
98
|
+
assert result["stderr"] == ""
|
|
99
|
+
|
|
100
|
+
def test_numpy_operations(self, sandbox_config):
|
|
101
|
+
"""Test numpy dependency availability and operations."""
|
|
102
|
+
code = """
|
|
103
|
+
import numpy as np
|
|
104
|
+
arr = np.array([1, 2, 3, 4, 5])
|
|
105
|
+
print(f'Array: {arr}')
|
|
106
|
+
print(f'Mean: {np.mean(arr)}')
|
|
107
|
+
"""
|
|
108
|
+
result = self.execute_code(sandbox_config, code)
|
|
109
|
+
|
|
110
|
+
assert result["process_status"] == "completed"
|
|
111
|
+
assert "Array: [1 2 3 4 5]" in result["stdout"]
|
|
112
|
+
assert "Mean: 3.0" in result["stdout"]
|
|
113
|
+
assert result["stderr"] == ""
|
|
114
|
+
|
|
115
|
+
def test_pandas_operations(self, sandbox_config):
|
|
116
|
+
"""Test pandas dependency availability and operations."""
|
|
117
|
+
code = """
|
|
118
|
+
import pandas as pd
|
|
119
|
+
df = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]})
|
|
120
|
+
print(df)
|
|
121
|
+
print(f'Sum of column A: {df["A"].sum()}')
|
|
122
|
+
"""
|
|
123
|
+
result = self.execute_code(sandbox_config, code)
|
|
124
|
+
|
|
125
|
+
assert result["process_status"] == "completed"
|
|
126
|
+
assert "Sum of column A: 6" in result["stdout"]
|
|
127
|
+
assert result["stderr"] == ""
|
|
128
|
+
|
|
129
|
+
def test_plotly_import(self, sandbox_config):
|
|
130
|
+
"""Test plotly dependency availability."""
|
|
131
|
+
code = """
|
|
132
|
+
import plotly.graph_objects as go
|
|
133
|
+
print('Plotly imported successfully')
|
|
134
|
+
fig = go.Figure()
|
|
135
|
+
fig.add_trace(go.Scatter(x=[1, 2, 3], y=[4, 5, 6]))
|
|
136
|
+
print('Plot created successfully')
|
|
137
|
+
"""
|
|
138
|
+
result = self.execute_code(sandbox_config, code)
|
|
139
|
+
|
|
140
|
+
assert result["process_status"] == "completed"
|
|
141
|
+
assert "Plotly imported successfully" in result["stdout"]
|
|
142
|
+
assert "Plot created successfully" in result["stdout"]
|
|
143
|
+
assert result["stderr"] == ""
|
|
144
|
+
|
|
145
|
+
def test_syntax_error_handling(self, sandbox_config):
|
|
146
|
+
"""Test handling of syntax errors."""
|
|
147
|
+
code = """
|
|
148
|
+
print('Hello World'
|
|
149
|
+
# Missing closing parenthesis
|
|
150
|
+
"""
|
|
151
|
+
result = self.execute_code(sandbox_config, code)
|
|
152
|
+
|
|
153
|
+
assert result["process_status"] == "error"
|
|
154
|
+
assert "SyntaxError" in result["stderr"] or "SyntaxError" in result["stdout"]
|
|
155
|
+
|
|
156
|
+
def test_runtime_error_handling(self, sandbox_config):
|
|
157
|
+
"""Test handling of runtime errors."""
|
|
158
|
+
code = """
|
|
159
|
+
x = 1 / 0
|
|
160
|
+
print('This should not print')
|
|
161
|
+
"""
|
|
162
|
+
result = self.execute_code(sandbox_config, code)
|
|
163
|
+
|
|
164
|
+
assert result["process_status"] == "error"
|
|
165
|
+
assert "ZeroDivisionError" in result["stderr"] or "ZeroDivisionError" in result["stdout"]
|
|
166
|
+
|
|
167
|
+
def test_import_error_handling(self, sandbox_config):
|
|
168
|
+
"""Test handling of import errors."""
|
|
169
|
+
code = """
|
|
170
|
+
import nonexistent_module
|
|
171
|
+
print('This should not print')
|
|
172
|
+
"""
|
|
173
|
+
result = self.execute_code(sandbox_config, code)
|
|
174
|
+
|
|
175
|
+
assert result["process_status"] == "error"
|
|
176
|
+
assert "ModuleNotFoundError" in result["stderr"] or "ImportError" in result["stderr"]
|
|
177
|
+
|
|
178
|
+
def test_mixed_output(self, sandbox_config):
|
|
179
|
+
"""Test code that produces both stdout and stderr output."""
|
|
180
|
+
code = """
|
|
181
|
+
import sys
|
|
182
|
+
print('This goes to stdout')
|
|
183
|
+
print('This goes to stderr', file=sys.stderr)
|
|
184
|
+
print('Back to stdout')
|
|
185
|
+
"""
|
|
186
|
+
result = self.execute_code(sandbox_config, code)
|
|
187
|
+
|
|
188
|
+
assert result["process_status"] == "completed"
|
|
189
|
+
assert "This goes to stdout" in result["stdout"]
|
|
190
|
+
assert "Back to stdout" in result["stdout"]
|
|
191
|
+
assert "This goes to stderr" in result["stderr"]
|
|
192
|
+
|
|
193
|
+
def test_long_running_code(self, sandbox_config):
|
|
194
|
+
"""Test code that takes some time to execute but completes within timeout."""
|
|
195
|
+
code = """
|
|
196
|
+
import time
|
|
197
|
+
for i in range(3):
|
|
198
|
+
print(f'Iteration {i}')
|
|
199
|
+
time.sleep(0.5)
|
|
200
|
+
print('Completed')
|
|
201
|
+
"""
|
|
202
|
+
result = self.execute_code(sandbox_config, code)
|
|
203
|
+
|
|
204
|
+
assert result["process_status"] == "completed"
|
|
205
|
+
assert "Iteration 0" in result["stdout"]
|
|
206
|
+
assert "Iteration 1" in result["stdout"]
|
|
207
|
+
assert "Iteration 2" in result["stdout"]
|
|
208
|
+
assert "Completed" in result["stdout"]
|
|
209
|
+
assert result["stderr"] == ""
|
|
210
|
+
|
|
211
|
+
def test_file_operations(self, sandbox_config):
|
|
212
|
+
"""Test basic file operations in the sandbox."""
|
|
213
|
+
code = """
|
|
214
|
+
import os
|
|
215
|
+
print(f'Current directory: {os.getcwd()}')
|
|
216
|
+
with open('test_file.txt', 'w') as f:
|
|
217
|
+
f.write('Hello, World!')
|
|
218
|
+
with open('test_file.txt', 'r') as f:
|
|
219
|
+
content = f.read()
|
|
220
|
+
print(f'File content: {content}')
|
|
221
|
+
os.remove('test_file.txt')
|
|
222
|
+
print('File operations completed')
|
|
223
|
+
"""
|
|
224
|
+
result = self.execute_code(sandbox_config, code)
|
|
225
|
+
|
|
226
|
+
assert result["process_status"] == "completed"
|
|
227
|
+
assert "File content: Hello, World!" in result["stdout"]
|
|
228
|
+
assert "File operations completed" in result["stdout"]
|
|
229
|
+
assert result["stderr"] == ""
|
|
230
|
+
|
|
231
|
+
def test_file_persistence_create(self, sandbox_config):
|
|
232
|
+
"""Test file persistence - create various file types."""
|
|
233
|
+
code = """
|
|
234
|
+
import os
|
|
235
|
+
import pandas as pd
|
|
236
|
+
import numpy as np
|
|
237
|
+
print('Current directory:', os.getcwd())
|
|
238
|
+
print('Directory contents:', os.listdir('.'))
|
|
239
|
+
|
|
240
|
+
# Create a test file
|
|
241
|
+
with open('persistence_test.txt', 'w') as f:
|
|
242
|
+
f.write('Hello from sandbox persistence test!')
|
|
243
|
+
|
|
244
|
+
# Create a CSV file
|
|
245
|
+
df = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]})
|
|
246
|
+
df.to_csv('persistence_test.csv', index=False)
|
|
247
|
+
|
|
248
|
+
# Create a numpy array file
|
|
249
|
+
arr = np.array([1, 2, 3, 4, 5])
|
|
250
|
+
np.save('persistence_test.npy', arr)
|
|
251
|
+
|
|
252
|
+
print('Files created:')
|
|
253
|
+
for file in os.listdir('.'):
|
|
254
|
+
if 'persistence_test' in file:
|
|
255
|
+
print(' -', file)
|
|
256
|
+
"""
|
|
257
|
+
result = self.execute_code(sandbox_config, code)
|
|
258
|
+
|
|
259
|
+
assert result["process_status"] == "completed"
|
|
260
|
+
assert "persistence_test.txt" in result["stdout"]
|
|
261
|
+
assert "persistence_test.csv" in result["stdout"]
|
|
262
|
+
assert "persistence_test.npy" in result["stdout"]
|
|
263
|
+
assert result["stderr"] == ""
|
|
264
|
+
|
|
265
|
+
def test_file_persistence_read(self, sandbox_config):
|
|
266
|
+
"""Test file persistence - read back created files."""
|
|
267
|
+
code = """
|
|
268
|
+
import pandas as pd
|
|
269
|
+
import numpy as np
|
|
270
|
+
|
|
271
|
+
# Read back the files we created
|
|
272
|
+
print('=== Reading persistence_test.txt ===')
|
|
273
|
+
with open('persistence_test.txt', 'r') as f:
|
|
274
|
+
content = f.read()
|
|
275
|
+
print(f'Content: {content}')
|
|
276
|
+
|
|
277
|
+
print('\\n=== Reading persistence_test.csv ===')
|
|
278
|
+
df = pd.read_csv('persistence_test.csv')
|
|
279
|
+
print(df)
|
|
280
|
+
print(f'DataFrame shape: {df.shape}')
|
|
281
|
+
|
|
282
|
+
print('\\n=== Reading persistence_test.npy ===')
|
|
283
|
+
arr = np.load('persistence_test.npy')
|
|
284
|
+
print(f'Array: {arr}')
|
|
285
|
+
print(f'Array sum: {np.sum(arr)}')
|
|
286
|
+
|
|
287
|
+
print('\\n=== File persistence test PASSED! ===')
|
|
288
|
+
"""
|
|
289
|
+
result = self.execute_code(sandbox_config, code)
|
|
290
|
+
|
|
291
|
+
assert result["process_status"] == "completed"
|
|
292
|
+
assert "Content: Hello from sandbox persistence test!" in result["stdout"]
|
|
293
|
+
assert "DataFrame shape: (3, 2)" in result["stdout"]
|
|
294
|
+
assert "Array: [1 2 3 4 5]" in result["stdout"]
|
|
295
|
+
assert "Array sum: 15" in result["stdout"]
|
|
296
|
+
assert "File persistence test PASSED!" in result["stdout"]
|
|
297
|
+
assert result["stderr"] == ""
|
|
298
|
+
|
|
299
|
+
def test_json_operations(self, sandbox_config):
|
|
300
|
+
"""Test JSON file operations for persistence."""
|
|
301
|
+
code = """
|
|
302
|
+
import json
|
|
303
|
+
import os
|
|
304
|
+
|
|
305
|
+
# Create a complex JSON file
|
|
306
|
+
data = {
|
|
307
|
+
'test_name': 'sandbox_persistence',
|
|
308
|
+
'timestamp': '2024-07-03',
|
|
309
|
+
'results': {
|
|
310
|
+
'numpy_test': True,
|
|
311
|
+
'pandas_test': True,
|
|
312
|
+
'file_operations': True
|
|
313
|
+
},
|
|
314
|
+
'metrics': [1.5, 2.3, 3.7, 4.1],
|
|
315
|
+
'metadata': {
|
|
316
|
+
'working_dir': os.getcwd(),
|
|
317
|
+
'python_version': '3.x'
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
# Save JSON file
|
|
322
|
+
with open('persistence_test.json', 'w') as f:
|
|
323
|
+
json.dump(data, f, indent=2)
|
|
324
|
+
|
|
325
|
+
# Read it back
|
|
326
|
+
with open('persistence_test.json', 'r') as f:
|
|
327
|
+
loaded_data = json.load(f)
|
|
328
|
+
|
|
329
|
+
print('JSON file created and loaded successfully')
|
|
330
|
+
print(f'Test name: {loaded_data["test_name"]}')
|
|
331
|
+
print(f'Results count: {len(loaded_data["results"])}')
|
|
332
|
+
print(f'Metrics: {loaded_data["metrics"]}')
|
|
333
|
+
print('JSON persistence test completed!')
|
|
334
|
+
"""
|
|
335
|
+
result = self.execute_code(sandbox_config, code)
|
|
336
|
+
|
|
337
|
+
assert result["process_status"] == "completed"
|
|
338
|
+
assert "JSON file created and loaded successfully" in result["stdout"]
|
|
339
|
+
assert "Test name: sandbox_persistence" in result["stdout"]
|
|
340
|
+
assert "Results count: 3" in result["stdout"]
|
|
341
|
+
assert "JSON persistence test completed!" in result["stdout"]
|
|
342
|
+
assert result["stderr"] == ""
|
|
343
|
+
|
|
344
|
+
def test_missing_generated_code_field(self, sandbox_config):
|
|
345
|
+
"""Test request missing the generated_code field."""
|
|
346
|
+
payload = {"timeout": 10, "language": "python"}
|
|
347
|
+
|
|
348
|
+
response = requests.post(sandbox_config["url"], json=payload)
|
|
349
|
+
|
|
350
|
+
# Should return an error status code or error in response
|
|
351
|
+
assert response.status_code != 200 or "error" in response.json()
|
|
352
|
+
|
|
353
|
+
def test_missing_timeout_field(self, sandbox_config):
|
|
354
|
+
"""Test request missing the timeout field."""
|
|
355
|
+
payload = {"generated_code": "print('test')", "language": "python"}
|
|
356
|
+
|
|
357
|
+
response = requests.post(sandbox_config["url"], json=payload)
|
|
358
|
+
|
|
359
|
+
# Should return error for missing timeout field
|
|
360
|
+
result = response.json()
|
|
361
|
+
assert response.status_code == 400 and result["process_status"] == "error"
|
|
362
|
+
|
|
363
|
+
def test_invalid_json(self, sandbox_config):
|
|
364
|
+
"""Test request with invalid JSON."""
|
|
365
|
+
invalid_json = '{"generated_code": "print("test")", "timeout": 10}'
|
|
366
|
+
|
|
367
|
+
response = requests.post(sandbox_config["url"], data=invalid_json, headers={"Content-Type": "application/json"})
|
|
368
|
+
|
|
369
|
+
# Should return error for invalid JSON
|
|
370
|
+
assert response.status_code != 200
|
|
371
|
+
|
|
372
|
+
def test_non_json_request(self, sandbox_config):
|
|
373
|
+
"""Test request with non-JSON content."""
|
|
374
|
+
response = requests.post(sandbox_config["url"], data="This is not JSON", headers={"Content-Type": "text/plain"})
|
|
375
|
+
|
|
376
|
+
# Should return error for non-JSON content
|
|
377
|
+
assert response.status_code != 200
|
|
378
|
+
|
|
379
|
+
def test_timeout_too_low(self, sandbox_config):
|
|
380
|
+
"""Test request with timeout too low."""
|
|
381
|
+
code = """
|
|
382
|
+
import time
|
|
383
|
+
time.sleep(2.0)
|
|
384
|
+
"""
|
|
385
|
+
payload = {"generated_code": code, "timeout": 1, "language": "python"}
|
|
386
|
+
response = requests.post(sandbox_config["url"], json=payload)
|
|
387
|
+
assert response.json()["process_status"] == "timeout"
|
|
388
|
+
assert response.status_code == 200
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
# Pytest configuration and fixtures for command-line options
|
|
392
|
+
def pytest_addoption(parser):
|
|
393
|
+
"""Add custom command-line options for pytest."""
|
|
394
|
+
parser.addoption("--sandbox-url",
|
|
395
|
+
action="store",
|
|
396
|
+
default="http://127.0.0.1:6000/execute",
|
|
397
|
+
help="Sandbox URL for testing")
|
|
398
|
+
parser.addoption("--sandbox-timeout",
|
|
399
|
+
action="store",
|
|
400
|
+
type=int,
|
|
401
|
+
default=30,
|
|
402
|
+
help="Timeout in seconds for sandbox operations")
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
@pytest.fixture(scope="session", autouse=True)
|
|
406
|
+
def setup_environment(request):
|
|
407
|
+
"""Setup environment variables from command-line options."""
|
|
408
|
+
os.environ["SANDBOX_URL"] = request.config.getoption("--sandbox-url", "http://127.0.0.1:6000/execute")
|
|
409
|
+
os.environ["SANDBOX_TIMEOUT"] = str(request.config.getoption("--sandbox-timeout", 30))
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
if __name__ == "__main__":
|
|
413
|
+
# Allow running as a script
|
|
414
|
+
pytest.main([__file__, "-v"])
|
|
@@ -0,0 +1,100 @@
|
|
|
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 glob
|
|
16
|
+
import logging
|
|
17
|
+
import re
|
|
18
|
+
|
|
19
|
+
LOG = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def format_code_output(execution_dict: dict[str, str],
|
|
23
|
+
code_output_begin: str,
|
|
24
|
+
code_output_end: str,
|
|
25
|
+
code_output_format: str = 'llama'):
|
|
26
|
+
"""Formatting code output to be displayed as an llm expects it."""
|
|
27
|
+
if code_output_format == 'llama':
|
|
28
|
+
output = execution_dict["process_status"]
|
|
29
|
+
if execution_dict['stdout']:
|
|
30
|
+
output += f"\n[stdout]\n{execution_dict['stdout']}[/stdout]"
|
|
31
|
+
if execution_dict['stderr']:
|
|
32
|
+
output += f"\n[stderr]\n{execution_dict['stderr']}[/stderr]"
|
|
33
|
+
output = f"{code_output_begin}\n\n{output}{code_output_end}\n\n"
|
|
34
|
+
elif code_output_format == 'qwen':
|
|
35
|
+
output = ""
|
|
36
|
+
if execution_dict['stdout']:
|
|
37
|
+
output += f"{execution_dict['stdout']}"
|
|
38
|
+
if execution_dict['stderr']:
|
|
39
|
+
output += f"{execution_dict['stderr']}"
|
|
40
|
+
if execution_dict['stderr'] and execution_dict['stdout']:
|
|
41
|
+
LOG.warning("Both stdout and stderr are not empty. This shouldn't normally happen! %s", execution_dict)
|
|
42
|
+
output = f"{code_output_begin}{output}{code_output_end}"
|
|
43
|
+
else:
|
|
44
|
+
raise ValueError(f"Unknown code_output_format: {code_output_format}")
|
|
45
|
+
|
|
46
|
+
# wrapping with code output separators
|
|
47
|
+
return output
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _extract_between_separators(generation: str, separators: tuple[str, str], extract_all: bool = False):
|
|
51
|
+
"""Extracting all text between last occurrence of separators[0] and [1].
|
|
52
|
+
|
|
53
|
+
If extract_all is True, returning a list with all occurrences of text between separators.
|
|
54
|
+
"""
|
|
55
|
+
if extract_all:
|
|
56
|
+
separators = [re.escape(sp) for sp in separators]
|
|
57
|
+
pattern = f'{separators[0]}(.*?){separators[1]}'
|
|
58
|
+
return re.findall(pattern, generation, re.DOTALL)
|
|
59
|
+
return generation.split(separators[0])[-1].split(separators[1])[0]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def extract_code_to_execute(generation: str, code_begin: str, code_end: str, extract_all: bool = False):
|
|
63
|
+
return _extract_between_separators(generation, [code_begin, code_end], extract_all)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def extract_code_output(generation: str, code_output_begin: str, code_output_end: str, extract_all: bool = False):
|
|
67
|
+
return _extract_between_separators(generation, [code_output_begin, code_output_end], extract_all)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def unroll_files(input_files):
|
|
71
|
+
if len(input_files) == 0:
|
|
72
|
+
raise ValueError("No files found with the given pattern.")
|
|
73
|
+
total_files = 0
|
|
74
|
+
for file_pattern in input_files:
|
|
75
|
+
for file in sorted(glob.glob(file_pattern, recursive=True)):
|
|
76
|
+
total_files += 1
|
|
77
|
+
yield file
|
|
78
|
+
if total_files == 0:
|
|
79
|
+
raise ValueError("No files found with the given pattern.")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def python_doc_to_cmd_help(doc_class, docs_prefix="", arg_prefix=""):
|
|
83
|
+
"""Converts python doc to cmd help format.
|
|
84
|
+
|
|
85
|
+
Will color the args and change the format to match what we use in cmd help.
|
|
86
|
+
"""
|
|
87
|
+
all_args = docs_prefix
|
|
88
|
+
all_args += doc_class.__doc__.split("Args:")[1].rstrip()
|
|
89
|
+
# \033[92m ... \033[0m - green in terminal
|
|
90
|
+
colored_args = ""
|
|
91
|
+
for line in all_args.split("\n"):
|
|
92
|
+
if " " in line and " - " in line:
|
|
93
|
+
# add colors
|
|
94
|
+
line = line.replace(" ", " \033[92m").replace(" - ", "\033[0m - ")
|
|
95
|
+
# fixing arg format
|
|
96
|
+
line = line.replace(' \033[92m', f' \033[92m{arg_prefix}')
|
|
97
|
+
# fixing indent
|
|
98
|
+
line = line.replace(" ", " ").replace(" ", " ")
|
|
99
|
+
colored_args += line + '\n'
|
|
100
|
+
return colored_args[:-1]
|