nvidia-nat 1.3.0.dev2__py3-none-any.whl → 1.3.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.
Files changed (250) hide show
  1. aiq/__init__.py +2 -2
  2. nat/agent/base.py +24 -15
  3. nat/agent/dual_node.py +9 -4
  4. nat/agent/prompt_optimizer/prompt.py +68 -0
  5. nat/agent/prompt_optimizer/register.py +149 -0
  6. nat/agent/react_agent/agent.py +79 -47
  7. nat/agent/react_agent/register.py +50 -22
  8. nat/agent/reasoning_agent/reasoning_agent.py +11 -9
  9. nat/agent/register.py +1 -1
  10. nat/agent/rewoo_agent/agent.py +326 -148
  11. nat/agent/rewoo_agent/prompt.py +19 -22
  12. nat/agent/rewoo_agent/register.py +54 -27
  13. nat/agent/tool_calling_agent/agent.py +84 -28
  14. nat/agent/tool_calling_agent/register.py +51 -28
  15. nat/authentication/api_key/api_key_auth_provider.py +2 -2
  16. nat/authentication/credential_validator/bearer_token_validator.py +557 -0
  17. nat/authentication/http_basic_auth/http_basic_auth_provider.py +1 -1
  18. nat/authentication/interfaces.py +5 -2
  19. nat/authentication/oauth2/oauth2_auth_code_flow_provider.py +69 -36
  20. nat/authentication/oauth2/oauth2_resource_server_config.py +124 -0
  21. nat/authentication/register.py +0 -1
  22. nat/builder/builder.py +56 -24
  23. nat/builder/component_utils.py +9 -5
  24. nat/builder/context.py +68 -17
  25. nat/builder/eval_builder.py +16 -11
  26. nat/builder/framework_enum.py +1 -0
  27. nat/builder/front_end.py +1 -1
  28. nat/builder/function.py +378 -8
  29. nat/builder/function_base.py +3 -3
  30. nat/builder/function_info.py +6 -8
  31. nat/builder/user_interaction_manager.py +2 -2
  32. nat/builder/workflow.py +13 -1
  33. nat/builder/workflow_builder.py +281 -76
  34. nat/cli/cli_utils/config_override.py +2 -2
  35. nat/cli/commands/evaluate.py +1 -1
  36. nat/cli/commands/info/info.py +16 -6
  37. nat/cli/commands/info/list_channels.py +1 -1
  38. nat/cli/commands/info/list_components.py +7 -8
  39. nat/cli/commands/mcp/__init__.py +14 -0
  40. nat/cli/commands/mcp/mcp.py +986 -0
  41. nat/cli/commands/object_store/__init__.py +14 -0
  42. nat/cli/commands/object_store/object_store.py +227 -0
  43. nat/cli/commands/optimize.py +90 -0
  44. nat/cli/commands/registry/publish.py +2 -2
  45. nat/cli/commands/registry/pull.py +2 -2
  46. nat/cli/commands/registry/remove.py +2 -2
  47. nat/cli/commands/registry/search.py +15 -17
  48. nat/cli/commands/start.py +16 -5
  49. nat/cli/commands/uninstall.py +1 -1
  50. nat/cli/commands/workflow/templates/config.yml.j2 +14 -13
  51. nat/cli/commands/workflow/templates/pyproject.toml.j2 +4 -1
  52. nat/cli/commands/workflow/templates/register.py.j2 +2 -3
  53. nat/cli/commands/workflow/templates/workflow.py.j2 +35 -21
  54. nat/cli/commands/workflow/workflow_commands.py +62 -22
  55. nat/cli/entrypoint.py +8 -10
  56. nat/cli/main.py +3 -0
  57. nat/cli/register_workflow.py +38 -4
  58. nat/cli/type_registry.py +75 -6
  59. nat/control_flow/__init__.py +0 -0
  60. nat/control_flow/register.py +20 -0
  61. nat/control_flow/router_agent/__init__.py +0 -0
  62. nat/control_flow/router_agent/agent.py +329 -0
  63. nat/control_flow/router_agent/prompt.py +48 -0
  64. nat/control_flow/router_agent/register.py +91 -0
  65. nat/control_flow/sequential_executor.py +166 -0
  66. nat/data_models/agent.py +34 -0
  67. nat/data_models/api_server.py +74 -66
  68. nat/data_models/authentication.py +23 -9
  69. nat/data_models/common.py +1 -1
  70. nat/data_models/component.py +2 -0
  71. nat/data_models/component_ref.py +11 -0
  72. nat/data_models/config.py +41 -17
  73. nat/data_models/dataset_handler.py +1 -1
  74. nat/data_models/discovery_metadata.py +4 -4
  75. nat/data_models/evaluate.py +4 -1
  76. nat/data_models/function.py +34 -0
  77. nat/data_models/function_dependencies.py +14 -6
  78. nat/data_models/gated_field_mixin.py +242 -0
  79. nat/data_models/intermediate_step.py +3 -3
  80. nat/data_models/optimizable.py +119 -0
  81. nat/data_models/optimizer.py +149 -0
  82. nat/data_models/span.py +41 -3
  83. nat/data_models/swe_bench_model.py +1 -1
  84. nat/data_models/temperature_mixin.py +44 -0
  85. nat/data_models/thinking_mixin.py +86 -0
  86. nat/data_models/top_p_mixin.py +44 -0
  87. nat/embedder/nim_embedder.py +1 -1
  88. nat/embedder/openai_embedder.py +1 -1
  89. nat/embedder/register.py +0 -1
  90. nat/eval/config.py +3 -1
  91. nat/eval/dataset_handler/dataset_handler.py +71 -7
  92. nat/eval/evaluate.py +86 -31
  93. nat/eval/evaluator/base_evaluator.py +1 -1
  94. nat/eval/evaluator/evaluator_model.py +13 -0
  95. nat/eval/intermediate_step_adapter.py +1 -1
  96. nat/eval/rag_evaluator/evaluate.py +2 -2
  97. nat/eval/rag_evaluator/register.py +3 -3
  98. nat/eval/register.py +4 -1
  99. nat/eval/remote_workflow.py +3 -3
  100. nat/eval/runtime_evaluator/__init__.py +14 -0
  101. nat/eval/runtime_evaluator/evaluate.py +123 -0
  102. nat/eval/runtime_evaluator/register.py +100 -0
  103. nat/eval/swe_bench_evaluator/evaluate.py +6 -6
  104. nat/eval/trajectory_evaluator/evaluate.py +1 -1
  105. nat/eval/trajectory_evaluator/register.py +1 -1
  106. nat/eval/tunable_rag_evaluator/evaluate.py +4 -7
  107. nat/eval/utils/eval_trace_ctx.py +89 -0
  108. nat/eval/utils/weave_eval.py +18 -9
  109. nat/experimental/decorators/experimental_warning_decorator.py +27 -7
  110. nat/experimental/test_time_compute/functions/plan_select_execute_function.py +7 -3
  111. nat/experimental/test_time_compute/functions/ttc_tool_orchestration_function.py +3 -3
  112. nat/experimental/test_time_compute/functions/ttc_tool_wrapper_function.py +1 -1
  113. nat/experimental/test_time_compute/models/strategy_base.py +5 -4
  114. nat/experimental/test_time_compute/register.py +0 -1
  115. nat/experimental/test_time_compute/selection/llm_based_output_merging_selector.py +1 -3
  116. nat/front_ends/console/authentication_flow_handler.py +82 -30
  117. nat/front_ends/console/console_front_end_plugin.py +8 -5
  118. nat/front_ends/fastapi/auth_flow_handlers/websocket_flow_handler.py +52 -17
  119. nat/front_ends/fastapi/dask_client_mixin.py +65 -0
  120. nat/front_ends/fastapi/fastapi_front_end_config.py +36 -5
  121. nat/front_ends/fastapi/fastapi_front_end_controller.py +4 -4
  122. nat/front_ends/fastapi/fastapi_front_end_plugin.py +135 -4
  123. nat/front_ends/fastapi/fastapi_front_end_plugin_worker.py +452 -282
  124. nat/front_ends/fastapi/job_store.py +518 -99
  125. nat/front_ends/fastapi/main.py +11 -19
  126. nat/front_ends/fastapi/message_handler.py +13 -14
  127. nat/front_ends/fastapi/message_validator.py +19 -19
  128. nat/front_ends/fastapi/response_helpers.py +4 -4
  129. nat/front_ends/fastapi/step_adaptor.py +2 -2
  130. nat/front_ends/fastapi/utils.py +57 -0
  131. nat/front_ends/mcp/introspection_token_verifier.py +73 -0
  132. nat/front_ends/mcp/mcp_front_end_config.py +10 -1
  133. nat/front_ends/mcp/mcp_front_end_plugin.py +45 -13
  134. nat/front_ends/mcp/mcp_front_end_plugin_worker.py +116 -8
  135. nat/front_ends/mcp/tool_converter.py +44 -14
  136. nat/front_ends/register.py +0 -1
  137. nat/front_ends/simple_base/simple_front_end_plugin_base.py +3 -1
  138. nat/llm/aws_bedrock_llm.py +24 -12
  139. nat/llm/azure_openai_llm.py +13 -6
  140. nat/llm/litellm_llm.py +69 -0
  141. nat/llm/nim_llm.py +20 -8
  142. nat/llm/openai_llm.py +14 -6
  143. nat/llm/register.py +4 -1
  144. nat/llm/utils/env_config_value.py +2 -3
  145. nat/llm/utils/thinking.py +215 -0
  146. nat/meta/pypi.md +9 -9
  147. nat/object_store/register.py +0 -1
  148. nat/observability/exporter/base_exporter.py +3 -3
  149. nat/observability/exporter/file_exporter.py +1 -1
  150. nat/observability/exporter/processing_exporter.py +309 -81
  151. nat/observability/exporter/span_exporter.py +35 -15
  152. nat/observability/exporter_manager.py +7 -7
  153. nat/observability/mixin/file_mixin.py +7 -7
  154. nat/observability/mixin/redaction_config_mixin.py +42 -0
  155. nat/observability/mixin/tagging_config_mixin.py +62 -0
  156. nat/observability/mixin/type_introspection_mixin.py +420 -107
  157. nat/observability/processor/batching_processor.py +5 -7
  158. nat/observability/processor/falsy_batch_filter_processor.py +55 -0
  159. nat/observability/processor/processor.py +3 -0
  160. nat/observability/processor/processor_factory.py +70 -0
  161. nat/observability/processor/redaction/__init__.py +24 -0
  162. nat/observability/processor/redaction/contextual_redaction_processor.py +125 -0
  163. nat/observability/processor/redaction/contextual_span_redaction_processor.py +66 -0
  164. nat/observability/processor/redaction/redaction_processor.py +177 -0
  165. nat/observability/processor/redaction/span_header_redaction_processor.py +92 -0
  166. nat/observability/processor/span_tagging_processor.py +68 -0
  167. nat/observability/register.py +6 -4
  168. nat/profiler/calc/calc_runner.py +3 -4
  169. nat/profiler/callbacks/agno_callback_handler.py +1 -1
  170. nat/profiler/callbacks/langchain_callback_handler.py +6 -6
  171. nat/profiler/callbacks/llama_index_callback_handler.py +3 -3
  172. nat/profiler/callbacks/semantic_kernel_callback_handler.py +3 -3
  173. nat/profiler/data_frame_row.py +1 -1
  174. nat/profiler/decorators/framework_wrapper.py +62 -13
  175. nat/profiler/decorators/function_tracking.py +160 -3
  176. nat/profiler/forecasting/models/forecasting_base_model.py +3 -1
  177. nat/profiler/forecasting/models/linear_model.py +1 -1
  178. nat/profiler/forecasting/models/random_forest_regressor.py +1 -1
  179. nat/profiler/inference_optimization/bottleneck_analysis/nested_stack_analysis.py +1 -1
  180. nat/profiler/inference_optimization/bottleneck_analysis/simple_stack_analysis.py +1 -1
  181. nat/profiler/inference_optimization/data_models.py +3 -3
  182. nat/profiler/inference_optimization/experimental/prefix_span_analysis.py +8 -9
  183. nat/profiler/inference_optimization/token_uniqueness.py +1 -1
  184. nat/profiler/parameter_optimization/__init__.py +0 -0
  185. nat/profiler/parameter_optimization/optimizable_utils.py +93 -0
  186. nat/profiler/parameter_optimization/optimizer_runtime.py +67 -0
  187. nat/profiler/parameter_optimization/parameter_optimizer.py +153 -0
  188. nat/profiler/parameter_optimization/parameter_selection.py +107 -0
  189. nat/profiler/parameter_optimization/pareto_visualizer.py +380 -0
  190. nat/profiler/parameter_optimization/prompt_optimizer.py +384 -0
  191. nat/profiler/parameter_optimization/update_helpers.py +66 -0
  192. nat/profiler/profile_runner.py +14 -9
  193. nat/profiler/utils.py +4 -2
  194. nat/registry_handlers/local/local_handler.py +2 -2
  195. nat/registry_handlers/package_utils.py +1 -2
  196. nat/registry_handlers/pypi/pypi_handler.py +23 -26
  197. nat/registry_handlers/register.py +3 -4
  198. nat/registry_handlers/rest/rest_handler.py +12 -13
  199. nat/retriever/milvus/retriever.py +2 -2
  200. nat/retriever/nemo_retriever/retriever.py +1 -1
  201. nat/retriever/register.py +0 -1
  202. nat/runtime/loader.py +2 -2
  203. nat/runtime/runner.py +106 -8
  204. nat/runtime/session.py +69 -8
  205. nat/settings/global_settings.py +16 -5
  206. nat/tool/chat_completion.py +5 -2
  207. nat/tool/code_execution/local_sandbox/local_sandbox_server.py +3 -3
  208. nat/tool/datetime_tools.py +49 -9
  209. nat/tool/document_search.py +2 -2
  210. nat/tool/github_tools.py +450 -0
  211. nat/tool/memory_tools/get_memory_tool.py +1 -1
  212. nat/tool/nvidia_rag.py +1 -1
  213. nat/tool/register.py +2 -9
  214. nat/tool/retriever.py +3 -2
  215. nat/utils/callable_utils.py +70 -0
  216. nat/utils/data_models/schema_validator.py +3 -3
  217. nat/utils/decorators.py +210 -0
  218. nat/utils/exception_handlers/automatic_retries.py +104 -51
  219. nat/utils/exception_handlers/schemas.py +1 -1
  220. nat/utils/io/yaml_tools.py +2 -2
  221. nat/utils/log_levels.py +25 -0
  222. nat/utils/reactive/base/observable_base.py +2 -2
  223. nat/utils/reactive/base/observer_base.py +1 -1
  224. nat/utils/reactive/observable.py +2 -2
  225. nat/utils/reactive/observer.py +4 -4
  226. nat/utils/reactive/subscription.py +1 -1
  227. nat/utils/settings/global_settings.py +6 -8
  228. nat/utils/type_converter.py +4 -3
  229. nat/utils/type_utils.py +9 -5
  230. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc2.dist-info}/METADATA +42 -18
  231. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc2.dist-info}/RECORD +238 -196
  232. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc2.dist-info}/entry_points.txt +1 -0
  233. nat/cli/commands/info/list_mcp.py +0 -304
  234. nat/tool/github_tools/create_github_commit.py +0 -133
  235. nat/tool/github_tools/create_github_issue.py +0 -87
  236. nat/tool/github_tools/create_github_pr.py +0 -106
  237. nat/tool/github_tools/get_github_file.py +0 -106
  238. nat/tool/github_tools/get_github_issue.py +0 -166
  239. nat/tool/github_tools/get_github_pr.py +0 -256
  240. nat/tool/github_tools/update_github_issue.py +0 -100
  241. nat/tool/mcp/exceptions.py +0 -142
  242. nat/tool/mcp/mcp_client.py +0 -255
  243. nat/tool/mcp/mcp_tool.py +0 -96
  244. nat/utils/exception_handlers/mcp.py +0 -211
  245. /nat/{tool/github_tools → agent/prompt_optimizer}/__init__.py +0 -0
  246. /nat/{tool/mcp → authentication/credential_validator}/__init__.py +0 -0
  247. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc2.dist-info}/WHEEL +0 -0
  248. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc2.dist-info}/licenses/LICENSE-3rd-party.txt +0 -0
  249. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc2.dist-info}/licenses/LICENSE.md +0 -0
  250. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc2.dist-info}/top_level.txt +0 -0
