nvidia-nat 1.3.0a20250910__py3-none-any.whl → 1.4.0a20251112__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 (213) hide show
  1. nat/agent/base.py +13 -8
  2. nat/agent/prompt_optimizer/prompt.py +68 -0
  3. nat/agent/prompt_optimizer/register.py +149 -0
  4. nat/agent/react_agent/agent.py +6 -5
  5. nat/agent/react_agent/register.py +49 -39
  6. nat/agent/reasoning_agent/reasoning_agent.py +17 -15
  7. nat/agent/register.py +2 -0
  8. nat/agent/responses_api_agent/__init__.py +14 -0
  9. nat/agent/responses_api_agent/register.py +126 -0
  10. nat/agent/rewoo_agent/agent.py +304 -117
  11. nat/agent/rewoo_agent/prompt.py +19 -22
  12. nat/agent/rewoo_agent/register.py +51 -38
  13. nat/agent/tool_calling_agent/agent.py +75 -17
  14. nat/agent/tool_calling_agent/register.py +46 -23
  15. nat/authentication/api_key/api_key_auth_provider.py +6 -11
  16. nat/authentication/api_key/api_key_auth_provider_config.py +8 -5
  17. nat/authentication/credential_validator/__init__.py +14 -0
  18. nat/authentication/credential_validator/bearer_token_validator.py +557 -0
  19. nat/authentication/http_basic_auth/http_basic_auth_provider.py +1 -1
  20. nat/authentication/interfaces.py +5 -2
  21. nat/authentication/oauth2/oauth2_auth_code_flow_provider.py +69 -36
  22. nat/authentication/oauth2/oauth2_auth_code_flow_provider_config.py +2 -1
  23. nat/authentication/oauth2/oauth2_resource_server_config.py +125 -0
  24. nat/builder/builder.py +55 -23
  25. nat/builder/component_utils.py +9 -5
  26. nat/builder/context.py +54 -15
  27. nat/builder/eval_builder.py +14 -9
  28. nat/builder/framework_enum.py +1 -0
  29. nat/builder/front_end.py +1 -1
  30. nat/builder/function.py +370 -0
  31. nat/builder/function_info.py +1 -1
  32. nat/builder/intermediate_step_manager.py +38 -2
  33. nat/builder/workflow.py +5 -0
  34. nat/builder/workflow_builder.py +306 -54
  35. nat/cli/cli_utils/config_override.py +1 -1
  36. nat/cli/commands/info/info.py +16 -6
  37. nat/cli/commands/mcp/__init__.py +14 -0
  38. nat/cli/commands/mcp/mcp.py +986 -0
  39. nat/cli/commands/optimize.py +90 -0
  40. nat/cli/commands/start.py +1 -1
  41. nat/cli/commands/workflow/templates/config.yml.j2 +14 -13
  42. nat/cli/commands/workflow/templates/register.py.j2 +2 -2
  43. nat/cli/commands/workflow/templates/workflow.py.j2 +35 -21
  44. nat/cli/commands/workflow/workflow_commands.py +60 -18
  45. nat/cli/entrypoint.py +15 -11
  46. nat/cli/main.py +3 -0
  47. nat/cli/register_workflow.py +38 -4
  48. nat/cli/type_registry.py +72 -1
  49. nat/control_flow/__init__.py +0 -0
  50. nat/control_flow/register.py +20 -0
  51. nat/control_flow/router_agent/__init__.py +0 -0
  52. nat/control_flow/router_agent/agent.py +329 -0
  53. nat/control_flow/router_agent/prompt.py +48 -0
  54. nat/control_flow/router_agent/register.py +91 -0
  55. nat/control_flow/sequential_executor.py +166 -0
  56. nat/data_models/agent.py +34 -0
  57. nat/data_models/api_server.py +199 -69
  58. nat/data_models/authentication.py +23 -9
  59. nat/data_models/common.py +47 -0
  60. nat/data_models/component.py +2 -0
  61. nat/data_models/component_ref.py +11 -0
  62. nat/data_models/config.py +41 -17
  63. nat/data_models/dataset_handler.py +4 -3
  64. nat/data_models/function.py +34 -0
  65. nat/data_models/function_dependencies.py +8 -0
  66. nat/data_models/intermediate_step.py +9 -1
  67. nat/data_models/llm.py +15 -1
  68. nat/data_models/openai_mcp.py +46 -0
  69. nat/data_models/optimizable.py +208 -0
  70. nat/data_models/optimizer.py +161 -0
  71. nat/data_models/span.py +41 -3
  72. nat/data_models/thinking_mixin.py +2 -2
  73. nat/embedder/azure_openai_embedder.py +2 -1
  74. nat/embedder/nim_embedder.py +3 -2
  75. nat/embedder/openai_embedder.py +3 -2
  76. nat/eval/config.py +1 -1
  77. nat/eval/dataset_handler/dataset_downloader.py +3 -2
  78. nat/eval/dataset_handler/dataset_filter.py +34 -2
  79. nat/eval/evaluate.py +10 -3
  80. nat/eval/evaluator/base_evaluator.py +1 -1
  81. nat/eval/rag_evaluator/evaluate.py +7 -4
  82. nat/eval/register.py +4 -0
  83. nat/eval/runtime_evaluator/__init__.py +14 -0
  84. nat/eval/runtime_evaluator/evaluate.py +123 -0
  85. nat/eval/runtime_evaluator/register.py +100 -0
  86. nat/eval/swe_bench_evaluator/evaluate.py +1 -1
  87. nat/eval/trajectory_evaluator/register.py +1 -1
  88. nat/eval/tunable_rag_evaluator/evaluate.py +1 -1
  89. nat/eval/usage_stats.py +2 -0
  90. nat/eval/utils/output_uploader.py +3 -2
  91. nat/eval/utils/weave_eval.py +17 -3
  92. nat/experimental/decorators/experimental_warning_decorator.py +27 -7
  93. nat/experimental/test_time_compute/functions/execute_score_select_function.py +1 -1
  94. nat/experimental/test_time_compute/functions/plan_select_execute_function.py +7 -3
  95. nat/experimental/test_time_compute/functions/ttc_tool_orchestration_function.py +1 -1
  96. nat/experimental/test_time_compute/functions/ttc_tool_wrapper_function.py +3 -3
  97. nat/experimental/test_time_compute/models/strategy_base.py +2 -2
  98. nat/experimental/test_time_compute/selection/llm_based_output_merging_selector.py +1 -1
  99. nat/front_ends/console/authentication_flow_handler.py +82 -30
  100. nat/front_ends/console/console_front_end_plugin.py +19 -7
  101. nat/front_ends/fastapi/auth_flow_handlers/http_flow_handler.py +1 -1
  102. nat/front_ends/fastapi/auth_flow_handlers/websocket_flow_handler.py +52 -17
  103. nat/front_ends/fastapi/dask_client_mixin.py +65 -0
  104. nat/front_ends/fastapi/fastapi_front_end_config.py +25 -3
  105. nat/front_ends/fastapi/fastapi_front_end_plugin.py +140 -3
  106. nat/front_ends/fastapi/fastapi_front_end_plugin_worker.py +445 -265
  107. nat/front_ends/fastapi/job_store.py +518 -99
  108. nat/front_ends/fastapi/main.py +11 -19
  109. nat/front_ends/fastapi/message_handler.py +69 -44
  110. nat/front_ends/fastapi/message_validator.py +8 -7
  111. nat/front_ends/fastapi/utils.py +57 -0
  112. nat/front_ends/mcp/introspection_token_verifier.py +73 -0
  113. nat/front_ends/mcp/mcp_front_end_config.py +71 -3
  114. nat/front_ends/mcp/mcp_front_end_plugin.py +85 -21
  115. nat/front_ends/mcp/mcp_front_end_plugin_worker.py +248 -29
  116. nat/front_ends/mcp/memory_profiler.py +320 -0
  117. nat/front_ends/mcp/tool_converter.py +78 -25
  118. nat/front_ends/simple_base/simple_front_end_plugin_base.py +3 -1
  119. nat/llm/aws_bedrock_llm.py +21 -8
  120. nat/llm/azure_openai_llm.py +14 -5
  121. nat/llm/litellm_llm.py +80 -0
  122. nat/llm/nim_llm.py +23 -9
  123. nat/llm/openai_llm.py +19 -7
  124. nat/llm/register.py +4 -0
  125. nat/llm/utils/thinking.py +1 -1
  126. nat/observability/exporter/base_exporter.py +1 -1
  127. nat/observability/exporter/processing_exporter.py +29 -55
  128. nat/observability/exporter/span_exporter.py +43 -15
  129. nat/observability/exporter_manager.py +2 -2
  130. nat/observability/mixin/redaction_config_mixin.py +5 -4
  131. nat/observability/mixin/tagging_config_mixin.py +26 -14
  132. nat/observability/mixin/type_introspection_mixin.py +420 -107
  133. nat/observability/processor/batching_processor.py +1 -1
  134. nat/observability/processor/processor.py +3 -0
  135. nat/observability/processor/redaction/__init__.py +24 -0
  136. nat/observability/processor/redaction/contextual_redaction_processor.py +125 -0
  137. nat/observability/processor/redaction/contextual_span_redaction_processor.py +66 -0
  138. nat/observability/processor/redaction/redaction_processor.py +177 -0
  139. nat/observability/processor/redaction/span_header_redaction_processor.py +92 -0
  140. nat/observability/processor/span_tagging_processor.py +21 -14
  141. nat/observability/register.py +16 -0
  142. nat/profiler/callbacks/langchain_callback_handler.py +32 -7
  143. nat/profiler/callbacks/llama_index_callback_handler.py +36 -2
  144. nat/profiler/callbacks/token_usage_base_model.py +2 -0
  145. nat/profiler/decorators/framework_wrapper.py +61 -9
  146. nat/profiler/decorators/function_tracking.py +35 -3
  147. nat/profiler/forecasting/models/linear_model.py +1 -1
  148. nat/profiler/forecasting/models/random_forest_regressor.py +1 -1
  149. nat/profiler/inference_optimization/bottleneck_analysis/nested_stack_analysis.py +1 -1
  150. nat/profiler/inference_optimization/experimental/prefix_span_analysis.py +1 -1
  151. nat/profiler/parameter_optimization/__init__.py +0 -0
  152. nat/profiler/parameter_optimization/optimizable_utils.py +93 -0
  153. nat/profiler/parameter_optimization/optimizer_runtime.py +67 -0
  154. nat/profiler/parameter_optimization/parameter_optimizer.py +189 -0
  155. nat/profiler/parameter_optimization/parameter_selection.py +107 -0
  156. nat/profiler/parameter_optimization/pareto_visualizer.py +460 -0
  157. nat/profiler/parameter_optimization/prompt_optimizer.py +384 -0
  158. nat/profiler/parameter_optimization/update_helpers.py +66 -0
  159. nat/profiler/utils.py +3 -1
  160. nat/registry_handlers/pypi/register_pypi.py +5 -3
  161. nat/registry_handlers/rest/register_rest.py +5 -3
  162. nat/retriever/milvus/retriever.py +1 -1
  163. nat/retriever/nemo_retriever/register.py +2 -1
  164. nat/runtime/loader.py +1 -1
  165. nat/runtime/runner.py +111 -6
  166. nat/runtime/session.py +49 -3
  167. nat/settings/global_settings.py +2 -2
  168. nat/tool/chat_completion.py +4 -1
  169. nat/tool/code_execution/code_sandbox.py +3 -6
  170. nat/tool/code_execution/local_sandbox/Dockerfile.sandbox +19 -32
  171. nat/tool/code_execution/local_sandbox/local_sandbox_server.py +6 -1
  172. nat/tool/code_execution/local_sandbox/sandbox.requirements.txt +2 -0
  173. nat/tool/code_execution/local_sandbox/start_local_sandbox.sh +10 -4
  174. nat/tool/datetime_tools.py +1 -1
  175. nat/tool/github_tools.py +450 -0
  176. nat/tool/memory_tools/add_memory_tool.py +3 -3
  177. nat/tool/memory_tools/delete_memory_tool.py +3 -4
  178. nat/tool/memory_tools/get_memory_tool.py +4 -4
  179. nat/tool/register.py +2 -7
  180. nat/tool/server_tools.py +15 -2
  181. nat/utils/__init__.py +76 -0
  182. nat/utils/callable_utils.py +70 -0
  183. nat/utils/data_models/schema_validator.py +1 -1
  184. nat/utils/decorators.py +210 -0
  185. nat/utils/exception_handlers/automatic_retries.py +278 -72
  186. nat/utils/io/yaml_tools.py +73 -3
  187. nat/utils/log_levels.py +25 -0
  188. nat/utils/responses_api.py +26 -0
  189. nat/utils/string_utils.py +16 -0
  190. nat/utils/type_converter.py +12 -3
  191. nat/utils/type_utils.py +6 -2
  192. nvidia_nat-1.4.0a20251112.dist-info/METADATA +197 -0
  193. {nvidia_nat-1.3.0a20250910.dist-info → nvidia_nat-1.4.0a20251112.dist-info}/RECORD +199 -165
  194. {nvidia_nat-1.3.0a20250910.dist-info → nvidia_nat-1.4.0a20251112.dist-info}/entry_points.txt +1 -0
  195. nat/cli/commands/info/list_mcp.py +0 -461
  196. nat/data_models/temperature_mixin.py +0 -43
  197. nat/data_models/top_p_mixin.py +0 -43
  198. nat/observability/processor/header_redaction_processor.py +0 -123
  199. nat/observability/processor/redaction_processor.py +0 -77
  200. nat/tool/code_execution/test_code_execution_sandbox.py +0 -414
  201. nat/tool/github_tools/create_github_commit.py +0 -133
  202. nat/tool/github_tools/create_github_issue.py +0 -87
  203. nat/tool/github_tools/create_github_pr.py +0 -106
  204. nat/tool/github_tools/get_github_file.py +0 -106
  205. nat/tool/github_tools/get_github_issue.py +0 -166
  206. nat/tool/github_tools/get_github_pr.py +0 -256
  207. nat/tool/github_tools/update_github_issue.py +0 -100
  208. nvidia_nat-1.3.0a20250910.dist-info/METADATA +0 -373
  209. /nat/{tool/github_tools → agent/prompt_optimizer}/__init__.py +0 -0
  210. {nvidia_nat-1.3.0a20250910.dist-info → nvidia_nat-1.4.0a20251112.dist-info}/WHEEL +0 -0
  211. {nvidia_nat-1.3.0a20250910.dist-info → nvidia_nat-1.4.0a20251112.dist-info}/licenses/LICENSE-3rd-party.txt +0 -0
  212. {nvidia_nat-1.3.0a20250910.dist-info → nvidia_nat-1.4.0a20251112.dist-info}/licenses/LICENSE.md +0 -0
  213. {nvidia_nat-1.3.0a20250910.dist-info → nvidia_nat-1.4.0a20251112.dist-info}/top_level.txt +0 -0
