nvidia-nat 1.2.0rc5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aiq/agent/__init__.py +0 -0
- aiq/agent/base.py +239 -0
- aiq/agent/dual_node.py +67 -0
- aiq/agent/react_agent/__init__.py +0 -0
- aiq/agent/react_agent/agent.py +355 -0
- aiq/agent/react_agent/output_parser.py +104 -0
- aiq/agent/react_agent/prompt.py +41 -0
- aiq/agent/react_agent/register.py +149 -0
- aiq/agent/reasoning_agent/__init__.py +0 -0
- aiq/agent/reasoning_agent/reasoning_agent.py +225 -0
- aiq/agent/register.py +23 -0
- aiq/agent/rewoo_agent/__init__.py +0 -0
- aiq/agent/rewoo_agent/agent.py +411 -0
- aiq/agent/rewoo_agent/prompt.py +108 -0
- aiq/agent/rewoo_agent/register.py +158 -0
- aiq/agent/tool_calling_agent/__init__.py +0 -0
- aiq/agent/tool_calling_agent/agent.py +119 -0
- aiq/agent/tool_calling_agent/register.py +106 -0
- aiq/authentication/__init__.py +14 -0
- aiq/authentication/api_key/__init__.py +14 -0
- aiq/authentication/api_key/api_key_auth_provider.py +96 -0
- aiq/authentication/api_key/api_key_auth_provider_config.py +124 -0
- aiq/authentication/api_key/register.py +26 -0
- aiq/authentication/exceptions/__init__.py +14 -0
- aiq/authentication/exceptions/api_key_exceptions.py +38 -0
- aiq/authentication/http_basic_auth/__init__.py +0 -0
- aiq/authentication/http_basic_auth/http_basic_auth_provider.py +81 -0
- aiq/authentication/http_basic_auth/register.py +30 -0
- aiq/authentication/interfaces.py +93 -0
- aiq/authentication/oauth2/__init__.py +14 -0
- aiq/authentication/oauth2/oauth2_auth_code_flow_provider.py +107 -0
- aiq/authentication/oauth2/oauth2_auth_code_flow_provider_config.py +39 -0
- aiq/authentication/oauth2/register.py +25 -0
- aiq/authentication/register.py +21 -0
- aiq/builder/__init__.py +0 -0
- aiq/builder/builder.py +285 -0
- aiq/builder/component_utils.py +316 -0
- aiq/builder/context.py +264 -0
- aiq/builder/embedder.py +24 -0
- aiq/builder/eval_builder.py +161 -0
- aiq/builder/evaluator.py +29 -0
- aiq/builder/framework_enum.py +24 -0
- aiq/builder/front_end.py +73 -0
- aiq/builder/function.py +344 -0
- aiq/builder/function_base.py +380 -0
- aiq/builder/function_info.py +627 -0
- aiq/builder/intermediate_step_manager.py +174 -0
- aiq/builder/llm.py +25 -0
- aiq/builder/retriever.py +25 -0
- aiq/builder/user_interaction_manager.py +74 -0
- aiq/builder/workflow.py +148 -0
- aiq/builder/workflow_builder.py +1117 -0
- aiq/cli/__init__.py +14 -0
- aiq/cli/cli_utils/__init__.py +0 -0
- aiq/cli/cli_utils/config_override.py +231 -0
- aiq/cli/cli_utils/validation.py +37 -0
- aiq/cli/commands/__init__.py +0 -0
- aiq/cli/commands/configure/__init__.py +0 -0
- aiq/cli/commands/configure/channel/__init__.py +0 -0
- aiq/cli/commands/configure/channel/add.py +28 -0
- aiq/cli/commands/configure/channel/channel.py +36 -0
- aiq/cli/commands/configure/channel/remove.py +30 -0
- aiq/cli/commands/configure/channel/update.py +30 -0
- aiq/cli/commands/configure/configure.py +33 -0
- aiq/cli/commands/evaluate.py +139 -0
- aiq/cli/commands/info/__init__.py +14 -0
- aiq/cli/commands/info/info.py +39 -0
- aiq/cli/commands/info/list_channels.py +32 -0
- aiq/cli/commands/info/list_components.py +129 -0
- aiq/cli/commands/info/list_mcp.py +213 -0
- aiq/cli/commands/registry/__init__.py +14 -0
- aiq/cli/commands/registry/publish.py +88 -0
- aiq/cli/commands/registry/pull.py +118 -0
- aiq/cli/commands/registry/registry.py +38 -0
- aiq/cli/commands/registry/remove.py +108 -0
- aiq/cli/commands/registry/search.py +155 -0
- aiq/cli/commands/sizing/__init__.py +14 -0
- aiq/cli/commands/sizing/calc.py +297 -0
- aiq/cli/commands/sizing/sizing.py +27 -0
- aiq/cli/commands/start.py +246 -0
- aiq/cli/commands/uninstall.py +81 -0
- aiq/cli/commands/validate.py +47 -0
- aiq/cli/commands/workflow/__init__.py +14 -0
- aiq/cli/commands/workflow/templates/__init__.py.j2 +0 -0
- aiq/cli/commands/workflow/templates/config.yml.j2 +16 -0
- aiq/cli/commands/workflow/templates/pyproject.toml.j2 +22 -0
- aiq/cli/commands/workflow/templates/register.py.j2 +5 -0
- aiq/cli/commands/workflow/templates/workflow.py.j2 +36 -0
- aiq/cli/commands/workflow/workflow.py +37 -0
- aiq/cli/commands/workflow/workflow_commands.py +313 -0
- aiq/cli/entrypoint.py +135 -0
- aiq/cli/main.py +44 -0
- aiq/cli/register_workflow.py +488 -0
- aiq/cli/type_registry.py +1000 -0
- aiq/data_models/__init__.py +14 -0
- aiq/data_models/api_server.py +694 -0
- aiq/data_models/authentication.py +231 -0
- aiq/data_models/common.py +171 -0
- aiq/data_models/component.py +54 -0
- aiq/data_models/component_ref.py +168 -0
- aiq/data_models/config.py +406 -0
- aiq/data_models/dataset_handler.py +123 -0
- aiq/data_models/discovery_metadata.py +335 -0
- aiq/data_models/embedder.py +27 -0
- aiq/data_models/evaluate.py +127 -0
- aiq/data_models/evaluator.py +26 -0
- aiq/data_models/front_end.py +26 -0
- aiq/data_models/function.py +30 -0
- aiq/data_models/function_dependencies.py +72 -0
- aiq/data_models/interactive.py +246 -0
- aiq/data_models/intermediate_step.py +302 -0
- aiq/data_models/invocation_node.py +38 -0
- aiq/data_models/llm.py +27 -0
- aiq/data_models/logging.py +26 -0
- aiq/data_models/memory.py +27 -0
- aiq/data_models/object_store.py +44 -0
- aiq/data_models/profiler.py +54 -0
- aiq/data_models/registry_handler.py +26 -0
- aiq/data_models/retriever.py +30 -0
- aiq/data_models/retry_mixin.py +35 -0
- aiq/data_models/span.py +187 -0
- aiq/data_models/step_adaptor.py +64 -0
- aiq/data_models/streaming.py +33 -0
- aiq/data_models/swe_bench_model.py +54 -0
- aiq/data_models/telemetry_exporter.py +26 -0
- aiq/data_models/ttc_strategy.py +30 -0
- aiq/embedder/__init__.py +0 -0
- aiq/embedder/langchain_client.py +41 -0
- aiq/embedder/nim_embedder.py +59 -0
- aiq/embedder/openai_embedder.py +43 -0
- aiq/embedder/register.py +24 -0
- aiq/eval/__init__.py +14 -0
- aiq/eval/config.py +60 -0
- aiq/eval/dataset_handler/__init__.py +0 -0
- aiq/eval/dataset_handler/dataset_downloader.py +106 -0
- aiq/eval/dataset_handler/dataset_filter.py +52 -0
- aiq/eval/dataset_handler/dataset_handler.py +254 -0
- aiq/eval/evaluate.py +506 -0
- aiq/eval/evaluator/__init__.py +14 -0
- aiq/eval/evaluator/base_evaluator.py +73 -0
- aiq/eval/evaluator/evaluator_model.py +45 -0
- aiq/eval/intermediate_step_adapter.py +99 -0
- aiq/eval/rag_evaluator/__init__.py +0 -0
- aiq/eval/rag_evaluator/evaluate.py +178 -0
- aiq/eval/rag_evaluator/register.py +143 -0
- aiq/eval/register.py +23 -0
- aiq/eval/remote_workflow.py +133 -0
- aiq/eval/runners/__init__.py +14 -0
- aiq/eval/runners/config.py +39 -0
- aiq/eval/runners/multi_eval_runner.py +54 -0
- aiq/eval/runtime_event_subscriber.py +52 -0
- aiq/eval/swe_bench_evaluator/__init__.py +0 -0
- aiq/eval/swe_bench_evaluator/evaluate.py +215 -0
- aiq/eval/swe_bench_evaluator/register.py +36 -0
- aiq/eval/trajectory_evaluator/__init__.py +0 -0
- aiq/eval/trajectory_evaluator/evaluate.py +75 -0
- aiq/eval/trajectory_evaluator/register.py +40 -0
- aiq/eval/tunable_rag_evaluator/__init__.py +0 -0
- aiq/eval/tunable_rag_evaluator/evaluate.py +245 -0
- aiq/eval/tunable_rag_evaluator/register.py +52 -0
- aiq/eval/usage_stats.py +41 -0
- aiq/eval/utils/__init__.py +0 -0
- aiq/eval/utils/output_uploader.py +140 -0
- aiq/eval/utils/tqdm_position_registry.py +40 -0
- aiq/eval/utils/weave_eval.py +184 -0
- aiq/experimental/__init__.py +0 -0
- aiq/experimental/decorators/__init__.py +0 -0
- aiq/experimental/decorators/experimental_warning_decorator.py +130 -0
- aiq/experimental/test_time_compute/__init__.py +0 -0
- aiq/experimental/test_time_compute/editing/__init__.py +0 -0
- aiq/experimental/test_time_compute/editing/iterative_plan_refinement_editor.py +147 -0
- aiq/experimental/test_time_compute/editing/llm_as_a_judge_editor.py +204 -0
- aiq/experimental/test_time_compute/editing/motivation_aware_summarization.py +107 -0
- aiq/experimental/test_time_compute/functions/__init__.py +0 -0
- aiq/experimental/test_time_compute/functions/execute_score_select_function.py +105 -0
- aiq/experimental/test_time_compute/functions/its_tool_orchestration_function.py +205 -0
- aiq/experimental/test_time_compute/functions/its_tool_wrapper_function.py +146 -0
- aiq/experimental/test_time_compute/functions/plan_select_execute_function.py +224 -0
- aiq/experimental/test_time_compute/models/__init__.py +0 -0
- aiq/experimental/test_time_compute/models/editor_config.py +132 -0
- aiq/experimental/test_time_compute/models/scoring_config.py +112 -0
- aiq/experimental/test_time_compute/models/search_config.py +120 -0
- aiq/experimental/test_time_compute/models/selection_config.py +154 -0
- aiq/experimental/test_time_compute/models/stage_enums.py +43 -0
- aiq/experimental/test_time_compute/models/strategy_base.py +66 -0
- aiq/experimental/test_time_compute/models/tool_use_config.py +41 -0
- aiq/experimental/test_time_compute/models/ttc_item.py +48 -0
- aiq/experimental/test_time_compute/register.py +36 -0
- aiq/experimental/test_time_compute/scoring/__init__.py +0 -0
- aiq/experimental/test_time_compute/scoring/llm_based_agent_scorer.py +168 -0
- aiq/experimental/test_time_compute/scoring/llm_based_plan_scorer.py +168 -0
- aiq/experimental/test_time_compute/scoring/motivation_aware_scorer.py +111 -0
- aiq/experimental/test_time_compute/search/__init__.py +0 -0
- aiq/experimental/test_time_compute/search/multi_llm_planner.py +128 -0
- aiq/experimental/test_time_compute/search/multi_query_retrieval_search.py +122 -0
- aiq/experimental/test_time_compute/search/single_shot_multi_plan_planner.py +128 -0
- aiq/experimental/test_time_compute/selection/__init__.py +0 -0
- aiq/experimental/test_time_compute/selection/best_of_n_selector.py +63 -0
- aiq/experimental/test_time_compute/selection/llm_based_agent_output_selector.py +131 -0
- aiq/experimental/test_time_compute/selection/llm_based_output_merging_selector.py +159 -0
- aiq/experimental/test_time_compute/selection/llm_based_plan_selector.py +128 -0
- aiq/experimental/test_time_compute/selection/threshold_selector.py +58 -0
- aiq/front_ends/__init__.py +14 -0
- aiq/front_ends/console/__init__.py +14 -0
- aiq/front_ends/console/authentication_flow_handler.py +233 -0
- aiq/front_ends/console/console_front_end_config.py +32 -0
- aiq/front_ends/console/console_front_end_plugin.py +96 -0
- aiq/front_ends/console/register.py +25 -0
- aiq/front_ends/cron/__init__.py +14 -0
- aiq/front_ends/fastapi/__init__.py +14 -0
- aiq/front_ends/fastapi/auth_flow_handlers/__init__.py +0 -0
- aiq/front_ends/fastapi/auth_flow_handlers/http_flow_handler.py +27 -0
- aiq/front_ends/fastapi/auth_flow_handlers/websocket_flow_handler.py +107 -0
- aiq/front_ends/fastapi/fastapi_front_end_config.py +234 -0
- aiq/front_ends/fastapi/fastapi_front_end_controller.py +68 -0
- aiq/front_ends/fastapi/fastapi_front_end_plugin.py +116 -0
- aiq/front_ends/fastapi/fastapi_front_end_plugin_worker.py +1092 -0
- aiq/front_ends/fastapi/html_snippets/__init__.py +14 -0
- aiq/front_ends/fastapi/html_snippets/auth_code_grant_success.py +35 -0
- aiq/front_ends/fastapi/intermediate_steps_subscriber.py +80 -0
- aiq/front_ends/fastapi/job_store.py +183 -0
- aiq/front_ends/fastapi/main.py +72 -0
- aiq/front_ends/fastapi/message_handler.py +298 -0
- aiq/front_ends/fastapi/message_validator.py +345 -0
- aiq/front_ends/fastapi/register.py +25 -0
- aiq/front_ends/fastapi/response_helpers.py +195 -0
- aiq/front_ends/fastapi/step_adaptor.py +321 -0
- aiq/front_ends/mcp/__init__.py +14 -0
- aiq/front_ends/mcp/mcp_front_end_config.py +32 -0
- aiq/front_ends/mcp/mcp_front_end_plugin.py +93 -0
- aiq/front_ends/mcp/register.py +27 -0
- aiq/front_ends/mcp/tool_converter.py +242 -0
- aiq/front_ends/register.py +22 -0
- aiq/front_ends/simple_base/__init__.py +14 -0
- aiq/front_ends/simple_base/simple_front_end_plugin_base.py +54 -0
- aiq/llm/__init__.py +0 -0
- aiq/llm/aws_bedrock_llm.py +57 -0
- aiq/llm/nim_llm.py +46 -0
- aiq/llm/openai_llm.py +46 -0
- aiq/llm/register.py +23 -0
- aiq/llm/utils/__init__.py +14 -0
- aiq/llm/utils/env_config_value.py +94 -0
- aiq/llm/utils/error.py +17 -0
- aiq/memory/__init__.py +20 -0
- aiq/memory/interfaces.py +183 -0
- aiq/memory/models.py +112 -0
- aiq/meta/module_to_distro.json +3 -0
- aiq/meta/pypi.md +58 -0
- aiq/object_store/__init__.py +20 -0
- aiq/object_store/in_memory_object_store.py +76 -0
- aiq/object_store/interfaces.py +84 -0
- aiq/object_store/models.py +36 -0
- aiq/object_store/register.py +20 -0
- aiq/observability/__init__.py +14 -0
- aiq/observability/exporter/__init__.py +14 -0
- aiq/observability/exporter/base_exporter.py +449 -0
- aiq/observability/exporter/exporter.py +78 -0
- aiq/observability/exporter/file_exporter.py +33 -0
- aiq/observability/exporter/processing_exporter.py +322 -0
- aiq/observability/exporter/raw_exporter.py +52 -0
- aiq/observability/exporter/span_exporter.py +265 -0
- aiq/observability/exporter_manager.py +335 -0
- aiq/observability/mixin/__init__.py +14 -0
- aiq/observability/mixin/batch_config_mixin.py +26 -0
- aiq/observability/mixin/collector_config_mixin.py +23 -0
- aiq/observability/mixin/file_mixin.py +288 -0
- aiq/observability/mixin/file_mode.py +23 -0
- aiq/observability/mixin/resource_conflict_mixin.py +134 -0
- aiq/observability/mixin/serialize_mixin.py +61 -0
- aiq/observability/mixin/type_introspection_mixin.py +183 -0
- aiq/observability/processor/__init__.py +14 -0
- aiq/observability/processor/batching_processor.py +310 -0
- aiq/observability/processor/callback_processor.py +42 -0
- aiq/observability/processor/intermediate_step_serializer.py +28 -0
- aiq/observability/processor/processor.py +71 -0
- aiq/observability/register.py +96 -0
- aiq/observability/utils/__init__.py +14 -0
- aiq/observability/utils/dict_utils.py +236 -0
- aiq/observability/utils/time_utils.py +31 -0
- aiq/plugins/.namespace +1 -0
- aiq/profiler/__init__.py +0 -0
- aiq/profiler/calc/__init__.py +14 -0
- aiq/profiler/calc/calc_runner.py +627 -0
- aiq/profiler/calc/calculations.py +288 -0
- aiq/profiler/calc/data_models.py +188 -0
- aiq/profiler/calc/plot.py +345 -0
- aiq/profiler/callbacks/__init__.py +0 -0
- aiq/profiler/callbacks/agno_callback_handler.py +295 -0
- aiq/profiler/callbacks/base_callback_class.py +20 -0
- aiq/profiler/callbacks/langchain_callback_handler.py +290 -0
- aiq/profiler/callbacks/llama_index_callback_handler.py +205 -0
- aiq/profiler/callbacks/semantic_kernel_callback_handler.py +238 -0
- aiq/profiler/callbacks/token_usage_base_model.py +27 -0
- aiq/profiler/data_frame_row.py +51 -0
- aiq/profiler/data_models.py +24 -0
- aiq/profiler/decorators/__init__.py +0 -0
- aiq/profiler/decorators/framework_wrapper.py +131 -0
- aiq/profiler/decorators/function_tracking.py +254 -0
- aiq/profiler/forecasting/__init__.py +0 -0
- aiq/profiler/forecasting/config.py +18 -0
- aiq/profiler/forecasting/model_trainer.py +75 -0
- aiq/profiler/forecasting/models/__init__.py +22 -0
- aiq/profiler/forecasting/models/forecasting_base_model.py +40 -0
- aiq/profiler/forecasting/models/linear_model.py +196 -0
- aiq/profiler/forecasting/models/random_forest_regressor.py +268 -0
- aiq/profiler/inference_metrics_model.py +28 -0
- aiq/profiler/inference_optimization/__init__.py +0 -0
- aiq/profiler/inference_optimization/bottleneck_analysis/__init__.py +0 -0
- aiq/profiler/inference_optimization/bottleneck_analysis/nested_stack_analysis.py +460 -0
- aiq/profiler/inference_optimization/bottleneck_analysis/simple_stack_analysis.py +258 -0
- aiq/profiler/inference_optimization/data_models.py +386 -0
- aiq/profiler/inference_optimization/experimental/__init__.py +0 -0
- aiq/profiler/inference_optimization/experimental/concurrency_spike_analysis.py +468 -0
- aiq/profiler/inference_optimization/experimental/prefix_span_analysis.py +405 -0
- aiq/profiler/inference_optimization/llm_metrics.py +212 -0
- aiq/profiler/inference_optimization/prompt_caching.py +163 -0
- aiq/profiler/inference_optimization/token_uniqueness.py +107 -0
- aiq/profiler/inference_optimization/workflow_runtimes.py +72 -0
- aiq/profiler/intermediate_property_adapter.py +102 -0
- aiq/profiler/profile_runner.py +473 -0
- aiq/profiler/utils.py +184 -0
- aiq/registry_handlers/__init__.py +0 -0
- aiq/registry_handlers/local/__init__.py +0 -0
- aiq/registry_handlers/local/local_handler.py +176 -0
- aiq/registry_handlers/local/register_local.py +37 -0
- aiq/registry_handlers/metadata_factory.py +60 -0
- aiq/registry_handlers/package_utils.py +567 -0
- aiq/registry_handlers/pypi/__init__.py +0 -0
- aiq/registry_handlers/pypi/pypi_handler.py +251 -0
- aiq/registry_handlers/pypi/register_pypi.py +40 -0
- aiq/registry_handlers/register.py +21 -0
- aiq/registry_handlers/registry_handler_base.py +157 -0
- aiq/registry_handlers/rest/__init__.py +0 -0
- aiq/registry_handlers/rest/register_rest.py +56 -0
- aiq/registry_handlers/rest/rest_handler.py +237 -0
- aiq/registry_handlers/schemas/__init__.py +0 -0
- aiq/registry_handlers/schemas/headers.py +42 -0
- aiq/registry_handlers/schemas/package.py +68 -0
- aiq/registry_handlers/schemas/publish.py +63 -0
- aiq/registry_handlers/schemas/pull.py +82 -0
- aiq/registry_handlers/schemas/remove.py +36 -0
- aiq/registry_handlers/schemas/search.py +91 -0
- aiq/registry_handlers/schemas/status.py +47 -0
- aiq/retriever/__init__.py +0 -0
- aiq/retriever/interface.py +37 -0
- aiq/retriever/milvus/__init__.py +14 -0
- aiq/retriever/milvus/register.py +81 -0
- aiq/retriever/milvus/retriever.py +228 -0
- aiq/retriever/models.py +74 -0
- aiq/retriever/nemo_retriever/__init__.py +14 -0
- aiq/retriever/nemo_retriever/register.py +60 -0
- aiq/retriever/nemo_retriever/retriever.py +190 -0
- aiq/retriever/register.py +22 -0
- aiq/runtime/__init__.py +14 -0
- aiq/runtime/loader.py +215 -0
- aiq/runtime/runner.py +190 -0
- aiq/runtime/session.py +158 -0
- aiq/runtime/user_metadata.py +130 -0
- aiq/settings/__init__.py +0 -0
- aiq/settings/global_settings.py +318 -0
- aiq/test/.namespace +1 -0
- aiq/tool/__init__.py +0 -0
- aiq/tool/chat_completion.py +74 -0
- aiq/tool/code_execution/README.md +151 -0
- aiq/tool/code_execution/__init__.py +0 -0
- aiq/tool/code_execution/code_sandbox.py +267 -0
- aiq/tool/code_execution/local_sandbox/.gitignore +1 -0
- aiq/tool/code_execution/local_sandbox/Dockerfile.sandbox +60 -0
- aiq/tool/code_execution/local_sandbox/__init__.py +13 -0
- aiq/tool/code_execution/local_sandbox/local_sandbox_server.py +198 -0
- aiq/tool/code_execution/local_sandbox/sandbox.requirements.txt +6 -0
- aiq/tool/code_execution/local_sandbox/start_local_sandbox.sh +50 -0
- aiq/tool/code_execution/register.py +74 -0
- aiq/tool/code_execution/test_code_execution_sandbox.py +414 -0
- aiq/tool/code_execution/utils.py +100 -0
- aiq/tool/datetime_tools.py +42 -0
- aiq/tool/document_search.py +141 -0
- aiq/tool/github_tools/__init__.py +0 -0
- aiq/tool/github_tools/create_github_commit.py +133 -0
- aiq/tool/github_tools/create_github_issue.py +87 -0
- aiq/tool/github_tools/create_github_pr.py +106 -0
- aiq/tool/github_tools/get_github_file.py +106 -0
- aiq/tool/github_tools/get_github_issue.py +166 -0
- aiq/tool/github_tools/get_github_pr.py +256 -0
- aiq/tool/github_tools/update_github_issue.py +100 -0
- aiq/tool/mcp/__init__.py +14 -0
- aiq/tool/mcp/exceptions.py +142 -0
- aiq/tool/mcp/mcp_client.py +255 -0
- aiq/tool/mcp/mcp_tool.py +96 -0
- aiq/tool/memory_tools/__init__.py +0 -0
- aiq/tool/memory_tools/add_memory_tool.py +79 -0
- aiq/tool/memory_tools/delete_memory_tool.py +67 -0
- aiq/tool/memory_tools/get_memory_tool.py +72 -0
- aiq/tool/nvidia_rag.py +95 -0
- aiq/tool/register.py +38 -0
- aiq/tool/retriever.py +89 -0
- aiq/tool/server_tools.py +66 -0
- aiq/utils/__init__.py +0 -0
- aiq/utils/data_models/__init__.py +0 -0
- aiq/utils/data_models/schema_validator.py +58 -0
- aiq/utils/debugging_utils.py +43 -0
- aiq/utils/dump_distro_mapping.py +32 -0
- aiq/utils/exception_handlers/__init__.py +0 -0
- aiq/utils/exception_handlers/automatic_retries.py +289 -0
- aiq/utils/exception_handlers/mcp.py +211 -0
- aiq/utils/exception_handlers/schemas.py +114 -0
- aiq/utils/io/__init__.py +0 -0
- aiq/utils/io/model_processing.py +28 -0
- aiq/utils/io/yaml_tools.py +119 -0
- aiq/utils/log_utils.py +37 -0
- aiq/utils/metadata_utils.py +74 -0
- aiq/utils/optional_imports.py +142 -0
- aiq/utils/producer_consumer_queue.py +178 -0
- aiq/utils/reactive/__init__.py +0 -0
- aiq/utils/reactive/base/__init__.py +0 -0
- aiq/utils/reactive/base/observable_base.py +65 -0
- aiq/utils/reactive/base/observer_base.py +55 -0
- aiq/utils/reactive/base/subject_base.py +79 -0
- aiq/utils/reactive/observable.py +59 -0
- aiq/utils/reactive/observer.py +76 -0
- aiq/utils/reactive/subject.py +131 -0
- aiq/utils/reactive/subscription.py +49 -0
- aiq/utils/settings/__init__.py +0 -0
- aiq/utils/settings/global_settings.py +197 -0
- aiq/utils/string_utils.py +38 -0
- aiq/utils/type_converter.py +290 -0
- aiq/utils/type_utils.py +484 -0
- aiq/utils/url_utils.py +27 -0
- nvidia_nat-1.2.0rc5.dist-info/METADATA +363 -0
- nvidia_nat-1.2.0rc5.dist-info/RECORD +435 -0
- nvidia_nat-1.2.0rc5.dist-info/WHEEL +5 -0
- nvidia_nat-1.2.0rc5.dist-info/entry_points.txt +20 -0
- nvidia_nat-1.2.0rc5.dist-info/licenses/LICENSE-3rd-party.txt +3686 -0
- nvidia_nat-1.2.0rc5.dist-info/licenses/LICENSE.md +201 -0
- nvidia_nat-1.2.0rc5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,131 @@
|
|
|
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 threading
|
|
17
|
+
from collections.abc import Callable
|
|
18
|
+
from typing import TypeVar
|
|
19
|
+
|
|
20
|
+
from aiq.utils.reactive.base.subject_base import SubjectBase
|
|
21
|
+
from aiq.utils.reactive.observable import Observable
|
|
22
|
+
from aiq.utils.reactive.observer import Observer
|
|
23
|
+
from aiq.utils.reactive.subscription import Subscription
|
|
24
|
+
|
|
25
|
+
T = TypeVar("T")
|
|
26
|
+
|
|
27
|
+
OnNext = Callable[[T], None]
|
|
28
|
+
OnError = Callable[[Exception], None]
|
|
29
|
+
OnComplete = Callable[[], None]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Subject(Observable[T], Observer[T], SubjectBase[T]):
|
|
33
|
+
"""
|
|
34
|
+
A Subject is both an Observer (receives events) and an Observable (sends events).
|
|
35
|
+
- Maintains a list of ObserverBase[T].
|
|
36
|
+
- No internal buffering or replay; events are only delivered to current subscribers.
|
|
37
|
+
- Thread-safe via a lock.
|
|
38
|
+
|
|
39
|
+
Once on_error or on_complete is called, the Subject is closed.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self) -> None:
|
|
43
|
+
super().__init__()
|
|
44
|
+
self._lock = threading.RLock()
|
|
45
|
+
self._closed = False
|
|
46
|
+
self._error: Exception | None = None
|
|
47
|
+
self._observers: list[Observer[T]] = []
|
|
48
|
+
self._disposed = False
|
|
49
|
+
|
|
50
|
+
# ==========================================================================
|
|
51
|
+
# Observable[T] - for consumers
|
|
52
|
+
# ==========================================================================
|
|
53
|
+
def _subscribe_core(self, observer: Observer[T]) -> Subscription:
|
|
54
|
+
"""
|
|
55
|
+
Subscribe to this subject. If disposed, returns a dummy subscription.
|
|
56
|
+
Otherwise, registers the given observer.
|
|
57
|
+
"""
|
|
58
|
+
with self._lock:
|
|
59
|
+
if self._disposed:
|
|
60
|
+
# Already disposed => no subscription
|
|
61
|
+
return Subscription(self, None)
|
|
62
|
+
|
|
63
|
+
self._observers.append(observer)
|
|
64
|
+
return Subscription(self, observer)
|
|
65
|
+
|
|
66
|
+
# ==========================================================================
|
|
67
|
+
# ObserverBase[T] - for producers
|
|
68
|
+
# ==========================================================================
|
|
69
|
+
def on_next(self, value: T) -> None:
|
|
70
|
+
"""
|
|
71
|
+
Called by producers to emit an item. Delivers synchronously to each observer.
|
|
72
|
+
If closed or disposed, do nothing.
|
|
73
|
+
"""
|
|
74
|
+
with self._lock:
|
|
75
|
+
if self._closed or self._disposed:
|
|
76
|
+
return
|
|
77
|
+
# Copy the current observers to avoid mutation issues
|
|
78
|
+
current_observers = list(self._observers)
|
|
79
|
+
|
|
80
|
+
# Deliver outside the lock
|
|
81
|
+
for obs in current_observers:
|
|
82
|
+
obs.on_next(value)
|
|
83
|
+
|
|
84
|
+
def on_error(self, exc: Exception) -> None:
|
|
85
|
+
"""
|
|
86
|
+
Called by producers to signal an error. Notifies all observers.
|
|
87
|
+
"""
|
|
88
|
+
with self._lock:
|
|
89
|
+
if self._closed or self._disposed:
|
|
90
|
+
return
|
|
91
|
+
current_obs = list(self._observers)
|
|
92
|
+
|
|
93
|
+
for obs in current_obs:
|
|
94
|
+
obs.on_error(exc)
|
|
95
|
+
|
|
96
|
+
def on_complete(self) -> None:
|
|
97
|
+
"""
|
|
98
|
+
Called by producers to signal completion. Notifies all observers, then
|
|
99
|
+
clears them. Subject is closed.
|
|
100
|
+
"""
|
|
101
|
+
with self._lock:
|
|
102
|
+
if self._closed or self._disposed:
|
|
103
|
+
return
|
|
104
|
+
current_observers = list(self._observers)
|
|
105
|
+
self.dispose()
|
|
106
|
+
|
|
107
|
+
for obs in current_observers:
|
|
108
|
+
obs.on_complete()
|
|
109
|
+
|
|
110
|
+
# ==========================================================================
|
|
111
|
+
# SubjectBase - internal unsubscribing
|
|
112
|
+
# ==========================================================================
|
|
113
|
+
def _unsubscribe_observer(self, observer: Observer[T]) -> None:
|
|
114
|
+
with self._lock:
|
|
115
|
+
if not self._disposed and observer in self._observers:
|
|
116
|
+
self._observers.remove(observer)
|
|
117
|
+
|
|
118
|
+
# ==========================================================================
|
|
119
|
+
# Disposal
|
|
120
|
+
# ==========================================================================
|
|
121
|
+
def dispose(self) -> None:
|
|
122
|
+
"""
|
|
123
|
+
Immediately close the Subject. No future on_next, on_error, or on_complete.
|
|
124
|
+
Clears all observers.
|
|
125
|
+
"""
|
|
126
|
+
with self._lock:
|
|
127
|
+
if not self._disposed:
|
|
128
|
+
self._disposed = True
|
|
129
|
+
self._observers.clear()
|
|
130
|
+
self._closed = True
|
|
131
|
+
self._error = None
|
|
@@ -0,0 +1,49 @@
|
|
|
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 typing
|
|
17
|
+
from collections.abc import Callable
|
|
18
|
+
from typing import Generic
|
|
19
|
+
from typing import TypeVar
|
|
20
|
+
|
|
21
|
+
if typing.TYPE_CHECKING:
|
|
22
|
+
from aiq.utils.reactive.base.subject_base import SubjectBase
|
|
23
|
+
|
|
24
|
+
_T = TypeVar("_T") # pylint: disable=invalid-name
|
|
25
|
+
|
|
26
|
+
OnNext = Callable[[_T], None]
|
|
27
|
+
OnError = Callable[[Exception], None]
|
|
28
|
+
OnComplete = Callable[[], None]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Subscription(Generic[_T]):
|
|
32
|
+
"""
|
|
33
|
+
Represents a subscription to a Subject.
|
|
34
|
+
Unsubscribing removes the associated observer from the Subject's subscriber list.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, subject: "SubjectBase", observer: object | None): # noqa: F821
|
|
38
|
+
self._subject = subject
|
|
39
|
+
self._observer = observer
|
|
40
|
+
self._unsubscribed = False
|
|
41
|
+
|
|
42
|
+
def unsubscribe(self) -> None:
|
|
43
|
+
"""
|
|
44
|
+
Stop receiving further events.
|
|
45
|
+
"""
|
|
46
|
+
if not self._unsubscribed and self._observer is not None:
|
|
47
|
+
self._subject._unsubscribe_observer(self._observer)
|
|
48
|
+
self._observer = None
|
|
49
|
+
self._unsubscribed = True
|
|
File without changes
|
|
@@ -0,0 +1,197 @@
|
|
|
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
|
+
|
|
18
|
+
from pydantic import create_model
|
|
19
|
+
|
|
20
|
+
from aiq.cli.type_registry import GlobalTypeRegistry
|
|
21
|
+
from aiq.data_models.registry_handler import RegistryHandlerBaseConfig
|
|
22
|
+
from aiq.settings.global_settings import GlobalSettings
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def configure_registry_channel(config_type: RegistryHandlerBaseConfig, channel_name: str) -> None:
|
|
28
|
+
"""Perform channel updates, gathering input from user and validatinig against the global settings data model.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
config_type (RegistryHandlerBaseConfig): The registry handler configuration object to ensure valid channel
|
|
32
|
+
settings
|
|
33
|
+
channel_name (str): The name to use to reference the remote registry channel.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
settings = GlobalSettings.get()
|
|
37
|
+
|
|
38
|
+
channel_registry_pre = {}
|
|
39
|
+
|
|
40
|
+
for field, info in config_type.model_fields.items():
|
|
41
|
+
|
|
42
|
+
if (field == "type"):
|
|
43
|
+
continue
|
|
44
|
+
|
|
45
|
+
while (True):
|
|
46
|
+
human_prompt = " ".join(field.title().split("_"))
|
|
47
|
+
user_input = input(f"{human_prompt}: ")
|
|
48
|
+
model_fields = {}
|
|
49
|
+
model_fields[field] = (info.annotation, ...)
|
|
50
|
+
DynamicFieldModel = create_model("DynamicFieldModel", **model_fields) # pylint: disable=C0103
|
|
51
|
+
dynamic_inputs = {field: user_input}
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
validated_field_model = DynamicFieldModel(**dynamic_inputs)
|
|
55
|
+
channel_registry_pre[field] = getattr(validated_field_model, field)
|
|
56
|
+
break
|
|
57
|
+
except Exception as e:
|
|
58
|
+
logger.exception(e, exc_info=True)
|
|
59
|
+
logger.warning("Invalid '%s' input, input must be of type %s.", field, info.annotation)
|
|
60
|
+
|
|
61
|
+
validated_model = config_type(**channel_registry_pre)
|
|
62
|
+
settings_dict = settings.model_dump(serialize_as_any=True, by_alias=True)
|
|
63
|
+
settings_dict["channels"] = {**settings_dict["channels"], **{channel_name: validated_model}}
|
|
64
|
+
|
|
65
|
+
settings.update_settings(config_obj=settings_dict)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def add_channel_interative(channel_type: str) -> None:
|
|
69
|
+
"""Add a remote registry channel to publish/search/pull AIQ Toolkit plugin packages.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
channel_type (str): They type of channel to configure.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
settings = GlobalSettings.get()
|
|
76
|
+
registry = GlobalTypeRegistry.get()
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
ChannelConfigType = registry.get_registered_channel_info_by_channel_type( # pylint: disable=C0103
|
|
80
|
+
channel_type=channel_type).config_type
|
|
81
|
+
except Exception as e:
|
|
82
|
+
logger.exception("Invalid channel type: %s", e, exc_info=True)
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
while (True):
|
|
86
|
+
channel_name = input("Channel Name: ").strip()
|
|
87
|
+
if len(channel_name) < 1:
|
|
88
|
+
logger.warning("Invalid channel name, cannot be empty or whitespace.")
|
|
89
|
+
if (channel_name in settings.channels):
|
|
90
|
+
logger.warning("Channel name '%s' already exists, choose a different name.", channel_name)
|
|
91
|
+
else:
|
|
92
|
+
settings.channels[channel_name] = {}
|
|
93
|
+
break
|
|
94
|
+
|
|
95
|
+
ChannelConfigType = registry.get_registered_channel_info_by_channel_type( # pylint: disable=C0103
|
|
96
|
+
channel_type=channel_type).config_type
|
|
97
|
+
|
|
98
|
+
configure_registry_channel(config_type=ChannelConfigType, channel_name=channel_name)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def get_existing_channel_interactive(channel_name: str) -> tuple[str, bool]:
|
|
102
|
+
"""Retrieve an existing channel by configured name.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
channel_name (str): The name to use to reference the remote registry channel.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
tuple[str, bool]: A tuple containing the retrieved channel name and a boolean representing a
|
|
109
|
+
valid match was or was not successful.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
settings = GlobalSettings.get()
|
|
113
|
+
valid_channel = False
|
|
114
|
+
remote_channels = settings.channels
|
|
115
|
+
|
|
116
|
+
if (len(remote_channels) == 0):
|
|
117
|
+
logger.warning("No are configured channels to remove.")
|
|
118
|
+
return channel_name, valid_channel
|
|
119
|
+
|
|
120
|
+
while (not valid_channel):
|
|
121
|
+
|
|
122
|
+
if (channel_name not in remote_channels):
|
|
123
|
+
logger.warning("Channel name '%s' does not exist, choose a name from %s",
|
|
124
|
+
channel_name,
|
|
125
|
+
settings.channel_names)
|
|
126
|
+
channel_name = input("Channel Name: ").strip()
|
|
127
|
+
continue
|
|
128
|
+
|
|
129
|
+
valid_channel = True
|
|
130
|
+
|
|
131
|
+
return channel_name, valid_channel
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def remove_channel(channel_name: str) -> None:
|
|
135
|
+
"""Remove a configured registry channel from the global settings.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
channel_name (str): The name to use to reference the remote registry channel.
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
settings = GlobalSettings.get()
|
|
142
|
+
settings_dict = settings.model_dump(serialize_as_any=True, by_alias=True).copy()
|
|
143
|
+
settings_dict["channels"].pop(channel_name)
|
|
144
|
+
settings.update_settings(config_obj=settings_dict)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def remove_channel_interactive(channel_name: str) -> None:
|
|
148
|
+
channel_name, valid_channel = get_existing_channel_interactive(channel_name=channel_name)
|
|
149
|
+
if (not valid_channel):
|
|
150
|
+
return
|
|
151
|
+
remove_channel(channel_name=channel_name)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def match_valid_channel(channel_name: str) -> None:
|
|
155
|
+
"""Performs a match by registry channel to perform a channel configuration update.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
channel_name (str): The name to use to reference the remote registry channel.
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
settings = GlobalSettings.get()
|
|
162
|
+
registry = GlobalTypeRegistry.get()
|
|
163
|
+
|
|
164
|
+
if len(settings.channel_names) == 0:
|
|
165
|
+
logger.warning("No channels have been configured, first add a channel.")
|
|
166
|
+
return
|
|
167
|
+
|
|
168
|
+
if (channel_name not in settings.channel_names):
|
|
169
|
+
logger.warning("Provided channel has not yet been configured, choose a different name "
|
|
170
|
+
"from %s .",
|
|
171
|
+
settings.channel_names)
|
|
172
|
+
while (True):
|
|
173
|
+
channel_name = input("Channel Name: ").strip()
|
|
174
|
+
if len(channel_name) < 1:
|
|
175
|
+
logger.warning("Invalid channel name, cannot be empty or whitespace.")
|
|
176
|
+
if (channel_name in settings.channel_names):
|
|
177
|
+
logger.warning("Channel name '%s' already exists, choose a different name.", channel_name)
|
|
178
|
+
else:
|
|
179
|
+
settings.channels[channel_name] = {}
|
|
180
|
+
break
|
|
181
|
+
|
|
182
|
+
channals_settings = settings.channels
|
|
183
|
+
channel_settings = channals_settings.get(channel_name)
|
|
184
|
+
ChannelConfigType = registry.get_registered_channel_info_by_channel_type( # pylint: disable=C0103
|
|
185
|
+
channel_type=channel_settings.static_type()).config_type
|
|
186
|
+
|
|
187
|
+
configure_registry_channel(config_type=ChannelConfigType, channel_name=channel_name)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def update_channel_interactive(channel_name: str):
|
|
191
|
+
"""Launch an interactive session to update a configured channels settings.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
channel_name (str): The name to use to reference the remote registry channel.
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
match_valid_channel(channel_name=channel_name)
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from pydantic import BaseModel
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def convert_to_str(value: Any) -> str:
|
|
22
|
+
"""
|
|
23
|
+
Convert a value to a string representation.
|
|
24
|
+
Handles various types including lists, dictionaries, and other objects.
|
|
25
|
+
"""
|
|
26
|
+
if isinstance(value, str):
|
|
27
|
+
return value
|
|
28
|
+
|
|
29
|
+
if isinstance(value, list):
|
|
30
|
+
return ", ".join(map(str, value))
|
|
31
|
+
elif isinstance(value, BaseModel):
|
|
32
|
+
return value.model_dump_json(exclude_none=True, exclude_unset=True)
|
|
33
|
+
elif isinstance(value, dict):
|
|
34
|
+
return ", ".join(f"{k}: {v}" for k, v in value.items())
|
|
35
|
+
elif hasattr(value, '__str__'):
|
|
36
|
+
return str(value)
|
|
37
|
+
else:
|
|
38
|
+
raise ValueError(f"Unsupported type for conversion to string: {type(value)}")
|