aiqtoolkit 1.1.0a20250429__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 (309) 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 +198 -0
  23. aiq/builder/embedder.py +24 -0
  24. aiq/builder/eval_builder.py +116 -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 +372 -0
  30. aiq/builder/function_info.py +627 -0
  31. aiq/builder/intermediate_step_manager.py +125 -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 +134 -0
  36. aiq/builder/workflow_builder.py +733 -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 +233 -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 +34 -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 +37 -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/registry/__init__.py +14 -0
  55. aiq/cli/commands/registry/publish.py +88 -0
  56. aiq/cli/commands/registry/pull.py +118 -0
  57. aiq/cli/commands/registry/registry.py +36 -0
  58. aiq/cli/commands/registry/remove.py +108 -0
  59. aiq/cli/commands/registry/search.py +155 -0
  60. aiq/cli/commands/start.py +250 -0
  61. aiq/cli/commands/uninstall.py +83 -0
  62. aiq/cli/commands/validate.py +47 -0
  63. aiq/cli/commands/workflow/__init__.py +14 -0
  64. aiq/cli/commands/workflow/templates/__init__.py.j2 +0 -0
  65. aiq/cli/commands/workflow/templates/config.yml.j2 +16 -0
  66. aiq/cli/commands/workflow/templates/pyproject.toml.j2 +22 -0
  67. aiq/cli/commands/workflow/templates/register.py.j2 +5 -0
  68. aiq/cli/commands/workflow/templates/workflow.py.j2 +36 -0
  69. aiq/cli/commands/workflow/workflow.py +37 -0
  70. aiq/cli/commands/workflow/workflow_commands.py +307 -0
  71. aiq/cli/entrypoint.py +133 -0
  72. aiq/cli/main.py +44 -0
  73. aiq/cli/register_workflow.py +408 -0
  74. aiq/cli/type_registry.py +869 -0
  75. aiq/data_models/__init__.py +14 -0
  76. aiq/data_models/api_server.py +550 -0
  77. aiq/data_models/common.py +143 -0
  78. aiq/data_models/component.py +46 -0
  79. aiq/data_models/component_ref.py +135 -0
  80. aiq/data_models/config.py +349 -0
  81. aiq/data_models/dataset_handler.py +122 -0
  82. aiq/data_models/discovery_metadata.py +269 -0
  83. aiq/data_models/embedder.py +26 -0
  84. aiq/data_models/evaluate.py +101 -0
  85. aiq/data_models/evaluator.py +26 -0
  86. aiq/data_models/front_end.py +26 -0
  87. aiq/data_models/function.py +30 -0
  88. aiq/data_models/function_dependencies.py +64 -0
  89. aiq/data_models/interactive.py +237 -0
  90. aiq/data_models/intermediate_step.py +269 -0
  91. aiq/data_models/invocation_node.py +38 -0
  92. aiq/data_models/llm.py +26 -0
  93. aiq/data_models/logging.py +26 -0
  94. aiq/data_models/memory.py +26 -0
  95. aiq/data_models/profiler.py +53 -0
  96. aiq/data_models/registry_handler.py +26 -0
  97. aiq/data_models/retriever.py +30 -0
  98. aiq/data_models/step_adaptor.py +64 -0
  99. aiq/data_models/streaming.py +33 -0
  100. aiq/data_models/swe_bench_model.py +54 -0
  101. aiq/data_models/telemetry_exporter.py +26 -0
  102. aiq/embedder/__init__.py +0 -0
  103. aiq/embedder/langchain_client.py +41 -0
  104. aiq/embedder/nim_embedder.py +58 -0
  105. aiq/embedder/openai_embedder.py +42 -0
  106. aiq/embedder/register.py +24 -0
  107. aiq/eval/__init__.py +14 -0
  108. aiq/eval/config.py +42 -0
  109. aiq/eval/dataset_handler/__init__.py +0 -0
  110. aiq/eval/dataset_handler/dataset_downloader.py +106 -0
  111. aiq/eval/dataset_handler/dataset_filter.py +52 -0
  112. aiq/eval/dataset_handler/dataset_handler.py +164 -0
  113. aiq/eval/evaluate.py +322 -0
  114. aiq/eval/evaluator/__init__.py +14 -0
  115. aiq/eval/evaluator/evaluator_model.py +44 -0
  116. aiq/eval/intermediate_step_adapter.py +93 -0
  117. aiq/eval/rag_evaluator/__init__.py +0 -0
  118. aiq/eval/rag_evaluator/evaluate.py +138 -0
  119. aiq/eval/rag_evaluator/register.py +138 -0
  120. aiq/eval/register.py +22 -0
  121. aiq/eval/remote_workflow.py +128 -0
  122. aiq/eval/runtime_event_subscriber.py +52 -0
  123. aiq/eval/swe_bench_evaluator/__init__.py +0 -0
  124. aiq/eval/swe_bench_evaluator/evaluate.py +215 -0
  125. aiq/eval/swe_bench_evaluator/register.py +36 -0
  126. aiq/eval/trajectory_evaluator/__init__.py +0 -0
  127. aiq/eval/trajectory_evaluator/evaluate.py +118 -0
  128. aiq/eval/trajectory_evaluator/register.py +40 -0
  129. aiq/eval/utils/__init__.py +0 -0
  130. aiq/eval/utils/output_uploader.py +131 -0
  131. aiq/eval/utils/tqdm_position_registry.py +40 -0
  132. aiq/front_ends/__init__.py +14 -0
  133. aiq/front_ends/console/__init__.py +14 -0
  134. aiq/front_ends/console/console_front_end_config.py +32 -0
  135. aiq/front_ends/console/console_front_end_plugin.py +107 -0
  136. aiq/front_ends/console/register.py +25 -0
  137. aiq/front_ends/cron/__init__.py +14 -0
  138. aiq/front_ends/fastapi/__init__.py +14 -0
  139. aiq/front_ends/fastapi/fastapi_front_end_config.py +150 -0
  140. aiq/front_ends/fastapi/fastapi_front_end_plugin.py +103 -0
  141. aiq/front_ends/fastapi/fastapi_front_end_plugin_worker.py +574 -0
  142. aiq/front_ends/fastapi/intermediate_steps_subscriber.py +80 -0
  143. aiq/front_ends/fastapi/job_store.py +161 -0
  144. aiq/front_ends/fastapi/main.py +70 -0
  145. aiq/front_ends/fastapi/message_handler.py +279 -0
  146. aiq/front_ends/fastapi/message_validator.py +345 -0
  147. aiq/front_ends/fastapi/register.py +25 -0
  148. aiq/front_ends/fastapi/response_helpers.py +181 -0
  149. aiq/front_ends/fastapi/step_adaptor.py +315 -0
  150. aiq/front_ends/fastapi/websocket.py +148 -0
  151. aiq/front_ends/mcp/__init__.py +14 -0
  152. aiq/front_ends/mcp/mcp_front_end_config.py +32 -0
  153. aiq/front_ends/mcp/mcp_front_end_plugin.py +93 -0
  154. aiq/front_ends/mcp/register.py +27 -0
  155. aiq/front_ends/mcp/tool_converter.py +242 -0
  156. aiq/front_ends/register.py +22 -0
  157. aiq/front_ends/simple_base/__init__.py +14 -0
  158. aiq/front_ends/simple_base/simple_front_end_plugin_base.py +52 -0
  159. aiq/llm/__init__.py +0 -0
  160. aiq/llm/nim_llm.py +45 -0
  161. aiq/llm/openai_llm.py +45 -0
  162. aiq/llm/register.py +22 -0
  163. aiq/llm/utils/__init__.py +14 -0
  164. aiq/llm/utils/env_config_value.py +94 -0
  165. aiq/llm/utils/error.py +17 -0
  166. aiq/memory/__init__.py +20 -0
  167. aiq/memory/interfaces.py +183 -0
  168. aiq/memory/models.py +102 -0
  169. aiq/meta/module_to_distro.json +3 -0
  170. aiq/meta/pypi.md +59 -0
  171. aiq/observability/__init__.py +0 -0
  172. aiq/observability/async_otel_listener.py +270 -0
  173. aiq/observability/register.py +97 -0
  174. aiq/plugins/.namespace +1 -0
  175. aiq/profiler/__init__.py +0 -0
  176. aiq/profiler/callbacks/__init__.py +0 -0
  177. aiq/profiler/callbacks/agno_callback_handler.py +295 -0
  178. aiq/profiler/callbacks/base_callback_class.py +20 -0
  179. aiq/profiler/callbacks/langchain_callback_handler.py +278 -0
  180. aiq/profiler/callbacks/llama_index_callback_handler.py +205 -0
  181. aiq/profiler/callbacks/semantic_kernel_callback_handler.py +238 -0
  182. aiq/profiler/callbacks/token_usage_base_model.py +27 -0
  183. aiq/profiler/data_frame_row.py +51 -0
  184. aiq/profiler/decorators/__init__.py +0 -0
  185. aiq/profiler/decorators/framework_wrapper.py +131 -0
  186. aiq/profiler/decorators/function_tracking.py +254 -0
  187. aiq/profiler/forecasting/__init__.py +0 -0
  188. aiq/profiler/forecasting/config.py +18 -0
  189. aiq/profiler/forecasting/model_trainer.py +75 -0
  190. aiq/profiler/forecasting/models/__init__.py +22 -0
  191. aiq/profiler/forecasting/models/forecasting_base_model.py +40 -0
  192. aiq/profiler/forecasting/models/linear_model.py +196 -0
  193. aiq/profiler/forecasting/models/random_forest_regressor.py +268 -0
  194. aiq/profiler/inference_metrics_model.py +25 -0
  195. aiq/profiler/inference_optimization/__init__.py +0 -0
  196. aiq/profiler/inference_optimization/bottleneck_analysis/__init__.py +0 -0
  197. aiq/profiler/inference_optimization/bottleneck_analysis/nested_stack_analysis.py +452 -0
  198. aiq/profiler/inference_optimization/bottleneck_analysis/simple_stack_analysis.py +258 -0
  199. aiq/profiler/inference_optimization/data_models.py +386 -0
  200. aiq/profiler/inference_optimization/experimental/__init__.py +0 -0
  201. aiq/profiler/inference_optimization/experimental/concurrency_spike_analysis.py +468 -0
  202. aiq/profiler/inference_optimization/experimental/prefix_span_analysis.py +405 -0
  203. aiq/profiler/inference_optimization/llm_metrics.py +212 -0
  204. aiq/profiler/inference_optimization/prompt_caching.py +163 -0
  205. aiq/profiler/inference_optimization/token_uniqueness.py +107 -0
  206. aiq/profiler/inference_optimization/workflow_runtimes.py +72 -0
  207. aiq/profiler/intermediate_property_adapter.py +102 -0
  208. aiq/profiler/profile_runner.py +433 -0
  209. aiq/profiler/utils.py +184 -0
  210. aiq/registry_handlers/__init__.py +0 -0
  211. aiq/registry_handlers/local/__init__.py +0 -0
  212. aiq/registry_handlers/local/local_handler.py +176 -0
  213. aiq/registry_handlers/local/register_local.py +37 -0
  214. aiq/registry_handlers/metadata_factory.py +60 -0
  215. aiq/registry_handlers/package_utils.py +198 -0
  216. aiq/registry_handlers/pypi/__init__.py +0 -0
  217. aiq/registry_handlers/pypi/pypi_handler.py +251 -0
  218. aiq/registry_handlers/pypi/register_pypi.py +40 -0
  219. aiq/registry_handlers/register.py +21 -0
  220. aiq/registry_handlers/registry_handler_base.py +157 -0
  221. aiq/registry_handlers/rest/__init__.py +0 -0
  222. aiq/registry_handlers/rest/register_rest.py +56 -0
  223. aiq/registry_handlers/rest/rest_handler.py +237 -0
  224. aiq/registry_handlers/schemas/__init__.py +0 -0
  225. aiq/registry_handlers/schemas/headers.py +42 -0
  226. aiq/registry_handlers/schemas/package.py +68 -0
  227. aiq/registry_handlers/schemas/publish.py +63 -0
  228. aiq/registry_handlers/schemas/pull.py +81 -0
  229. aiq/registry_handlers/schemas/remove.py +36 -0
  230. aiq/registry_handlers/schemas/search.py +91 -0
  231. aiq/registry_handlers/schemas/status.py +47 -0
  232. aiq/retriever/__init__.py +0 -0
  233. aiq/retriever/interface.py +37 -0
  234. aiq/retriever/milvus/__init__.py +14 -0
  235. aiq/retriever/milvus/register.py +81 -0
  236. aiq/retriever/milvus/retriever.py +228 -0
  237. aiq/retriever/models.py +74 -0
  238. aiq/retriever/nemo_retriever/__init__.py +14 -0
  239. aiq/retriever/nemo_retriever/register.py +60 -0
  240. aiq/retriever/nemo_retriever/retriever.py +190 -0
  241. aiq/retriever/register.py +22 -0
  242. aiq/runtime/__init__.py +14 -0
  243. aiq/runtime/loader.py +188 -0
  244. aiq/runtime/runner.py +176 -0
  245. aiq/runtime/session.py +116 -0
  246. aiq/settings/__init__.py +0 -0
  247. aiq/settings/global_settings.py +318 -0
  248. aiq/test/.namespace +1 -0
  249. aiq/tool/__init__.py +0 -0
  250. aiq/tool/code_execution/__init__.py +0 -0
  251. aiq/tool/code_execution/code_sandbox.py +188 -0
  252. aiq/tool/code_execution/local_sandbox/Dockerfile.sandbox +60 -0
  253. aiq/tool/code_execution/local_sandbox/__init__.py +13 -0
  254. aiq/tool/code_execution/local_sandbox/local_sandbox_server.py +79 -0
  255. aiq/tool/code_execution/local_sandbox/sandbox.requirements.txt +4 -0
  256. aiq/tool/code_execution/local_sandbox/start_local_sandbox.sh +25 -0
  257. aiq/tool/code_execution/register.py +70 -0
  258. aiq/tool/code_execution/utils.py +100 -0
  259. aiq/tool/datetime_tools.py +42 -0
  260. aiq/tool/document_search.py +141 -0
  261. aiq/tool/github_tools/__init__.py +0 -0
  262. aiq/tool/github_tools/create_github_commit.py +133 -0
  263. aiq/tool/github_tools/create_github_issue.py +87 -0
  264. aiq/tool/github_tools/create_github_pr.py +106 -0
  265. aiq/tool/github_tools/get_github_file.py +106 -0
  266. aiq/tool/github_tools/get_github_issue.py +166 -0
  267. aiq/tool/github_tools/get_github_pr.py +256 -0
  268. aiq/tool/github_tools/update_github_issue.py +100 -0
  269. aiq/tool/mcp/__init__.py +14 -0
  270. aiq/tool/mcp/mcp_client.py +220 -0
  271. aiq/tool/mcp/mcp_tool.py +75 -0
  272. aiq/tool/memory_tools/__init__.py +0 -0
  273. aiq/tool/memory_tools/add_memory_tool.py +67 -0
  274. aiq/tool/memory_tools/delete_memory_tool.py +67 -0
  275. aiq/tool/memory_tools/get_memory_tool.py +72 -0
  276. aiq/tool/nvidia_rag.py +95 -0
  277. aiq/tool/register.py +36 -0
  278. aiq/tool/retriever.py +89 -0
  279. aiq/utils/__init__.py +0 -0
  280. aiq/utils/data_models/__init__.py +0 -0
  281. aiq/utils/data_models/schema_validator.py +58 -0
  282. aiq/utils/debugging_utils.py +43 -0
  283. aiq/utils/exception_handlers/__init__.py +0 -0
  284. aiq/utils/exception_handlers/schemas.py +114 -0
  285. aiq/utils/io/__init__.py +0 -0
  286. aiq/utils/io/yaml_tools.py +50 -0
  287. aiq/utils/metadata_utils.py +74 -0
  288. aiq/utils/producer_consumer_queue.py +178 -0
  289. aiq/utils/reactive/__init__.py +0 -0
  290. aiq/utils/reactive/base/__init__.py +0 -0
  291. aiq/utils/reactive/base/observable_base.py +65 -0
  292. aiq/utils/reactive/base/observer_base.py +55 -0
  293. aiq/utils/reactive/base/subject_base.py +79 -0
  294. aiq/utils/reactive/observable.py +59 -0
  295. aiq/utils/reactive/observer.py +76 -0
  296. aiq/utils/reactive/subject.py +131 -0
  297. aiq/utils/reactive/subscription.py +49 -0
  298. aiq/utils/settings/__init__.py +0 -0
  299. aiq/utils/settings/global_settings.py +197 -0
  300. aiq/utils/type_converter.py +232 -0
  301. aiq/utils/type_utils.py +397 -0
  302. aiq/utils/url_utils.py +27 -0
  303. aiqtoolkit-1.1.0a20250429.dist-info/METADATA +326 -0
  304. aiqtoolkit-1.1.0a20250429.dist-info/RECORD +309 -0
  305. aiqtoolkit-1.1.0a20250429.dist-info/WHEEL +5 -0
  306. aiqtoolkit-1.1.0a20250429.dist-info/entry_points.txt +17 -0
  307. aiqtoolkit-1.1.0a20250429.dist-info/licenses/LICENSE-3rd-party.txt +3686 -0
  308. aiqtoolkit-1.1.0a20250429.dist-info/licenses/LICENSE.md +201 -0
  309. aiqtoolkit-1.1.0a20250429.dist-info/top_level.txt +1 -0