@@ -28,14 +28,25 @@ from pydantic import HttpUrl
28
28
  from pydantic import conlist
29
29
  from pydantic import field_serializer
30
30
  from pydantic import field_validator
31
+ from pydantic import model_validator
31
32
  from pydantic_core.core_schema import ValidationInfo
32
33
 
34
+ from nat.data_models.common import SerializableSecretStr
33
35
  from nat.data_models.interactive import HumanPrompt
34
36
  from nat.utils.type_converter import GlobalTypeConverter
35
37
 
36
38
  FINISH_REASONS = frozenset({'stop', 'length', 'tool_calls', 'content_filter', 'function_call'})
37
39
 
38
40
 
41
+ class UserMessageContentRoleType(str, Enum):
42
+ """
43
+ Enum representing chat message roles in API requests and responses.
44
+ """
45
+ USER = "user"
46
+ ASSISTANT = "assistant"
47
+ SYSTEM = "system"
48
+
49
+
39
50
  class Request(BaseModel):
40
51
  """
41
52
  Request is a data model that represents HTTP request attributes.
@@ -99,8 +110,8 @@ class TextContent(BaseModel):
99
110
  class Security(BaseModel):
100
111
  model_config = ConfigDict(extra="forbid")
101
112
 
102
- api_key: str = "default"
103
- token: str = "default"
113
+ api_key: SerializableSecretStr = Field(default="default")
114
+ token: SerializableSecretStr = Field(default="default")
104
115
 
105
116
 
106
117
  UserContent = typing.Annotated[TextContent | ImageContent | AudioContent, Discriminator("type")]
@@ -108,7 +119,7 @@ UserContent = typing.Annotated[TextContent | ImageContent | AudioContent, Discri
108
119
 
109
120
  class Message(BaseModel):
110
121
  content: str | list[UserContent]
111
- role: str
122
+ role: UserMessageContentRoleType
112
123
 
113
124
 
114
125
  class ChatRequest(BaseModel):
@@ -143,7 +154,6 @@ class ChatRequest(BaseModel):
143
154
  tool_choice: str | dict[str, typing.Any] | None = Field(default=None, description="Controls which tool is called")
144
155
  parallel_tool_calls: bool | None = Field(default=True, description="Whether to enable parallel function calling")
145
156
  user: str | None = Field(default=None, description="Unique identifier representing end-user")
146
-
147
157
  model_config = ConfigDict(extra="allow",
148
158
  json_schema_extra={
149
159
  "example": {
@@ -164,7 +174,7 @@ class ChatRequest(BaseModel):
164
174
  max_tokens: int | None = None,
165
175
  top_p: float | None = None) -> "ChatRequest":
166
176
 
167
- return ChatRequest(messages=[Message(content=data, role="user")],
177
+ return ChatRequest(messages=[Message(content=data, role=UserMessageContentRoleType.USER)],
168
178
  model=model,
169
179
  temperature=temperature,
170
180
  max_tokens=max_tokens,
@@ -178,38 +188,128 @@ class ChatRequest(BaseModel):
178
188
  max_tokens: int | None = None,
179
189
  top_p: float | None = None) -> "ChatRequest":
180
190
 
181
- return ChatRequest(messages=[Message(content=content, role="user")],
191
+ return ChatRequest(messages=[Message(content=content, role=UserMessageContentRoleType.USER)],
182
192
  model=model,
183
193
  temperature=temperature,
184
194
  max_tokens=max_tokens,
185
195
  top_p=top_p)
186
196
 
187
197
 
198
+ class ChatRequestOrMessage(BaseModel):
199
+ """
200
+ `ChatRequestOrMessage` is a data model that represents either a conversation or a string input.
201
+ This is useful for functions that can handle either type of input.
202
+
203
+ - `messages` is compatible with the OpenAI Chat Completions API specification.
204
+ - `input_message` is a string input that can be used for functions that do not require a conversation.
205
+
206
+ Note: When `messages` is provided, extra fields are allowed to enable lossless round-trip
207
+ conversion with ChatRequest. When `input_message` is provided, no extra fields are permitted.
208
+ """
209
+ model_config = ConfigDict(
210
+ extra="allow",
211
+ json_schema_extra={
212
+ "examples": [
213
+ {
214
+ "input_message": "What can you do?"
215
+ },
216
+ {
217
+ "messages": [{
218
+ "role": "user", "content": "What can you do?"
219
+ }],
220
+ "model": "nvidia/nemotron",
221
+ "temperature": 0.7
222
+ },
223
+ ],
224
+ "oneOf": [
225
+ {
226
+ "required": ["input_message"],
227
+ "properties": {
228
+ "input_message": {
229
+ "type": "string"
230
+ },
231
+ },
232
+ "additionalProperties": {
233
+ "not": True, "errorMessage": 'remove additional property ${0#}'
234
+ },
235
+ },
236
+ {
237
+ "required": ["messages"],
238
+ "properties": {
239
+ "messages": {
240
+ "type": "array"
241
+ },
242
+ },
243
+ "additionalProperties": True
244
+ },
245
+ ]
246
+ },
247
+ )
248
+
249
+ messages: typing.Annotated[list[Message] | None, conlist(Message, min_length=1)] = Field(
250
+ default=None, description="A non-empty conversation of messages to process.")
251
+
252
+ input_message: str | None = Field(
253
+ default=None,
254
+ description="A single input message to process. Useful for functions that do not require a conversation")
255
+
256
+ @property
257
+ def is_string(self) -> bool:
258
+ return self.input_message is not None
259
+
260
+ @property
261
+ def is_conversation(self) -> bool:
262
+ return self.messages is not None
263
+
264
+ @model_validator(mode="after")
265
+ def validate_model(self):
266
+ if self.messages is not None and self.input_message is not None:
267
+ raise ValueError("Either messages or input_message must be provided, not both")
268
+ if self.messages is None and self.input_message is None:
269
+ raise ValueError("Either messages or input_message must be provided")
270
+ if self.input_message is not None:
271
+ extra_fields = self.model_dump(exclude={"input_message"}, exclude_none=True, exclude_unset=True)
272
+ if len(extra_fields) > 0:
273
+ raise ValueError("no extra fields are permitted when input_message is provided")
274
+ return self
275
+
276
+
188
277
  class ChoiceMessage(BaseModel):
189
278
  content: str | None = None
190
- role: str | None = None
279
+ role: UserMessageContentRoleType | None = None
191
280
 
192
281
 
193
282
  class ChoiceDelta(BaseModel):
194
283
  """Delta object for streaming responses (OpenAI-compatible)"""
195
284
  content: str | None = None
196
- role: str | None = None
285
+ role: UserMessageContentRoleType | None = None
197
286
 
198
287
 
199
- class Choice(BaseModel):
288
+ class ChoiceBase(BaseModel):
289
+ """Base choice model with common fields for both streaming and non-streaming responses"""
200
290
  model_config = ConfigDict(extra="allow")
201
-
202
- message: ChoiceMessage | None = None
203
- delta: ChoiceDelta | None = None
204
291
  finish_reason: typing.Literal['stop', 'length', 'tool_calls', 'content_filter', 'function_call'] | None = None
205
292
  index: int
206
- # logprobs: ChoiceLogprobs | None = None
293
+
294
+
295
+ class ChatResponseChoice(ChoiceBase):
296
+ """Choice model for non-streaming responses - contains message field"""
297
+ message: ChoiceMessage
298
+
299
+
300
+ class ChatResponseChunkChoice(ChoiceBase):
301
+ """Choice model for streaming responses - contains delta field"""
302
+ delta: ChoiceDelta
303
+
304
+
305
+ # Backward compatibility alias
306
+ Choice = ChatResponseChoice
207
307
 
208
308
 
209
309
  class Usage(BaseModel):
210
- prompt_tokens: int
211
- completion_tokens: int
212
- total_tokens: int
310
+ prompt_tokens: int | None = None
311
+ completion_tokens: int | None = None
312
+ total_tokens: int | None = None
213
313
 
214
314
 
215
315
  class ResponseSerializable(abc.ABC):
@@ -245,10 +345,10 @@ class ChatResponse(ResponseBaseModelOutput):
245
345
  model_config = ConfigDict(extra="allow")
246
346
  id: str
247
347
  object: str = "chat.completion"
248
- model: str = ""
348
+ model: str = "unknown-model"
249
349
  created: datetime.datetime
250
- choices: list[Choice]
251
- usage: Usage | None = None
350
+ choices: list[ChatResponseChoice]
351
+ usage: Usage
252
352
  system_fingerprint: str | None = None
253
353
  service_tier: typing.Literal["scale", "default"] | None = None
254
354
 
@@ -264,22 +364,27 @@ class ChatResponse(ResponseBaseModelOutput):
264
364
  object_: str | None = None,
265
365
  model: str | None = None,
266
366
  created: datetime.datetime | None = None,
267
- usage: Usage | None = None) -> "ChatResponse":
367
+ usage: Usage) -> "ChatResponse":
268
368
 
269
369
  if id_ is None:
270
370
  id_ = str(uuid.uuid4())
271
371
  if object_ is None:
272
372
  object_ = "chat.completion"
273
373
  if model is None:
274
- model = ""
374
+ model = "unknown-model"
275
375
  if created is None:
276
- created = datetime.datetime.now(datetime.timezone.utc)
376
+ created = datetime.datetime.now(datetime.UTC)
277
377
 
278
378
  return ChatResponse(id=id_,
279
379
  object=object_,
280
380
  model=model,
281
381
  created=created,
282
- choices=[Choice(index=0, message=ChoiceMessage(content=data), finish_reason="stop")],
382
+ choices=[
383
+ ChatResponseChoice(index=0,
384
+ message=ChoiceMessage(content=data,
385
+ role=UserMessageContentRoleType.ASSISTANT),
386
+ finish_reason="stop")
387
+ ],
283
388
  usage=usage)
284
389
 
285
390
 
@@ -293,9 +398,9 @@ class ChatResponseChunk(ResponseBaseModelOutput):
293
398
  model_config = ConfigDict(extra="allow")
294
399
 
295
400
  id: str
296
- choices: list[Choice]
401
+ choices: list[ChatResponseChunkChoice]
297
402
  created: datetime.datetime
298
- model: str = ""
403
+ model: str = "unknown-model"
299
404
  object: str = "chat.completion.chunk"
300
405
  system_fingerprint: str | None = None
301
406
  service_tier: typing.Literal["scale", "default"] | None = None
@@ -317,14 +422,20 @@ class ChatResponseChunk(ResponseBaseModelOutput):
317
422
  if id_ is None:
318
423
  id_ = str(uuid.uuid4())
319
424
  if created is None:
320
- created = datetime.datetime.now(datetime.timezone.utc)
425
+ created = datetime.datetime.now(datetime.UTC)
321
426
  if model is None:
322
- model = ""
427
+ model = "unknown-model"
323
428
  if object_ is None:
324
429
  object_ = "chat.completion.chunk"
325
430
 
326
431
  return ChatResponseChunk(id=id_,
327
- choices=[Choice(index=0, message=ChoiceMessage(content=data), finish_reason="stop")],
432
+ choices=[
433
+ ChatResponseChunkChoice(index=0,
434
+ delta=ChoiceDelta(
435
+ content=data,
436
+ role=UserMessageContentRoleType.ASSISTANT),
437
+ finish_reason="stop")
438
+ ],
328
439
  created=created,
329
440
  model=model,
330
441
  object=object_)
@@ -335,7 +446,7 @@ class ChatResponseChunk(ResponseBaseModelOutput):
335
446
  id_: str | None = None,
336
447
  created: datetime.datetime | None = None,
337
448
  model: str | None = None,
338
- role: str | None = None,
449
+ role: UserMessageContentRoleType | None = None,
339
450
  finish_reason: str | None = None,
340
451
  usage: Usage | None = None,
341
452
  system_fingerprint: str | None = None) -> "ChatResponseChunk":
@@ -343,9 +454,9 @@ class ChatResponseChunk(ResponseBaseModelOutput):
343
454
  if id_ is None:
344
455
  id_ = str(uuid.uuid4())
345
456
  if created is None:
346
- created = datetime.datetime.now(datetime.timezone.utc)
457
+ created = datetime.datetime.now(datetime.UTC)
347
458
  if model is None:
348
- model = ""
459
+ model = "unknown-model"
349
460
 
350
461
  delta = ChoiceDelta(content=content, role=role) if content is not None or role is not None else ChoiceDelta()
351
462
 
@@ -353,7 +464,14 @@ class ChatResponseChunk(ResponseBaseModelOutput):
353
464
 
354
465
  return ChatResponseChunk(
355
466
  id=id_,
356
- choices=[Choice(index=0, message=None, delta=delta, finish_reason=final_finish_reason)],
467
+ choices=[
468
+ ChatResponseChunkChoice(
469
+ index=0,
470
+ delta=delta,
471
+ finish_reason=typing.cast(
472
+ typing.Literal['stop', 'length', 'tool_calls', 'content_filter', 'function_call'] | None,
473
+ final_finish_reason))
474
+ ],
357
475
  created=created,
358
476
  model=model,
359
477
  object="chat.completion.chunk",
@@ -398,11 +516,6 @@ class GenerateResponse(BaseModel):
398
516
  value: str | None = "default"
399
517
 
400
518
 
401
- class UserMessageContentRoleType(str, Enum):
402
- USER = "user"
403
- ASSISTANT = "assistant"
404
-
405
-
406
519
  class WebSocketMessageType(str, Enum):
407
520
  """