nat/builder/context.py CHANGED
@@ -31,6 +31,7 @@ from nat.data_models.intermediate_step import IntermediateStep
31
31
  from nat.data_models.intermediate_step import IntermediateStepPayload
32
32
  from nat.data_models.intermediate_step import IntermediateStepType
33
33
  from nat.data_models.intermediate_step import StreamEventData
34
+ from nat.data_models.intermediate_step import TraceMetadata
34
35
  from nat.data_models.invocation_node import InvocationNode
35
36
  from nat.runtime.user_metadata import RequestAttributes
36
37
  from nat.utils.reactive.subject import Subject
@@ -38,13 +39,13 @@ from nat.utils.reactive.subject import Subject
38
39
 
39
40
  class Singleton(type):
40
41
 
41
- def __init__(cls, name, bases, dict): # pylint: disable=W0622
42
- super(Singleton, cls).__init__(name, bases, dict)
42
+ def __init__(cls, name, bases, dict):
43
+ super().__init__(name, bases, dict)
43
44
  cls.instance = None
44
45
 
45
46
  def __call__(cls, *args, **kw):
46
47
  if cls.instance is None:
47
- cls.instance = super(Singleton, cls).__call__(*args, **kw)
48
+ cls.instance = super().__call__(*args, **kw)
48
49
  return cls.instance
