nvidia-nat 1.3.0.dev2__py3-none-any.whl → 1.3.0rc1__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 (242) 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 +41 -21
  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 +46 -26
  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 +40 -20
  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 +46 -11
  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 +0 -1
  51. nat/cli/commands/workflow/templates/pyproject.toml.j2 +4 -1
  52. nat/cli/commands/workflow/templates/register.py.j2 +0 -1
  53. nat/cli/commands/workflow/workflow_commands.py +9 -13
  54. nat/cli/entrypoint.py +8 -10
  55. nat/cli/register_workflow.py +38 -4
  56. nat/cli/type_registry.py +75 -6
  57. nat/control_flow/__init__.py +0 -0
  58. nat/control_flow/register.py +20 -0
  59. nat/control_flow/router_agent/__init__.py +0 -0
  60. nat/control_flow/router_agent/agent.py +329 -0
  61. nat/control_flow/router_agent/prompt.py +48 -0
  62. nat/control_flow/router_agent/register.py +91 -0
  63. nat/control_flow/sequential_executor.py +166 -0
  64. nat/data_models/agent.py +34 -0
  65. nat/data_models/api_server.py +10 -10
  66. nat/data_models/authentication.py +23 -9
  67. nat/data_models/common.py +1 -1
  68. nat/data_models/component.py +2 -0
  69. nat/data_models/component_ref.py +11 -0
  70. nat/data_models/config.py +41 -17
  71. nat/data_models/dataset_handler.py +1 -1
  72. nat/data_models/discovery_metadata.py +4 -4
  73. nat/data_models/evaluate.py +4 -1
  74. nat/data_models/function.py +34 -0
  75. nat/data_models/function_dependencies.py +14 -6
  76. nat/data_models/gated_field_mixin.py +242 -0
  77. nat/data_models/intermediate_step.py +3 -3
  78. nat/data_models/optimizable.py +119 -0
  79. nat/data_models/optimizer.py +149 -0
  80. nat/data_models/swe_bench_model.py +1 -1
  81. nat/data_models/temperature_mixin.py +44 -0
  82. nat/data_models/thinking_mixin.py +86 -0
  83. nat/data_models/top_p_mixin.py +44 -0
  84. nat/embedder/nim_embedder.py +1 -1
  85. nat/embedder/openai_embedder.py +1 -1
  86. nat/embedder/register.py +0 -1
  87. nat/eval/config.py +3 -1
  88. nat/eval/dataset_handler/dataset_handler.py +71 -7
  89. nat/eval/evaluate.py +86 -31
  90. nat/eval/evaluator/base_evaluator.py +1 -1
  91. nat/eval/evaluator/evaluator_model.py +13 -0
  92. nat/eval/intermediate_step_adapter.py +1 -1
  93. nat/eval/rag_evaluator/evaluate.py +2 -2
  94. nat/eval/rag_evaluator/register.py +3 -3
  95. nat/eval/register.py +4 -1
  96. nat/eval/remote_workflow.py +3 -3
  97. nat/eval/runtime_evaluator/__init__.py +14 -0
  98. nat/eval/runtime_evaluator/evaluate.py +123 -0
  99. nat/eval/runtime_evaluator/register.py +100 -0
  100. nat/eval/swe_bench_evaluator/evaluate.py +6 -6
  101. nat/eval/trajectory_evaluator/evaluate.py +1 -1
  102. nat/eval/trajectory_evaluator/register.py +1 -1
  103. nat/eval/tunable_rag_evaluator/evaluate.py +4 -7
  104. nat/eval/utils/eval_trace_ctx.py +89 -0
  105. nat/eval/utils/weave_eval.py +18 -9
  106. nat/experimental/decorators/experimental_warning_decorator.py +27 -7
  107. nat/experimental/test_time_compute/functions/plan_select_execute_function.py +7 -3
  108. nat/experimental/test_time_compute/functions/ttc_tool_orchestration_function.py +3 -3
  109. nat/experimental/test_time_compute/functions/ttc_tool_wrapper_function.py +1 -1
  110. nat/experimental/test_time_compute/models/strategy_base.py +5 -4
  111. nat/experimental/test_time_compute/register.py +0 -1
  112. nat/experimental/test_time_compute/selection/llm_based_output_merging_selector.py +1 -3
  113. nat/front_ends/console/authentication_flow_handler.py +82 -30
  114. nat/front_ends/console/console_front_end_plugin.py +8 -5
  115. nat/front_ends/fastapi/auth_flow_handlers/websocket_flow_handler.py +52 -17
  116. nat/front_ends/fastapi/dask_client_mixin.py +65 -0
  117. nat/front_ends/fastapi/fastapi_front_end_config.py +36 -5
  118. nat/front_ends/fastapi/fastapi_front_end_controller.py +4 -4
  119. nat/front_ends/fastapi/fastapi_front_end_plugin.py +135 -4
  120. nat/front_ends/fastapi/fastapi_front_end_plugin_worker.py +481 -281
  121. nat/front_ends/fastapi/job_store.py +518 -99
  122. nat/front_ends/fastapi/main.py +11 -19
  123. nat/front_ends/fastapi/message_handler.py +13 -14
  124. nat/front_ends/fastapi/message_validator.py +17 -19
  125. nat/front_ends/fastapi/response_helpers.py +4 -4
  126. nat/front_ends/fastapi/step_adaptor.py +2 -2
  127. nat/front_ends/fastapi/utils.py +57 -0
  128. nat/front_ends/mcp/introspection_token_verifier.py +73 -0
  129. nat/front_ends/mcp/mcp_front_end_config.py +10 -1
  130. nat/front_ends/mcp/mcp_front_end_plugin.py +45 -13
  131. nat/front_ends/mcp/mcp_front_end_plugin_worker.py +116 -8
  132. nat/front_ends/mcp/tool_converter.py +44 -14
  133. nat/front_ends/register.py +0 -1
  134. nat/front_ends/simple_base/simple_front_end_plugin_base.py +3 -1
  135. nat/llm/aws_bedrock_llm.py +24 -12
  136. nat/llm/azure_openai_llm.py +13 -6
  137. nat/llm/litellm_llm.py +69 -0
  138. nat/llm/nim_llm.py +20 -8
  139. nat/llm/openai_llm.py +14 -6
  140. nat/llm/register.py +4 -1
  141. nat/llm/utils/env_config_value.py +2 -3
  142. nat/llm/utils/thinking.py +215 -0
  143. nat/meta/pypi.md +9 -9
  144. nat/object_store/register.py +0 -1
  145. nat/observability/exporter/base_exporter.py +3 -3
  146. nat/observability/exporter/file_exporter.py +1 -1
  147. nat/observability/exporter/processing_exporter.py +309 -81
  148. nat/observability/exporter/span_exporter.py +1 -1
  149. nat/observability/exporter_manager.py +7 -7
  150. nat/observability/mixin/file_mixin.py +7 -7
  151. nat/observability/mixin/redaction_config_mixin.py +42 -0
  152. nat/observability/mixin/tagging_config_mixin.py +62 -0
  153. nat/observability/mixin/type_introspection_mixin.py +420 -107
  154. nat/observability/processor/batching_processor.py +5 -7
  155. nat/observability/processor/falsy_batch_filter_processor.py +55 -0
  156. nat/observability/processor/processor.py +3 -0
  157. nat/observability/processor/processor_factory.py +70 -0
  158. nat/observability/processor/redaction/__init__.py +24 -0
  159. nat/observability/processor/redaction/contextual_redaction_processor.py +125 -0
  160. nat/observability/processor/redaction/contextual_span_redaction_processor.py +66 -0
  161. nat/observability/processor/redaction/redaction_processor.py +177 -0
  162. nat/observability/processor/redaction/span_header_redaction_processor.py +92 -0
  163. nat/observability/processor/span_tagging_processor.py +68 -0
  164. nat/observability/register.py +6 -4
  165. nat/profiler/calc/calc_runner.py +3 -4
  166. nat/profiler/callbacks/agno_callback_handler.py +1 -1
  167. nat/profiler/callbacks/langchain_callback_handler.py +6 -6
  168. nat/profiler/callbacks/llama_index_callback_handler.py +3 -3
  169. nat/profiler/callbacks/semantic_kernel_callback_handler.py +3 -3
  170. nat/profiler/data_frame_row.py +1 -1
  171. nat/profiler/decorators/framework_wrapper.py +62 -13
  172. nat/profiler/decorators/function_tracking.py +160 -3
  173. nat/profiler/forecasting/models/forecasting_base_model.py +3 -1
  174. nat/profiler/inference_optimization/bottleneck_analysis/simple_stack_analysis.py +1 -1
  175. nat/profiler/inference_optimization/data_models.py +3 -3
  176. nat/profiler/inference_optimization/experimental/prefix_span_analysis.py +7 -8
  177. nat/profiler/inference_optimization/token_uniqueness.py +1 -1
  178. nat/profiler/parameter_optimization/__init__.py +0 -0
  179. nat/profiler/parameter_optimization/optimizable_utils.py +93 -0
  180. nat/profiler/parameter_optimization/optimizer_runtime.py +67 -0
  181. nat/profiler/parameter_optimization/parameter_optimizer.py +153 -0
  182. nat/profiler/parameter_optimization/parameter_selection.py +107 -0
  183. nat/profiler/parameter_optimization/pareto_visualizer.py +380 -0
  184. nat/profiler/parameter_optimization/prompt_optimizer.py +384 -0
  185. nat/profiler/parameter_optimization/update_helpers.py +66 -0
  186. nat/profiler/profile_runner.py +14 -9
  187. nat/profiler/utils.py +4 -2
  188. nat/registry_handlers/local/local_handler.py +2 -2
  189. nat/registry_handlers/package_utils.py +1 -2
  190. nat/registry_handlers/pypi/pypi_handler.py +23 -26
  191. nat/registry_handlers/register.py +3 -4
  192. nat/registry_handlers/rest/rest_handler.py +12 -13
  193. nat/retriever/milvus/retriever.py +2 -2
  194. nat/retriever/nemo_retriever/retriever.py +1 -1
  195. nat/retriever/register.py +0 -1
  196. nat/runtime/loader.py +2 -2
  197. nat/runtime/runner.py +3 -2
  198. nat/runtime/session.py +43 -8
  199. nat/settings/global_settings.py +16 -5
  200. nat/tool/chat_completion.py +5 -2
  201. nat/tool/code_execution/local_sandbox/local_sandbox_server.py +3 -3
  202. nat/tool/datetime_tools.py +49 -9
  203. nat/tool/document_search.py +2 -2
  204. nat/tool/github_tools.py +450 -0
  205. nat/tool/nvidia_rag.py +1 -1
  206. nat/tool/register.py +2 -9
  207. nat/tool/retriever.py +3 -2
  208. nat/utils/callable_utils.py +70 -0
  209. nat/utils/data_models/schema_validator.py +3 -3
  210. nat/utils/exception_handlers/automatic_retries.py +104 -51
  211. nat/utils/exception_handlers/schemas.py +1 -1
  212. nat/utils/io/yaml_tools.py +2 -2
  213. nat/utils/log_levels.py +25 -0
  214. nat/utils/reactive/base/observable_base.py +2 -2
  215. nat/utils/reactive/base/observer_base.py +1 -1
  216. nat/utils/reactive/observable.py +2 -2
  217. nat/utils/reactive/observer.py +4 -4
  218. nat/utils/reactive/subscription.py +1 -1
  219. nat/utils/settings/global_settings.py +6 -8
  220. nat/utils/type_converter.py +4 -3
  221. nat/utils/type_utils.py +9 -5
  222. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc1.dist-info}/METADATA +42 -16
  223. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc1.dist-info}/RECORD +230 -189
  224. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc1.dist-info}/entry_points.txt +1 -0
  225. nat/cli/commands/info/list_mcp.py +0 -304
  226. nat/tool/github_tools/create_github_commit.py +0 -133
  227. nat/tool/github_tools/create_github_issue.py +0 -87
  228. nat/tool/github_tools/create_github_pr.py +0 -106
  229. nat/tool/github_tools/get_github_file.py +0 -106
  230. nat/tool/github_tools/get_github_issue.py +0 -166
  231. nat/tool/github_tools/get_github_pr.py +0 -256
  232. nat/tool/github_tools/update_github_issue.py +0 -100
  233. nat/tool/mcp/exceptions.py +0 -142
  234. nat/tool/mcp/mcp_client.py +0 -255
  235. nat/tool/mcp/mcp_tool.py +0 -96
  236. nat/utils/exception_handlers/mcp.py +0 -211
  237. /nat/{tool/github_tools → agent/prompt_optimizer}/__init__.py +0 -0
  238. /nat/{tool/mcp → authentication/credential_validator}/__init__.py +0 -0
  239. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc1.dist-info}/WHEEL +0 -0
  240. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc1.dist-info}/licenses/LICENSE-3rd-party.txt +0 -0
  241. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc1.dist-info}/licenses/LICENSE.md +0 -0
  242. {nvidia_nat-1.3.0.dev2.dist-info → nvidia_nat-1.3.0rc1.dist-info}/top_level.txt +0 -0