408
521
  WebSocketMessageType is an Enum that represents WebSocket Message types.
@@ -485,7 +598,7 @@ class WebSocketUserMessage(BaseModel):
485
598
  security: Security = Security()
486
599
  error: Error = Error()
487
600
  schema_version: str = "1.0.0"
488
- timestamp: str = str(datetime.datetime.now(datetime.timezone.utc))
601
+ timestamp: str = str(datetime.datetime.now(datetime.UTC))
489
602
 
490
603
 
491
604
  class WebSocketUserInteractionResponseMessage(BaseModel):
@@ -496,12 +609,14 @@ class WebSocketUserInteractionResponseMessage(BaseModel):
496
609
  type: typing.Literal[WebSocketMessageType.USER_INTERACTION_MESSAGE]
497
610
  id: str = "default"
498
611
  thread_id: str = "default"
612
+ parent_id: str = "default"
613
+ conversation_id: str | None = None
499
614
  content: UserMessageContent
500
615
  user: User = User()
501
616
  security: Security = Security()
502
617
  error: Error = Error()
503
618
  schema_version: str = "1.0.0"
504
- timestamp: str = str(datetime.datetime.now(datetime.timezone.utc))
619
+ timestamp: str = str(datetime.datetime.now(datetime.UTC))
505
620
 