49
50
 
50
51
 
@@ -65,14 +66,15 @@ class ContextState(metaclass=Singleton):
65
66
 
66
67
  def __init__(self):
67
68
  self.conversation_id: ContextVar[str | None] = ContextVar("conversation_id", default=None)
69
+ self.user_message_id: ContextVar[str | None] = ContextVar("user_message_id", default=None)
70
+ self.workflow_run_id: ContextVar[str | None] = ContextVar("workflow_run_id", default=None)
71
+ self.workflow_trace_id: ContextVar[int | None] = ContextVar("workflow_trace_id", default=None)
68
72
  self.input_message: ContextVar[typing.Any] = ContextVar("input_message", default=None)
69
73
  self.user_manager: ContextVar[typing.Any] = ContextVar("user_manager", default=None)
70
- self.metadata: ContextVar[RequestAttributes] = ContextVar("request_attributes", default=RequestAttributes())
71
- self.event_stream: ContextVar[Subject[IntermediateStep] | None] = ContextVar("event_stream", default=Subject())
72
- self.active_function: ContextVar[InvocationNode] = ContextVar("active_function",
73
- default=InvocationNode(function_id="root",
74
- function_name="root"))
75
- self.active_span_id_stack: ContextVar[list[str]] = ContextVar("active_span_id_stack", default=["root"])
74
+ self._metadata: ContextVar[RequestAttributes | None] = ContextVar("request_attributes", default=None)
75
+ self._event_stream: ContextVar[Subject[IntermediateStep] | None] = ContextVar("event_stream", default=None)
76
+ self._active_function: ContextVar[InvocationNode | None] = ContextVar("active_function", default=None)
77
+ self._active_span_id_stack: ContextVar[list[str] | None] = ContextVar("active_span_id_stack", default=None)
76
78
 