@@ -42,15 +42,14 @@ logger = logging.getLogger(__name__)
42
42
  class RestRegistryHandler(AbstractRegistryHandler):
43
43
  """A registry handler for interactions with a remote REST registry."""
44
44
 
45
- def __init__( # pylint: disable=R0917
46
- self,
47
- endpoint: str,
48
- token: str,
49
- timeout: int = 30,
50
- publish_route: str = "",
51
- pull_route: str = "",
52
- search_route: str = "",
53
- remove_route: str = ""):
45
+ def __init__(self,
46
+ endpoint: str,
47
+ token: str,
48
+ timeout: int = 30,
49
+ publish_route: str = "",
50
+ pull_route: str = "",
51
+ search_route: str = "",
52
+ remove_route: str = ""):
54
53
  super().__init__()
55
54
  self._endpoint = endpoint.rstrip("/")
56
55
  self._timeout = timeout
@@ -89,7 +88,7 @@ class RestRegistryHandler(AbstractRegistryHandler):
89
88
  validated_publish_response = PublishResponse(status={
90
89
  "status": StatusEnum.ERROR, "message": msg, "action": ActionEnum.PUBLISH
91
90
  })
92
- logger.exception(validated_publish_response.status.message, exc_info=True)
91
+ logger.exception(validated_publish_response.status.message)
93
92
 