506
621
 
507
622
  class SystemIntermediateStepContent(BaseModel):
@@ -527,7 +642,7 @@ class WebSocketSystemIntermediateStepMessage(BaseModel):
527
642
  conversation_id: str | None = None
528
643
  content: SystemIntermediateStepContent
529
644
  status: WebSocketMessageStatus
530
- timestamp: str = str(datetime.datetime.now(datetime.timezone.utc))
645
+ timestamp: str = str(datetime.datetime.now(datetime.UTC))
531
646
 
532
647
 
533
648
  class SystemResponseContent(BaseModel):
@@ -551,7 +666,7 @@ class WebSocketSystemResponseTokenMessage(BaseModel):
551
666
  conversation_id: str | None = None
552
667
  content: SystemResponseContent | Error | GenerateResponse
553
668
  status: WebSocketMessageStatus
554
- timestamp: str = str(datetime.datetime.now(datetime.timezone.utc))
669
+ timestamp: str = str(datetime.datetime.now(datetime.UTC))
555
670
 
556
671
  @field_validator("content")
557
672
  @classmethod
@@ -560,7 +675,7 @@ class WebSocketSystemResponseTokenMessage(BaseModel):
560
675
  raise ValueError(f"Field: content must be 'Error' when type is {WebSocketMessageType.ERROR_MESSAGE}")