77
79
  # Default is a lambda no-op which returns NoneType
78
80
  self.user_input_callback: ContextVar[Callable[[InteractionPrompt], Awaitable[HumanResponse | None]]
@@ -83,6 +85,30 @@ class ContextState(metaclass=Singleton):
83
85
  Awaitable[AuthenticatedContext]]
84
86
  | None] = ContextVar("user_auth_callback", default=None)
85
87
 
88
+ @property
89
+ def metadata(self) -> ContextVar[RequestAttributes]:
90
+ if self._metadata.get() is None:
91
+ self._metadata.set(RequestAttributes())
92
+ return typing.cast(ContextVar[RequestAttributes], self._metadata)
93
+
94
+ @property
95
+ def active_function(self) -> ContextVar[InvocationNode]:
96
+ if self._active_function.get() is None:
97
+ self._active_function.set(InvocationNode(function_id="root", function_name="root"))
98
+ return typing.cast(ContextVar[InvocationNode], self._active_function)
99
+
100
+ @property
101
+ def event_stream(self) -> ContextVar[Subject[IntermediateStep]]:
102
+ if self._event_stream.get() is None:
103
+ self._event_stream.set(Subject())
104
+ return typing.cast(ContextVar[Subject[IntermediateStep]], self._event_stream)
105
+
106
+ @property
107
+ def active_span_id_stack(self) -> ContextVar[list[str]]:
108
+ if self._active_span_id_stack.get() is None:
109
+ self._active_span_id_stack.set(["root"])
110
+ return typing.cast(ContextVar[list[str]], self._active_span_id_stack)
111
+
86
112
  @staticmethod
87
113
  def get() -> "ContextState":
88
114
  return ContextState()
@@ -96,14 +122,14 @@ class Context:
96
122
  @property
97
123
  def input_message(self):
98
124
  """
99
- Retrieves the input message from the context state.
125
+ Retrieves the input message from the context state.
100
126
 
101
- The input_message property is used to access the message stored in the
102
- context state. This property returns the message as it is currently
103
- maintained in the context.
127
+ The input_message property is used to access the message stored in the
128
+ context state. This property returns the message as it is currently
129
+ maintained in the context.
104
130
 
105
- Returns:
106
- str: The input message retrieved from the context state.
131
+ Returns:
132
+ str: The input message retrieved from the context state.
107
133
  """
108
134
  return self._context_state.input_message.get()
109
135
 
@@ -165,8 +191,32 @@ class Context:
165
191
  """
166
192
  return self._context_state.conversation_id.get()
167
193
 
194
+ @property
195
+ def user_message_id(self) -> str | None:
196
+ """
197
+ This property retrieves the user message ID which is the unique identifier for the current user message.
198
+ """
199
+ return self._context_state.user_message_id.get()
200
+
201
+ @property
202
+ def workflow_run_id(self) -> str | None:
203
+ """
204
+ Returns a stable identifier for the current workflow/agent invocation (UUID string).
205
+ """
206
+ return self._context_state.workflow_run_id.get()
207
+
208
+ @property
209
+ def workflow_trace_id(self) -> int | None:
210
+ """
211
+ Returns the 128-bit trace identifier for the current run, used as the OpenTelemetry trace_id.
212
+ """
213
+ return self._context_state.workflow_trace_id.get()
214
+
168
215
  @contextmanager
169
- def push_active_function(self, function_name: str, input_data: typing.Any | None):
216
+ def push_active_function(self,
217
+ function_name: str,
218
+ input_data: typing.Any | None,
219
+ metadata: dict[str, typing.Any] | TraceMetadata | None = None):
170
220
  """
