aiqtoolkit 1.2.0.dev0__py3-none-any.whl → 1.2.0rc2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of aiqtoolkit might be problematic. Click here for more details.
- aiq/agent/base.py +170 -8
- aiq/agent/dual_node.py +1 -1
- aiq/agent/react_agent/agent.py +146 -112
- aiq/agent/react_agent/prompt.py +1 -6
- aiq/agent/react_agent/register.py +36 -35
- aiq/agent/rewoo_agent/agent.py +36 -35
- aiq/agent/rewoo_agent/register.py +2 -2
- aiq/agent/tool_calling_agent/agent.py +3 -7
- aiq/agent/tool_calling_agent/register.py +1 -1
- aiq/authentication/__init__.py +14 -0
- aiq/authentication/api_key/__init__.py +14 -0
- aiq/authentication/api_key/api_key_auth_provider.py +92 -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/exceptions/auth_code_grant_exceptions.py +86 -0
- aiq/authentication/exceptions/call_back_exceptions.py +38 -0
- aiq/authentication/exceptions/request_exceptions.py +54 -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/builder.py +64 -2
- aiq/builder/component_utils.py +16 -3
- aiq/builder/context.py +37 -0
- aiq/builder/eval_builder.py +43 -2
- aiq/builder/function.py +44 -12
- aiq/builder/function_base.py +1 -1
- aiq/builder/intermediate_step_manager.py +6 -8
- aiq/builder/user_interaction_manager.py +3 -0
- aiq/builder/workflow.py +23 -18
- aiq/builder/workflow_builder.py +421 -61
- aiq/cli/commands/info/list_mcp.py +103 -16
- aiq/cli/commands/sizing/__init__.py +14 -0
- aiq/cli/commands/sizing/calc.py +294 -0
- aiq/cli/commands/sizing/sizing.py +27 -0
- aiq/cli/commands/start.py +2 -1
- aiq/cli/entrypoint.py +2 -0
- aiq/cli/register_workflow.py +80 -0
- aiq/cli/type_registry.py +151 -30
- aiq/data_models/api_server.py +124 -12
- aiq/data_models/authentication.py +231 -0
- aiq/data_models/common.py +35 -7
- aiq/data_models/component.py +17 -9
- aiq/data_models/component_ref.py +33 -0
- aiq/data_models/config.py +60 -3
- aiq/data_models/dataset_handler.py +2 -1
- aiq/data_models/embedder.py +1 -0
- aiq/data_models/evaluate.py +23 -0
- aiq/data_models/function_dependencies.py +8 -0
- aiq/data_models/interactive.py +10 -1
- aiq/data_models/intermediate_step.py +38 -5
- aiq/data_models/its_strategy.py +30 -0
- aiq/data_models/llm.py +1 -0
- aiq/data_models/memory.py +1 -0
- aiq/data_models/object_store.py +44 -0
- aiq/data_models/profiler.py +1 -0
- aiq/data_models/retry_mixin.py +35 -0
- aiq/data_models/span.py +187 -0
- aiq/data_models/telemetry_exporter.py +2 -2
- aiq/embedder/nim_embedder.py +2 -1
- aiq/embedder/openai_embedder.py +2 -1
- aiq/eval/config.py +19 -1
- aiq/eval/dataset_handler/dataset_handler.py +87 -2
- aiq/eval/evaluate.py +208 -27
- aiq/eval/evaluator/base_evaluator.py +73 -0
- aiq/eval/evaluator/evaluator_model.py +1 -0
- aiq/eval/intermediate_step_adapter.py +11 -5
- aiq/eval/rag_evaluator/evaluate.py +55 -15
- aiq/eval/rag_evaluator/register.py +6 -1
- aiq/eval/remote_workflow.py +7 -2
- 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/trajectory_evaluator/evaluate.py +22 -65
- aiq/eval/tunable_rag_evaluator/evaluate.py +150 -168
- aiq/eval/tunable_rag_evaluator/register.py +2 -0
- aiq/eval/usage_stats.py +41 -0
- aiq/eval/utils/output_uploader.py +10 -1
- 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/inference_time_scaling/__init__.py +0 -0
- aiq/experimental/inference_time_scaling/editing/__init__.py +0 -0
- aiq/experimental/inference_time_scaling/editing/iterative_plan_refinement_editor.py +147 -0
- aiq/experimental/inference_time_scaling/editing/llm_as_a_judge_editor.py +204 -0
- aiq/experimental/inference_time_scaling/editing/motivation_aware_summarization.py +107 -0
- aiq/experimental/inference_time_scaling/functions/__init__.py +0 -0
- aiq/experimental/inference_time_scaling/functions/execute_score_select_function.py +105 -0
- aiq/experimental/inference_time_scaling/functions/its_tool_orchestration_function.py +205 -0
- aiq/experimental/inference_time_scaling/functions/its_tool_wrapper_function.py +146 -0
- aiq/experimental/inference_time_scaling/functions/plan_select_execute_function.py +224 -0
- aiq/experimental/inference_time_scaling/models/__init__.py +0 -0
- aiq/experimental/inference_time_scaling/models/editor_config.py +132 -0
- aiq/experimental/inference_time_scaling/models/its_item.py +48 -0
- aiq/experimental/inference_time_scaling/models/scoring_config.py +112 -0
- aiq/experimental/inference_time_scaling/models/search_config.py +120 -0
- aiq/experimental/inference_time_scaling/models/selection_config.py +154 -0
- aiq/experimental/inference_time_scaling/models/stage_enums.py +43 -0
- aiq/experimental/inference_time_scaling/models/strategy_base.py +66 -0
- aiq/experimental/inference_time_scaling/models/tool_use_config.py +41 -0
- aiq/experimental/inference_time_scaling/register.py +36 -0
- aiq/experimental/inference_time_scaling/scoring/__init__.py +0 -0
- aiq/experimental/inference_time_scaling/scoring/llm_based_agent_scorer.py +168 -0
- aiq/experimental/inference_time_scaling/scoring/llm_based_plan_scorer.py +168 -0
- aiq/experimental/inference_time_scaling/scoring/motivation_aware_scorer.py +111 -0
- aiq/experimental/inference_time_scaling/search/__init__.py +0 -0
- aiq/experimental/inference_time_scaling/search/multi_llm_planner.py +128 -0
- aiq/experimental/inference_time_scaling/search/multi_query_retrieval_search.py +122 -0
- aiq/experimental/inference_time_scaling/search/single_shot_multi_plan_planner.py +128 -0
- aiq/experimental/inference_time_scaling/selection/__init__.py +0 -0
- aiq/experimental/inference_time_scaling/selection/best_of_n_selector.py +63 -0
- aiq/experimental/inference_time_scaling/selection/llm_based_agent_output_selector.py +131 -0
- aiq/experimental/inference_time_scaling/selection/llm_based_output_merging_selector.py +159 -0
- aiq/experimental/inference_time_scaling/selection/llm_based_plan_selector.py +128 -0
- aiq/experimental/inference_time_scaling/selection/threshold_selector.py +58 -0
- aiq/front_ends/console/authentication_flow_handler.py +233 -0
- aiq/front_ends/console/console_front_end_plugin.py +11 -2
- 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 +93 -9
- aiq/front_ends/fastapi/fastapi_front_end_controller.py +68 -0
- aiq/front_ends/fastapi/fastapi_front_end_plugin.py +14 -1
- aiq/front_ends/fastapi/fastapi_front_end_plugin_worker.py +537 -52
- 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/job_store.py +47 -25
- aiq/front_ends/fastapi/main.py +2 -0
- aiq/front_ends/fastapi/message_handler.py +108 -89
- aiq/front_ends/fastapi/step_adaptor.py +2 -1
- aiq/llm/aws_bedrock_llm.py +57 -0
- aiq/llm/nim_llm.py +2 -1
- aiq/llm/openai_llm.py +3 -2
- aiq/llm/register.py +1 -0
- aiq/meta/pypi.md +12 -12
- aiq/object_store/__init__.py +20 -0
- aiq/object_store/in_memory_object_store.py +74 -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 +269 -0
- aiq/observability/exporter/raw_exporter.py +52 -0
- aiq/observability/exporter/span_exporter.py +264 -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 +316 -0
- aiq/observability/processor/intermediate_step_serializer.py +28 -0
- aiq/observability/processor/processor.py +68 -0
- aiq/observability/register.py +36 -39
- aiq/observability/utils/__init__.py +14 -0
- aiq/observability/utils/dict_utils.py +236 -0
- aiq/observability/utils/time_utils.py +31 -0
- aiq/profiler/calc/__init__.py +14 -0
- aiq/profiler/calc/calc_runner.py +623 -0
- aiq/profiler/calc/calculations.py +288 -0
- aiq/profiler/calc/data_models.py +176 -0
- aiq/profiler/calc/plot.py +345 -0
- aiq/profiler/callbacks/langchain_callback_handler.py +22 -10
- aiq/profiler/data_models.py +24 -0
- aiq/profiler/inference_metrics_model.py +3 -0
- aiq/profiler/inference_optimization/bottleneck_analysis/nested_stack_analysis.py +8 -0
- aiq/profiler/inference_optimization/data_models.py +2 -2
- aiq/profiler/inference_optimization/llm_metrics.py +2 -2
- aiq/profiler/profile_runner.py +61 -21
- aiq/runtime/loader.py +9 -3
- aiq/runtime/runner.py +23 -9
- aiq/runtime/session.py +25 -7
- aiq/runtime/user_metadata.py +2 -3
- aiq/tool/chat_completion.py +74 -0
- aiq/tool/code_execution/README.md +152 -0
- aiq/tool/code_execution/code_sandbox.py +151 -72
- aiq/tool/code_execution/local_sandbox/.gitignore +1 -0
- aiq/tool/code_execution/local_sandbox/local_sandbox_server.py +139 -24
- aiq/tool/code_execution/local_sandbox/sandbox.requirements.txt +3 -1
- aiq/tool/code_execution/local_sandbox/start_local_sandbox.sh +27 -2
- aiq/tool/code_execution/register.py +7 -3
- aiq/tool/code_execution/test_code_execution_sandbox.py +414 -0
- aiq/tool/mcp/exceptions.py +142 -0
- aiq/tool/mcp/mcp_client.py +41 -6
- aiq/tool/mcp/mcp_tool.py +3 -2
- aiq/tool/register.py +1 -0
- aiq/tool/server_tools.py +6 -3
- aiq/utils/exception_handlers/automatic_retries.py +289 -0
- aiq/utils/exception_handlers/mcp.py +211 -0
- aiq/utils/io/model_processing.py +28 -0
- aiq/utils/log_utils.py +37 -0
- aiq/utils/string_utils.py +38 -0
- aiq/utils/type_converter.py +18 -2
- aiq/utils/type_utils.py +87 -0
- {aiqtoolkit-1.2.0.dev0.dist-info → aiqtoolkit-1.2.0rc2.dist-info}/METADATA +53 -21
- aiqtoolkit-1.2.0rc2.dist-info/RECORD +436 -0
- {aiqtoolkit-1.2.0.dev0.dist-info → aiqtoolkit-1.2.0rc2.dist-info}/WHEEL +1 -1
- {aiqtoolkit-1.2.0.dev0.dist-info → aiqtoolkit-1.2.0rc2.dist-info}/entry_points.txt +3 -0
- aiq/front_ends/fastapi/websocket.py +0 -148
- aiq/observability/async_otel_listener.py +0 -429
- aiqtoolkit-1.2.0.dev0.dist-info/RECORD +0 -316
- {aiqtoolkit-1.2.0.dev0.dist-info → aiqtoolkit-1.2.0rc2.dist-info}/licenses/LICENSE-3rd-party.txt +0 -0
- {aiqtoolkit-1.2.0.dev0.dist-info → aiqtoolkit-1.2.0rc2.dist-info}/licenses/LICENSE.md +0 -0
- {aiqtoolkit-1.2.0.dev0.dist-info → aiqtoolkit-1.2.0rc2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,14 @@
|
|
|
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.
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
AUTH_REDIRECT_SUCCESS_HTML = """
|
|
17
|
+
<!DOCTYPE html>
|
|
18
|
+
<html>
|
|
19
|
+
<head>
|
|
20
|
+
<title>Authentication Complete</title>
|
|
21
|
+
<script>
|
|
22
|
+
(function () {
|
|
23
|
+
window.history.replaceState(null, "", window.location.pathname);
|
|
24
|
+
|
|
25
|
+
window.opener?.postMessage({ type: 'AUTH_SUCCESS' }, '*');
|
|
26
|
+
|
|
27
|
+
window.close();
|
|
28
|
+
})();
|
|
29
|
+
</script>
|
|
30
|
+
</head>
|
|
31
|
+
<body>
|
|
32
|
+
<p>Authentication complete. You may now close this window.</p>
|
|
33
|
+
</body>
|
|
34
|
+
</html>
|
|
35
|
+
"""
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import logging
|
|
17
17
|
import os
|
|
18
18
|
import shutil
|
|
19
|
+
import threading
|
|
19
20
|
from datetime import UTC
|
|
20
21
|
from datetime import datetime
|
|
21
22
|
from datetime import timedelta
|
|
@@ -40,12 +41,13 @@ class JobStatus(str, Enum):
|
|
|
40
41
|
class JobInfo(BaseModel):
|
|
41
42
|
job_id: str
|
|
42
43
|
status: JobStatus
|
|
43
|
-
config_file: str
|
|
44
|
+
config_file: str | None
|
|
44
45
|
error: str | None
|
|
45
46
|
output_path: str | None
|
|
46
47
|
created_at: datetime
|
|
47
48
|
updated_at: datetime
|
|
48
49
|
expiry_seconds: int
|
|
50
|
+
output: BaseModel | None = None
|
|
49
51
|
|
|
50
52
|
|
|
51
53
|
class JobStore:
|
|
@@ -59,8 +61,12 @@ class JobStore:
|
|
|
59
61
|
|
|
60
62
|
def __init__(self):
|
|
61
63
|
self._jobs = {}
|
|
64
|
+
self._lock = threading.Lock() # Ensure thread safety for job operations
|
|
62
65
|
|
|
63
|
-
def create_job(self,
|
|
66
|
+
def create_job(self,
|
|
67
|
+
config_file: str | None = None,
|
|
68
|
+
job_id: str | None = None,
|
|
69
|
+
expiry_seconds: int = DEFAULT_EXPIRY) -> str:
|
|
64
70
|
if job_id is None:
|
|
65
71
|
job_id = str(uuid4())
|
|
66
72
|
|
|
@@ -76,46 +82,62 @@ class JobStore:
|
|
|
76
82
|
error=None,
|
|
77
83
|
output_path=None,
|
|
78
84
|
expiry_seconds=clamped_expiry)
|
|
79
|
-
|
|
85
|
+
|
|
86
|
+
with self._lock:
|
|
87
|
+
self._jobs[job_id] = job
|
|
88
|
+
|
|
80
89
|
logger.info("Created new job %s with config %s", job_id, config_file)
|
|
81
90
|
return job_id
|
|
82
91
|
|
|
83
|
-
def update_status(self,
|
|
92
|
+
def update_status(self,
|
|
93
|
+
job_id: str,
|
|
94
|
+
status: str,
|
|
95
|
+
error: str | None = None,
|
|
96
|
+
output_path: str | None = None,
|
|
97
|
+
output: BaseModel | None = None):
|
|
84
98
|
if job_id not in self._jobs:
|
|
85
99
|
raise ValueError(f"Job {job_id} not found")
|
|
86
100
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
101
|
+
with self._lock:
|
|
102
|
+
job = self._jobs[job_id]
|
|
103
|
+
job.status = status
|
|
104
|
+
job.error = error
|
|
105
|
+
job.output_path = output_path
|
|
106
|
+
job.updated_at = datetime.now(UTC)
|
|
107
|
+
job.output = output
|
|
92
108
|
|
|
93
109
|
def get_status(self, job_id: str) -> JobInfo | None:
|
|
94
|
-
|
|
110
|
+
with self._lock:
|
|
111
|
+
return self._jobs.get(job_id)
|
|
95
112
|
|
|
96
113
|
def list_jobs(self):
|
|
97
|
-
|
|
114
|
+
with self._lock:
|
|
115
|
+
return self._jobs
|
|
98
116
|
|
|
99
117
|
def get_job(self, job_id: str) -> JobInfo | None:
|
|
100
118
|
"""Get a job by its ID."""
|
|
101
|
-
|
|
119
|
+
with self._lock:
|
|
120
|
+
return self._jobs.get(job_id)
|
|
102
121
|
|
|
103
122
|
def get_last_job(self) -> JobInfo | None:
|
|
104
123
|
"""Get the last created job."""
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
124
|
+
with self._lock:
|
|
125
|
+
if not self._jobs:
|
|
126
|
+
logger.info("No jobs found in job store")
|
|
127
|
+
return None
|
|
128
|
+
last_job = max(self._jobs.values(), key=lambda job: job.created_at)
|
|
129
|
+
logger.info("Retrieved last job %s created at %s", last_job.job_id, last_job.created_at)
|
|
130
|
+
return last_job
|
|
111
131
|
|
|
112
132
|
def get_jobs_by_status(self, status: str) -> list[JobInfo]:
|
|
113
133
|
"""Get all jobs with the specified status."""
|
|
114
|
-
|
|
134
|
+
with self._lock:
|
|
135
|
+
return [job for job in self._jobs.values() if job.status == status]
|
|
115
136
|
|
|
116
137
|
def get_all_jobs(self) -> list[JobInfo]:
|
|
117
138
|
"""Get all jobs in the store."""
|
|
118
|
-
|
|
139
|
+
with self._lock:
|
|
140
|
+
return list(self._jobs.values())
|
|
119
141
|
|
|
120
142
|
def get_expires_at(self, job: JobInfo) -> datetime | None:
|
|
121
143
|
"""Get the time for a job to expire."""
|
|
@@ -132,7 +154,8 @@ class JobStore:
|
|
|
132
154
|
now = datetime.now(UTC)
|
|
133
155
|
|
|
134
156
|
# Filter out active jobs
|
|
135
|
-
|
|
157
|
+
with self._lock:
|
|
158
|
+
finished_jobs = {job_id: job for job_id, job in self._jobs.items() if job.status not in self.ACTIVE_STATUS}
|
|
136
159
|
|
|
137
160
|
# Sort finished jobs by updated_at descending
|
|
138
161
|
sorted_finished = sorted(finished_jobs.items(), key=lambda item: item[1].updated_at, reverse=True)
|
|
@@ -155,7 +178,6 @@ class JobStore:
|
|
|
155
178
|
elif os.path.isdir(job.output_path):
|
|
156
179
|
shutil.rmtree(job.output_path)
|
|
157
180
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
del self._jobs[job_id]
|
|
181
|
+
with self._lock:
|
|
182
|
+
for job_id in expired_ids:
|
|
183
|
+
del self._jobs[job_id]
|
aiq/front_ends/fastapi/main.py
CHANGED
|
@@ -15,14 +15,19 @@
|
|
|
15
15
|
|
|
16
16
|
import asyncio
|
|
17
17
|
import logging
|
|
18
|
+
import typing
|
|
18
19
|
import uuid
|
|
19
20
|
from typing import Any
|
|
20
21
|
|
|
21
22
|
from fastapi import WebSocket
|
|
22
23
|
from pydantic import BaseModel
|
|
23
24
|
from pydantic import ValidationError
|
|
24
|
-
from starlette.
|
|
25
|
+
from starlette.websockets import WebSocketDisconnect
|
|
25
26
|
|
|
27
|
+
from aiq.authentication.interfaces import FlowHandlerBase
|
|
28
|
+
from aiq.data_models.api_server import AIQChatResponse
|
|
29
|
+
from aiq.data_models.api_server import AIQResponsePayloadOutput
|
|
30
|
+
from aiq.data_models.api_server import AIQResponseSerializable
|
|
26
31
|
from aiq.data_models.api_server import Error
|
|
27
32
|
from aiq.data_models.api_server import ErrorTypes
|
|
28
33
|
from aiq.data_models.api_server import SystemResponseContent
|
|
@@ -39,74 +44,72 @@ from aiq.data_models.interactive import HumanResponse
|
|
|
39
44
|
from aiq.data_models.interactive import HumanResponseNotification
|
|
40
45
|
from aiq.data_models.interactive import InteractionPrompt
|
|
41
46
|
from aiq.front_ends.fastapi.message_validator import MessageValidator
|
|
47
|
+
from aiq.front_ends.fastapi.response_helpers import generate_streaming_response
|
|
48
|
+
from aiq.front_ends.fastapi.step_adaptor import StepAdaptor
|
|
49
|
+
from aiq.runtime.session import AIQSessionManager
|
|
42
50
|
|
|
43
51
|
logger = logging.getLogger(__name__)
|
|
44
52
|
|
|
45
53
|
|
|
46
|
-
class
|
|
54
|
+
class WebSocketMessageHandler:
|
|
55
|
+
|
|
56
|
+
def __init__(self, socket: WebSocket, session_manager: AIQSessionManager, step_adaptor: StepAdaptor):
|
|
57
|
+
self._socket: WebSocket = socket
|
|
58
|
+
self._session_manager: AIQSessionManager = session_manager
|
|
59
|
+
self._step_adaptor: StepAdaptor = step_adaptor
|
|
47
60
|
|
|
48
|
-
def __init__(self, websocket_reference: WebSocketEndpoint):
|
|
49
|
-
self._websocket_reference: WebSocketEndpoint = websocket_reference
|
|
50
61
|
self._message_validator: MessageValidator = MessageValidator()
|
|
51
|
-
self.
|
|
52
|
-
self._out_going_messages_queue: asyncio.Queue[dict] = asyncio.Queue()
|
|
53
|
-
self._process_messages_task: asyncio.Task | None = None
|
|
54
|
-
self._process_out_going_messages_task: asyncio.Task = None
|
|
55
|
-
self._background_task: asyncio.Task = None
|
|
62
|
+
self._running_workflow_task: asyncio.Task | None = None
|
|
56
63
|
self._message_parent_id: str = "default_id"
|
|
57
64
|
self._workflow_schema_type: str = None
|
|
58
|
-
self._user_interaction_response: asyncio.Future[
|
|
65
|
+
self._user_interaction_response: asyncio.Future[HumanResponse] | None = None
|
|
59
66
|
|
|
60
|
-
|
|
61
|
-
def messages_queue(self) -> asyncio.Queue[dict[str, str]]:
|
|
62
|
-
return self._messages_queue
|
|
67
|
+
self._flow_handler: FlowHandlerBase | None = None
|
|
63
68
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return self._background_task
|
|
69
|
+
def set_flow_handler(self, flow_handler: FlowHandlerBase) -> None:
|
|
70
|
+
self._flow_handler = flow_handler
|
|
67
71
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
return self._process_messages_task
|
|
72
|
+
async def __aenter__(self) -> "WebSocketMessageHandler":
|
|
73
|
+
await self._socket.accept()
|
|
71
74
|
|
|
72
|
-
|
|
73
|
-
def process_messages_task(self, process_messages_task) -> None:
|
|
74
|
-
self._process_messages_task = process_messages_task
|
|
75
|
+
return self
|
|
75
76
|
|
|
76
|
-
|
|
77
|
-
def process_out_going_messages_task(self) -> asyncio.Task:
|
|
78
|
-
return self._process_out_going_messages_task
|
|
77
|
+
async def __aexit__(self, exc_type, exc_value, traceback) -> None:
|
|
79
78
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
self._process_out_going_messages_task = process_out_going_messages_task
|
|
79
|
+
# TODO: Handle the exit
|
|
80
|
+
pass
|
|
83
81
|
|
|
84
|
-
async def
|
|
82
|
+
async def run(self) -> None:
|
|
85
83
|
"""
|
|
86
84
|
Processes received messages from websocket and routes them appropriately.
|
|
87
85
|
"""
|
|
88
86
|
while True:
|
|
89
87
|
|
|
90
88
|
try:
|
|
91
|
-
|
|
89
|
+
|
|
90
|
+
message: dict[str, Any] = await self._socket.receive_json()
|
|
92
91
|
|
|
93
92
|
validated_message: BaseModel = await self._message_validator.validate_message(message)
|
|
94
93
|
|
|
94
|
+
# Received a request to start a workflow
|
|
95
95
|
if (isinstance(validated_message, WebSocketUserMessage)):
|
|
96
|
-
await self.
|
|
96
|
+
await self.process_workflow_request(validated_message)
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
elif isinstance(
|
|
99
99
|
validated_message,
|
|
100
100
|
( # noqa: E131
|
|
101
101
|
WebSocketSystemResponseTokenMessage,
|
|
102
102
|
WebSocketSystemIntermediateStepMessage,
|
|
103
103
|
WebSocketSystemInteractionMessage)):
|
|
104
|
-
|
|
104
|
+
# These messages are already handled by self.create_websocket_message(data_model=value, …)
|
|
105
|
+
# No further processing is needed here.
|
|
106
|
+
pass
|
|
105
107
|
|
|
106
|
-
|
|
108
|
+
elif (isinstance(validated_message, WebSocketUserInteractionResponseMessage)):
|
|
107
109
|
user_content = await self.process_user_message_content(validated_message)
|
|
108
110
|
self._user_interaction_response.set_result(user_content)
|
|
109
|
-
except (asyncio.CancelledError):
|
|
111
|
+
except (asyncio.CancelledError, WebSocketDisconnect):
|
|
112
|
+
# TODO: Handle the disconnect
|
|
110
113
|
break
|
|
111
114
|
|
|
112
115
|
return None
|
|
@@ -130,29 +133,32 @@ class MessageHandler:
|
|
|
130
133
|
|
|
131
134
|
return None
|
|
132
135
|
|
|
133
|
-
async def
|
|
136
|
+
async def process_workflow_request(self, user_message_as_validated_type: WebSocketUserMessage) -> None:
|
|
134
137
|
"""
|
|
135
138
|
Process user messages and routes them appropriately.
|
|
136
139
|
|
|
137
|
-
:param
|
|
140
|
+
:param user_message_as_validated_type: A WebSocketUserMessage Data Model instance.
|
|
138
141
|
"""
|
|
139
142
|
|
|
140
143
|
try:
|
|
141
|
-
self._message_parent_id =
|
|
142
|
-
self._workflow_schema_type =
|
|
144
|
+
self._message_parent_id = user_message_as_validated_type.id
|
|
145
|
+
self._workflow_schema_type = user_message_as_validated_type.schema_type
|
|
146
|
+
conversation_id: str = user_message_as_validated_type.conversation_id
|
|
143
147
|
|
|
144
|
-
content: BaseModel | None = await self.process_user_message_content(
|
|
148
|
+
content: BaseModel | None = await self.process_user_message_content(user_message_as_validated_type)
|
|
145
149
|
|
|
146
150
|
if content is None:
|
|
147
|
-
raise ValueError(f"User message content could not be found: {
|
|
151
|
+
raise ValueError(f"User message content could not be found: {user_message_as_validated_type}")
|
|
148
152
|
|
|
149
|
-
if isinstance(content, TextContent) and (self.
|
|
153
|
+
if isinstance(content, TextContent) and (self._running_workflow_task is None):
|
|
150
154
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
155
|
+
def _done_callback(task: asyncio.Task):
|
|
156
|
+
self._running_workflow_task = None
|
|
157
|
+
|
|
158
|
+
# await self._process_response()
|
|
159
|
+
self._running_workflow_task = asyncio.create_task(
|
|
160
|
+
self._run_workflow(content.text, conversation_id,
|
|
161
|
+
result_type=AIQChatResponse)).add_done_callback(_done_callback)
|
|
156
162
|
|
|
157
163
|
except ValueError as e:
|
|
158
164
|
logger.error("User message content not found: %s", str(e), exc_info=True)
|
|
@@ -220,60 +226,73 @@ class MessageHandler:
|
|
|
220
226
|
content=Error(code=ErrorTypes.UNKNOWN_ERROR, message="default", details=str(e)))
|
|
221
227
|
|
|
222
228
|
finally:
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
async def _on_process_stream_task_done(self, task: asyncio.Task) -> None:
|
|
226
|
-
await self.create_websocket_message(data_model=SystemResponseContent(),
|
|
227
|
-
message_type=WebSocketMessageType.RESPONSE_MESSAGE,
|
|
228
|
-
status=WebSocketMessageStatus.COMPLETE)
|
|
229
|
+
if (message is not None):
|
|
230
|
+
await self._socket.send_json(message.model_dump())
|
|
229
231
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
async def process_out_going_messages(self, websocket: WebSocket) -> None:
|
|
232
|
+
async def human_interaction_callback(self, prompt: InteractionPrompt) -> HumanResponse:
|
|
233
233
|
"""
|
|
234
|
-
|
|
234
|
+
Registered human interaction callback that processes human interactions and returns
|
|
235
|
+
responses from websocket connection.
|
|
235
236
|
|
|
236
|
-
:param
|
|
237
|
+
:param prompt: Incoming interaction content data model.
|
|
238
|
+
:return: A Text Content Base Pydantic model.
|
|
237
239
|
"""
|
|
238
|
-
while True:
|
|
239
|
-
try:
|
|
240
|
-
out_going_message = await self._out_going_messages_queue.get()
|
|
241
|
-
await self._websocket_reference.on_send(websocket, out_going_message)
|
|
242
240
|
|
|
243
|
-
|
|
244
|
-
|
|
241
|
+
# First create a future from the loop for the human response
|
|
242
|
+
human_response_future: asyncio.Future[HumanResponse] = asyncio.get_running_loop().create_future()
|
|
245
243
|
|
|
246
|
-
|
|
244
|
+
# Then add the future to the outstanding human prompts dictionary
|
|
245
|
+
self._user_interaction_response = human_response_future
|
|
246
|
+
|
|
247
|
+
try:
|
|
247
248
|
|
|
248
|
-
|
|
249
|
-
|
|
249
|
+
await self.create_websocket_message(data_model=prompt.content,
|
|
250
|
+
message_type=WebSocketMessageType.SYSTEM_INTERACTION_MESSAGE,
|
|
251
|
+
status=WebSocketMessageStatus.IN_PROGRESS)
|
|
250
252
|
|
|
251
|
-
|
|
252
|
-
self._websocket_reference.process_response_event.clear()
|
|
253
|
+
if (isinstance(prompt.content, HumanPromptNotification)):
|
|
253
254
|
|
|
254
|
-
|
|
255
|
-
self._user_interaction_response = asyncio.Future()
|
|
255
|
+
return HumanResponseNotification()
|
|
256
256
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
Registered human interaction callback that processes human interactions and returns
|
|
260
|
-
responses from websocket connection.
|
|
257
|
+
# Wait for the human response future to complete
|
|
258
|
+
interaction_response: HumanResponse = await human_response_future
|
|
261
259
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
"""
|
|
265
|
-
await self.create_websocket_message(data_model=prompt.content,
|
|
266
|
-
message_type=WebSocketMessageType.SYSTEM_INTERACTION_MESSAGE,
|
|
267
|
-
status=WebSocketMessageStatus.IN_PROGRESS)
|
|
260
|
+
interaction_response: HumanResponse = await self._message_validator.convert_text_content_to_human_response(
|
|
261
|
+
interaction_response, prompt.content)
|
|
268
262
|
|
|
269
|
-
|
|
270
|
-
return HumanResponseNotification()
|
|
263
|
+
return interaction_response
|
|
271
264
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
265
|
+
finally:
|
|
266
|
+
# Delete the future from the outstanding human prompts dictionary
|
|
267
|
+
self._user_interaction_response = None
|
|
268
|
+
|
|
269
|
+
async def _run_workflow(self,
|
|
270
|
+
payload: typing.Any,
|
|
271
|
+
conversation_id: str | None = None,
|
|
272
|
+
result_type: type | None = None,
|
|
273
|
+
output_type: type | None = None) -> None:
|
|
274
|
+
|
|
275
|
+
try:
|
|
276
|
+
async with self._session_manager.session(
|
|
277
|
+
conversation_id=conversation_id,
|
|
278
|
+
request=self._socket,
|
|
279
|
+
user_input_callback=self.human_interaction_callback,
|
|
280
|
+
user_authentication_callback=(self._flow_handler.authenticate
|
|
281
|
+
if self._flow_handler else None)) as session:
|
|
275
282
|
|
|
276
|
-
|
|
277
|
-
|
|
283
|
+
async for value in generate_streaming_response(payload,
|
|
284
|
+
session_manager=session,
|
|
285
|
+
streaming=True,
|
|
286
|
+
step_adaptor=self._step_adaptor,
|
|
287
|
+
result_type=result_type,
|
|
288
|
+
output_type=output_type):
|
|
278
289
|
|
|
279
|
-
|
|
290
|
+
if not isinstance(value, AIQResponseSerializable):
|
|
291
|
+
value = AIQResponsePayloadOutput(payload=value)
|
|
292
|
+
|
|
293
|
+
await self.create_websocket_message(data_model=value, status=WebSocketMessageStatus.IN_PROGRESS)
|
|
294
|
+
|
|
295
|
+
finally:
|
|
296
|
+
await self.create_websocket_message(data_model=SystemResponseContent(),
|
|
297
|
+
message_type=WebSocketMessageType.RESPONSE_MESSAGE,
|
|
298
|
+
status=WebSocketMessageStatus.COMPLETE)
|
|
@@ -291,7 +291,8 @@ class StepAdaptor:
|
|
|
291
291
|
|
|
292
292
|
return event
|
|
293
293
|
|
|
294
|
-
def process(self, step: IntermediateStep) -> AIQResponseSerializable | None:
|
|
294
|
+
def process(self, step: IntermediateStep) -> AIQResponseSerializable | None: # pylint: disable=R1710
|
|
295
|
+
|
|
295
296
|
# Track the chunk
|
|
296
297
|
self._history.append(step)
|
|
297
298
|
payload = step.payload
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2024-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 pydantic import AliasChoices
|
|
17
|
+
from pydantic import ConfigDict
|
|
18
|
+
from pydantic import Field
|
|
19
|
+
|
|
20
|
+
from aiq.builder.builder import Builder
|
|
21
|
+
from aiq.builder.llm import LLMProviderInfo
|
|
22
|
+
from aiq.cli.register_workflow import register_llm_provider
|
|
23
|
+
from aiq.data_models.llm import LLMBaseConfig
|
|
24
|
+
from aiq.data_models.retry_mixin import RetryMixin
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class AWSBedrockModelConfig(LLMBaseConfig, RetryMixin, name="aws_bedrock"):
|
|
28
|
+
"""An AWS Bedrock llm provider to be used with an LLM client."""
|
|
29
|
+
|
|
30
|
+
model_config = ConfigDict(protected_namespaces=())
|
|
31
|
+
|
|
32
|
+
# Completion parameters
|
|
33
|
+
model_name: str = Field(validation_alias=AliasChoices("model_name", "model"),
|
|
34
|
+
serialization_alias="model",
|
|
35
|
+
description="The model name for the hosted AWS Bedrock.")
|
|
36
|
+
temperature: float = Field(default=0.0, ge=0.0, le=1.0, description="Sampling temperature in [0, 1].")
|
|
37
|
+
max_tokens: int | None = Field(default=1024,
|
|
38
|
+
gt=0,
|
|
39
|
+
description="Maximum number of tokens to generate."
|
|
40
|
+
"This field is ONLY required when using AWS Bedrock with Langchain.")
|
|
41
|
+
context_size: int | None = Field(default=1024,
|
|
42
|
+
gt=0,
|
|
43
|
+
description="Maximum number of tokens to generate."
|
|
44
|
+
"This field is ONLY required when using AWS Bedrock with LlamaIndex.")
|
|
45
|
+
|
|
46
|
+
# Client parameters
|
|
47
|
+
region_name: str | None = Field(default="None", description="AWS region to use.")
|
|
48
|
+
base_url: str | None = Field(
|
|
49
|
+
default=None, description="Bedrock endpoint to use. Needed if you don't want to default to us-east-1 endpoint.")
|
|
50
|
+
credentials_profile_name: str | None = Field(
|
|
51
|
+
default=None, description="The name of the profile in the ~/.aws/credentials or ~/.aws/config files.")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@register_llm_provider(config_type=AWSBedrockModelConfig)
|
|
55
|
+
async def aws_bedrock_model(llm_config: AWSBedrockModelConfig, builder: Builder):
|
|
56
|
+
|
|
57
|
+
yield LLMProviderInfo(config=llm_config, description="A AWS Bedrock model for use with an LLM client.")
|
aiq/llm/nim_llm.py
CHANGED
|
@@ -22,9 +22,10 @@ from aiq.builder.builder import Builder
|
|
|
22
22
|
from aiq.builder.llm import LLMProviderInfo
|
|
23
23
|
from aiq.cli.register_workflow import register_llm_provider
|
|
24
24
|
from aiq.data_models.llm import LLMBaseConfig
|
|
25
|
+
from aiq.data_models.retry_mixin import RetryMixin
|
|
25
26
|
|
|
26
27
|
|
|
27
|
-
class NIMModelConfig(LLMBaseConfig, name="nim"):
|
|
28
|
+
class NIMModelConfig(LLMBaseConfig, RetryMixin, name="nim"):
|
|
28
29
|
"""An NVIDIA Inference Microservice (NIM) llm provider to be used with an LLM client."""
|
|
29
30
|
|
|
30
31
|
model_config = ConfigDict(protected_namespaces=())
|
aiq/llm/openai_llm.py
CHANGED
|
@@ -21,12 +21,13 @@ from aiq.builder.builder import Builder
|
|
|
21
21
|
from aiq.builder.llm import LLMProviderInfo
|
|
22
22
|
from aiq.cli.register_workflow import register_llm_provider
|
|
23
23
|
from aiq.data_models.llm import LLMBaseConfig
|
|
24
|
+
from aiq.data_models.retry_mixin import RetryMixin
|
|
24
25
|
|
|
25
26
|
|
|
26
|
-
class OpenAIModelConfig(LLMBaseConfig, name="openai"):
|
|
27
|
+
class OpenAIModelConfig(LLMBaseConfig, RetryMixin, name="openai"):
|
|
27
28
|
"""An OpenAI LLM provider to be used with an LLM client."""
|
|
28
29
|
|
|
29
|
-
model_config = ConfigDict(protected_namespaces=())
|
|
30
|
+
model_config = ConfigDict(protected_namespaces=(), extra="allow")
|
|
30
31
|
|
|
31
32
|
api_key: str | None = Field(default=None, description="OpenAI API key to interact with hosted model.")
|
|
32
33
|
base_url: str | None = Field(default=None, description="Base url to the hosted model.")
|
aiq/llm/register.py
CHANGED
aiq/meta/pypi.md
CHANGED
|
@@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
|
|
|
15
15
|
limitations under the License.
|
|
16
16
|
-->
|
|
17
17
|
|
|
18
|
-

|
|
19
19
|
|
|
20
20
|
# NVIDIA Agent Intelligence Toolkit
|
|
21
21
|
|
|
@@ -23,26 +23,26 @@ AIQ toolkit is a flexible library designed to seamlessly integrate your enterpri
|
|
|
23
23
|
|
|
24
24
|
## Key Features
|
|
25
25
|
|
|
26
|
-
- [**Framework Agnostic:**](https://docs.nvidia.com/aiqtoolkit/
|
|
27
|
-
- [**Reusability:**](https://docs.nvidia.com/aiqtoolkit/
|
|
28
|
-
- [**Rapid Development:**](https://docs.nvidia.com/aiqtoolkit/
|
|
29
|
-
- [**Profiling:**](https://docs.nvidia.com/aiqtoolkit/
|
|
30
|
-
- [**Observability:**](https://docs.nvidia.com/aiqtoolkit/
|
|
31
|
-
- [**Evaluation System:**](https://docs.nvidia.com/aiqtoolkit/
|
|
32
|
-
- [**User Interface:**](https://docs.nvidia.com/aiqtoolkit/
|
|
33
|
-
- [**MCP Compatibility**](https://docs.nvidia.com/aiqtoolkit/
|
|
26
|
+
- [**Framework Agnostic:**](https://docs.nvidia.com/aiqtoolkit/1.2.0/extend/plugins.html) Works with any agentic framework, so you can use your current technology stack without replatforming.
|
|
27
|
+
- [**Reusability:**](https://docs.nvidia.com/aiqtoolkit/1.2.0/extend/sharing-components.html) Every agent, tool, or workflow can be combined and repurposed, allowing developers to leverage existing work in new scenarios.
|
|
28
|
+
- [**Rapid Development:**](https://docs.nvidia.com/aiqtoolkit/1.2.0/tutorials/index.html) Start with a pre-built agent, tool, or workflow, and customize it to your needs.
|
|
29
|
+
- [**Profiling:**](https://docs.nvidia.com/aiqtoolkit/1.2.0/workflows/profiler.html) Profile entire workflows down to the tool and agent level, track input/output tokens and timings, and identify bottlenecks.
|
|
30
|
+
- [**Observability:**](https://docs.nvidia.com/aiqtoolkit/1.2.0/workflows/observe/observe-workflow-with-phoenix.html) Monitor and debug your workflows with any OpenTelemetry-compatible observability tool, with examples using [Phoenix](https://docs.nvidia.com/aiqtoolkit/1.2.0/workflows/observe/observe-workflow-with-phoenix.html) and [W&B Weave](https://docs.nvidia.com/aiqtoolkit/1.2.0/workflows/observe/observe-workflow-with-weave.html).
|
|
31
|
+
- [**Evaluation System:**](https://docs.nvidia.com/aiqtoolkit/1.2.0/workflows/evaluate.html) Validate and maintain accuracy of agentic workflows with built-in evaluation tools.
|
|
32
|
+
- [**User Interface:**](https://docs.nvidia.com/aiqtoolkit/1.2.0/quick-start/launching-ui.html) Use the AIQ toolkit UI chat interface to interact with your agents, visualize output, and debug workflows.
|
|
33
|
+
- [**MCP Compatibility**](https://docs.nvidia.com/aiqtoolkit/1.2.0/workflows/mcp/mcp-client.html) Compatible with Model Context Protocol (MCP), allowing tools served by MCP Servers to be used as AIQ toolkit functions.
|
|
34
34
|
|
|
35
35
|
With AIQ toolkit, you can move quickly, experiment freely, and ensure reliability across all your agent-driven projects.
|
|
36
36
|
|
|
37
37
|
## Links
|
|
38
|
-
* [Documentation](https://docs.nvidia.com/aiqtoolkit/
|
|
38
|
+
* [Documentation](https://docs.nvidia.com/aiqtoolkit/1.2.0/index.html): Explore the full documentation for AIQ toolkit.
|
|
39
39
|
|
|
40
40
|
## First time user?
|
|
41
|
-
If this is your first time using AIQ toolkit, it is recommended to install the latest version from the [source repository](https://github.com/NVIDIA/
|
|
41
|
+
If this is your first time using AIQ toolkit, it is recommended to install the latest version from the [source repository](https://github.com/NVIDIA/NeMo-Agent-Toolkit?tab=readme-ov-file#quick-start) on GitHub. This package is intended for users who are familiar with AIQ toolkit applications and need to add AIQ toolkit as a dependency to their project.
|
|
42
42
|
|
|
43
43
|
## Feedback
|
|
44
44
|
|
|
45
|
-
We would love to hear from you! Please file an issue on [GitHub](https://github.com/NVIDIA/
|
|
45
|
+
We would love to hear from you! Please file an issue on [GitHub](https://github.com/NVIDIA/NeMo-Agent-Toolkit/issues) if you have any feedback or feature requests.
|
|
46
46
|
|
|
47
47
|
## Acknowledgements
|
|
48
48
|
|