561
676
 
562
677
  if info.data.get("type") == WebSocketMessageType.RESPONSE_MESSAGE and not isinstance(
563
- value, (SystemResponseContent, GenerateResponse)):
678
+ value, SystemResponseContent | GenerateResponse):
564
679
  raise ValueError(
565
680
  f"Field: content must be 'SystemResponseContent' when type is {WebSocketMessageType.RESPONSE_MESSAGE}")
566
681
  return value
@@ -582,7 +697,7 @@ class WebSocketSystemInteractionMessage(BaseModel):
582
697
  conversation_id: str | None = None
583
698
  content: HumanPrompt
584
699
  status: WebSocketMessageStatus
585
- timestamp: str = str(datetime.datetime.now(datetime.timezone.utc))
700
+ timestamp: str = str(datetime.datetime.now(datetime.UTC))
586
701
 
587
702
 
588
703
  # ======== GenerateResponse Converters ========
@@ -622,12 +737,52 @@ GlobalTypeConverter.register_converter(_nat_chat_request_to_string)
622
737
 
623
738
 
624
739
  def _string_to_nat_chat_request(data: str) -> ChatRequest:
625
- return ChatRequest.from_string(data, model="")
740
+ return ChatRequest.from_string(data, model="unknown-model")
626
741
 