171
221
  Set the 'active_function' in context, push an invocation node,
172
222
  AND create an OTel child span for that function call.
@@ -187,7 +237,8 @@ class Context:
187
237
  IntermediateStepPayload(UUID=current_function_id,
188
238
  event_type=IntermediateStepType.FUNCTION_START,
189
239
  name=function_name,
190
- data=StreamEventData(input=input_data)))
240
+ data=StreamEventData(input=input_data),
241
+ metadata=metadata))
191
242
 
192
243
  manager = ActiveFunctionContextManager()
193
244
 
@@ -13,6 +13,7 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
 
16
+ import asyncio
16
17
  import dataclasses
17
18
  import logging
18
19
  from contextlib import asynccontextmanager
@@ -61,7 +62,7 @@ class WorkflowEvalBuilder(WorkflowBuilder, EvalBuilder):
61
62
  # Store the evaluator
62
63
  self._evaluators[name] = ConfiguredEvaluator(config=config, instance=info_obj)
63
64
  except Exception as e:
64
- logger.error("Error %s adding evaluator `%s` with config `%s`", e, name, config, exc_info=True)
65
+ logger.error("Error %s adding evaluator `%s` with config `%s`", e, name, config)
65
66
  raise
66
67
 
67
68
  @override
@@ -90,17 +91,20 @@ class WorkflowEvalBuilder(WorkflowBuilder, EvalBuilder):
90
91
  return self.eval_general_config.output_dir
91
92
 
92
93
  @override
93
- def get_all_tools(self, wrapper_type: LLMFrameworkEnum | str):
94
- tools = []
94
+ async def get_all_tools(self, wrapper_type: LLMFrameworkEnum | str):
95
95
  tool_wrapper_reg = self._registry.get_tool_wrapper(llm_framework=wrapper_type)
96
- for fn_name in self._functions:
97
- fn = self.get_function(fn_name)
96
+
97
+ async def get_tool(fn_name: str):
98
+ fn = await self.get_function(fn_name)
98
99
  try:
99
- tools.append(tool_wrapper_reg.build_fn(fn_name, fn, self))
100
+ return tool_wrapper_reg.build_fn(fn_name, fn, self)
100
101
  except Exception:
101
- logger.exception("Error fetching tool `%s`", fn_name, exc_info=True)
102
+ logger.exception("Error fetching tool `%s`", fn_name)
103
+ return None
102
104
 
103
- return tools
105
+ tasks = [get_tool(fn_name) for fn_name in self._functions]
106
+ tools = await asyncio.gather(*tasks, return_exceptions=False)
107
+ return [tool for tool in tools if tool is not None]
104
108
 
105
109
  def _log_build_failure_evaluator(self,
106
110
  failing_evaluator_name: str,
@@ -127,11 +131,12 @@ class WorkflowEvalBuilder(WorkflowBuilder, EvalBuilder):
127
131
  remaining_components,
128
132
  original_error)
129
133
 
130
- async def populate_builder(self, config: Config):
134
+ @override
135
+ async def populate_builder(self, config: Config, skip_workflow: bool = False):
131
136
  # Skip setting workflow if workflow config is EmptyFunctionConfig
132
- skip_workflow = isinstance(config.workflow, EmptyFunctionConfig)
137
+ skip_workflow = skip_workflow or isinstance(config.workflow, EmptyFunctionConfig)
133
138
 
134
- await super().populate_builder(config, skip_workflow)
139
+ await super().populate_builder(config, skip_workflow=skip_workflow)
135
140
 
136
141
  # Initialize progress tracking for evaluators
137
142
  completed_evaluators = []
@@ -22,3 +22,4 @@ class LLMFrameworkEnum(str, Enum):
22
22
  CREWAI = "crewai"
23
23
  SEMANTIC_KERNEL = "semantic_kernel"
24
24
  AGNO = "agno"
25
+ ADK = "adk"
nat/builder/front_end.py CHANGED
@@ -37,7 +37,7 @@ class FrontEndBase(typing.Generic[FrontEndConfigT], ABC):
37
37
 
38
38
  super().__init__()
39
39
 
40
- self._full_config: "Config" = full_config
40
+ self._full_config: Config = full_config
41
41
  self._front_end_config: FrontEndConfigT = typing.cast(FrontEndConfigT, full_config.general.front_end)
42
42
 
43
43
  @property
nat/builder/function.py CHANGED
@@ -14,12 +14,14 @@
14
14
  # limitations under the License.
15
15
 
16
16
  import logging
17
+ import re
17
18
  import typing
18
19
  from abc import ABC
19
20
  from abc import abstractmethod
20
21
  from collections.abc import AsyncGenerator
21
22
  from collections.abc import Awaitable
22
23
  from collections.abc import Callable
24
+ from collections.abc import Sequence
23
25
 
24
26
  from pydantic import BaseModel
25
27
 
@@ -29,7 +31,9 @@ from nat.builder.function_base import InputT
29
31
  from nat.builder.function_base import SingleOutputT
30
32
  from nat.builder.function_base import StreamingOutputT
31
33
  from nat.builder.function_info import FunctionInfo
