nvidia-nat 1.4.0a20251120__py3-none-any.whl → 1.4.0a20260113__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 +1 -1
- nat/{front_ends/mcp → agent/auto_memory_wrapper}/__init__.py +1 -1
- nat/agent/auto_memory_wrapper/agent.py +278 -0
- nat/agent/auto_memory_wrapper/register.py +227 -0
- nat/agent/auto_memory_wrapper/state.py +30 -0
- nat/agent/base.py +1 -1
- nat/agent/dual_node.py +1 -1
- nat/agent/prompt_optimizer/prompt.py +1 -1
- nat/agent/prompt_optimizer/register.py +1 -1
- nat/agent/react_agent/agent.py +16 -9
- nat/agent/react_agent/output_parser.py +2 -2
- nat/agent/react_agent/prompt.py +3 -2
- nat/agent/react_agent/register.py +2 -2
- nat/agent/react_agent/register_per_user_agent.py +104 -0
- nat/agent/reasoning_agent/reasoning_agent.py +1 -1
- nat/agent/register.py +3 -1
- nat/agent/responses_api_agent/__init__.py +1 -1
- nat/agent/responses_api_agent/register.py +1 -1
- nat/agent/rewoo_agent/agent.py +9 -4
- nat/agent/rewoo_agent/prompt.py +1 -1
- nat/agent/rewoo_agent/register.py +1 -1
- nat/agent/tool_calling_agent/agent.py +5 -4
- nat/agent/tool_calling_agent/register.py +1 -1
- nat/authentication/__init__.py +1 -1
- nat/authentication/api_key/__init__.py +1 -1
- nat/authentication/api_key/api_key_auth_provider.py +1 -1
- nat/authentication/api_key/api_key_auth_provider_config.py +22 -7
- nat/authentication/api_key/register.py +1 -1
- nat/authentication/credential_validator/__init__.py +1 -1
- nat/authentication/credential_validator/bearer_token_validator.py +1 -1
- nat/authentication/exceptions/__init__.py +1 -1
- nat/authentication/exceptions/api_key_exceptions.py +1 -1
- nat/authentication/http_basic_auth/http_basic_auth_provider.py +1 -1
- nat/authentication/http_basic_auth/register.py +1 -1
- nat/authentication/interfaces.py +1 -1
- nat/authentication/oauth2/__init__.py +1 -1
- nat/authentication/oauth2/oauth2_auth_code_flow_provider.py +1 -1
- nat/authentication/oauth2/oauth2_auth_code_flow_provider_config.py +1 -1
- nat/authentication/oauth2/oauth2_resource_server_config.py +1 -1
- nat/authentication/oauth2/register.py +1 -1
- nat/authentication/register.py +1 -1
- nat/builder/builder.py +511 -1
- nat/builder/child_builder.py +385 -0
- nat/builder/component_utils.py +28 -4
- nat/builder/context.py +17 -1
- nat/builder/embedder.py +1 -1
- nat/builder/eval_builder.py +19 -7
- nat/builder/evaluator.py +1 -1
- nat/builder/framework_enum.py +2 -1
- nat/builder/front_end.py +1 -1
- nat/builder/function.py +40 -3
- nat/builder/function_base.py +1 -1
- nat/builder/function_info.py +1 -1
- nat/builder/intermediate_step_manager.py +1 -1
- nat/builder/llm.py +1 -1
- nat/builder/per_user_workflow_builder.py +843 -0
- nat/builder/retriever.py +1 -1
- nat/builder/sync_builder.py +571 -0
- nat/builder/user_interaction_manager.py +1 -1
- nat/builder/workflow.py +1 -1
- nat/builder/workflow_builder.py +536 -424
- nat/cli/__init__.py +1 -1
- nat/cli/cli_utils/config_override.py +1 -1
- nat/cli/cli_utils/validation.py +32 -1
- nat/cli/commands/configure/channel/add.py +1 -1
- nat/cli/commands/configure/channel/channel.py +1 -1
- nat/cli/commands/configure/channel/remove.py +1 -1
- nat/cli/commands/configure/channel/update.py +1 -1
- nat/cli/commands/configure/configure.py +1 -1
- nat/cli/commands/evaluate.py +87 -13
- nat/cli/commands/finetune.py +132 -0
- nat/cli/commands/info/__init__.py +1 -1
- nat/cli/commands/info/info.py +1 -1
- nat/cli/commands/info/list_channels.py +1 -1
- nat/cli/commands/info/list_components.py +1 -1
- nat/cli/commands/object_store/__init__.py +1 -1
- nat/cli/commands/object_store/object_store.py +1 -1
- nat/cli/commands/optimize.py +1 -1
- nat/cli/commands/{mcp → red_teaming}/__init__.py +1 -1
- nat/cli/commands/red_teaming/red_teaming.py +138 -0
- nat/cli/commands/red_teaming/red_teaming_utils.py +73 -0
- nat/cli/commands/registry/__init__.py +1 -1
- nat/cli/commands/registry/publish.py +1 -1
- nat/cli/commands/registry/pull.py +1 -1
- nat/cli/commands/registry/registry.py +1 -1
- nat/cli/commands/registry/remove.py +1 -1
- nat/cli/commands/registry/search.py +1 -1
- nat/cli/commands/sizing/__init__.py +1 -1
- nat/cli/commands/sizing/calc.py +1 -1
- nat/cli/commands/sizing/sizing.py +1 -1
- nat/cli/commands/start.py +1 -1
- nat/cli/commands/uninstall.py +1 -1
- nat/cli/commands/validate.py +1 -1
- nat/cli/commands/workflow/__init__.py +1 -1
- nat/cli/commands/workflow/workflow.py +1 -1
- nat/cli/commands/workflow/workflow_commands.py +3 -2
- nat/cli/entrypoint.py +15 -37
- nat/cli/main.py +2 -2
- nat/cli/plugin_loader.py +69 -0
- nat/cli/register_workflow.py +183 -5
- nat/cli/type_registry.py +169 -3
- nat/control_flow/register.py +1 -1
- nat/control_flow/router_agent/agent.py +1 -1
- nat/control_flow/router_agent/prompt.py +1 -1
- nat/control_flow/router_agent/register.py +1 -1
- nat/control_flow/sequential_executor.py +28 -7
- nat/data_models/__init__.py +1 -1
- nat/data_models/agent.py +1 -1
- nat/data_models/api_server.py +38 -3
- nat/data_models/authentication.py +1 -1
- nat/data_models/common.py +1 -1
- nat/data_models/component.py +7 -1
- nat/data_models/component_ref.py +34 -1
- nat/data_models/config.py +62 -1
- nat/data_models/dataset_handler.py +15 -2
- nat/data_models/discovery_metadata.py +1 -1
- nat/data_models/embedder.py +1 -1
- nat/data_models/evaluate.py +6 -1
- nat/data_models/evaluator.py +1 -1
- nat/data_models/finetuning.py +260 -0
- nat/data_models/front_end.py +1 -1
- nat/data_models/function.py +1 -1
- nat/data_models/function_dependencies.py +1 -1
- nat/data_models/gated_field_mixin.py +1 -1
- nat/data_models/interactive.py +1 -1
- nat/data_models/intermediate_step.py +29 -2
- nat/data_models/invocation_node.py +1 -1
- nat/data_models/llm.py +1 -1
- nat/data_models/logging.py +1 -1
- nat/data_models/memory.py +1 -1
- nat/data_models/middleware.py +3 -1
- nat/data_models/object_store.py +1 -1
- nat/data_models/openai_mcp.py +1 -1
- nat/data_models/optimizable.py +1 -1
- nat/data_models/optimizer.py +1 -1
- nat/data_models/profiler.py +1 -1
- nat/data_models/registry_handler.py +1 -1
- nat/data_models/retriever.py +1 -1
- nat/data_models/retry_mixin.py +1 -1
- nat/data_models/runtime_enum.py +1 -1
- nat/data_models/span.py +1 -1
- nat/data_models/step_adaptor.py +1 -1
- nat/data_models/streaming.py +1 -1
- nat/data_models/swe_bench_model.py +1 -1
- nat/data_models/telemetry_exporter.py +1 -1
- nat/data_models/thinking_mixin.py +1 -1
- nat/data_models/ttc_strategy.py +1 -1
- nat/embedder/azure_openai_embedder.py +1 -1
- nat/embedder/nim_embedder.py +1 -1
- nat/embedder/openai_embedder.py +1 -1
- nat/embedder/register.py +1 -1
- nat/eval/__init__.py +1 -1
- nat/eval/config.py +8 -1
- nat/eval/dataset_handler/dataset_downloader.py +1 -1
- nat/eval/dataset_handler/dataset_filter.py +1 -1
- nat/eval/dataset_handler/dataset_handler.py +4 -2
- nat/eval/evaluate.py +217 -80
- nat/eval/evaluator/__init__.py +1 -1
- nat/eval/evaluator/base_evaluator.py +2 -2
- nat/eval/evaluator/evaluator_model.py +3 -2
- nat/eval/intermediate_step_adapter.py +1 -1
- nat/eval/llm_validator.py +336 -0
- nat/eval/rag_evaluator/evaluate.py +17 -10
- nat/eval/rag_evaluator/register.py +1 -1
- nat/eval/red_teaming_evaluator/__init__.py +14 -0
- nat/eval/red_teaming_evaluator/data_models.py +66 -0
- nat/eval/red_teaming_evaluator/evaluate.py +327 -0
- nat/eval/red_teaming_evaluator/filter_conditions.py +75 -0
- nat/eval/red_teaming_evaluator/register.py +55 -0
- nat/eval/register.py +2 -1
- nat/eval/remote_workflow.py +1 -1
- nat/eval/runners/__init__.py +1 -1
- nat/eval/runners/config.py +1 -1
- nat/eval/runners/multi_eval_runner.py +1 -1
- nat/eval/runners/red_teaming_runner/__init__.py +24 -0
- nat/eval/runners/red_teaming_runner/config.py +282 -0
- nat/eval/runners/red_teaming_runner/report_utils.py +707 -0
- nat/eval/runners/red_teaming_runner/runner.py +867 -0
- nat/eval/runtime_evaluator/__init__.py +1 -1
- nat/eval/runtime_evaluator/evaluate.py +1 -1
- nat/eval/runtime_evaluator/register.py +1 -1
- nat/eval/runtime_event_subscriber.py +1 -1
- nat/eval/swe_bench_evaluator/evaluate.py +1 -1
- nat/eval/swe_bench_evaluator/register.py +1 -1
- nat/eval/trajectory_evaluator/evaluate.py +2 -2
- nat/eval/trajectory_evaluator/register.py +1 -1
- nat/eval/tunable_rag_evaluator/evaluate.py +5 -5
- nat/eval/tunable_rag_evaluator/register.py +1 -1
- nat/eval/usage_stats.py +1 -1
- nat/eval/utils/eval_trace_ctx.py +1 -1
- nat/eval/utils/output_uploader.py +1 -1
- nat/eval/utils/tqdm_position_registry.py +1 -1
- nat/eval/utils/weave_eval.py +1 -1
- nat/experimental/decorators/experimental_warning_decorator.py +1 -1
- nat/experimental/test_time_compute/editing/iterative_plan_refinement_editor.py +1 -1
- nat/experimental/test_time_compute/editing/llm_as_a_judge_editor.py +1 -1
- nat/experimental/test_time_compute/editing/motivation_aware_summarization.py +1 -1
- nat/experimental/test_time_compute/functions/execute_score_select_function.py +1 -1
- nat/experimental/test_time_compute/functions/multi_llm_judge_function.py +88 -0
- nat/experimental/test_time_compute/functions/plan_select_execute_function.py +1 -1
- nat/experimental/test_time_compute/functions/ttc_tool_orchestration_function.py +1 -1
- nat/experimental/test_time_compute/functions/ttc_tool_wrapper_function.py +1 -1
- nat/experimental/test_time_compute/models/editor_config.py +1 -1
- nat/experimental/test_time_compute/models/scoring_config.py +1 -1
- nat/experimental/test_time_compute/models/search_config.py +20 -2
- nat/experimental/test_time_compute/models/selection_config.py +33 -2
- nat/experimental/test_time_compute/models/stage_enums.py +1 -1
- nat/experimental/test_time_compute/models/strategy_base.py +1 -1
- nat/experimental/test_time_compute/models/tool_use_config.py +1 -1
- nat/experimental/test_time_compute/models/ttc_item.py +1 -1
- nat/experimental/test_time_compute/register.py +4 -1
- nat/experimental/test_time_compute/scoring/llm_based_agent_scorer.py +1 -1
- nat/experimental/test_time_compute/scoring/llm_based_plan_scorer.py +1 -1
- nat/experimental/test_time_compute/scoring/motivation_aware_scorer.py +1 -1
- nat/experimental/test_time_compute/search/multi_llm_generation.py +115 -0
- nat/experimental/test_time_compute/search/multi_llm_planner.py +1 -1
- nat/experimental/test_time_compute/search/multi_query_retrieval_search.py +1 -1
- nat/experimental/test_time_compute/search/single_shot_multi_plan_planner.py +1 -1
- nat/experimental/test_time_compute/selection/best_of_n_selector.py +1 -1
- nat/experimental/test_time_compute/selection/llm_based_agent_output_selector.py +1 -1
- nat/experimental/test_time_compute/selection/llm_based_output_merging_selector.py +1 -1
- nat/experimental/test_time_compute/selection/llm_based_plan_selector.py +1 -1
- nat/experimental/test_time_compute/selection/llm_judge_selection.py +127 -0
- nat/experimental/test_time_compute/selection/threshold_selector.py +1 -1
- nat/finetuning/__init__.py +24 -0
- nat/finetuning/finetuning_runtime.py +143 -0
- nat/finetuning/interfaces/__init__.py +24 -0
- nat/finetuning/interfaces/finetuning_runner.py +261 -0
- nat/finetuning/interfaces/trainer_adapter.py +103 -0
- nat/finetuning/interfaces/trajectory_builder.py +115 -0
- nat/finetuning/utils/__init__.py +15 -0
- nat/finetuning/utils/parsers/__init__.py +15 -0
- nat/finetuning/utils/parsers/adk_parser.py +141 -0
- nat/finetuning/utils/parsers/base_parser.py +238 -0
- nat/finetuning/utils/parsers/common.py +91 -0
- nat/finetuning/utils/parsers/langchain_parser.py +267 -0
- nat/finetuning/utils/parsers/llama_index_parser.py +218 -0
- nat/front_ends/__init__.py +1 -1
- nat/front_ends/console/__init__.py +1 -1
- nat/front_ends/console/authentication_flow_handler.py +1 -1
- nat/front_ends/console/console_front_end_config.py +4 -1
- nat/front_ends/console/console_front_end_plugin.py +5 -4
- nat/front_ends/console/register.py +1 -1
- nat/front_ends/cron/__init__.py +1 -1
- nat/front_ends/fastapi/__init__.py +1 -1
- nat/front_ends/fastapi/async_job.py +128 -0
- nat/front_ends/fastapi/auth_flow_handlers/http_flow_handler.py +1 -1
- nat/front_ends/fastapi/auth_flow_handlers/websocket_flow_handler.py +13 -9
- nat/front_ends/fastapi/dask_client_mixin.py +1 -1
- nat/front_ends/fastapi/fastapi_front_end_config.py +1 -1
- nat/front_ends/fastapi/fastapi_front_end_controller.py +1 -1
- nat/front_ends/fastapi/fastapi_front_end_plugin.py +25 -30
- nat/front_ends/fastapi/fastapi_front_end_plugin_worker.py +195 -60
- nat/front_ends/fastapi/html_snippets/__init__.py +1 -1
- nat/front_ends/fastapi/html_snippets/auth_code_grant_success.py +1 -1
- nat/front_ends/fastapi/intermediate_steps_subscriber.py +12 -1
- nat/front_ends/fastapi/job_store.py +23 -11
- nat/front_ends/fastapi/main.py +1 -1
- nat/front_ends/fastapi/message_handler.py +27 -4
- nat/front_ends/fastapi/message_validator.py +54 -2
- nat/front_ends/fastapi/register.py +1 -1
- nat/front_ends/fastapi/response_helpers.py +16 -15
- nat/front_ends/fastapi/step_adaptor.py +1 -1
- nat/front_ends/fastapi/utils.py +1 -1
- nat/front_ends/register.py +1 -2
- nat/front_ends/simple_base/__init__.py +1 -1
- nat/front_ends/simple_base/simple_front_end_plugin_base.py +6 -4
- nat/llm/aws_bedrock_llm.py +1 -1
- nat/llm/azure_openai_llm.py +10 -1
- nat/llm/dynamo_llm.py +363 -0
- nat/llm/huggingface_llm.py +177 -0
- nat/llm/litellm_llm.py +1 -1
- nat/llm/nim_llm.py +1 -1
- nat/llm/openai_llm.py +1 -1
- nat/llm/register.py +3 -1
- nat/llm/utils/__init__.py +1 -1
- nat/llm/utils/env_config_value.py +1 -1
- nat/llm/utils/error.py +1 -1
- nat/llm/utils/thinking.py +1 -1
- nat/memory/__init__.py +1 -1
- nat/memory/interfaces.py +1 -1
- nat/memory/models.py +1 -1
- nat/meta/pypi.md +1 -1
- nat/middleware/__init__.py +5 -5
- nat/middleware/cache/__init__.py +14 -0
- nat/middleware/{cache_middleware.py → cache/cache_middleware.py} +39 -42
- nat/middleware/cache/cache_middleware_config.py +44 -0
- nat/middleware/cache/register.py +33 -0
- nat/middleware/defense/__init__.py +14 -0
- nat/middleware/defense/defense_middleware.py +362 -0
- nat/middleware/defense/defense_middleware_content_guard.py +455 -0
- nat/middleware/defense/defense_middleware_data_models.py +91 -0
- nat/middleware/defense/defense_middleware_output_verifier.py +440 -0
- nat/middleware/defense/defense_middleware_pii.py +356 -0
- nat/middleware/defense/register.py +82 -0
- nat/middleware/dynamic/__init__.py +14 -0
- nat/middleware/dynamic/dynamic_function_middleware.py +962 -0
- nat/middleware/dynamic/dynamic_middleware_config.py +132 -0
- nat/middleware/dynamic/register.py +34 -0
- nat/middleware/function_middleware.py +236 -52
- nat/middleware/logging/__init__.py +14 -0
- nat/middleware/logging/logging_middleware.py +67 -0
- nat/middleware/logging/logging_middleware_config.py +28 -0
- nat/middleware/logging/register.py +33 -0
- nat/middleware/middleware.py +142 -28
- nat/middleware/red_teaming/__init__.py +14 -0
- nat/middleware/red_teaming/red_teaming_middleware.py +344 -0
- nat/middleware/red_teaming/red_teaming_middleware_config.py +112 -0
- nat/middleware/red_teaming/register.py +47 -0
- nat/middleware/register.py +7 -20
- nat/middleware/utils/__init__.py +14 -0
- nat/middleware/utils/workflow_inventory.py +155 -0
- nat/object_store/__init__.py +1 -1
- nat/object_store/in_memory_object_store.py +1 -1
- nat/object_store/interfaces.py +1 -1
- nat/object_store/models.py +1 -1
- nat/object_store/register.py +1 -1
- nat/observability/__init__.py +1 -1
- nat/observability/exporter/__init__.py +1 -1
- nat/observability/exporter/base_exporter.py +1 -1
- nat/observability/exporter/exporter.py +1 -1
- nat/observability/exporter/file_exporter.py +1 -1
- nat/observability/exporter/processing_exporter.py +1 -1
- nat/observability/exporter/raw_exporter.py +1 -1
- nat/observability/exporter/span_exporter.py +7 -1
- nat/observability/exporter_manager.py +1 -1
- nat/observability/mixin/__init__.py +1 -1
- nat/observability/mixin/batch_config_mixin.py +1 -1
- nat/observability/mixin/collector_config_mixin.py +1 -1
- nat/observability/mixin/file_mixin.py +1 -1
- nat/observability/mixin/file_mode.py +1 -1
- nat/observability/mixin/redaction_config_mixin.py +1 -1
- nat/observability/mixin/resource_conflict_mixin.py +1 -1
- nat/observability/mixin/serialize_mixin.py +1 -1
- nat/observability/mixin/tagging_config_mixin.py +1 -1
- nat/observability/mixin/type_introspection_mixin.py +1 -1
- nat/observability/processor/__init__.py +1 -1
- nat/observability/processor/batching_processor.py +1 -1
- nat/observability/processor/callback_processor.py +1 -1
- nat/observability/processor/falsy_batch_filter_processor.py +1 -1
- nat/observability/processor/intermediate_step_serializer.py +1 -1
- nat/observability/processor/processor.py +1 -1
- nat/observability/processor/processor_factory.py +1 -1
- nat/observability/processor/redaction/__init__.py +1 -1
- nat/observability/processor/redaction/contextual_redaction_processor.py +1 -1
- nat/observability/processor/redaction/contextual_span_redaction_processor.py +1 -1
- nat/observability/processor/redaction/redaction_processor.py +1 -1
- nat/observability/processor/redaction/span_header_redaction_processor.py +1 -1
- nat/observability/processor/span_tagging_processor.py +1 -1
- nat/observability/register.py +1 -1
- nat/observability/utils/__init__.py +1 -1
- nat/observability/utils/dict_utils.py +1 -1
- nat/observability/utils/time_utils.py +1 -1
- nat/profiler/calc/__init__.py +1 -1
- nat/profiler/calc/calc_runner.py +3 -3
- nat/profiler/calc/calculations.py +1 -1
- nat/profiler/calc/data_models.py +1 -1
- nat/profiler/calc/plot.py +30 -3
- nat/profiler/callbacks/agno_callback_handler.py +1 -1
- nat/profiler/callbacks/base_callback_class.py +1 -1
- nat/profiler/callbacks/langchain_callback_handler.py +33 -3
- nat/profiler/callbacks/llama_index_callback_handler.py +13 -10
- nat/profiler/callbacks/semantic_kernel_callback_handler.py +1 -1
- nat/profiler/callbacks/token_usage_base_model.py +1 -1
- nat/profiler/data_frame_row.py +1 -1
- nat/profiler/data_models.py +1 -1
- nat/profiler/decorators/framework_wrapper.py +16 -1
- nat/profiler/decorators/function_tracking.py +1 -1
- nat/profiler/forecasting/config.py +1 -1
- nat/profiler/forecasting/model_trainer.py +1 -1
- nat/profiler/forecasting/models/__init__.py +1 -1
- nat/profiler/forecasting/models/forecasting_base_model.py +1 -1
- nat/profiler/forecasting/models/linear_model.py +1 -1
- nat/profiler/forecasting/models/random_forest_regressor.py +1 -1
- nat/profiler/inference_metrics_model.py +1 -1
- nat/profiler/inference_optimization/bottleneck_analysis/nested_stack_analysis.py +1 -1
- nat/profiler/inference_optimization/bottleneck_analysis/simple_stack_analysis.py +1 -1
- nat/profiler/inference_optimization/data_models.py +1 -1
- nat/profiler/inference_optimization/experimental/concurrency_spike_analysis.py +1 -1
- nat/profiler/inference_optimization/experimental/prefix_span_analysis.py +1 -1
- nat/profiler/inference_optimization/llm_metrics.py +1 -1
- nat/profiler/inference_optimization/prompt_caching.py +1 -1
- nat/profiler/inference_optimization/token_uniqueness.py +1 -1
- nat/profiler/inference_optimization/workflow_runtimes.py +1 -1
- nat/profiler/intermediate_property_adapter.py +1 -1
- nat/profiler/parameter_optimization/optimizable_utils.py +1 -1
- nat/profiler/parameter_optimization/optimizer_runtime.py +1 -1
- nat/profiler/parameter_optimization/parameter_optimizer.py +1 -1
- nat/profiler/parameter_optimization/parameter_selection.py +1 -1
- nat/profiler/parameter_optimization/pareto_visualizer.py +1 -1
- nat/profiler/parameter_optimization/prompt_optimizer.py +1 -1
- nat/profiler/parameter_optimization/update_helpers.py +1 -1
- nat/profiler/profile_runner.py +1 -1
- nat/profiler/utils.py +1 -1
- nat/registry_handlers/local/local_handler.py +1 -1
- nat/registry_handlers/local/register_local.py +1 -1
- nat/registry_handlers/metadata_factory.py +1 -1
- nat/registry_handlers/package_utils.py +1 -1
- nat/registry_handlers/pypi/pypi_handler.py +1 -1
- nat/registry_handlers/pypi/register_pypi.py +1 -1
- nat/registry_handlers/register.py +1 -1
- nat/registry_handlers/registry_handler_base.py +1 -1
- nat/registry_handlers/rest/register_rest.py +1 -1
- nat/registry_handlers/rest/rest_handler.py +1 -1
- nat/registry_handlers/schemas/headers.py +1 -1
- nat/registry_handlers/schemas/package.py +1 -1
- nat/registry_handlers/schemas/publish.py +1 -1
- nat/registry_handlers/schemas/pull.py +1 -1
- nat/registry_handlers/schemas/remove.py +1 -1
- nat/registry_handlers/schemas/search.py +1 -1
- nat/registry_handlers/schemas/status.py +1 -1
- nat/retriever/interface.py +1 -1
- nat/retriever/milvus/__init__.py +1 -1
- nat/retriever/milvus/register.py +1 -1
- nat/retriever/milvus/retriever.py +1 -1
- nat/retriever/models.py +1 -1
- nat/retriever/nemo_retriever/__init__.py +1 -1
- nat/retriever/nemo_retriever/register.py +1 -1
- nat/retriever/nemo_retriever/retriever.py +5 -5
- nat/retriever/register.py +1 -1
- nat/runtime/__init__.py +1 -1
- nat/runtime/loader.py +10 -3
- nat/runtime/metrics.py +180 -0
- nat/runtime/runner.py +1 -5
- nat/runtime/session.py +451 -32
- nat/runtime/user_metadata.py +1 -1
- nat/settings/global_settings.py +1 -1
- nat/tool/chat_completion.py +1 -1
- nat/tool/code_execution/README.md +1 -1
- nat/tool/code_execution/code_sandbox.py +1 -1
- nat/tool/code_execution/local_sandbox/Dockerfile.sandbox +1 -1
- nat/tool/code_execution/local_sandbox/__init__.py +1 -1
- nat/tool/code_execution/local_sandbox/local_sandbox_server.py +1 -1
- nat/tool/code_execution/local_sandbox/start_local_sandbox.sh +1 -1
- nat/tool/code_execution/register.py +1 -1
- nat/tool/code_execution/utils.py +1 -1
- nat/tool/datetime_tools.py +1 -1
- nat/tool/document_search.py +1 -1
- nat/tool/github_tools.py +1 -1
- nat/tool/memory_tools/add_memory_tool.py +1 -1
- nat/tool/memory_tools/delete_memory_tool.py +1 -1
- nat/tool/memory_tools/get_memory_tool.py +1 -1
- nat/tool/nvidia_rag.py +2 -2
- nat/tool/register.py +1 -1
- nat/tool/retriever.py +1 -1
- nat/tool/server_tools.py +1 -1
- nat/utils/__init__.py +8 -5
- nat/utils/callable_utils.py +1 -1
- nat/utils/data_models/schema_validator.py +1 -1
- nat/utils/debugging_utils.py +1 -1
- nat/utils/decorators.py +1 -1
- nat/utils/dump_distro_mapping.py +1 -1
- nat/utils/exception_handlers/automatic_retries.py +3 -3
- nat/utils/exception_handlers/schemas.py +1 -1
- nat/utils/io/model_processing.py +1 -1
- nat/utils/io/supress_logs.py +33 -0
- nat/utils/io/yaml_tools.py +1 -1
- nat/utils/log_levels.py +1 -1
- nat/utils/log_utils.py +13 -1
- nat/utils/metadata_utils.py +1 -1
- nat/utils/optional_imports.py +1 -1
- nat/utils/producer_consumer_queue.py +1 -1
- nat/utils/reactive/base/observable_base.py +1 -1
- nat/utils/reactive/base/observer_base.py +1 -1
- nat/utils/reactive/base/subject_base.py +1 -1
- nat/utils/reactive/observable.py +1 -1
- nat/utils/reactive/observer.py +1 -1
- nat/utils/reactive/subject.py +1 -1
- nat/utils/reactive/subscription.py +1 -1
- nat/utils/responses_api.py +1 -1
- nat/utils/settings/global_settings.py +1 -1
- nat/utils/string_utils.py +1 -1
- nat/utils/type_converter.py +18 -5
- nat/utils/type_utils.py +1 -1
- nat/utils/url_utils.py +1 -1
- {nvidia_nat-1.4.0a20251120.dist-info → nvidia_nat-1.4.0a20260113.dist-info}/METADATA +39 -14
- nvidia_nat-1.4.0a20260113.dist-info/RECORD +547 -0
- nvidia_nat-1.4.0a20260113.dist-info/entry_points.txt +38 -0
- nat/cli/commands/mcp/mcp.py +0 -986
- nat/front_ends/mcp/introspection_token_verifier.py +0 -73
- nat/front_ends/mcp/mcp_front_end_config.py +0 -109
- nat/front_ends/mcp/mcp_front_end_plugin.py +0 -155
- nat/front_ends/mcp/mcp_front_end_plugin_worker.py +0 -388
- nat/front_ends/mcp/memory_profiler.py +0 -320
- nat/front_ends/mcp/register.py +0 -27
- nat/front_ends/mcp/tool_converter.py +0 -321
- nvidia_nat-1.4.0a20251120.dist-info/RECORD +0 -488
- nvidia_nat-1.4.0a20251120.dist-info/entry_points.txt +0 -23
- {nvidia_nat-1.4.0a20251120.dist-info → nvidia_nat-1.4.0a20260113.dist-info}/WHEEL +0 -0
- {nvidia_nat-1.4.0a20251120.dist-info → nvidia_nat-1.4.0a20260113.dist-info}/licenses/LICENSE-3rd-party.txt +0 -0
- {nvidia_nat-1.4.0a20251120.dist-info → nvidia_nat-1.4.0a20260113.dist-info}/licenses/LICENSE.md +0 -0
- {nvidia_nat-1.4.0a20251120.dist-info → nvidia_nat-1.4.0a20260113.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026, 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
|
+
Content Safety Guard Middleware.
|
|
17
|
+
|
|
18
|
+
This middleware uses guard models to classify content as safe or harmful
|
|
19
|
+
with simple Yes/No answers.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
import logging
|
|
24
|
+
import re
|
|
25
|
+
from collections.abc import AsyncIterator
|
|
26
|
+
from typing import Any
|
|
27
|
+
|
|
28
|
+
from pydantic import Field
|
|
29
|
+
|
|
30
|
+
from nat.middleware.defense.defense_middleware import DefenseMiddleware
|
|
31
|
+
from nat.middleware.defense.defense_middleware import DefenseMiddlewareConfig
|
|
32
|
+
from nat.middleware.defense.defense_middleware_data_models import ContentAnalysisResult
|
|
33
|
+
from nat.middleware.defense.defense_middleware_data_models import GuardResponseResult
|
|
34
|
+
from nat.middleware.function_middleware import CallNext
|
|
35
|
+
from nat.middleware.function_middleware import CallNextStream
|
|
36
|
+
from nat.middleware.middleware import FunctionMiddlewareContext
|
|
37
|
+
|
|
38
|
+
logger = logging.getLogger(__name__)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ContentSafetyGuardMiddlewareConfig(DefenseMiddlewareConfig, name="content_safety_guard"):
|
|
42
|
+
"""Configuration for Content Safety Guard middleware.
|
|
43
|
+
|
|
44
|
+
This middleware uses guard models to classify content as safe or harmful.
|
|
45
|
+
|
|
46
|
+
Actions: partial_compliance (log warning but allow), refusal (block content),
|
|
47
|
+
or redirection (replace with polite refusal message).
|
|
48
|
+
|
|
49
|
+
Note: Only output analysis is currently supported (target_location='output').
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
llm_name: str = Field(description="Name of the guard model LLM (must be defined in llms section)")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class ContentSafetyGuardMiddleware(DefenseMiddleware):
|
|
56
|
+
"""Safety guard middleware using guard models to classify content as safe or unsafe.
|
|
57
|
+
|
|
58
|
+
This middleware analyzes content using guard models (e.g., NVIDIA Nemoguard, Qwen Guard)
|
|
59
|
+
that return "Safe" or "Unsafe" classifications. The middleware extracts safety categories
|
|
60
|
+
when unsafe content is detected.
|
|
61
|
+
|
|
62
|
+
Only output analysis is currently supported (``target_location='output'``).
|
|
63
|
+
|
|
64
|
+
Streaming Behavior:
|
|
65
|
+
For 'refusal' and 'redirection' actions, chunks are buffered and checked
|
|
66
|
+
before yielding to prevent unsafe content from being streamed to clients.
|
|
67
|
+
For 'partial_compliance' action, chunks are yielded immediately; violations
|
|
68
|
+
are logged but content passes through.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
def __init__(self, config: ContentSafetyGuardMiddlewareConfig, builder):
|
|
72
|
+
"""Initialize content safety guard middleware.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
config: Configuration for content safety guard middleware
|
|
76
|
+
builder: Builder instance for loading LLMs
|
|
77
|
+
"""
|
|
78
|
+
super().__init__(config, builder)
|
|
79
|
+
# Store config with correct type for linter
|
|
80
|
+
self.config: ContentSafetyGuardMiddlewareConfig = config
|
|
81
|
+
self._llm = None # Lazy loaded LLM
|
|
82
|
+
|
|
83
|
+
# Content Safety Guard only supports output analysis
|
|
84
|
+
if config.target_location == "input":
|
|
85
|
+
raise ValueError("ContentSafetyGuardMiddleware only supports target_location='output'. "
|
|
86
|
+
"Input analysis is not yet supported.")
|
|
87
|
+
|
|
88
|
+
async def _get_llm(self):
|
|
89
|
+
"""Lazy load the guard model LLM when first needed."""
|
|
90
|
+
if self._llm is None:
|
|
91
|
+
self._llm = await self._get_llm_for_defense(self.config.llm_name)
|
|
92
|
+
return self._llm
|
|
93
|
+
|
|
94
|
+
def _extract_unsafe_categories(self, response_text: str, is_safe: bool) -> list[str]:
|
|
95
|
+
"""Extract safety categories only if content is unsafe.
|
|
96
|
+
|
|
97
|
+
Supports both JSON formats (Safety Categories field) and text formats
|
|
98
|
+
(Categories: line).
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
response_text: Raw response from guard model.
|
|
102
|
+
is_safe: Whether the content was detected as safe.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
List of category strings if unsafe, empty list otherwise or on parsing error.
|
|
106
|
+
"""
|
|
107
|
+
if is_safe:
|
|
108
|
+
return []
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
categories = []
|
|
112
|
+
|
|
113
|
+
# Try parsing as JSON first (for Nemoguard)
|
|
114
|
+
try:
|
|
115
|
+
json_data = json.loads(response_text)
|
|
116
|
+
# Look for common category field names
|
|
117
|
+
category_field = None
|
|
118
|
+
for field in ["Safety Categories", "Categories", "Category", "safety_categories", "categories"]:
|
|
119
|
+
if field in json_data:
|
|
120
|
+
category_field = json_data[field]
|
|
121
|
+
break
|
|
122
|
+
|
|
123
|
+
if category_field:
|
|
124
|
+
if isinstance(category_field, str):
|
|
125
|
+
# Split by comma if it's a comma-separated string
|
|
126
|
+
categories = [cat.strip() for cat in category_field.split(",")]
|
|
127
|
+
elif isinstance(category_field, list):
|
|
128
|
+
categories = [str(cat).strip() for cat in category_field]
|
|
129
|
+
except (json.JSONDecodeError, ValueError, AttributeError):
|
|
130
|
+
# Not JSON, try text parsing (for Qwen Guard)
|
|
131
|
+
# Look for "Categories:" or "Category:" followed by text
|
|
132
|
+
category_patterns = [
|
|
133
|
+
r'Categories?:\s*([^\n]+)', # Categories: Violent
|
|
134
|
+
r'Categories?\s*=\s*([^\n]+)', # Categories = Violent
|
|
135
|
+
r'"Safety Categories":\s*"([^"]+)"', # JSON-like in text
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
for pattern in category_patterns:
|
|
139
|
+
match = re.search(pattern, response_text, re.IGNORECASE)
|
|
140
|
+
if match:
|
|
141
|
+
category_text = match.group(1).strip()
|
|
142
|
+
# Split by comma if comma-separated
|
|
143
|
+
categories = [cat.strip() for cat in category_text.split(",")]
|
|
144
|
+
break
|
|
145
|
+
|
|
146
|
+
return categories
|
|
147
|
+
except Exception:
|
|
148
|
+
# If any error occurs during category extraction, return empty list
|
|
149
|
+
logger.debug("Failed to extract categories from guard response, returning empty list")
|
|
150
|
+
return []
|
|
151
|
+
|
|
152
|
+
def _parse_guard_response(self, response_text: str) -> GuardResponseResult:
|
|
153
|
+
"""Parse guard model response.
|
|
154
|
+
|
|
155
|
+
Searches for Safe or Unsafe keywords anywhere in the response (case-insensitive).
|
|
156
|
+
Works with any guard model format (JSON, structured text, or plain text).
|
|
157
|
+
Also extracts safety categories from both JSON and text formats.
|
|
158
|
+
If neither keyword is found, falls back to implicit refusal detection.
|
|
159
|
+
Prioritizes Unsafe if both keywords are present.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
response_text: Raw response from guard model.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
GuardResponseResult with is_safe boolean, categories list, and raw response.
|
|
166
|
+
"""
|
|
167
|
+
cleaned_text = re.sub(r'[*_]+', '', response_text).strip()
|
|
168
|
+
response_lower = cleaned_text.lower()
|
|
169
|
+
|
|
170
|
+
# Search for "Unsafe" or "Safe" anywhere in the response (case-insensitive)
|
|
171
|
+
# Prioritize "Unsafe" if both are present
|
|
172
|
+
if re.search(r'\bunsafe\b', response_lower):
|
|
173
|
+
is_safe = False
|
|
174
|
+
elif re.search(r'\bsafe\b', response_lower):
|
|
175
|
+
is_safe = True
|
|
176
|
+
else:
|
|
177
|
+
# Detect implicit refusals (model refuses = harmful content detected)
|
|
178
|
+
refusal_indicators = [
|
|
179
|
+
"i cannot",
|
|
180
|
+
"i can't",
|
|
181
|
+
"i won't",
|
|
182
|
+
"i will not",
|
|
183
|
+
"i'm unable",
|
|
184
|
+
"i am unable",
|
|
185
|
+
"i refuse",
|
|
186
|
+
"cannot provide",
|
|
187
|
+
"can't provide",
|
|
188
|
+
"won't provide",
|
|
189
|
+
"illegal",
|
|
190
|
+
"harmful",
|
|
191
|
+
"dangerous",
|
|
192
|
+
"inappropriate"
|
|
193
|
+
]
|
|
194
|
+
|
|
195
|
+
if any(indicator in response_lower for indicator in refusal_indicators):
|
|
196
|
+
is_safe = False # Model refused = harmful detected
|
|
197
|
+
else:
|
|
198
|
+
is_safe = True # Default to safe if unclear
|
|
199
|
+
|
|
200
|
+
# Extract categories only if unsafe
|
|
201
|
+
categories = self._extract_unsafe_categories(response_text, is_safe)
|
|
202
|
+
|
|
203
|
+
return GuardResponseResult(is_safe=is_safe, categories=categories, raw_response=response_text)
|
|
204
|
+
|
|
205
|
+
def _should_refuse(self, parsed_result: GuardResponseResult) -> bool:
|
|
206
|
+
"""Determine if content should be refused.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
parsed_result: Result from _parse_guard_response.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
True if content should be refused.
|
|
213
|
+
"""
|
|
214
|
+
return not parsed_result.is_safe
|
|
215
|
+
|
|
216
|
+
async def _analyze_content(self,
|
|
217
|
+
content: Any,
|
|
218
|
+
original_input: Any = None,
|
|
219
|
+
context: FunctionMiddlewareContext | None = None) -> ContentAnalysisResult:
|
|
220
|
+
"""Check content safety using guard model.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
content: The content to analyze
|
|
224
|
+
original_input: The original input to the function (for context)
|
|
225
|
+
context: Function metadata
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Safety classification result with should_refuse flag
|
|
229
|
+
"""
|
|
230
|
+
try:
|
|
231
|
+
# Get the guard model LLM
|
|
232
|
+
llm = await self._get_llm()
|
|
233
|
+
|
|
234
|
+
content_str = str(content)
|
|
235
|
+
|
|
236
|
+
# Call the guard model using messages format to ensure chat template is applied
|
|
237
|
+
# Format matches: messages = [{"role": "user", "content": prompt}]
|
|
238
|
+
messages = [{"role": "user", "content": content_str}]
|
|
239
|
+
response = await llm.ainvoke(messages)
|
|
240
|
+
|
|
241
|
+
# Extract text from response
|
|
242
|
+
if hasattr(response, 'content'):
|
|
243
|
+
response_text = response.content.strip()
|
|
244
|
+
elif isinstance(response, str):
|
|
245
|
+
response_text = response.strip()
|
|
246
|
+
else:
|
|
247
|
+
response_text = str(response).strip()
|
|
248
|
+
# Parse the guard model response
|
|
249
|
+
|
|
250
|
+
parsed = self._parse_guard_response(response_text)
|
|
251
|
+
should_refuse = self._should_refuse(parsed)
|
|
252
|
+
|
|
253
|
+
return ContentAnalysisResult(is_safe=parsed.is_safe,
|
|
254
|
+
categories=parsed.categories,
|
|
255
|
+
raw_response=parsed.raw_response,
|
|
256
|
+
should_refuse=should_refuse,
|
|
257
|
+
error=False,
|
|
258
|
+
error_message=None)
|
|
259
|
+
|
|
260
|
+
except Exception as e:
|
|
261
|
+
logger.exception("Content Safety Guard analysis failed: %s", e)
|
|
262
|
+
return ContentAnalysisResult(is_safe=True,
|
|
263
|
+
categories=[],
|
|
264
|
+
raw_response="",
|
|
265
|
+
should_refuse=False,
|
|
266
|
+
error=True,
|
|
267
|
+
error_message=str(e))
|
|
268
|
+
|
|
269
|
+
async def _handle_threat(self,
|
|
270
|
+
content: Any,
|
|
271
|
+
analysis_result: ContentAnalysisResult,
|
|
272
|
+
context: FunctionMiddlewareContext) -> Any:
|
|
273
|
+
"""Handle unsafe content based on configured action.
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
content: The unsafe content
|
|
277
|
+
analysis_result: Safety classification result.
|
|
278
|
+
context: Function context
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
Handled content (blocked, sanitized, or original)
|
|
282
|
+
"""
|
|
283
|
+
action = self.config.action
|
|
284
|
+
|
|
285
|
+
categories = analysis_result.categories
|
|
286
|
+
logger.warning("Content Safety Guard detected unsafe content in %s (categories: %s)",
|
|
287
|
+
context.name,
|
|
288
|
+
", ".join(categories) if categories else "none")
|
|
289
|
+
|
|
290
|
+
if action == "refusal":
|
|
291
|
+
logger.error("Content Safety Guard refusing output of %s", context.name)
|
|
292
|
+
raise ValueError("Content blocked by safety policy")
|
|
293
|
+
|
|
294
|
+
elif action == "redirection":
|
|
295
|
+
return "I'm sorry, I cannot help you with that request."
|
|
296
|
+
|
|
297
|
+
else: # action == "partial_compliance"
|
|
298
|
+
logger.warning("Safety violation logged but content passed through")
|
|
299
|
+
return content
|
|
300
|
+
|
|
301
|
+
async def _process_content_safety_detection(
|
|
302
|
+
self,
|
|
303
|
+
value: Any,
|
|
304
|
+
location: str,
|
|
305
|
+
context: FunctionMiddlewareContext,
|
|
306
|
+
original_input: Any = None,
|
|
307
|
+
) -> Any:
|
|
308
|
+
"""Process content safety detection and handling for a given value.
|
|
309
|
+
|
|
310
|
+
Handles field extraction, content safety analysis, threat handling,
|
|
311
|
+
and applying sanitized value back to original structure.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
value: The value to analyze (input or output).
|
|
315
|
+
location: Either input or output (for logging).
|
|
316
|
+
context: Function context metadata.
|
|
317
|
+
original_input: Original function input (for output analysis context).
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
The value after content safety handling (may be unchanged, sanitized, or raise).
|
|
321
|
+
"""
|
|
322
|
+
# Extract field from value if target_field is specified
|
|
323
|
+
content_to_analyze, field_info = self._extract_field_from_value(value)
|
|
324
|
+
|
|
325
|
+
logger.info("ContentSafetyGuardMiddleware: Checking %s %s for %s",
|
|
326
|
+
f"field '{self.config.target_field}'" if field_info else "entire",
|
|
327
|
+
location,
|
|
328
|
+
context.name)
|
|
329
|
+
analysis_result = await self._analyze_content(content_to_analyze,
|
|
330
|
+
original_input=original_input,
|
|
331
|
+
context=context)
|
|
332
|
+
|
|
333
|
+
if not analysis_result.should_refuse:
|
|
334
|
+
# Content is safe, return original value
|
|
335
|
+
logger.info("ContentSafetyGuardMiddleware: Verified %s of %s as safe", location, context.name)
|
|
336
|
+
return value
|
|
337
|
+
|
|
338
|
+
# Unsafe content detected - handle based on action
|
|
339
|
+
logger.warning("ContentSafetyGuardMiddleware: Blocking %s for %s (unsafe content detected)",
|
|
340
|
+
location,
|
|
341
|
+
context.name)
|
|
342
|
+
sanitized_content = await self._handle_threat(content_to_analyze, analysis_result, context)
|
|
343
|
+
|
|
344
|
+
# If field was extracted, apply sanitized value back to original structure
|
|
345
|
+
if field_info is not None:
|
|
346
|
+
return self._apply_field_result_to_value(value, field_info, sanitized_content)
|
|
347
|
+
else:
|
|
348
|
+
# No field extraction - return sanitized content directly
|
|
349
|
+
return sanitized_content
|
|
350
|
+
|
|
351
|
+
async def function_middleware_invoke(self,
|
|
352
|
+
*args: Any,
|
|
353
|
+
call_next: CallNext,
|
|
354
|
+
context: FunctionMiddlewareContext,
|
|
355
|
+
**kwargs: Any) -> Any:
|
|
356
|
+
"""Apply content safety guard check to function invocation.
|
|
357
|
+
|
|
358
|
+
This is the core logic for content safety guard defense - each defense implements
|
|
359
|
+
its own invoke/stream based on its specific strategy.
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
args: Positional arguments passed to the function (first arg is typically the input value).
|
|
363
|
+
call_next: Next middleware/function to call.
|
|
364
|
+
context: Function metadata (provides context state).
|
|
365
|
+
kwargs: Keyword arguments passed to the function.
|
|
366
|
+
|
|
367
|
+
Returns:
|
|
368
|
+
Function output (potentially blocked or sanitized).
|
|
369
|
+
"""
|
|
370
|
+
value = args[0] if args else None
|
|
371
|
+
|
|
372
|
+
# Check if defense should apply to this function
|
|
373
|
+
if not self._should_apply_defense(context.name):
|
|
374
|
+
logger.debug("ContentSafetyGuardMiddleware: Skipping %s (not targeted)", context.name)
|
|
375
|
+
return await call_next(value, *args[1:], **kwargs)
|
|
376
|
+
|
|
377
|
+
try:
|
|
378
|
+
# Call the function
|
|
379
|
+
output = await call_next(value, *args[1:], **kwargs)
|
|
380
|
+
|
|
381
|
+
# Handle output analysis (only output is supported)
|
|
382
|
+
output = await self._process_content_safety_detection(output, "output", context, original_input=value)
|
|
383
|
+
|
|
384
|
+
return output
|
|
385
|
+
|
|
386
|
+
except Exception as e:
|
|
387
|
+
logger.error("Failed to apply content safety guard to function %s: %s", context.name, e, exc_info=True)
|
|
388
|
+
raise
|
|
389
|
+
|
|
390
|
+
async def function_middleware_stream(self,
|
|
391
|
+
*args: Any,
|
|
392
|
+
call_next: CallNextStream,
|
|
393
|
+
context: FunctionMiddlewareContext,
|
|
394
|
+
**kwargs: Any) -> AsyncIterator[Any]:
|
|
395
|
+
"""Apply content safety guard check to streaming function.
|
|
396
|
+
|
|
397
|
+
For 'refusal' and 'redirection' actions: Chunks are buffered and checked before yielding.
|
|
398
|
+
For 'partial_compliance' action: Chunks are yielded immediately; violations are logged.
|
|
399
|
+
|
|
400
|
+
Args:
|
|
401
|
+
args: Positional arguments passed to the function (first arg is typically the input value).
|
|
402
|
+
call_next: Next middleware/function to call.
|
|
403
|
+
context: Function metadata.
|
|
404
|
+
kwargs: Keyword arguments passed to the function.
|
|
405
|
+
|
|
406
|
+
Yields:
|
|
407
|
+
Function output chunks (potentially blocked or sanitized).
|
|
408
|
+
"""
|
|
409
|
+
value = args[0] if args else None
|
|
410
|
+
|
|
411
|
+
# Check if defense should apply to this function
|
|
412
|
+
if not self._should_apply_defense(context.name):
|
|
413
|
+
logger.debug("ContentSafetyGuardMiddleware: Skipping %s (not targeted)", context.name)
|
|
414
|
+
async for chunk in call_next(value, *args[1:], **kwargs):
|
|
415
|
+
yield chunk
|
|
416
|
+
return
|
|
417
|
+
|
|
418
|
+
try:
|
|
419
|
+
buffer_chunks = self.config.action in ("refusal", "redirection")
|
|
420
|
+
accumulated_chunks: list[Any] = []
|
|
421
|
+
|
|
422
|
+
async for chunk in call_next(value, *args[1:], **kwargs):
|
|
423
|
+
if buffer_chunks:
|
|
424
|
+
accumulated_chunks.append(chunk)
|
|
425
|
+
else:
|
|
426
|
+
# partial_compliance: stream through, but still accumulate for analysis/logging
|
|
427
|
+
yield chunk
|
|
428
|
+
accumulated_chunks.append(chunk)
|
|
429
|
+
|
|
430
|
+
# Join chunks efficiently (only convert to string if needed)
|
|
431
|
+
full_output = "".join(chunk if isinstance(chunk, str) else str(chunk) for chunk in accumulated_chunks)
|
|
432
|
+
|
|
433
|
+
processed_output = await self._process_content_safety_detection(full_output,
|
|
434
|
+
"output",
|
|
435
|
+
context,
|
|
436
|
+
original_input=value)
|
|
437
|
+
|
|
438
|
+
processed_str = str(processed_output)
|
|
439
|
+
if self.config.action == "redirection" and processed_str != full_output:
|
|
440
|
+
# Redirected: yield replacement once (and stop).
|
|
441
|
+
yield processed_output
|
|
442
|
+
return
|
|
443
|
+
|
|
444
|
+
if buffer_chunks:
|
|
445
|
+
# refusal: would have raised; safe content: preserve chunking
|
|
446
|
+
for chunk in accumulated_chunks:
|
|
447
|
+
yield chunk
|
|
448
|
+
|
|
449
|
+
except Exception:
|
|
450
|
+
logger.error(
|
|
451
|
+
"Failed to apply content safety guard to streaming function %s",
|
|
452
|
+
context.name,
|
|
453
|
+
exc_info=True,
|
|
454
|
+
)
|
|
455
|
+
raise
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2025-2026, 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
|
+
"""Data models for defense middleware output."""
|
|
16
|
+
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from pydantic import BaseModel
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PIIAnalysisResult(BaseModel):
|
|
23
|
+
"""Result of PII analysis using Presidio.
|
|
24
|
+
|
|
25
|
+
Attributes:
|
|
26
|
+
pii_detected: Whether PII was detected in the analyzed text.
|
|
27
|
+
entities: Dictionary mapping entity types to lists of detection metadata (score, start, end).
|
|
28
|
+
anonymized_text: Text with PII replaced by entity type placeholders (e.g., <EMAIL_ADDRESS>).
|
|
29
|
+
original_text: The unmodified original text that was analyzed.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
pii_detected: bool
|
|
33
|
+
entities: dict[str, list[dict[str, Any]]]
|
|
34
|
+
anonymized_text: str
|
|
35
|
+
original_text: str
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class GuardResponseResult(BaseModel):
|
|
39
|
+
"""Result of parsing guard model response.
|
|
40
|
+
|
|
41
|
+
Attributes:
|
|
42
|
+
is_safe: Whether the content is classified as safe by the guard model.
|
|
43
|
+
categories: List of unsafe content categories detected (empty if safe).
|
|
44
|
+
raw_response: The unprocessed response text from the guard model.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
is_safe: bool
|
|
48
|
+
categories: list[str]
|
|
49
|
+
raw_response: str
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ContentAnalysisResult(BaseModel):
|
|
53
|
+
"""Result of content safety analysis with guard models.
|
|
54
|
+
|
|
55
|
+
Attributes:
|
|
56
|
+
is_safe: Whether the content is classified as safe by the guard model.
|
|
57
|
+
categories: List of unsafe content categories detected (empty if safe).
|
|
58
|
+
raw_response: The unprocessed response text from the guard model.
|
|
59
|
+
should_refuse: Whether the content should be refused based on the analysis.
|
|
60
|
+
error: Whether an error occurred during analysis.
|
|
61
|
+
error_message: Error message if error occurred, otherwise None.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
is_safe: bool
|
|
65
|
+
categories: list[str]
|
|
66
|
+
raw_response: str
|
|
67
|
+
should_refuse: bool
|
|
68
|
+
error: bool = False
|
|
69
|
+
error_message: str | None = None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class OutputVerificationResult(BaseModel):
|
|
73
|
+
"""Result of output verification using LLM.
|
|
74
|
+
|
|
75
|
+
Attributes:
|
|
76
|
+
threat_detected: Whether a threat (incorrect or manipulated output) was detected.
|
|
77
|
+
confidence: Confidence score (0.0-1.0) in the threat detection.
|
|
78
|
+
reason: Explanation for the detection result.
|
|
79
|
+
correct_answer: The correct output value if threat detected, otherwise None.
|
|
80
|
+
content_type: Type of content analyzed ('input' or 'output').
|
|
81
|
+
should_refuse: Whether the content should be refused based on threshold.
|
|
82
|
+
error: Whether an error occurred during verification.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
threat_detected: bool
|
|
86
|
+
confidence: float
|
|
87
|
+
reason: str
|
|
88
|
+
correct_answer: Any | None
|
|
89
|
+
content_type: str
|
|
90
|
+
should_refuse: bool
|
|
91
|
+
error: bool = False
|