627
742
 
628
743
  GlobalTypeConverter.register_converter(_string_to_nat_chat_request)
629
744
 
630
745
 
746
+ def _chat_request_or_message_to_chat_request(data: ChatRequestOrMessage) -> ChatRequest:
747
+ if data.input_message is not None:
748
+ return _string_to_nat_chat_request(data.input_message)
749
+ return ChatRequest(**data.model_dump(exclude={"input_message"}))
750
+
751
+
752
+ GlobalTypeConverter.register_converter(_chat_request_or_message_to_chat_request)
753
+
754
+
755
+ def _chat_request_to_chat_request_or_message(data: ChatRequest) -> ChatRequestOrMessage:
756
+ return ChatRequestOrMessage(**data.model_dump(by_alias=True))
757
+
758
+
759
+ GlobalTypeConverter.register_converter(_chat_request_to_chat_request_or_message)
760
+
761
+
762
+ def _chat_request_or_message_to_string(data: ChatRequestOrMessage) -> str:
763
+ if data.input_message is not None:
764
+ return data.input_message
765
+ # Extract content from last message in conversation
766
+ if data.messages is None:
767
+ return ""
768
+ content = data.messages[-1].content
769
+ if content is None:
770
+ return ""
771
+ if isinstance(content, str):
772
+ return content
773
+ return str(content)
774
+
775
+
776
+ GlobalTypeConverter.register_converter(_chat_request_or_message_to_string)
777
+
778
+
779
+ def _string_to_chat_request_or_message(data: str) -> ChatRequestOrMessage:
780
+ return ChatRequestOrMessage(input_message=data)
781
+
782
+
783
+ GlobalTypeConverter.register_converter(_string_to_chat_request_or_message)
784
+
785
+
631
786
  # ======== ChatResponse Converters ========