@@ -0,0 +1,123 @@
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
+ # pylint: disable=R0917
17
+ import logging
18
+
19
+ from langchain_core.callbacks.base import AsyncCallbackHandler
20
+ from langchain_core.language_models import BaseChatModel
21
+ from langchain_core.messages.base import BaseMessage
22
+ from langchain_core.runnables import RunnableConfig
23
+ from langchain_core.tools import BaseTool
24
+ from langgraph.prebuilt import ToolNode
25
+ from pydantic import BaseModel
26
+ from pydantic import Field
27
+
28
+ from aiq.agent.base import AGENT_LOG_PREFIX
29
+ from aiq.agent.base import AGENT_RESPONSE_LOG_MESSAGE
30
+ from aiq.agent.base import TOOL_RESPONSE_LOG_MESSAGE
31
+ from aiq.agent.base import AgentDecision
32
+ from aiq.agent.dual_node import DualNodeAgent
33
+
34
+ logger = logging.getLogger(__name__)
35
+
36
+
37
+ class ToolCallAgentGraphState(BaseModel):
38
+ """State schema for the Tool Calling Agent Graph"""
39
+ messages: list[BaseMessage] = Field(default_factory=list) # input and output of the Agent
40
+
41
+
42
+ class ToolCallAgentGraph(DualNodeAgent):
43
+ """Configurable LangGraph Tool Calling Agent. A Tool Calling Agent requires an LLM which supports tool calling.
44
+ A tool Calling Agent utilizes the tool input parameters to select the optimal tool. Supports handling tool errors.
45
+ Argument "detailed_logs" toggles logging of inputs, outputs, and intermediate steps."""
46
+
47
+ def __init__(self,
48
+ llm: BaseChatModel,
49
+ tools: list[BaseTool],
50
+ callbacks: list[AsyncCallbackHandler] = None,
51
+ detailed_logs: bool = False,
52
+ handle_tool_errors: bool = True):
53
+ super().__init__(llm=llm, tools=tools, callbacks=callbacks, detailed_logs=detailed_logs)
54
+ self.tool_caller = ToolNode(tools, handle_tool_errors=handle_tool_errors)
55
+ logger.debug("%s Initialized Tool Calling Agent Graph", AGENT_LOG_PREFIX)
56
+
57
+ async def agent_node(self, state: ToolCallAgentGraphState):
58
+ try:
59
+ logger.debug('%s Starting the Tool Calling Agent Node', AGENT_LOG_PREFIX)
60
+ if len(state.messages) == 0:
61
+ raise RuntimeError('No input received in state: "messages"')
62
+ response = await self.llm.ainvoke(state.messages, config=RunnableConfig(callbacks=self.callbacks))
63
+ if self.detailed_logs:
64
+ agent_input = "\n".join(str(message.content) for message in state.messages)
65
+ logger.info(AGENT_RESPONSE_LOG_MESSAGE, agent_input, response)
66
+
67
+ state.messages += [response]
68
+ return state
69
+ except Exception as ex:
70
+ logger.exception("%s Failed to call agent_node: %s", AGENT_LOG_PREFIX, ex, exc_info=True)
71
+ raise ex
72
+
73
+ async def conditional_edge(self, state: ToolCallAgentGraphState):
74
+ try:
75
+ logger.debug("%s Starting the Tool Calling Conditional Edge", AGENT_LOG_PREFIX)
76
+ last_message = state.messages[-1]
77
+ if last_message.tool_calls:
78
+ # the agent wants to call a tool
79
+ logger.debug('%s Agent is calling a tool', AGENT_LOG_PREFIX)
80
+ return AgentDecision.TOOL
81
+ if self.detailed_logs:
82
+ logger.debug("%s Final answer:\n%s", AGENT_LOG_PREFIX, state.messages[-1].content)
83
+ return AgentDecision.END
84
+ except Exception as ex:
85
+ logger.exception("%s Failed to determine whether agent is calling a tool: %s",
86
+ AGENT_LOG_PREFIX,
87
+ ex,
88
+ exc_info=True)
89
+ logger.warning("%s Ending graph traversal", AGENT_LOG_PREFIX)
90
+ return AgentDecision.END
91
+
92
+ async def tool_node(self, state: ToolCallAgentGraphState):
93
+ try:
94
+ logger.debug("%s Starting Tool Node", AGENT_LOG_PREFIX)
95
+ tool_calls = state.messages[-1].tool_calls
96
+ tools = [tool.get('name') for tool in tool_calls]
97
+ tool_input = state.messages[-1]
98
+ tool_response = await self.tool_caller.ainvoke(input={"messages": [tool_input]},
99
+ config=RunnableConfig(callbacks=self.callbacks,
100
+ configurable={}))
101
+ # this configurable = {} argument is needed due to a bug in LangGraph PreBuilt ToolNode ^
102
+
103
+ for response in tool_response.get('messages'):
104
+ if self.detailed_logs:
105
+ # The tool response can be very large, so we log only the first 1000 characters
106
+ response.content = response.content[:1000] + "..." if len(
107
+ response.content) > 1000 else response.content
108
+ logger.info(TOOL_RESPONSE_LOG_MESSAGE, tools, tool_input, response.content)
109
+ state.messages += [response]
110
+
111
+ return state
112
+ except Exception as ex:
113
+ logger.exception("%s Failed to call tool_node: %s", AGENT_LOG_PREFIX, ex, exc_info=ex)
114
+ raise ex
115
+
116
+ async def build_graph(self):
117
+ try:
118
+ await super()._build_graph(state_schema=ToolCallAgentGraphState)
119
+ logger.debug("%s Tool Calling Agent Graph built and compiled successfully", AGENT_LOG_PREFIX)
120
+ return self.graph
121
+ except Exception as ex:
122
+ logger.exception("%s Failed to build Tool Calling Agent Graph: %s", AGENT_LOG_PREFIX, ex, exc_info=ex)
123
+ raise ex
@@ -0,0 +1,105 @@
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 Field
19
+
20
+ from aiq.agent.base import AGENT_LOG_PREFIX
21
+ from aiq.builder.builder import Builder
22
+ from aiq.builder.framework_enum import LLMFrameworkEnum
23
+ from aiq.builder.function_info import FunctionInfo
24
+ from aiq.cli.register_workflow import register_function
25
+ from aiq.data_models.component_ref import FunctionRef
26
+ from aiq.data_models.component_ref import LLMRef
27
+ from aiq.data_models.function import FunctionBaseConfig
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ class ToolCallAgentWorkflowConfig(FunctionBaseConfig, name="tool_calling_agent"):
33
+ """
34
+ A Tool Calling Agent requires an LLM which supports tool calling. A tool Calling Agent utilizes the tool
35
+ input parameters to select the optimal tool. Supports handling tool errors.
36
+ """
37
+
38
+ tool_names: list[FunctionRef] = Field(default_factory=list,
39
+ description="The list of tools to provide to the tool calling agent.")
40
+ llm_name: LLMRef = Field(description="The LLM model to use with the tool calling agent.")
41
+ verbose: bool = Field(default=False, description="Set the verbosity of the react agent's logging.")
42
+ handle_tool_errors: bool = Field(default=True, description="Specify ability to handle tool calling errors.")
43
+ description: str = Field(default="Tool Calling Agent Workflow", description="Description of this functions use.")
44
+ max_iterations: int = Field(default=15, description="Number of tool calls before stoping the tool calling agent.")
45
+
46
+
47
+ @register_function(config_type=ToolCallAgentWorkflowConfig, framework_wrappers=[LLMFrameworkEnum.LANGCHAIN])
48
+ async def tool_calling_agent_workflow(config: ToolCallAgentWorkflowConfig, builder: Builder):
49
+ from langchain_core.messages.human import HumanMessage
50
+ from langgraph.graph.graph import CompiledGraph
51
+
52
+ from .agent import ToolCallAgentGraph
53
+ from .agent import ToolCallAgentGraphState
54
+
55
+ # we can choose an LLM for the ReAct agent in the config file
56
+ llm = await builder.get_llm(config.llm_name, wrapper_type=LLMFrameworkEnum.LANGCHAIN)
57
+ # the agent can run any installed tool, simply install the tool and add it to the config file
58
+ # the sample tools provided can easily be copied or changed
59
+ tools = builder.get_tools(tool_names=config.tool_names, wrapper_type=LLMFrameworkEnum.LANGCHAIN)
60
+ if not tools:
61
+ raise ValueError(f"No tools specified for Tool Calling Agent '{config.llm_name}'")
62
+
63
+ # some LLMs support tool calling
64
+ # these models accept the tool's input schema and decide when to use a tool based on the input's relevance
65
+ try:
66
+ # in tool calling agents, we bind the tools to the LLM, to pass the tools' input schemas at runtime
67
+ llm = llm.bind_tools(tools)
68
+ except NotImplementedError as ex:
69
+ logger.error("%s Failed to bind tools: %s", AGENT_LOG_PREFIX, ex, exc_info=True)
70
+ raise ex
71
+
72
+ # construct the Tool Calling Agent Graph from the configured llm, and tools
73
+ graph: CompiledGraph = await ToolCallAgentGraph(llm=llm,
74
+ tools=tools,
75
+ detailed_logs=config.verbose,
76
+ handle_tool_errors=config.handle_tool_errors).build_graph()
77
+
78
+ async def _response_fn(input_message: str) -> str:
79
+ try:
80
+ # initialize the starting state with the user query
81
+ input_message = HumanMessage(content=input_message)
82
+ state = ToolCallAgentGraphState(messages=[input_message])
83
+
84
+ # run the Tool Calling Agent Graph
85
+ state = await graph.ainvoke(state, config={'recursion_limit': (config.max_iterations + 1) * 2})
86
+ # setting recursion_limit: 4 allows 1 tool call
87
+ # - allows the Tool Calling Agent to perform 1 cycle / call 1 single tool,
88
+ # - but stops the agent when it tries to call a tool a second time
89
+
90
+ # get and return the output from the state
91
+ state = ToolCallAgentGraphState(**state)
92
+ output_message = state.messages[-1] # pylint: disable=E1136
93
+ return output_message.content
94
+ except Exception as ex:
95
+ logger.exception("%s Tool Calling Agent failed with exception: %s", AGENT_LOG_PREFIX, ex, exc_info=ex)
96
+ if config.verbose:
97
+ return str(ex)
98
+ return "I seem to be having a problem."
99
+
100
+ try:
101
+ yield FunctionInfo.from_fn(_response_fn, description=config.description)
102
+ except GeneratorExit:
103
+ logger.exception("%s Workflow exited early!", AGENT_LOG_PREFIX, exc_info=True)
104
+ finally:
105
+ logger.debug("%s Cleaning up react_agent workflow.", AGENT_LOG_PREFIX)
File without changes
aiq/builder/builder.py ADDED
@@ -0,0 +1,223 @@
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
+ import asyncio
17
+ import typing
18
+ from abc import ABC
19
+ from abc import abstractmethod
20
+ from collections.abc import Sequence
21
+ from pathlib import Path
22
+
23
+ from aiq.builder.context import AIQContext
24
+ from aiq.builder.framework_enum import LLMFrameworkEnum
25
+ from aiq.builder.function import Function
26
+ from aiq.data_models.component_ref import EmbedderRef
27
+ from aiq.data_models.component_ref import FunctionRef
28
+ from aiq.data_models.component_ref import LLMRef
29
+ from aiq.data_models.component_ref import MemoryRef
30
+ from aiq.data_models.component_ref import RetrieverRef
31
+ from aiq.data_models.embedder import EmbedderBaseConfig
32
+ from aiq.data_models.evaluator import EvaluatorBaseConfig
33
+ from aiq.data_models.function import FunctionBaseConfig
34
+ from aiq.data_models.function_dependencies import FunctionDependencies
35
+ from aiq.data_models.llm import LLMBaseConfig
36
+ from aiq.data_models.memory import MemoryBaseConfig
37
+ from aiq.data_models.retriever import RetrieverBaseConfig
38
+ from aiq.memory.interfaces import MemoryEditor
39
+ from aiq.retriever.interface import AIQRetriever
40
+
41
+
42
+ class UserManagerHolder():
43
+
44
+ def __init__(self, context: AIQContext) -> None:
45
+ self._context = context
46
+
47
+ def get_id(self):
48
+ return self._context.user_manager.get_id()
49
+
50
+
51
+ class Builder(ABC): # pylint: disable=too-many-public-methods
52
+
53
+ @abstractmethod
54
+ async def add_function(self, name: str | FunctionRef, config: FunctionBaseConfig) -> Function:
55
+ pass
56
+
57
+ @abstractmethod
58
+ def get_function(self, name: str | FunctionRef) -> Function:
59
+ pass
60
+
61
+ def get_functions(self, function_names: Sequence[str | FunctionRef]) -> list[Function]:
62
+
63
+ return [self.get_function(name) for name in function_names]
64
+
65
+ @abstractmethod
66
+ def get_function_config(self, name: str | FunctionRef) -> FunctionBaseConfig:
67
+ pass
68
+
69
+ @abstractmethod
70
+ async def set_workflow(self, config: FunctionBaseConfig) -> Function:
71
+ pass
72
+
73
+ @abstractmethod
74
+ def get_workflow(self) -> Function:
75
+ pass
76
+
77
+ @abstractmethod
78
+ def get_workflow_config(self) -> FunctionBaseConfig:
79
+ pass
80
+
81
+ def get_tools(self, tool_names: Sequence[str | FunctionRef],
82
+ wrapper_type: LLMFrameworkEnum | str) -> list[typing.Any]:
83
+
84
+ return [self.get_tool(fn_name=n, wrapper_type=wrapper_type) for n in tool_names]
85
+
86
+ @abstractmethod
87
+ def get_tool(self, fn_name: str | FunctionRef, wrapper_type: LLMFrameworkEnum | str) -> typing.Any:
88
+ pass
89
+
90
+ @abstractmethod
91
+ async def add_llm(self, name: str | LLMRef, config: LLMBaseConfig):
92
+ pass
93
+
94
+ async def get_llms(self, llm_names: Sequence[str | LLMRef],
95
+ wrapper_type: LLMFrameworkEnum | str) -> list[typing.Any]:
96
+
97
+ coros = [self.get_llm(llm_name=n, wrapper_type=wrapper_type) for n in llm_names]
98
+
99
+ llms = await asyncio.gather(*coros, return_exceptions=False)
100
+
101
+ return list(llms)
102
+
103
+ @abstractmethod
104
+ async def get_llm(self, llm_name: str | LLMRef, wrapper_type: LLMFrameworkEnum | str) -> typing.Any:
105
+ pass
106
+
107
+ @abstractmethod
108
+ def get_llm_config(self, llm_name: str | LLMRef) -> LLMBaseConfig:
109
+ pass
110
+
111
+ @abstractmethod
112
+ async def add_embedder(self, name: str | EmbedderRef, config: EmbedderBaseConfig):
113
+ pass
114
+
115
+ async def get_embedders(self, embedder_names: Sequence[str | EmbedderRef],
116
+ wrapper_type: LLMFrameworkEnum | str) -> list[typing.Any]:
117
+
118
+ coros = [self.get_embedder(embedder_name=n, wrapper_type=wrapper_type) for n in embedder_names]
119
+
120
+ embedders = await asyncio.gather(*coros, return_exceptions=False)
121
+
122
+ return list(embedders)
123
+
124
+ @abstractmethod
125
+ async def get_embedder(self, embedder_name: str | EmbedderRef, wrapper_type: LLMFrameworkEnum | str) -> typing.Any:
126
+ pass
127
+
128
+ @abstractmethod
129
+ def get_embedder_config(self, embedder_name: str | EmbedderRef) -> EmbedderBaseConfig:
130
+ pass
131
+
132
+ @abstractmethod
133
+ async def add_memory_client(self, name: str | MemoryRef, config: MemoryBaseConfig):
134
+ pass
135
+
136
+ def get_memory_clients(self, memory_names: Sequence[str | MemoryRef]) -> list[MemoryEditor]:
137
+ """
138
+ Return a list of memory clients for the specified names.
139
+ """
140
+ return [self.get_memory_client(n) for n in memory_names]
141
+
142
+ @abstractmethod
143
+ def get_memory_client(self, memory_name: str | MemoryRef) -> MemoryEditor:
144
+ """
145
+ Return the instantiated memory client for the given name.
146
+ """
147
+ pass
148
+
149
+ @abstractmethod
150
+ def get_memory_client_config(self, memory_name: str | MemoryRef) -> MemoryBaseConfig:
151
+ pass
152
+
153
+ @abstractmethod
154
+ async def add_retriever(self, name: str | RetrieverRef, config: RetrieverBaseConfig):
155
+ pass
156
+
157
+ async def get_retrievers(self,
158
+ retriever_names: Sequence[str | RetrieverRef],
159
+ wrapper_type: LLMFrameworkEnum | str | None = None):
160
+
161
+ tasks = [self.get_retriever(n, wrapper_type=wrapper_type) for n in retriever_names]
162
+
163
+ retrievers = await asyncio.gather(*tasks, return_exceptions=False)
164
+
165
+ return list(retrievers)
166
+
167
+ @typing.overload
168
+ async def get_retriever(self, retriever_name: str | RetrieverRef,
169
+ wrapper_type: LLMFrameworkEnum | str) -> typing.Any:
170
+ ...
171
+
172
+ @typing.overload
173
+ async def get_retriever(self, retriever_name: str | RetrieverRef, wrapper_type: None) -> AIQRetriever:
174
+ ...
175
+
176
+ @typing.overload
177
+ async def get_retriever(self, retriever_name: str | RetrieverRef) -> AIQRetriever:
178
+ ...
179
+
180
+ @abstractmethod
181
+ async def get_retriever(self,
182
+ retriever_name: str | RetrieverRef,
183
+ wrapper_type: LLMFrameworkEnum | str | None = None) -> typing.Any:
184
+ pass
185
+
186
+ @abstractmethod
187
+ async def get_retriever_config(self, retriever_name: str | RetrieverRef) -> RetrieverBaseConfig:
188
+ pass
189
+
190
+ @abstractmethod
191
+ def get_user_manager(self) -> UserManagerHolder:
192
+ pass
193
+
194
+ @abstractmethod
195
+ def get_function_dependencies(self, fn_name: str) -> FunctionDependencies:
196
+ pass
197
+
198
+
199
+ class EvalBuilder(Builder):
200
+
201
+ @abstractmethod
202
+ async def add_evaluator(self, name: str, config: EvaluatorBaseConfig):
203
+ pass
204
+
205
+ @abstractmethod
206
+ def get_evaluator(self, evaluator_name: str) -> typing.Any:
207
+ pass
208
+
209
+ @abstractmethod
210
+ def get_evaluator_config(self, evaluator_name: str) -> EvaluatorBaseConfig:
211
+ pass
212
+
213
+ @abstractmethod
214
+ def get_max_concurrency(self) -> int:
215
+ pass
216
+
217
+ @abstractmethod
218
+ def get_output_dir(self) -> Path:
219
+ pass
220
+
221
+ @abstractmethod
222
+ def get_all_tools(self, wrapper_type: LLMFrameworkEnum | str) -> list[typing.Any]:
223
+ pass