94
93
  yield validated_publish_response
95
94
 
@@ -156,7 +155,7 @@ class RestRegistryHandler(AbstractRegistryHandler):
156
155
  validated_pull_response = PullResponse(status={
157
156
  "status": StatusEnum.ERROR, "message": msg, "action": ActionEnum.PULL
158
157
  })
159
- logger.exception(validated_pull_response.status.message, exc_info=True)
158
+ logger.exception(validated_pull_response.status.message)
160
159
 
161
160
  yield validated_pull_response
162
161
 
@@ -194,7 +193,7 @@ class RestRegistryHandler(AbstractRegistryHandler):
194
193
  "message": msg,
195
194
  "action": ActionEnum.SEARCH
196
195
  })
197
- logger.exception(validated_search_response.status.message, exc_info=True)
196
+ logger.exception(validated_search_response.status.message)
198
197
 
199
198
  yield validated_search_response
200
199
 
@@ -229,7 +228,7 @@ class RestRegistryHandler(AbstractRegistryHandler):
229
228
  validated_remove_response = RemoveResponse(status={
230
229
  "status": StatusEnum.ERROR, "message": msg, "action": ActionEnum.REMOVE
231
230
  })
232
- logger.exception(validated_remove_response.status.message, exc_info=True)
231
+ logger.exception(validated_remove_response.status.message)
233
232
 