632
787
  def _nat_chat_response_to_string(data: ChatResponse) -> str:
633
788
  if data.choices and data.choices[0].message:
@@ -654,22 +809,12 @@ def _string_to_nat_chat_response(data: str) -> ChatResponse:
654
809
  GlobalTypeConverter.register_converter(_string_to_nat_chat_response)
655
810
 
656
811
 
657
- def _chat_response_to_chat_response_chunk(data: ChatResponse) -> ChatResponseChunk:
658
- # Preserve original message structure for backward compatibility
659
- return ChatResponseChunk(id=data.id, choices=data.choices, created=data.created, model=data.model)
660
-
661
-
662
- GlobalTypeConverter.register_converter(_chat_response_to_chat_response_chunk)
663
-
664
-
665
812
  # ======== ChatResponseChunk Converters ========
666
813
  def _chat_response_chunk_to_string(data: ChatResponseChunk) -> str:
667
814
  if data.choices and len(data.choices) > 0:
668
815
  choice = data.choices[0]
669
816
  if choice.delta and choice.delta.content:
670
817
  return choice.delta.content
671
- if choice.message and choice.message.content:
672
- return choice.message.content
673
818
  return ""
674
819
 
675
820
 
@@ -685,21 +830,6 @@ def _string_to_nat_chat_response_chunk(data: str) -> ChatResponseChunk:
685
830
 
686
831
  GlobalTypeConverter.register_converter(_string_to_nat_chat_response_chunk)
687
832
 
688
-
689
- # ======== AINodeMessageChunk Converters ========
690
- def _ai_message_chunk_to_nat_chat_response_chunk(data) -> ChatResponseChunk:
691
- '''Converts LangChain/LangGraph AINodeMessageChunk to ChatResponseChunk'''
692
- content = ""
693
- if hasattr(data, 'content') and data.content is not None:
694
- content = str(data.content)
695
- elif hasattr(data, 'text') and data.text is not None:
696
- content = str(data.text)
697
- elif hasattr(data, 'message') and data.message is not None:
698
- content = str(data.message)
699
-
700
- return ChatResponseChunk.create_streaming_chunk(content=content, role="assistant", finish_reason=None)
701
-
702
-
703
833
  # Compatibility aliases with previous releases
704
834
  AIQChatRequest = ChatRequest
705
835
  AIQChoiceMessage = ChoiceMessage
@@ -14,8 +14,8 @@
14
14
  # limitations under the License.
15
15
 
16
16
  import typing
17
+ from datetime import UTC
17
18
  from datetime import datetime
18
- from datetime import timezone
19
19
  from enum import Enum
20
20
 
21
21
  import httpx
@@ -166,17 +166,31 @@ class BearerTokenCred(_CredBase):
166
166
 
167
167
 
168
168
  Credential = typing.Annotated[
169
- typing.Union[
170
- HeaderCred,
171
- QueryCred,
172
- CookieCred,
173
- BasicAuthCred,
174
- BearerTokenCred,
175
- ],
169
+ HeaderCred | QueryCred | CookieCred | BasicAuthCred | BearerTokenCred,
176
170
  Field(discriminator="kind"),
177
171
  ]
178
172
 
179
173
 
