aiqtoolkit 1.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of aiqtoolkit might be problematic. Click here for more details.

Files changed (316) hide show
  1. aiq/agent/__init__.py +0 -0
  2. aiq/agent/base.py +76 -0
  3. aiq/agent/dual_node.py +67 -0
  4. aiq/agent/react_agent/__init__.py +0 -0
  5. aiq/agent/react_agent/agent.py +322 -0
  6. aiq/agent/react_agent/output_parser.py +104 -0
  7. aiq/agent/react_agent/prompt.py +46 -0
  8. aiq/agent/react_agent/register.py +148 -0
  9. aiq/agent/reasoning_agent/__init__.py +0 -0
  10. aiq/agent/reasoning_agent/reasoning_agent.py +224 -0
  11. aiq/agent/register.py +23 -0
  12. aiq/agent/rewoo_agent/__init__.py +0 -0
  13. aiq/agent/rewoo_agent/agent.py +410 -0
  14. aiq/agent/rewoo_agent/prompt.py +108 -0
  15. aiq/agent/rewoo_agent/register.py +158 -0
  16. aiq/agent/tool_calling_agent/__init__.py +0 -0
  17. aiq/agent/tool_calling_agent/agent.py +123 -0
  18. aiq/agent/tool_calling_agent/register.py +105 -0
  19. aiq/builder/__init__.py +0 -0
  20. aiq/builder/builder.py +223 -0
  21. aiq/builder/component_utils.py +303 -0
  22. aiq/builder/context.py +227 -0
  23. aiq/builder/embedder.py +24 -0
  24. aiq/builder/eval_builder.py +120 -0
  25. aiq/builder/evaluator.py +29 -0
  26. aiq/builder/framework_enum.py +24 -0
  27. aiq/builder/front_end.py +73 -0
  28. aiq/builder/function.py +297 -0
  29. aiq/builder/function_base.py +376 -0
  30. aiq/builder/function_info.py +627 -0
  31. aiq/builder/intermediate_step_manager.py +176 -0
  32. aiq/builder/llm.py +25 -0
  33. aiq/builder/retriever.py +25 -0
  34. aiq/builder/user_interaction_manager.py +71 -0
  35. aiq/builder/workflow.py +143 -0
  36. aiq/builder/workflow_builder.py +757 -0
  37. aiq/cli/__init__.py +14 -0
  38. aiq/cli/cli_utils/__init__.py +0 -0
  39. aiq/cli/cli_utils/config_override.py +231 -0
  40. aiq/cli/cli_utils/validation.py +37 -0
  41. aiq/cli/commands/__init__.py +0 -0
  42. aiq/cli/commands/configure/__init__.py +0 -0
  43. aiq/cli/commands/configure/channel/__init__.py +0 -0
  44. aiq/cli/commands/configure/channel/add.py +28 -0
  45. aiq/cli/commands/configure/channel/channel.py +36 -0
  46. aiq/cli/commands/configure/channel/remove.py +30 -0
  47. aiq/cli/commands/configure/channel/update.py +30 -0
  48. aiq/cli/commands/configure/configure.py +33 -0
  49. aiq/cli/commands/evaluate.py +139 -0
  50. aiq/cli/commands/info/__init__.py +14 -0
  51. aiq/cli/commands/info/info.py +39 -0
  52. aiq/cli/commands/info/list_channels.py +32 -0
  53. aiq/cli/commands/info/list_components.py +129 -0
  54. aiq/cli/commands/info/list_mcp.py +126 -0
  55. aiq/cli/commands/registry/__init__.py +14 -0
  56. aiq/cli/commands/registry/publish.py +88 -0
  57. aiq/cli/commands/registry/pull.py +118 -0
  58. aiq/cli/commands/registry/registry.py +38 -0
  59. aiq/cli/commands/registry/remove.py +108 -0
  60. aiq/cli/commands/registry/search.py +155 -0
  61. aiq/cli/commands/start.py +250 -0
  62. aiq/cli/commands/uninstall.py +83 -0
  63. aiq/cli/commands/validate.py +47 -0
  64. aiq/cli/commands/workflow/__init__.py +14 -0
  65. aiq/cli/commands/workflow/templates/__init__.py.j2 +0 -0
  66. aiq/cli/commands/workflow/templates/config.yml.j2 +16 -0
  67. aiq/cli/commands/workflow/templates/pyproject.toml.j2 +22 -0
  68. aiq/cli/commands/workflow/templates/register.py.j2 +5 -0
  69. aiq/cli/commands/workflow/templates/workflow.py.j2 +36 -0
  70. aiq/cli/commands/workflow/workflow.py +37 -0
  71. aiq/cli/commands/workflow/workflow_commands.py +313 -0
  72. aiq/cli/entrypoint.py +133 -0
  73. aiq/cli/main.py +44 -0
  74. aiq/cli/register_workflow.py +408 -0
  75. aiq/cli/type_registry.py +879 -0
  76. aiq/data_models/__init__.py +14 -0
  77. aiq/data_models/api_server.py +588 -0
  78. aiq/data_models/common.py +143 -0
  79. aiq/data_models/component.py +46 -0
  80. aiq/data_models/component_ref.py +135 -0
  81. aiq/data_models/config.py +349 -0
  82. aiq/data_models/dataset_handler.py +122 -0
  83. aiq/data_models/discovery_metadata.py +286 -0
  84. aiq/data_models/embedder.py +26 -0
  85. aiq/data_models/evaluate.py +104 -0
  86. aiq/data_models/evaluator.py +26 -0
  87. aiq/data_models/front_end.py +26 -0
  88. aiq/data_models/function.py +30 -0
  89. aiq/data_models/function_dependencies.py +64 -0
  90. aiq/data_models/interactive.py +237 -0
  91. aiq/data_models/intermediate_step.py +269 -0
  92. aiq/data_models/invocation_node.py +38 -0
  93. aiq/data_models/llm.py +26 -0
  94. aiq/data_models/logging.py +26 -0
  95. aiq/data_models/memory.py +26 -0
  96. aiq/data_models/profiler.py +53 -0
  97. aiq/data_models/registry_handler.py +26 -0
  98. aiq/data_models/retriever.py +30 -0
  99. aiq/data_models/step_adaptor.py +64 -0
  100. aiq/data_models/streaming.py +33 -0
  101. aiq/data_models/swe_bench_model.py +54 -0
  102. aiq/data_models/telemetry_exporter.py +26 -0
  103. aiq/embedder/__init__.py +0 -0
  104. aiq/embedder/langchain_client.py +41 -0
  105. aiq/embedder/nim_embedder.py +58 -0
  106. aiq/embedder/openai_embedder.py +42 -0
  107. aiq/embedder/register.py +24 -0
  108. aiq/eval/__init__.py +14 -0
  109. aiq/eval/config.py +42 -0
  110. aiq/eval/dataset_handler/__init__.py +0 -0
  111. aiq/eval/dataset_handler/dataset_downloader.py +106 -0
  112. aiq/eval/dataset_handler/dataset_filter.py +52 -0
  113. aiq/eval/dataset_handler/dataset_handler.py +169 -0
  114. aiq/eval/evaluate.py +325 -0
  115. aiq/eval/evaluator/__init__.py +14 -0
  116. aiq/eval/evaluator/evaluator_model.py +44 -0
  117. aiq/eval/intermediate_step_adapter.py +93 -0
  118. aiq/eval/rag_evaluator/__init__.py +0 -0
  119. aiq/eval/rag_evaluator/evaluate.py +138 -0
  120. aiq/eval/rag_evaluator/register.py +138 -0
  121. aiq/eval/register.py +23 -0
  122. aiq/eval/remote_workflow.py +128 -0
  123. aiq/eval/runtime_event_subscriber.py +52 -0
  124. aiq/eval/swe_bench_evaluator/__init__.py +0 -0
  125. aiq/eval/swe_bench_evaluator/evaluate.py +215 -0
  126. aiq/eval/swe_bench_evaluator/register.py +36 -0
  127. aiq/eval/trajectory_evaluator/__init__.py +0 -0
  128. aiq/eval/trajectory_evaluator/evaluate.py +118 -0
  129. aiq/eval/trajectory_evaluator/register.py +40 -0
  130. aiq/eval/tunable_rag_evaluator/__init__.py +0 -0
  131. aiq/eval/tunable_rag_evaluator/evaluate.py +263 -0
  132. aiq/eval/tunable_rag_evaluator/register.py +50 -0
  133. aiq/eval/utils/__init__.py +0 -0
  134. aiq/eval/utils/output_uploader.py +131 -0
  135. aiq/eval/utils/tqdm_position_registry.py +40 -0
  136. aiq/front_ends/__init__.py +14 -0
  137. aiq/front_ends/console/__init__.py +14 -0
  138. aiq/front_ends/console/console_front_end_config.py +32 -0
  139. aiq/front_ends/console/console_front_end_plugin.py +107 -0
  140. aiq/front_ends/console/register.py +25 -0
  141. aiq/front_ends/cron/__init__.py +14 -0
  142. aiq/front_ends/fastapi/__init__.py +14 -0
  143. aiq/front_ends/fastapi/fastapi_front_end_config.py +150 -0
  144. aiq/front_ends/fastapi/fastapi_front_end_plugin.py +103 -0
  145. aiq/front_ends/fastapi/fastapi_front_end_plugin_worker.py +607 -0
  146. aiq/front_ends/fastapi/intermediate_steps_subscriber.py +80 -0
  147. aiq/front_ends/fastapi/job_store.py +161 -0
  148. aiq/front_ends/fastapi/main.py +70 -0
  149. aiq/front_ends/fastapi/message_handler.py +279 -0
  150. aiq/front_ends/fastapi/message_validator.py +345 -0
  151. aiq/front_ends/fastapi/register.py +25 -0
  152. aiq/front_ends/fastapi/response_helpers.py +195 -0
  153. aiq/front_ends/fastapi/step_adaptor.py +320 -0
  154. aiq/front_ends/fastapi/websocket.py +148 -0
  155. aiq/front_ends/mcp/__init__.py +14 -0
  156. aiq/front_ends/mcp/mcp_front_end_config.py +32 -0
  157. aiq/front_ends/mcp/mcp_front_end_plugin.py +93 -0
  158. aiq/front_ends/mcp/register.py +27 -0
  159. aiq/front_ends/mcp/tool_converter.py +242 -0
  160. aiq/front_ends/register.py +22 -0
  161. aiq/front_ends/simple_base/__init__.py +14 -0
  162. aiq/front_ends/simple_base/simple_front_end_plugin_base.py +52 -0
  163. aiq/llm/__init__.py +0 -0
  164. aiq/llm/nim_llm.py +45 -0
  165. aiq/llm/openai_llm.py +45 -0
  166. aiq/llm/register.py +22 -0
  167. aiq/llm/utils/__init__.py +14 -0
  168. aiq/llm/utils/env_config_value.py +94 -0
  169. aiq/llm/utils/error.py +17 -0
  170. aiq/memory/__init__.py +20 -0
  171. aiq/memory/interfaces.py +183 -0
  172. aiq/memory/models.py +112 -0
  173. aiq/meta/module_to_distro.json +3 -0
  174. aiq/meta/pypi.md +58 -0
  175. aiq/observability/__init__.py +0 -0
  176. aiq/observability/async_otel_listener.py +429 -0
  177. aiq/observability/register.py +99 -0
  178. aiq/plugins/.namespace +1 -0
  179. aiq/profiler/__init__.py +0 -0
  180. aiq/profiler/callbacks/__init__.py +0 -0
  181. aiq/profiler/callbacks/agno_callback_handler.py +295 -0
  182. aiq/profiler/callbacks/base_callback_class.py +20 -0
  183. aiq/profiler/callbacks/langchain_callback_handler.py +278 -0
  184. aiq/profiler/callbacks/llama_index_callback_handler.py +205 -0
  185. aiq/profiler/callbacks/semantic_kernel_callback_handler.py +238 -0
  186. aiq/profiler/callbacks/token_usage_base_model.py +27 -0
  187. aiq/profiler/data_frame_row.py +51 -0
  188. aiq/profiler/decorators/__init__.py +0 -0
  189. aiq/profiler/decorators/framework_wrapper.py +131 -0
  190. aiq/profiler/decorators/function_tracking.py +254 -0
  191. aiq/profiler/forecasting/__init__.py +0 -0
  192. aiq/profiler/forecasting/config.py +18 -0
  193. aiq/profiler/forecasting/model_trainer.py +75 -0
  194. aiq/profiler/forecasting/models/__init__.py +22 -0
  195. aiq/profiler/forecasting/models/forecasting_base_model.py +40 -0
  196. aiq/profiler/forecasting/models/linear_model.py +196 -0
  197. aiq/profiler/forecasting/models/random_forest_regressor.py +268 -0
  198. aiq/profiler/inference_metrics_model.py +25 -0
  199. aiq/profiler/inference_optimization/__init__.py +0 -0
  200. aiq/profiler/inference_optimization/bottleneck_analysis/__init__.py +0 -0
  201. aiq/profiler/inference_optimization/bottleneck_analysis/nested_stack_analysis.py +452 -0
  202. aiq/profiler/inference_optimization/bottleneck_analysis/simple_stack_analysis.py +258 -0
  203. aiq/profiler/inference_optimization/data_models.py +386 -0
  204. aiq/profiler/inference_optimization/experimental/__init__.py +0 -0
  205. aiq/profiler/inference_optimization/experimental/concurrency_spike_analysis.py +468 -0
  206. aiq/profiler/inference_optimization/experimental/prefix_span_analysis.py +405 -0
  207. aiq/profiler/inference_optimization/llm_metrics.py +212 -0
  208. aiq/profiler/inference_optimization/prompt_caching.py +163 -0
  209. aiq/profiler/inference_optimization/token_uniqueness.py +107 -0
  210. aiq/profiler/inference_optimization/workflow_runtimes.py +72 -0
  211. aiq/profiler/intermediate_property_adapter.py +102 -0
  212. aiq/profiler/profile_runner.py +433 -0
  213. aiq/profiler/utils.py +184 -0
  214. aiq/registry_handlers/__init__.py +0 -0
  215. aiq/registry_handlers/local/__init__.py +0 -0
  216. aiq/registry_handlers/local/local_handler.py +176 -0
  217. aiq/registry_handlers/local/register_local.py +37 -0
  218. aiq/registry_handlers/metadata_factory.py +60 -0
  219. aiq/registry_handlers/package_utils.py +198 -0
  220. aiq/registry_handlers/pypi/__init__.py +0 -0
  221. aiq/registry_handlers/pypi/pypi_handler.py +251 -0
  222. aiq/registry_handlers/pypi/register_pypi.py +40 -0
  223. aiq/registry_handlers/register.py +21 -0
  224. aiq/registry_handlers/registry_handler_base.py +157 -0
  225. aiq/registry_handlers/rest/__init__.py +0 -0
  226. aiq/registry_handlers/rest/register_rest.py +56 -0
  227. aiq/registry_handlers/rest/rest_handler.py +237 -0
  228. aiq/registry_handlers/schemas/__init__.py +0 -0
  229. aiq/registry_handlers/schemas/headers.py +42 -0
  230. aiq/registry_handlers/schemas/package.py +68 -0
  231. aiq/registry_handlers/schemas/publish.py +63 -0
  232. aiq/registry_handlers/schemas/pull.py +82 -0
  233. aiq/registry_handlers/schemas/remove.py +36 -0
  234. aiq/registry_handlers/schemas/search.py +91 -0
  235. aiq/registry_handlers/schemas/status.py +47 -0
  236. aiq/retriever/__init__.py +0 -0
  237. aiq/retriever/interface.py +37 -0
  238. aiq/retriever/milvus/__init__.py +14 -0
  239. aiq/retriever/milvus/register.py +81 -0
  240. aiq/retriever/milvus/retriever.py +228 -0
  241. aiq/retriever/models.py +74 -0
  242. aiq/retriever/nemo_retriever/__init__.py +14 -0
  243. aiq/retriever/nemo_retriever/register.py +60 -0
  244. aiq/retriever/nemo_retriever/retriever.py +190 -0
  245. aiq/retriever/register.py +22 -0
  246. aiq/runtime/__init__.py +14 -0
  247. aiq/runtime/loader.py +188 -0
  248. aiq/runtime/runner.py +176 -0
  249. aiq/runtime/session.py +140 -0
  250. aiq/runtime/user_metadata.py +131 -0
  251. aiq/settings/__init__.py +0 -0
  252. aiq/settings/global_settings.py +318 -0
  253. aiq/test/.namespace +1 -0
  254. aiq/tool/__init__.py +0 -0
  255. aiq/tool/code_execution/__init__.py +0 -0
  256. aiq/tool/code_execution/code_sandbox.py +188 -0
  257. aiq/tool/code_execution/local_sandbox/Dockerfile.sandbox +60 -0
  258. aiq/tool/code_execution/local_sandbox/__init__.py +13 -0
  259. aiq/tool/code_execution/local_sandbox/local_sandbox_server.py +83 -0
  260. aiq/tool/code_execution/local_sandbox/sandbox.requirements.txt +4 -0
  261. aiq/tool/code_execution/local_sandbox/start_local_sandbox.sh +25 -0
  262. aiq/tool/code_execution/register.py +70 -0
  263. aiq/tool/code_execution/utils.py +100 -0
  264. aiq/tool/datetime_tools.py +42 -0
  265. aiq/tool/document_search.py +141 -0
  266. aiq/tool/github_tools/__init__.py +0 -0
  267. aiq/tool/github_tools/create_github_commit.py +133 -0
  268. aiq/tool/github_tools/create_github_issue.py +87 -0
  269. aiq/tool/github_tools/create_github_pr.py +106 -0
  270. aiq/tool/github_tools/get_github_file.py +106 -0
  271. aiq/tool/github_tools/get_github_issue.py +166 -0
  272. aiq/tool/github_tools/get_github_pr.py +256 -0
  273. aiq/tool/github_tools/update_github_issue.py +100 -0
  274. aiq/tool/mcp/__init__.py +14 -0
  275. aiq/tool/mcp/mcp_client.py +220 -0
  276. aiq/tool/mcp/mcp_tool.py +95 -0
  277. aiq/tool/memory_tools/__init__.py +0 -0
  278. aiq/tool/memory_tools/add_memory_tool.py +79 -0
  279. aiq/tool/memory_tools/delete_memory_tool.py +67 -0
  280. aiq/tool/memory_tools/get_memory_tool.py +72 -0
  281. aiq/tool/nvidia_rag.py +95 -0
  282. aiq/tool/register.py +37 -0
  283. aiq/tool/retriever.py +89 -0
  284. aiq/tool/server_tools.py +63 -0
  285. aiq/utils/__init__.py +0 -0
  286. aiq/utils/data_models/__init__.py +0 -0
  287. aiq/utils/data_models/schema_validator.py +58 -0
  288. aiq/utils/debugging_utils.py +43 -0
  289. aiq/utils/exception_handlers/__init__.py +0 -0
  290. aiq/utils/exception_handlers/schemas.py +114 -0
  291. aiq/utils/io/__init__.py +0 -0
  292. aiq/utils/io/yaml_tools.py +119 -0
  293. aiq/utils/metadata_utils.py +74 -0
  294. aiq/utils/optional_imports.py +142 -0
  295. aiq/utils/producer_consumer_queue.py +178 -0
  296. aiq/utils/reactive/__init__.py +0 -0
  297. aiq/utils/reactive/base/__init__.py +0 -0
  298. aiq/utils/reactive/base/observable_base.py +65 -0
  299. aiq/utils/reactive/base/observer_base.py +55 -0
  300. aiq/utils/reactive/base/subject_base.py +79 -0
  301. aiq/utils/reactive/observable.py +59 -0
  302. aiq/utils/reactive/observer.py +76 -0
  303. aiq/utils/reactive/subject.py +131 -0
  304. aiq/utils/reactive/subscription.py +49 -0
  305. aiq/utils/settings/__init__.py +0 -0
  306. aiq/utils/settings/global_settings.py +197 -0
  307. aiq/utils/type_converter.py +232 -0
  308. aiq/utils/type_utils.py +397 -0
  309. aiq/utils/url_utils.py +27 -0
  310. aiqtoolkit-1.1.0.dist-info/METADATA +331 -0
  311. aiqtoolkit-1.1.0.dist-info/RECORD +316 -0
  312. aiqtoolkit-1.1.0.dist-info/WHEEL +5 -0
  313. aiqtoolkit-1.1.0.dist-info/entry_points.txt +17 -0
  314. aiqtoolkit-1.1.0.dist-info/licenses/LICENSE-3rd-party.txt +3686 -0
  315. aiqtoolkit-1.1.0.dist-info/licenses/LICENSE.md +201 -0
  316. aiqtoolkit-1.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,50 @@
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 pydantic import Field
17
+
18
+ from aiq.builder.builder import EvalBuilder
19
+ from aiq.builder.evaluator import EvaluatorInfo
20
+ from aiq.builder.framework_enum import LLMFrameworkEnum
21
+ from aiq.cli.register_workflow import register_evaluator
22
+ from aiq.data_models.component_ref import LLMRef
23
+ from aiq.data_models.evaluator import EvaluatorBaseConfig
24
+
25
+
26
+ class TunableRagEvaluatorConfig(EvaluatorBaseConfig, name="tunable_rag_evaluator"):
27
+ '''Configuration for tunable RAG evaluator'''
28
+ llm_name: LLMRef = Field(description="Name of the judge LLM")
29
+ judge_llm_prompt: str = Field(description="LLM prompt for the judge LLM")
30
+ default_scoring: bool = Field(description="Whether to use default scoring", default=False)
31
+ default_score_weights: dict = Field(
32
+ default={
33
+ "coverage": 0.5, "correctness": 0.3, "relevance": 0.2
34
+ },
35
+ description="Weights for the different scoring components when using default scoring")
36
+
37
+
38
+ @register_evaluator(config_type=TunableRagEvaluatorConfig)
39
+ async def register_tunable_rag_evaluator(config: TunableRagEvaluatorConfig, builder: EvalBuilder):
40
+ '''Register tunable RAG evaluator'''
41
+ from .evaluate import TunableRagEvaluator
42
+
43
+ llm = await builder.get_llm(config.llm_name, wrapper_type=LLMFrameworkEnum.LANGCHAIN)
44
+ evaluator = TunableRagEvaluator(llm,
45
+ config.judge_llm_prompt,
46
+ builder.get_max_concurrency(),
47
+ config.default_scoring,
48
+ config.default_score_weights)
49
+
50
+ yield EvaluatorInfo(config=config, evaluate_fn=evaluator.evaluate, description="Tunable RAG Evaluator")
File without changes
@@ -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 asyncio
17
+ import logging
18
+ import os
19
+ import subprocess
20
+ import sys
21
+ from pathlib import Path
22
+
23
+ import aioboto3
24
+ from botocore.exceptions import NoCredentialsError
25
+ from tqdm import tqdm
26
+
27
+ from aiq.data_models.evaluate import EvalOutputConfig
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ class OutputUploader:
33
+ """
34
+ Run custom scripts and upload evaluation outputs using the configured s3
35
+ credentials.
36
+ """
37
+
38
+ def __init__(self, output_config: EvalOutputConfig, job_id: str | None = None):
39
+ self.output_config = output_config
40
+ self._s3_client = None
41
+ self.job_id = job_id
42
+
43
+ @property
44
+ def s3_config(self):
45
+ return self.output_config.s3
46
+
47
+ async def _upload_file(self, s3_client, bucket, s3_key, local_path, pbar):
48
+ try:
49
+ await s3_client.upload_file(str(local_path), bucket, s3_key)
50
+ logger.info("Uploaded %s to s3://%s/%s", local_path, bucket, s3_key)
51
+ pbar.update(1)
52
+ except Exception as e:
53
+ logger.error("Failed to upload %s to s3://%s/%s: %s", local_path, bucket, s3_key, e)
54
+ raise
55
+
56
+ async def upload_directory(self):
57
+ """
58
+ Upload the contents of the local output directory to the remote S3 bucket in parallel.
59
+ """
60
+ if not self.output_config.s3:
61
+ logger.info("No S3 config provided; skipping upload.")
62
+ return
63
+
64
+ local_dir = self.output_config.dir
65
+ bucket = self.s3_config.bucket
66
+ remote_prefix = self.output_config.remote_dir or ""
67
+ if self.job_id:
68
+ remote_prefix = str(Path(remote_prefix) / f"jobs/{self.job_id}")
69
+
70
+ file_entries = []
71
+ for root, _, files in os.walk(local_dir):
72
+ for file in files:
73
+ local_path = Path(root) / file
74
+ relative_path = local_path.relative_to(local_dir)
75
+ s3_path = Path(remote_prefix) / relative_path
76
+ s3_key = str(s3_path).replace("\\", "/") # Normalize for S3
77
+ file_entries.append((local_path, s3_key))
78
+
79
+ session = aioboto3.Session()
80
+ try:
81
+ async with session.client(
82
+ "s3",
83
+ endpoint_url=self.s3_config.endpoint_url,
84
+ aws_access_key_id=self.s3_config.access_key,
85
+ aws_secret_access_key=self.s3_config.secret_key,
86
+ ) as s3_client:
87
+ with tqdm(total=len(file_entries), desc="Uploading files to S3") as pbar:
88
+ upload_tasks = [
89
+ self._upload_file(s3_client, bucket, s3_key, local_path, pbar)
90
+ for local_path, s3_key in file_entries
91
+ ]
92
+ await asyncio.gather(*upload_tasks)
93
+
94
+ except NoCredentialsError as e:
95
+ logger.error("AWS credentials not available: %s", e)
96
+ raise
97
+ except Exception as e:
98
+ logger.error("Failed to upload files to S3: %s", e)
99
+ raise
100
+
101
+ def run_custom_scripts(self):
102
+ """
103
+ Run custom Python scripts defined in the EvalOutputConfig.
104
+ Each script is run with its kwargs passed as command-line arguments.
105
+ The output directory is passed as the first argument.
106
+ """
107
+ for _, script_config in self.output_config.custom_scripts.items():
108
+ script_path = script_config.script
109
+ if not script_path.exists():
110
+ logger.error("Custom script %s does not exist.", script_path)
111
+ continue
112
+
113
+ # use python interpreter
114
+ args = [sys.executable, str(script_path)]
115
+ # add output directory as first keyword argument
116
+ args.append("--output_dir")
117
+ args.append(str(self.output_config.dir))
118
+ if script_config.kwargs:
119
+ for key, value in script_config.kwargs.items():
120
+ args.append(f"--{key}")
121
+ args.append(str(value))
122
+
123
+ display_args = " ".join(f'"{arg}"' if " " in arg else arg for arg in args[1:])
124
+
125
+ try:
126
+ logger.info("Running custom script: %s %s", script_path, display_args)
127
+ subprocess.run(args, check=True, text=True)
128
+ logger.info("Custom script %s completed successfully.", script_path)
129
+ except subprocess.CalledProcessError as e:
130
+ logger.error("Custom script %s failed with return code %s", script_path, e.returncode)
131
+ raise
@@ -0,0 +1,40 @@
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
+
17
+ class TqdmPositionRegistry:
18
+ """
19
+ A simple registry for tqdm positions.
20
+ """
21
+ _positions = set()
22
+ _max_positions = 100
23
+
24
+ @classmethod
25
+ def claim(cls) -> int:
26
+ """
27
+ Claim a tqdm position in the range of 0-99.
28
+ """
29
+ for i in range(cls._max_positions):
30
+ if i not in cls._positions:
31
+ cls._positions.add(i)
32
+ return i
33
+ raise RuntimeError("No available tqdm positions.")
34
+
35
+ @classmethod
36
+ def release(cls, pos: int):
37
+ """
38
+ Release a tqdm position.
39
+ """
40
+ cls._positions.discard(pos)
@@ -0,0 +1,14 @@
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.
@@ -0,0 +1,14 @@
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.
@@ -0,0 +1,32 @@
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 pathlib import Path
17
+
18
+ from pydantic import Field
19
+
20
+ from aiq.data_models.front_end import FrontEndBaseConfig
21
+
22
+
23
+ class ConsoleFrontEndConfig(FrontEndBaseConfig, name="console"):
24
+ """
25
+ A front end that allows an AIQ Toolkit workflow to be run from the console.
26
+ """
27
+
28
+ input_query: list[str] | None = Field(default=None,
29
+ alias="input",
30
+ description="A single input to submit the the workflow.")
31
+ input_file: Path | None = Field(default=None,
32
+ description="Path to a json file of inputs to submit to the workflow.")
@@ -0,0 +1,107 @@
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 asyncio
17
+ import logging
18
+ from io import StringIO
19
+
20
+ import click
21
+ from colorama import Fore
22
+
23
+ from aiq.builder.workflow_builder import WorkflowBuilder
24
+ from aiq.data_models.interactive import HumanPromptModelType
25
+ from aiq.data_models.interactive import HumanResponse
26
+ from aiq.data_models.interactive import HumanResponseText
27
+ from aiq.data_models.interactive import InteractionPrompt
28
+ from aiq.front_ends.console.console_front_end_config import ConsoleFrontEndConfig
29
+ from aiq.front_ends.simple_base.simple_front_end_plugin_base import SimpleFrontEndPluginBase
30
+ from aiq.runtime.session import AIQSessionManager
31
+
32
+ logger = logging.getLogger(__name__)
33
+
34
+
35
+ async def prompt_for_input_cli(question: InteractionPrompt) -> HumanResponse:
36
+ """
37
+ A simple CLI-based callback.
38
+ Takes question as str, returns the typed line as str.
39
+ """
40
+
41
+ if question.content.input_type == HumanPromptModelType.TEXT:
42
+ user_response = click.prompt(text=question.content.text)
43
+
44
+ return HumanResponseText(text=user_response)
45
+
46
+ raise ValueError("Unsupported human propmt input type. The run command only supports the 'HumanPromptText' "
47
+ "input type. Please use the 'serve' command to ensure full support for all input types.")
48
+
49
+
50
+ class ConsoleFrontEndPlugin(SimpleFrontEndPluginBase[ConsoleFrontEndConfig]):
51
+
52
+ async def pre_run(self):
53
+
54
+ if (not self.front_end_config.input_query and not self.front_end_config.input_file):
55
+ raise click.UsageError("Must specify either --input_query or --input_file")
56
+
57
+ async def run(self):
58
+
59
+ # Must yield the workflow function otherwise it cleans up
60
+ async with WorkflowBuilder.from_config(config=self.full_config) as builder:
61
+
62
+ session_manager: AIQSessionManager = None
63
+
64
+ if logger.isEnabledFor(logging.INFO):
65
+ stream = StringIO()
66
+
67
+ self.full_config.print_summary(stream=stream)
68
+
69
+ click.echo(stream.getvalue())
70
+
71
+ workflow = builder.build()
72
+ session_manager = AIQSessionManager(workflow)
73
+
74
+ await self.run_workflow(session_manager)
75
+
76
+ async def run_workflow(self, session_manager: AIQSessionManager = None):
77
+
78
+ runner_outputs = None
79
+
80
+ if (self.front_end_config.input_query):
81
+
82
+ async def run_single_query(query):
83
+
84
+ async with session_manager.session(user_input_callback=prompt_for_input_cli) as session:
85
+ async with session.run(query) as runner:
86
+ base_output = await runner.result(to_type=str)
87
+
88
+ return base_output
89
+
90
+ # Convert to a list
91
+ input_list = list(self.front_end_config.input_query)
92
+ logger.debug("Processing input: %s", self.front_end_config.input_query)
93
+
94
+ runner_outputs = await asyncio.gather(*[run_single_query(query) for query in input_list])
95
+
96
+ elif (self.front_end_config.input_file):
97
+
98
+ # Run the workflow
99
+ with open(self.front_end_config.input_file, "r", encoding="utf-8") as f:
100
+
101
+ async with session_manager.workflow.run(f) as runner:
102
+ runner_outputs = await runner.result(to_type=str)
103
+ else:
104
+ assert False, "Should not reach here. Should have been caught by pre_run"
105
+
106
+ # Print result
107
+ logger.info(f"\n{'-' * 50}\n{Fore.GREEN}Workflow Result:\n%s{Fore.RESET}\n{'-' * 50}", runner_outputs)
@@ -0,0 +1,25 @@
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 aiq.cli.register_workflow import register_front_end
17
+ from aiq.data_models.config import AIQConfig
18
+ from aiq.front_ends.console.console_front_end_config import ConsoleFrontEndConfig
19
+
20
+
21
+ @register_front_end(config_type=ConsoleFrontEndConfig)
22
+ async def register_fastapi_front_end(config: ConsoleFrontEndConfig, full_config: AIQConfig):
23
+ from aiq.front_ends.console.console_front_end_plugin import ConsoleFrontEndPlugin
24
+
25
+ yield ConsoleFrontEndPlugin(full_config=full_config)
@@ -0,0 +1,14 @@
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.
@@ -0,0 +1,14 @@
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.
@@ -0,0 +1,150 @@
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
+ import typing
18
+ from datetime import datetime
19
+
20
+ from pydantic import BaseModel
21
+ from pydantic import Field
22
+
23
+ from aiq.data_models.front_end import FrontEndBaseConfig
24
+ from aiq.data_models.step_adaptor import StepAdaptorConfig
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ class AIQEvaluateRequest(BaseModel):
30
+ """Request model for the evaluate endpoint."""
31
+ config_file: str = Field(description="Path to the configuration file for evaluation")
32
+ job_id: str | None = Field(default=None, description="Unique identifier for the evaluation job")
33
+ reps: int = Field(default=1, description="Number of repetitions for the evaluation, defaults to 1")
34
+ expiry_seconds: int = Field(
35
+ default=3600,
36
+ description="Optional time (in seconds) before the job expires. Clamped between 600 (10 min) and 86400 (24h).")
37
+
38
+
39
+ class AIQEvaluateResponse(BaseModel):
40
+ """Response model for the evaluate endpoint."""
41
+ job_id: str = Field(description="Unique identifier for the evaluation job")
42
+ status: str = Field(description="Current status of the evaluation job")
43
+
44
+
45
+ class AIQEvaluateStatusResponse(BaseModel):
46
+ """Response model for the evaluate status endpoint."""
47
+ job_id: str = Field(description="Unique identifier for the evaluation job")
48
+ status: str = Field(description="Current status of the evaluation job")
49
+ config_file: str = Field(description="Path to the configuration file used for evaluation")
50
+ error: str | None = Field(default=None, description="Error message if the job failed")
51
+ output_path: str | None = Field(default=None,
52
+ description="Path to the output file if the job completed successfully")
53
+ created_at: datetime = Field(description="Timestamp when the job was created")
54
+ updated_at: datetime = Field(description="Timestamp when the job was last updated")
55
+ expires_at: datetime | None = Field(default=None, description="Timestamp when the job will expire")
56
+
57
+
58
+ class FastApiFrontEndConfig(FrontEndBaseConfig, name="fastapi"):
59
+ """
60
+ A FastAPI based front end that allows an AIQ Toolkit workflow to be served as a microservice.
61
+ """
62
+
63
+ class EndpointBase(BaseModel):
64
+
65
+ method: typing.Literal["GET", "POST", "PUT", "DELETE"]
66
+ description: str
67
+ path: str | None = Field(
68
+ default=None,
69
+ description=("Path for the default workflow. If None, no workflow endpoint is created."),
70
+ )
71
+ websocket_path: str | None = Field(
72
+ default=None,
73
+ description=("Path for the websocket. If None, no websocket is created."),
74
+ )
75
+ openai_api_path: str | None = Field(
76
+ default=None,
77
+ description=("Path for the default workflow using the OpenAI API Specification. "
78
+ "If None, no workflow endpoint with the OpenAI API Specification is created."),
79
+ )
80
+
81
+ class Endpoint(EndpointBase):
82
+ function_name: str = Field(description="The name of the function to call for this endpoint")
83
+
84
+ class CrossOriginResourceSharing(BaseModel):
85
+ allow_origins: list[str] | None = Field(
86
+ default=None, description=" A list of origins that should be permitted to make cross-origin requests.")
87
+ allow_origin_regex: str | None = Field(
88
+ default=None,
89
+ description="A permitted regex string to match against origins to make cross-origin requests",
90
+ )
91
+ allow_methods: list[str] | None = Field(
92
+ default_factory=lambda: ['GET'],
93
+ description="A list of HTTP methods that should be allowed for cross-origin requests.")
94
+ allow_headers: list[str] | None = Field(
95
+ default_factory=list,
96
+ description="A list of HTTP request headers that should be supported for cross-origin requests.")
97
+ allow_credentials: bool | None = Field(
98
+ default=False,
99
+ description="Indicate that cookies should be supported for cross-origin requests.",
100
+ )
101
+ expose_headers: list[str] | None = Field(
102
+ default_factory=list,
103
+ description="Indicate any response headers that should be made accessible to the browser.",
104
+ )
105
+ max_age: int | None = Field(
106
+ default=600,
107
+ description="Sets a maximum time in seconds for browsers to cache CORS responses.",
108
+ )
109
+
110
+ root_path: str = Field(default="", description="The root path for the API")
111
+ host: str = Field(default="localhost", description="Host to bind the server to")
112
+ port: int = Field(default=8000, description="Port to bind the server to", ge=0, le=65535)
113
+ reload: bool = Field(default=False, description="Enable auto-reload for development")
114
+ workers: int = Field(default=1, description="Number of workers to run", ge=1)
115
+ step_adaptor: StepAdaptorConfig = StepAdaptorConfig()
116
+
117
+ workflow: typing.Annotated[EndpointBase, Field(description="Endpoint for the default workflow.")] = EndpointBase(
118
+ method="POST",
119
+ path="/generate",
120
+ websocket_path="/websocket",
121
+ openai_api_path="/chat",
122
+ description="Executes the default AIQ Toolkit workflow from the loaded configuration ",
123
+ )
124
+
125
+ evaluate: typing.Annotated[EndpointBase, Field(description="Endpoint for evaluating workflows.")] = EndpointBase(
126
+ method="POST",
127
+ path="/evaluate",
128
+ description="Evaluates the performance and accuracy of the workflow on a dataset",
129
+ )
130
+
131
+ endpoints: list[Endpoint] = Field(
132
+ default_factory=list,
133
+ description=(
134
+ "Additional endpoints to add to the FastAPI app which run functions within the AIQ Toolkit configuration. "
135
+ "Each endpoint must have a unique path."))
136
+
137
+ cors: CrossOriginResourceSharing = Field(
138
+ default_factory=CrossOriginResourceSharing,
139
+ description="Cross origin resource sharing configuration for the FastAPI app")
140
+
141
+ use_gunicorn: bool = Field(
142
+ default=False,
143
+ description="Use Gunicorn to run the FastAPI app",
144
+ )
145
+ runner_class: str | None = Field(
146
+ default=None,
147
+ description=("The AIQ Toolkit runner class to use when launching the FastAPI app from multiple processes. "
148
+ "Each runner is responsible for loading and running the AIQ Toolkit workflow. "
149
+ "Note: This is different from the worker class used by Gunicorn."),
150
+ )