234
233
  yield validated_remove_response
235
234
 
@@ -154,7 +154,7 @@ class MilvusRetriever(Retriever):
154
154
  return _wrap_milvus_results(results, content_field=self.content_field)
155
155
 
156
156
  except Exception as e:
157
- logger.exception("Exception when retrieving results from milvus for query %s: %s", query, e)
157
+ logger.error("Exception when retrieving results from milvus for query %s: %s", query, e)
158
158
  raise RetrieverError(f"Error when retrieving documents from {collection_name} for query '{query}'") from e
159
159
 
160
160
  async def _search(self,
@@ -214,7 +214,7 @@ def _wrap_milvus_results(res: list[Hit], content_field: str):
214
214
 
215
215
 
216
216
  def _wrap_milvus_single_results(res: Hit | dict, content_field: str) -> Document:
217
- if not isinstance(res, (Hit, dict)):
217
+ if not isinstance(res, Hit | dict):
218
218
  raise ValueError(f"Milvus search returned object of type {type(res)}. Expected 'Hit' or 'dict'.")
219
219
 
220
220
  if isinstance(res, Hit):
@@ -143,7 +143,7 @@ class NemoRetriever(Retriever):
143
143
  return _wrap_nemo_results(output=output, content_field="content")
144
144
 
145
145
  except Exception as e:
146
- logger.exception("Encountered an error when retrieving results from Nemo Retriever: %s", e)
146
+ logger.error("Encountered an error when retrieving results from Nemo Retriever: %s", e)
147
147
  raise CollectionUnavailableError(
148
148
  f"Error when retrieving documents from {collection_name} for query '{query}'") from e
149
149
 
nat/retriever/register.py CHANGED
@@ -13,7 +13,6 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
 
16
- # pylint: disable=unused-import
17
16
  # flake8: noqa
18
17
  # isort:skip_file
19
18
 
nat/runtime/loader.py CHANGED
@@ -114,7 +114,7 @@ async def load_workflow(config_file: StrPath, max_concurrency: int = -1):
114
114
  # Must yield the workflow function otherwise it cleans up
115
115
  async with WorkflowBuilder.from_config(config=config) as workflow:
116
116
 
117
- yield SessionManager(workflow.build(), max_concurrency=max_concurrency)
117
+ yield SessionManager(await workflow.build(), max_concurrency=max_concurrency)
118
118
 
119
119
 
120
120
  @lru_cache
@@ -210,7 +210,7 @@ def discover_and_register_plugins(plugin_type: PluginTypes):
210
210
  # Optionally, you can mark the plugin as unavailable or take other actions
211
211
 
212
212
  except Exception:
213
- logger.exception("An error occurred while loading plugin '%s': {e}", entry_point.name, exc_info=True)
213
+ logger.exception("An error occurred while loading plugin '%s'", entry_point.name)
214
214
 
215
215
  finally:
216
216
  count += 1
nat/runtime/runner.py CHANGED
@@ -149,7 +149,8 @@ class Runner:
149
149
 
150
150
  return result
151
151
  except Exception as e:
152
- logger.exception("Error running workflow: %s", e)
152
+ err_msg = f": {e}" if str(e).strip() else "."
153
+ logger.error("Error running workflow%s", err_msg)
153
154
  event_stream = self._context_state.event_stream.get()
154
155
  if event_stream:
155
156
  event_stream.on_complete()
@@ -181,7 +182,7 @@ class Runner:
181
182
  event_stream.on_complete()
182
183
 
183
184
  except Exception as e:
184
- logger.exception("Error running workflow: %s", e)
185
+ logger.error("Error running workflow: %s", e)
185
186
  event_stream = self._context_state.event_stream.get()
186
187
  if event_stream:
187
188
  event_stream.on_complete()
nat/runtime/session.py CHANGED
@@ -21,7 +21,9 @@ from collections.abc import Callable
21
21
  from contextlib import asynccontextmanager
22
22
  from contextlib import nullcontext
23
23
 
24
+ from fastapi import WebSocket
24
25
  from starlette.requests import HTTPConnection
26
+ from starlette.requests import Request
25
27
 
26
28
  from nat.builder.context import Context
27
29
  from nat.builder.context import ContextState
@@ -89,7 +91,8 @@ class SessionManager:
89
91
  @asynccontextmanager
90
92
  async def session(self,
91
93
  user_manager=None,
92
- request: HTTPConnection | None = None,
94
+ http_connection: HTTPConnection | None = None,
95
+ user_message_id: str | None = None,
93
96
  conversation_id: str | None = None,
94
97
  user_input_callback: Callable[[InteractionPrompt], Awaitable[HumanResponse]] = None,
95
98
  user_authentication_callback: Callable[[AuthProviderBaseConfig, AuthFlowType],
@@ -107,10 +110,11 @@ class SessionManager:
107
110
  if user_authentication_callback is not None:
108
111
  token_user_authentication = self._context_state.user_auth_callback.set(user_authentication_callback)
109
112
 
110
- if conversation_id is not None and request is None:
111
- self._context_state.conversation_id.set(conversation_id)
113
+ if isinstance(http_connection, WebSocket):
114
+ self.set_metadata_from_websocket(http_connection, user_message_id, conversation_id)
112
115
 
113
- self.set_metadata_from_http_request(request)
116
+ if isinstance(http_connection, Request):
117
+ self.set_metadata_from_http_request(http_connection)
114
118
 
115
119
  try:
116
120
  yield self
@@ -135,14 +139,11 @@ class SessionManager:
135
139
  async with self._workflow.run(message) as runner:
136
140
  yield runner
137
141
 
138
- def set_metadata_from_http_request(self, request: HTTPConnection | None) -> None:
142
+ def set_metadata_from_http_request(self, request: Request) -> None:
139
143
  """
140
144
  Extracts and sets user metadata request attributes from a HTTP request.
141
145
  If request is None, no attributes are set.
142
146
  """
143
- if request is None:
144
- return
145
-
146
147
  self._context.metadata._request.method = getattr(request, "method", None)
147
148
  self._context.metadata._request.url_path = request.url.path
148
149
  self._context.metadata._request.url_port = request.url.port
@@ -157,6 +158,40 @@ class SessionManager:
157
158
  if request.headers.get("conversation-id"):
158
159
  self._context_state.conversation_id.set(request.headers["conversation-id"])
159
160
 
161
+ if request.headers.get("user-message-id"):
162
+ self._context_state.user_message_id.set(request.headers["user-message-id"])
163
+
164
+ def set_metadata_from_websocket(self,
165
+ websocket: WebSocket,
166
+ user_message_id: str | None,
167
+ conversation_id: str | None) -> None:
168
+ """
169
+ Extracts and sets user metadata for Websocket connections.
170
+ """
171
+
172
+ # Extract cookies from WebSocket headers (similar to HTTP request)
173
+ if websocket and hasattr(websocket, 'scope') and 'headers' in websocket.scope:
174
+ cookies = {}
175
+ for header_name, header_value in websocket.scope.get('headers', []):
176
+ if header_name == b'cookie':
177
+ cookie_header = header_value.decode('utf-8')
178
+ # Parse cookie header: "name1=value1; name2=value2"
179
+ for cookie in cookie_header.split(';'):
180
+ cookie = cookie.strip()
181
+ if '=' in cookie:
182
+ name, value = cookie.split('=', 1)
183
+ cookies[name.strip()] = value.strip()
184
+
185
+ # Set cookies in metadata (same as HTTP request)
186
+ self._context.metadata._request.cookies = cookies
187
+ self._context_state.metadata.set(self._context.metadata)
188
+
189
+ if conversation_id is not None:
190
+ self._context_state.conversation_id.set(conversation_id)
191
+
192
+ if user_message_id is not None:
193
+ self._context_state.user_message_id.set(user_message_id)
194
+
160
195
 
161
196
  # Compatibility aliases with previous releases
162
197
  AIQSessionManager = SessionManager
@@ -47,6 +47,12 @@ class Settings(HashableBaseModel):
47
47
  # Registry Handeler Configuration
48
48
  channels: dict[str, RegistryHandlerBaseConfig] = {}
49
49
 
50
+ # Timezone fallback behavior
51
+ # Options:
52
+ # - "utc": default to UTC
53
+ # - "system": use the system's local timezone
54
+ fallback_timezone: typing.Literal["system", "utc"] = "utc"
55
+
50
56
  _configuration_directory: typing.ClassVar[str]
51
57
  _settings_changed_hooks: typing.ClassVar[list[Callable[[], None]]] = []
52
58
  _settings_changed_hooks_active: bool = True
@@ -118,8 +124,7 @@ class Settings(HashableBaseModel):
118
124
  if (short_names[key.local_name] == 1):
119
125
  type_list.append((key.local_name, key.config_type))
120
126
 
121
- # pylint: disable=consider-alternative-union-syntax
122
- return typing.Union[tuple(typing.Annotated[x_type, Tag(x_id)] for x_id, x_type in type_list)]
127
+ return typing.Union[*tuple(typing.Annotated[x_type, Tag(x_id)] for x_id, x_type in type_list)]
123
128
 
124
129
  RegistryHandlerAnnotation = dict[
125
130
  str,
@@ -164,8 +169,12 @@ class Settings(HashableBaseModel):
164
169
  if (not os.path.exists(configuration_file)):
165
170
  loaded_config = {}
166
171
  else:
167
- with open(file_path, mode="r", encoding="utf-8") as f:
168
- loaded_config = json.load(f)
172
+ with open(file_path, encoding="utf-8") as f:
173
+ try:
174
+ loaded_config = json.load(f)
175
+ except Exception as e:
176
+ logger.exception("Error loading configuration file %s: %s", file_path, e)
177
+ loaded_config = {}
169
178
 
170
179
  settings = Settings(**loaded_config)
171
180
  settings.set_configuration_directory(configuration_directory)
@@ -214,13 +223,15 @@ class Settings(HashableBaseModel):
214
223
  match field:
215
224
  case "channels":
216
225
  self.channels = validated_data.channels
226
+ case "fallback_timezone":
227
+ self.fallback_timezone = validated_data.fallback_timezone
217
228
  case _:
218
229
  raise ValueError(f"Encountered invalid model field: {field}")
219
230
 
220
231
  return True
221
232
 
222
233
  except Exception as e:
223
- logger.exception("Unable to validate user settings configuration: %s", e, exc_info=True)
234
+ logger.exception("Unable to validate user settings configuration: %s", e)
224
235
  return False
225
236
 
226
237
  def print_channel_settings(self, channel_type: str | None = None) -> None:
@@ -44,7 +44,7 @@ async def register_chat_completion(config: ChatCompletionConfig, builder: Builde
44
44
  """Registers a chat completion function that can handle natural language queries."""
45
45
 
46
46
  # Get the LLM from the builder context using the configured LLM reference
47
- # Use LangChain framework wrapper since we're using LangChain-based LLM
47
+ # Use LangChain/LangGraph framework wrapper since we're using LangChain/LangGraph-based LLM
48
48
  llm = await builder.get_llm(config.llm_name, wrapper_type=LLMFrameworkEnum.LANGCHAIN)
49
49
 
50
50
  async def _chat_completion(query: str) -> str:
@@ -63,7 +63,10 @@ async def register_chat_completion(config: ChatCompletionConfig, builder: Builde
63
63
  # Generate response using the LLM
64
64
  response = await llm.ainvoke(prompt)
65
65
 
66
- return response
66
+ if isinstance(response, str):
67
+ return response
68
+
69
+ return response.text()
67
70
 
68
71
  except Exception as e:
69
72
  # Fallback response if LLM call fails
@@ -62,7 +62,7 @@ class CodeExecutionResponse(Response):
62
62
  super().__init__(status=status_code, mimetype="application/json", response=result.model_dump_json())
63
63
 
64
64
  @classmethod
65
- def with_error(cls, status_code: int, error_message: str) -> 'CodeExecutionResponse':
65
+ def with_error(cls, status_code: int, error_message: str) -> CodeExecutionResponse:
66
66
  return cls(status_code,
67
67
  CodeExecutionResult(process_status=CodeExecutionStatus.ERROR, stdout="", stderr=error_message))
68
68
 
@@ -121,13 +121,13 @@ def execute_code_subprocess(generated_code: str, queue):
121
121
  resource.setrlimit(resource.RLIMIT_AS, (limit, limit))
122
122
  resource.setrlimit(resource.RLIMIT_DATA, (limit, limit))
123
123
  except Exception as e:
124
- logger.error("Failed to set resource limits, PID: %s, error: %s", os.getpid(), e)
124
+ logger.exception("Failed to set resource limits, PID: %s, error: %s", os.getpid(), e)
125
125
 
126
126
  stdout_capture = StringIO()
127
127
  stderr_capture = StringIO()
128
128
  try:
129
129
  with contextlib.redirect_stdout(stdout_capture), contextlib.redirect_stderr(stderr_capture):
130
- exec(generated_code, {}) # pylint: disable=W0122
130
+ exec(generated_code, {})
131
131
  logger.debug("execute_code_subprocess finished, PID: %s", os.getpid())
132
132
  queue.put(CodeExecutionResult(stdout=stdout_capture.getvalue(), stderr=stderr_capture.getvalue()))
133
133
  except Exception as e:
@@ -13,30 +13,70 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
 
16
+ import datetime
17
+ import zoneinfo
18
+
19
+ from starlette.datastructures import Headers
20
+
16
21
  from nat.builder.builder import Builder
17
22
  from nat.builder.function_info import FunctionInfo
18
23
  from nat.cli.register_workflow import register_function
19
24
  from nat.data_models.function import FunctionBaseConfig
25
+ from nat.settings.global_settings import GlobalSettings
20
26
 
21
27
 
22
28
  class CurrentTimeToolConfig(FunctionBaseConfig, name="current_datetime"):
23
29
  """
24
- Simple tool which returns the current date and time in human readable format.
30
+ Simple tool which returns the current date and time in human readable format with timezone information. By default,
31
+ the timezone is in Etc/UTC. If the user provides a timezone in the header, we will use it. Timezone will be
32
+ provided in IANA zone name format. For example, "America/New_York" or "Etc/UTC".
25
33
  """
26
34
  pass
27
35
 
28
36
 
29
- @register_function(config_type=CurrentTimeToolConfig)
30
- async def current_datetime(config: CurrentTimeToolConfig, builder: Builder):
37
+ def _get_timezone_obj(headers: Headers | None) -> zoneinfo.ZoneInfo | datetime.tzinfo:
38
+ # Default to UTC
39
+ timezone_obj = zoneinfo.ZoneInfo("Etc/UTC")
40
+
41
+ if headers:
42
+ # If user has provided a timezone in the header, we will prioritize on using it
43
+ timezone_header = headers.get("x-timezone")
44
+ if timezone_header:
45
+ try:
46
+ timezone_obj = zoneinfo.ZoneInfo(timezone_header)
47
+ except Exception:
48
+ pass
49
+ else:
50
+ # Only if a timezone is not in the header, we will determine default timezone based on global settings
51
+ fallback_tz = GlobalSettings.get().fallback_timezone
52
+
53
+ if fallback_tz == "system":
54
+ # Use the system's local timezone. Avoid requiring external deps.
55
+ timezone_obj = datetime.datetime.now().astimezone().tzinfo or zoneinfo.ZoneInfo("Etc/UTC")
56
+
57
+ return timezone_obj
31
58
 
32
- import datetime
59
+
60
+ @register_function(config_type=CurrentTimeToolConfig)
61
+ async def current_datetime(_config: CurrentTimeToolConfig, _builder: Builder):
33
62
 
34
63
  async def _get_current_time(unused: str) -> str:
35
64
 
36
- now = datetime.datetime.now() # Get current time
37
- now_human_readable = now.strftime(("%Y-%m-%d %H:%M:%S"))
65
+ del unused # Unused parameter to avoid linting error
66
+
67
+ from nat.builder.context import Context
68
+ nat_context = Context.get()
69
+
70
+ headers: Headers | None = nat_context.metadata.headers
71
+
72
+ timezone_obj = _get_timezone_obj(headers)
73
+
74
+ now = datetime.datetime.now(timezone_obj)
75
+ now_machine_readable = now.strftime("%Y-%m-%d %H:%M:%S %z")
38
76
 
39
- return f"The current time of day is {now_human_readable}" # Format time in H:MM AM/PM format
77
+ # Returns the current time in machine readable format with timezone offset.
78
+ return f"The current time of day is {now_machine_readable}"
40
79
 
41
- yield FunctionInfo.from_fn(_get_current_time,
42
- description="Returns the current date and time in human readable format.")
80
+ yield FunctionInfo.from_fn(
81
+ _get_current_time,
82
+ description="Returns the current date and time in human readable format with timezone information.")
@@ -53,7 +53,7 @@ async def document_search(config: MilvusDocumentSearchToolConfig, builder: Build
53
53
  from langchain_core.messages import HumanMessage
54
54
  from langchain_core.messages import SystemMessage
55
55
  from langchain_core.pydantic_v1 import BaseModel
56
- from langchain_core.pydantic_v1 import Field # pylint: disable=redefined-outer-name, reimported
56
+ from langchain_core.pydantic_v1 import Field
57
57
 
58
58
  # define collection store
59
59
  # create a list of tuples using enumerate()
@@ -119,7 +119,7 @@ Return only the name of the predicted collection."""
119
119
  if len(results["chunks"]) == 0:
120
120
  return DocumentSearchOutput(collection_name=llm_pred.collection_name, documents="")
121
121
 
122
- # parse docs from Langchain Document object to string
122
+ # parse docs from LangChain/LangGraph Document object to string
123
123
  parsed_docs = []
124
124
 
125
125
  # iterate over results and store parsed content