174
+ class TokenValidationResult(BaseModel):
175
+ """
176
+ Standard result for Bearer Token Validation.
177
+ """
178
+ model_config = ConfigDict(extra="forbid")
179
+
180
+ client_id: str | None = Field(description="OAuth2 client identifier")
181
+ scopes: list[str] | None = Field(default=None, description="List of granted scopes (introspection only)")
182
+ expires_at: int | None = Field(default=None, description="Token expiration time (Unix timestamp)")
183
+ audience: list[str] | None = Field(default=None, description="Token audiences (aud claim)")
184
+ subject: str | None = Field(default=None, description="Token subject (sub claim)")
185
+ issuer: str | None = Field(default=None, description="Token issuer (iss claim)")
186
+ token_type: str = Field(description="Token type")
187
+ active: bool | None = Field(default=True, description="Token active status")
188
+ nbf: int | None = Field(default=None, description="Not before time (Unix timestamp)")
189
+ iat: int | None = Field(default=None, description="Issued at time (Unix timestamp)")
190
+ jti: str | None = Field(default=None, description="JWT ID")
191
+ username: str | None = Field(default=None, description="Username (introspection only)")
192
+
193
+
180
194
  class AuthResult(BaseModel):
181
195
  """
182
196
  Represents the result of an authentication process.
@@ -193,7 +207,7 @@ class AuthResult(BaseModel):
193
207
  """
194
208
  Checks if the authentication token has expired.
195
209
  """
196
- return bool(self.token_expires_at and datetime.now(timezone.utc) >= self.token_expires_at)
210
+ return bool(self.token_expires_at and datetime.now(UTC) >= self.token_expires_at)
197
211
 
198
212
  def as_requests_kwargs(self) -> dict[str, typing.Any]:
199
213
  """
nat/data_models/common.py CHANGED
@@ -14,6 +14,7 @@
14
14
  # limitations under the License.
15
15
 
16
16
  import inspect
17
+ import os
17
18
  import sys
18
19
  import typing
19
20
  from hashlib import sha512
@@ -21,6 +22,8 @@ from hashlib import sha512
21
22
  from pydantic import AliasChoices
22
23
  from pydantic import BaseModel
23
24
  from pydantic import Field
25
+ from pydantic import PlainSerializer
26
+ from pydantic import SecretStr
24
27
  from pydantic.json_schema import GenerateJsonSchema
25
28
  from pydantic.json_schema import JsonSchemaMode
26
29
 
@@ -169,3 +172,47 @@ class TypedBaseModel(BaseModel):
169
172
 
170
173
 
171
174
  TypedBaseModelT = typing.TypeVar("TypedBaseModelT", bound=TypedBaseModel)
175
+
176
+
177
+ def get_secret_value(v: SecretStr | None) -> str | None:
178
+ """
179
+ Extract the secret value from a SecretStr or return None.
180
+
181
+ Parameters
182
+ ----------
183
+ v: SecretStr or None.
184
+ A field defined as OptionalSecretStr, which is either a SecretStr or None.
185
+
186
+ Returns
187
+ -------
188
+ str | None
189
+ The secret value as a plain string, or None if v is None.
190
+ """
191
+ if v is None:
192
+ return None
193
+ return v.get_secret_value()
194
+
195
+
196
+ def set_secret_from_env(model: BaseModel, field_name: str, env_var: str):
197
+ """
198
+ Set a SecretStr field in a Pydantic model from an environment variable, but only if the environment variable is set.
199
+
200
+ Parameters
201
+ ----------
202
+ model: BaseModel
203
+ The Pydantic model instance containing the field to set.
204
+ field_name: str
205
+ The name of the field in the model to set.
206
+ env_var: str
207
+ The name of the environment variable to read the secret value from.
208
+ """
209
+ env_value = os.getenv(env_var)
210
+ if env_value is not None:
211
+ setattr(model, field_name, SecretStr(env_value))
212
+
213
+
214
+ # A SecretStr that serializes to plain string
215
+ SerializableSecretStr = typing.Annotated[SecretStr, PlainSerializer(get_secret_value)]
216
+
217
+ # A SecretStr or None that serializes to plain string
218
+ OptionalSecretStr = typing.Annotated[SecretStr | None, PlainSerializer(get_secret_value)]
@@ -27,6 +27,7 @@ class ComponentEnum(StrEnum):
27
27
  EVALUATOR = "evaluator"
28
28
  FRONT_END = "front_end"
29
29
  FUNCTION = "function"
30
+ FUNCTION_GROUP = "function_group"
30
31
  TTC_STRATEGY = "ttc_strategy"
31
32
  LLM_CLIENT = "llm_client"
32
33
  LLM_PROVIDER = "llm_provider"
@@ -47,6 +48,7 @@ class ComponentGroup(StrEnum):
47
48
  AUTHENTICATION = "authentication"
48
49
  EMBEDDERS = "embedders"
49
50
  FUNCTIONS = "functions"
51
+ FUNCTION_GROUPS = "function_groups"
50
52
  TTC_STRATEGIES = "ttc_strategies"
51
53
  LLMS = "llms"
52
54
  MEMORY = "memory"
@@ -102,6 +102,17 @@ class FunctionRef(ComponentRef):
102
102
  return ComponentGroup.FUNCTIONS
103
103
 
104
104
 
105
+ class FunctionGroupRef(ComponentRef):
106
+ """
107
+ A reference to a function group in a NAT configuration object.
108
+ """
109
+
110
+ @property
111
+ @override
112
+ def component_group(self):
113
+ return ComponentGroup.FUNCTION_GROUPS
114
+
115
+
105
116
  class LLMRef(ComponentRef):
106
117
  """
107
118
  A reference to an LLM in a NAT configuration object.