34
+ from nat.data_models.function import EmptyFunctionConfig
32
35
  from nat.data_models.function import FunctionBaseConfig
36
+ from nat.data_models.function import FunctionGroupBaseConfig
33
37
 
34
38
  _InvokeFnT = Callable[[InputT], Awaitable[SingleOutputT]]
35
39
  _StreamFnT = Callable[[InputT], AsyncGenerator[StreamingOutputT]]
@@ -155,8 +159,9 @@ class Function(FunctionBase[InputT, StreamingOutputT, SingleOutputT], ABC):
155
159
 
156
160
  return result
157
161
  except Exception as e:
158
- logger.error("Error with ainvoke in function with input: %s.", value, exc_info=True)
159
- raise e
162
+ err_msg = f"Error: {e}" if str(e).strip() else ""
163
+ logger.error("Error with ainvoke in function with input: %s. %s", value, err_msg)
164
+ raise
160
165
 
161
166
  @typing.final
162
167
  async def acall_invoke(self, *args, **kwargs):
@@ -186,14 +191,14 @@ class Function(FunctionBase[InputT, StreamingOutputT, SingleOutputT], ABC):
186
191
  input_obj = self.input_schema(*args, **kwargs)
187
192
 
188
193
  return await self.ainvoke(value=input_obj)
189
- except Exception as e:
194
+ except Exception:
190
195
  logger.error(
191
196
  "Error in acall_invoke() converting input to function schema. Both args and kwargs were "
192
197
  "supplied which could not be converted to the input schema. args: %s\nkwargs: %s\nschema: %s",
193
198
  args,
194
199
  kwargs,
195
200
  self.input_schema)
196
- raise e
201
+ raise
197
202
 
198
203
  @abstractmethod
199
204
  async def _astream(self, value: InputT) -> AsyncGenerator[StreamingOutputT]:
@@ -252,8 +257,8 @@ class Function(FunctionBase[InputT, StreamingOutputT, SingleOutputT], ABC):
252
257
  manager.set_output(final_output)
253
258
 
254
259
  except Exception as e:
255
- logger.error("Error with astream in function with input: %s.", value, exc_info=True)
256
- raise e
260
+ logger.error("Error with astream in function with input: %s. Error: %s", value, e)
261
+ raise
257
262
 
258
263
  @typing.final
259
264
  async def acall_stream(self, *args, **kwargs):
@@ -287,14 +292,14 @@ class Function(FunctionBase[InputT, StreamingOutputT, SingleOutputT], ABC):
287
292
 
288
293
  async for x in self.astream(value=input_obj):
289
294
  yield x
290
- except Exception as e:
295
+ except Exception:
291
296
  logger.error(
292
297
  "Error in acall_stream() converting input to function schema. Both args and kwargs were "
293
298
  "supplied which could not be converted to the input schema. args: %s\nkwargs: %s\nschema: %s",
294
299
  args,
295
300
  kwargs,
296
301
  self.input_schema)
297
- raise e
302
+ raise
298
303
 
299
304
 
300
305
  class LambdaFunction(Function[InputT, StreamingOutputT, SingleOutputT]):
@@ -342,3 +347,368 @@ class LambdaFunction(Function[InputT, StreamingOutputT, SingleOutputT]):
342
347
  pass
343
348
 
344
349
  return FunctionImpl(config=config, info=info, instance_name=instance_name)
350
+
351
+
352
+ class FunctionGroup:
353
+ """
354
+ A group of functions that can be used together, sharing the same configuration, context, and resources.
355
+ """
356
+
357
+ def __init__(self,
358
+ *,
359
+ config: FunctionGroupBaseConfig,
360
+ instance_name: str | None = None,
361
+ filter_fn: Callable[[Sequence[str]], Awaitable[Sequence[str]]] | None = None):
362
+ """
363
+ Creates a new function group.
364
+
365
+ Parameters
366
+ ----------
367
+ config : FunctionGroupBaseConfig
368
+ The configuration for the function group.
369
+ instance_name : str | None, optional
370
+ The name of the function group. If not provided, the type of the function group will be used.
371
+ filter_fn : Callable[[Sequence[str]], Awaitable[Sequence[str]]] | None, optional
372
+ A callback function to additionally filter the functions in the function group dynamically when
373
+ the functions are accessed via any accessor method.
374
+ """
375
+ self._config = config
376
+ self._instance_name = instance_name or config.type
377
+ self._functions: dict[str, Function] = dict()
378
+ self._filter_fn = filter_fn
379
+ self._per_function_filter_fn: dict[str, Callable[[str], Awaitable[bool]]] = dict()
380
+
381
+ def add_function(self,
382
+ name: str,
383
+ fn: Callable,
384
+ *,
385
+ input_schema: type[BaseModel] | None = None,
386
+ description: str | None = None,
387
+ converters: list[Callable] | None = None,
388
+ filter_fn: Callable[[str], Awaitable[bool]] | None = None):
389
+ """
390
+ Adds a function to the function group.
391
+
392
+ Parameters
393
+ ----------
394
+ name : str
395
+ The name of the function.
396
+ fn : Callable
397
+ The function to add to the function group.
398
+ input_schema : type[BaseModel] | None, optional
399
+ The input schema for the function.
400
+ description : str | None, optional
401
+ The description of the function.
402
+ converters : list[Callable] | None, optional
403
+ The converters to use for the function.
404
+ filter_fn : Callable[[str], Awaitable[bool]] | None, optional
405
+ A callback to determine if the function should be included in the function group. The
406
+ callback will be called with the function name. The callback is invoked dynamically when
407
+ the functions are accessed via any accessor method such as `get_accessible_functions`,
408
+ `get_included_functions`, `get_excluded_functions`, `get_all_functions`.
409
+
410
+ Raises
411
+ ------
412
+ ValueError
413
+ When the function name is empty or blank.
414
+ When the function name contains invalid characters.
415
+ When the function already exists in the function group.
416
+ """
417
+ if not name.strip():
418
+ raise ValueError("Function name cannot be empty or blank")
419
+ if not re.match(r"^[a-zA-Z0-9_-]+$", name):
420
+ raise ValueError(f"Function name can only contain letters, numbers, underscores, and hyphens: {name}")
421
+ if name in self._functions:
422
+ raise ValueError(f"Function {name} already exists in function group {self._instance_name}")
423
+
424
+ info = FunctionInfo.from_fn(fn, input_schema=input_schema, description=description, converters=converters)
425
+ full_name = self._get_fn_name(name)
426
+ lambda_fn = LambdaFunction.from_info(config=EmptyFunctionConfig(), info=info, instance_name=full_name)
427
+ self._functions[name] = lambda_fn
428
+ if filter_fn:
429
+ self._per_function_filter_fn[name] = filter_fn
430
+
431
+ def get_config(self) -> FunctionGroupBaseConfig:
432
+ """
433
+ Returns the configuration for the function group.
434
+
435
+ Returns
436
+ -------
437
+ FunctionGroupBaseConfig
438
+ The configuration for the function group.
439
+ """
440
+ return self._config
441
+
442
+ def _get_fn_name(self, name: str) -> str:
443
+ return f"{self._instance_name}.{name}"
444
+
445
+ async def _fn_should_be_included(self, name: str) -> bool:
446
+ if name not in self._per_function_filter_fn:
447
+ return True
448
+ return await self._per_function_filter_fn[name](name)
449
+
450
+ async def _get_all_but_excluded_functions(
451
+ self,
452
+ filter_fn: Callable[[Sequence[str]], Awaitable[Sequence[str]]] | None = None,
453
+ ) -> dict[str, Function]:
454
+ """
455
+ Returns a dictionary of all functions in the function group except the excluded functions.
456
+ """
457
+ missing = set(self._config.exclude) - set(self._functions.keys())
458
+ if missing:
459
+ raise ValueError(f"Unknown excluded functions: {sorted(missing)}")
460
+
461
+ if filter_fn is None:
462
+ if self._filter_fn is None:
463
+
464
+ async def identity_filter(x: Sequence[str]) -> Sequence[str]:
465
+ return x
466
+
467
+ filter_fn = identity_filter
468
+ else:
469
+ filter_fn = self._filter_fn
470
+
471
+ excluded = set(self._config.exclude)
472
+ included = set(await filter_fn(list(self._functions.keys())))
473
+
474
+ result = {}
475
+ for name in self._functions:
476
+ if name in excluded:
477
+ continue
478
+ if not await self._fn_should_be_included(name):
479
+ continue
480
+ if name not in included:
481
+ continue
482
+ result[self._get_fn_name(name)] = self._functions[name]
483
+
484
+ return result
485
+
486
+ async def get_accessible_functions(
487
+ self,
488
+ filter_fn: Callable[[Sequence[str]], Awaitable[Sequence[str]]] | None = None,
489
+ ) -> dict[str, Function]:
490
+ """
491
+ Returns a dictionary of all accessible functions in the function group.
492
+
493
+ First, the functions are filtered by the function group's configuration.
494
+ If the function group is configured to:
495
+ - include some functions, this will return only the included functions.
496
+ - not include or exclude any function, this will return all functions in the group.
497
+ - exclude some functions, this will return all functions in the group except the excluded functions.
498
+
499
+ Then, the functions are filtered by filter function and per-function filter functions.
500
+
501
+ Parameters
502
+ ----------
503
+ filter_fn : Callable[[Sequence[str]], Awaitable[Sequence[str]]] | None, optional
504
+ A callback function to additionally filter the functions in the function group dynamically. If not provided
505
+ then fall back to the function group's filter function. If no filter function is set for the function group
506
+ all functions will be returned.
507
+
508
+ Returns
509
+ -------
510
+ dict[str, Function]
511
+ A dictionary of all accessible functions in the function group.
512
+
513
+ Raises
514
+ ------
515
+ ValueError
516
+ When the function group is configured to include functions that are not found in the group.
517
+ """
518
+ if self._config.include:
519
+ return await self.get_included_functions(filter_fn=filter_fn)
520
+ if self._config.exclude:
521
+ return await self._get_all_but_excluded_functions(filter_fn=filter_fn)
522
+ return await self.get_all_functions(filter_fn=filter_fn)
523
+
524
+ async def get_excluded_functions(
525
+ self,
526
+ filter_fn: Callable[[Sequence[str]], Awaitable[Sequence[str]]] | None = None,
527
+ ) -> dict[str, Function]:
528
+ """
529
+ Returns a dictionary of all functions in the function group which are configured to be excluded or filtered
530
+ out by a filter function or per-function filter function.
531
+
532
+ Parameters
533
+ ----------
534
+ filter_fn : Callable[[Sequence[str]], Awaitable[Sequence[str]]] | None, optional
535
+ A callback function to additionally filter the functions in the function group dynamically. If not provided
536
+ then fall back to the function group's filter function. If no filter function is set for the function group
537
+ then no functions will be added to the returned dictionary.
538
+
539
+ Returns
540
+ -------
541
+ dict[str, Function]
542
+ A dictionary of all excluded functions in the function group.
543
+
544
+ Raises
545
+ ------
546
+ ValueError
547
+ When the function group is configured to exclude functions that are not found in the group.
548
+ """
549
+ missing = set(self._config.exclude) - set(self._functions.keys())
550
+ if missing:
551
+ raise ValueError(f"Unknown excluded functions: {sorted(missing)}")
552
+
553
+ if filter_fn is None:
554
+ if self._filter_fn is None:
555
+
556
+ async def identity_filter(x: Sequence[str]) -> Sequence[str]:
557
+ return x
558
+
559
+ filter_fn = identity_filter
560
+ else:
561
+ filter_fn = self._filter_fn
562
+
563
+ excluded = set(self._config.exclude)
564
+ included = set(await filter_fn(list(self._functions.keys())))
565
+
566
+ result = {}
567
+ for name in self._functions:
568
+ is_excluded = False
569
+ if name in excluded:
570
+ is_excluded = True
571
+ elif not await self._fn_should_be_included(name):
572
+ is_excluded = True
573
+ elif name not in included:
574
+ is_excluded = True
575
+
576
+ if is_excluded:
577
+ result[self._get_fn_name(name)] = self._functions[name]
578
+
579
+ return result
580
+
581
+ async def get_included_functions(
582
+ self,
583
+ filter_fn: Callable[[Sequence[str]], Awaitable[Sequence[str]]] | None = None,
584
+ ) -> dict[str, Function]:
585
+ """
586
+ Returns a dictionary of all functions in the function group which are:
587
+ - configured to be included and added to the global function registry
588
+ - not configured to be excluded.
589
+ - not filtered out by a filter function.
590
+
591
+ Parameters
592
+ ----------
593
+ filter_fn : Callable[[Sequence[str]], Awaitable[Sequence[str]]] | None, optional
594
+ A callback function to additionally filter the functions in the function group dynamically. If not provided
595
+ then fall back to the function group's filter function. If no filter function is set for the function group
596
+ all functions will be returned.
597
+
598
+ Returns
599
+ -------
600
+ dict[str, Function]
601
+ A dictionary of all included functions in the function group.
602
+
603
+ Raises
604
+ ------
605
+ ValueError
606
+ When the function group is configured to include functions that are not found in the group.
607
+ """
608
+ missing = set(self._config.include) - set(self._functions.keys())
609
+ if missing:
610
+ raise ValueError(f"Unknown included functions: {sorted(missing)}")
611
+
612
+ if filter_fn is None:
613
+ if self._filter_fn is None:
614
+
615
+ async def identity_filter(x: Sequence[str]) -> Sequence[str]:
616
+ return x
617
+
618
+ filter_fn = identity_filter
619
+ else:
620
+ filter_fn = self._filter_fn
621
+
622
+ included = set(await filter_fn(list(self._config.include)))
623
+ result = {}
624
+ for name in included:
625
+ if await self._fn_should_be_included(name):
626
+ result[self._get_fn_name(name)] = self._functions[name]
627
+ return result
628
+
629
+ async def get_all_functions(
630
+ self,
631
+ filter_fn: Callable[[Sequence[str]], Awaitable[Sequence[str]]] | None = None,
632
+ ) -> dict[str, Function]:
633
+ """
634
+ Returns a dictionary of all functions in the function group, regardless if they are included or excluded.
635
+
636
+ If a filter function has been set, the returned functions will additionally be filtered by the callback.
637
+
638
+ Parameters
639
+ ----------
640
+ filter_fn : Callable[[Sequence[str]], Awaitable[Sequence[str]]] | None, optional
641
+ A callback function to additionally filter the functions in the function group dynamically. If not provided
642
+ then fall back to the function group's filter function. If no filter function is set for the function group
643
+ all functions will be returned.
644
+
645
+ Returns
646
+ -------
647
+ dict[str, Function]
648
+ A dictionary of all functions in the function group.
649
+ """
650
+ if filter_fn is None:
651
+ if self._filter_fn is None:
652
+
653
+ async def identity_filter(x: Sequence[str]) -> Sequence[str]:
654
+ return x
655
+
656
+ filter_fn = identity_filter
657
+ else:
658
+ filter_fn = self._filter_fn
659
+
660
+ included = set(await filter_fn(list(self._functions.keys())))
661
+ result = {}
662
+ for name in included:
663
+ if await self._fn_should_be_included(name):
664
+ result[self._get_fn_name(name)] = self._functions[name]
665
+ return result
666
+
667
+ def set_filter_fn(self, filter_fn: Callable[[Sequence[str]], Awaitable[Sequence[str]]]):
668
+ """
669
+ Sets the filter function for the function group.
670
+
671
+ Parameters
672
+ ----------
673
+ filter_fn : Callable[[Sequence[str]], Awaitable[Sequence[str]]]
674
+ The filter function to set for the function group.
675
+ """
676
+ self._filter_fn = filter_fn
677
+
678
+ def set_per_function_filter_fn(self, name: str, filter_fn: Callable[[str], Awaitable[bool]]):
679
+ """
680
+ Sets the a per-function filter function for the a function within the function group.
681
+
682
+ Parameters
683
+ ----------
684
+ name : str
685
+ The name of the function.
686
+ filter_fn : Callable[[str], Awaitable[bool]]
687
+ The per-function filter function to set for the function group.
688
+
689
+ Raises
690
+ ------
691
+ ValueError
692
+ When the function is not found in the function group.
693
+ """
694
+ if name not in self._functions:
695
+ raise ValueError(f"Function {name} not found in function group {self._instance_name}")
696
+ self._per_function_filter_fn[name] = filter_fn
697
+
698
+ def set_instance_name(self, instance_name: str):
699
+ """
700
+ Sets the instance name for the function group.
701
+
702
+ Parameters
703
+ ----------
704
+ instance_name : str
705
+ The instance name to set for the function group.
706
+ """
707
+ self._instance_name = instance_name
708
+
709
+ @property
710
+ def instance_name(self) -> str:
711
+ """
712
+ Returns the instance name for the function group.
713
+ """
714
+ return self._instance_name