nvidia-nat 1.3.0a20251023__py3-none-any.whl → 1.3.0a20251031__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.
@@ -217,7 +217,7 @@ class ReWOOAgentGraph(BaseAgent):
217
217
  if value is not None:
218
218
  if value == placeholder:
219
219
  tool_input[key] = tool_output
220
- elif placeholder in value:
220
+ elif isinstance(value, str) and placeholder in value:
221
221
  # If the placeholder is part of the value, replace it with the stringified output
222
222
  tool_input[key] = value.replace(placeholder, str(tool_output))
223
223
 
@@ -211,6 +211,13 @@ class FastApiFrontEndConfig(FrontEndBaseConfig, name="fastapi"):
211
211
  "Maximum number of async jobs to run concurrently, this controls the number of dask workers created. "
212
212
  "This parameter is only used when scheduler_address is `None` and a Dask local cluster is created."),
213
213
  ge=1)
214
+ dask_workers: typing.Literal["threads", "processes"] = Field(
215
+ default="processes",
216
+ description=(
217
+ "Type of Dask workers to use. Options are 'threads' for Threaded Dask workers or 'processes' for "
218
+ "Process based Dask workers. This parameter is only used when scheduler_address is `None` and a local Dask "
219
+ "cluster is created."),
220
+ )
214
221
  dask_log_level: str = Field(
215
222
  default="WARNING",
216
223
  description="Logging level for Dask.",
@@ -120,18 +120,24 @@ class FastApiFrontEndPlugin(DaskClientMixin, FrontEndBase[FastApiFrontEndConfig]
120
120
 
121
121
  from dask.distributed import LocalCluster
122
122
 
123
- self._cluster = LocalCluster(processes=True,
123
+ use_threads = self.front_end_config.dask_workers == 'threads'
124
+
125
+ # set n_workers to max_running_async_jobs + 1 to allow for one worker to handle the cleanup task
126
+ self._cluster = LocalCluster(processes=not use_threads,
124
127
  silence_logs=dask_log_level,
125
- n_workers=self.front_end_config.max_running_async_jobs,
126
- threads_per_worker=1)
128
+ protocol="tcp",
129
+ n_workers=self.front_end_config.max_running_async_jobs + 1)
127
130
 
128
131
  self._scheduler_address = self._cluster.scheduler.address
129
132
 
130
- with self.blocking_client(self._scheduler_address) as client:
131
- # Client.run submits a function to be run on each worker
132
- client.run(self._setup_worker)
133
+ if not use_threads and sys.platform != "win32":
134
+ with self.blocking_client(self._scheduler_address) as client:
135
+ # Client.run submits a function to be run on each worker
136
+ client.run(self._setup_worker)
133
137
 
134
- logger.info("Created local Dask cluster with scheduler at %s", self._scheduler_address)
138
+ logger.info("Created local Dask cluster with scheduler at %s using %s workers",
139
+ self._scheduler_address,
140
+ self.front_end_config.dask_workers)
135
141
 
136
142
  except ImportError:
137
143
  logger.warning("Dask is not installed, async execution and evaluation will not be available.")
@@ -562,6 +562,10 @@ class FastApiFrontEndPluginWorker(FastApiFrontEndPluginWorkerBase):
562
562
  description="Optional time (in seconds) before the job expires. "
563
563
  "Clamped between 600 (10 min) and 86400 (24h).")
564
564
 
565
+ def validate_model(self):
566
+ # Override to ensure that the parent class validator is not called
567
+ return self
568
+
565
569
  # Ensure that the input is in the body. POD types are treated as query parameters
566
570
  if (not issubclass(GenerateBodyType, BaseModel)):
567
571
  GenerateBodyType = typing.Annotated[GenerateBodyType, Body()]
@@ -760,17 +764,18 @@ class FastApiFrontEndPluginWorker(FastApiFrontEndPluginWorkerBase):
760
764
  return AsyncGenerateResponse(job_id=job.job_id, status=job.status)
761
765
 
762
766
  job_id = self._job_store.ensure_job_id(request.job_id)
763
- (_, job) = await self._job_store.submit_job(job_id=job_id,
764
- expiry_seconds=request.expiry_seconds,
765
- job_fn=run_generation,
766
- sync_timeout=request.sync_timeout,
767
- job_args=[
768
- self._scheduler_address,
769
- self._db_url,
770
- self._config_file_path,
771
- job_id,
772
- request.model_dump(mode="json")
773
- ])
767
+ (_, job) = await self._job_store.submit_job(
768
+ job_id=job_id,
769
+ expiry_seconds=request.expiry_seconds,
770
+ job_fn=run_generation,
771
+ sync_timeout=request.sync_timeout,
772
+ job_args=[
773
+ self._scheduler_address,
774
+ self._db_url,
775
+ self._config_file_path,
776
+ job_id,
777
+ request.model_dump(mode="json", exclude=["job_id", "sync_timeout", "expiry_seconds"])
778
+ ])
774
779
 
775
780
  if job is not None:
776
781
  response.status_code = 200
@@ -37,8 +37,11 @@ class MCPFrontEndConfig(FrontEndBaseConfig, name="mcp"):
37
37
  port: int = Field(default=9901, description="Port to bind the server to (default: 9901)", ge=0, le=65535)
38
38
  debug: bool = Field(default=False, description="Enable debug mode (default: False)")
39
39
  log_level: str = Field(default="INFO", description="Log level for the MCP server (default: INFO)")
40
- tool_names: list[str] = Field(default_factory=list,
41
- description="The list of tools MCP server will expose (default: all tools)")
40
+ tool_names: list[str] = Field(
41
+ default_factory=list,
42
+ description="The list of tools MCP server will expose (default: all tools)."
43
+ "Tool names can be functions or function groups",
44
+ )
42
45
  transport: Literal["sse", "streamable-http"] = Field(
43
46
  default="streamable-http",
44
47
  description="Transport type for the MCP server (default: streamable-http, backwards compatible with sse)")
@@ -234,6 +234,10 @@ class MCPFrontEndPluginWorker(MCPFrontEndPluginWorkerBase):
234
234
  filtered_functions: dict[str, Function] = {}
235
235
  for function_name, function in functions.items():
236
236
  if function_name in self.front_end_config.tool_names:
237
+ # Treat current tool_names as function names, so check if the function name is in the list
238
+ filtered_functions[function_name] = function
239
+ elif any(function_name.startswith(f"{group_name}.") for group_name in self.front_end_config.tool_names):
240
+ # Treat tool_names as function group names, so check if the function name starts with the group name
237
241
  filtered_functions[function_name] = function
238
242
  else:
239
243
  logger.debug("Skipping function %s as it's not in tool_names", function_name)
@@ -18,9 +18,12 @@ import logging
18
18
  from inspect import Parameter
19
19
  from inspect import Signature
20
20
  from typing import TYPE_CHECKING
21
+ from typing import Any
21
22
 
22
23
  from mcp.server.fastmcp import FastMCP
23
24
  from pydantic import BaseModel
25
+ from pydantic.fields import FieldInfo
26
+ from pydantic_core import PydanticUndefined
24
27
 
25
28
  from nat.builder.context import ContextState
26
29
  from nat.builder.function import Function
@@ -31,6 +34,41 @@ if TYPE_CHECKING:
31
34
 
32
35
  logger = logging.getLogger(__name__)
33
36
 
37
+ # Sentinel: marks "optional; let Pydantic supply default/factory"
38
+ _USE_PYDANTIC_DEFAULT = object()
39
+
40
+
41
+ def is_field_optional(field: FieldInfo) -> tuple[bool, Any]:
42
+ """Determine if a Pydantic field is optional and extract its default value for MCP signatures.
43
+
44
+ For MCP tool signatures, we need to distinguish:
45
+ - Required fields: marked with Parameter.empty
46
+ - Optional with concrete default: use that default
47
+ - Optional with factory: use sentinel so Pydantic can apply the factory later
48
+
49
+ Args:
50
+ field: The Pydantic FieldInfo to check
51
+
52
+ Returns:
53
+ A tuple of (is_optional, default_value):
54
+ - (False, Parameter.empty) for required fields
55
+ - (True, actual_default) for optional fields with explicit defaults
56
+ - (True, _USE_PYDANTIC_DEFAULT) for optional fields with default_factory
57
+ """
58
+ if field.is_required():
59
+ return False, Parameter.empty
60
+
61
+ # Field is optional - has either default or factory
62
+ if field.default is not PydanticUndefined:
63
+ return True, field.default
64
+
65
+ # Factory case: mark optional in signature but don't fabricate a value
66
+ if field.default_factory is not None:
67
+ return True, _USE_PYDANTIC_DEFAULT
68
+
69
+ # Rare corner case: non-required yet no default surfaced
70
+ return True, _USE_PYDANTIC_DEFAULT
71
+
34
72
 
35
73
  def create_function_wrapper(
36
74
  function_name: str,
@@ -76,12 +114,15 @@ def create_function_wrapper(
76
114
  # Get the field type and convert to appropriate Python type
77
115
  field_type = field.annotation
78
116
 
117
+ # Check if field is optional and get its default value
118
+ _is_optional, param_default = is_field_optional(field)
119
+
79
120
  # Add the parameter to our list
80
121
  parameters.append(
81
122
  Parameter(
82
123
  name=name,
83
124
  kind=Parameter.KEYWORD_ONLY,
84
- default=Parameter.empty if field.is_required else None,
125
+ default=param_default,
85
126
  annotation=field_type,
86
127
  ))
87
128
 
@@ -140,33 +181,23 @@ def create_function_wrapper(
140
181
  result = await call_with_observability(lambda: function.ainvoke(chat_request, to_type=str))
141
182
  else:
142
183
  # Regular handling
143
- # Handle complex input schema - if we extracted fields from a nested schema,
144
- # we need to reconstruct the input
145
- if len(schema.model_fields) == 1 and len(parameters) > 1:
146
- # Get the field name from the original schema
147
- field_name = next(iter(schema.model_fields.keys()))
148
- field_type = schema.model_fields[field_name].annotation
149
-
150
- # If it's a pydantic model, we need to create an instance
151
- if field_type and hasattr(field_type, "model_validate"):
152
- # Create the nested object
153
- nested_obj = field_type.model_validate(kwargs)
154
- # Call with the nested object
155
- kwargs = {field_name: nested_obj}
184
+ # Strip sentinel values so Pydantic can apply defaults/factories
185
+ cleaned_kwargs = {k: v for k, v in kwargs.items() if v is not _USE_PYDANTIC_DEFAULT}
186
+
187
+ # Always validate with the declared schema
188
+ # This handles defaults, factories, nested models, validators, etc.
189
+ model_input = schema.model_validate(cleaned_kwargs)
156
190
 
157
191
  # Call the NAT function with the parameters - special handling for Workflow
158
192
  if is_workflow:
159
- # For workflow with regular input, we'll assume the first parameter is the input
160
- input_value = list(kwargs.values())[0] if kwargs else ""
161
-
162
- # Workflows have a run method that is an async context manager
163
- # that returns a Runner
164
- async with function.run(input_value) as runner:
193
+ # Workflows expect the model instance directly
194
+ async with function.run(model_input) as runner:
165
195
  # Get the result from the runner
166
196
  result = await runner.result(to_type=str)
167
197
  else:
168
- # Regular function call
169
- result = await call_with_observability(lambda: function.acall_invoke(**kwargs))
198
+ # Regular function call - unpack the validated model
199
+ result = await call_with_observability(lambda: function.acall_invoke(**model_input.model_dump())
200
+ )
170
201
 
171
202
  # Report completion
172
203
  if ctx:
@@ -117,7 +117,7 @@ def optimize_parameters(
117
117
 
118
118
  # Save final results (out_dir already created and defined above)
119
119
  with (out_dir / "optimized_config.yml").open("w") as fh:
120
- yaml.dump(tuned_cfg.model_dump(), fh)
120
+ yaml.dump(tuned_cfg.model_dump(mode='json'), fh)
121
121
  with (out_dir / "trials_dataframe_params.csv").open("w") as fh:
122
122
  # Export full trials DataFrame (values, params, timings, etc.).
123
123
  df = study.trials_dataframe()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nvidia-nat
3
- Version: 1.3.0a20251023
3
+ Version: 1.3.0a20251031
4
4
  Summary: NVIDIA NeMo Agent toolkit
5
5
  Author: NVIDIA Corporation
6
6
  Maintainer: NVIDIA Corporation
@@ -14,7 +14,7 @@ nat/agent/react_agent/register.py,sha256=qkPaK6AvXjolL-q_Z3waVobXDz24GMfuqGqCn-2
14
14
  nat/agent/reasoning_agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  nat/agent/reasoning_agent/reasoning_agent.py,sha256=fFQtzvaBWtmr_B6S9KSkqAfyl1BdcOc9xkhnbO4O8Pk,9603
16
16
  nat/agent/rewoo_agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
- nat/agent/rewoo_agent/agent.py,sha256=XXgVXY9xwkyxnr093KXUtfgyNxAQbyGAecoGqN5mMLY,26199
17
+ nat/agent/rewoo_agent/agent.py,sha256=ZcTHOX2cj5zJBgy6suLf_DMFEyBLhjQklPrJuMCjAtU,26226
18
18
  nat/agent/rewoo_agent/prompt.py,sha256=B0JeL1xDX4VKcShlkkviEcAsOKAwzSlX8NcAQdmUUPw,3645
19
19
  nat/agent/rewoo_agent/register.py,sha256=XArlOR37QOBtAvsdKJUjRok5qTmx39S2mJHSteOwU58,9283
20
20
  nat/agent/tool_calling_agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -239,10 +239,10 @@ nat/front_ends/console/register.py,sha256=2Kf6Mthx6jzWzU8YdhYIR1iABmZDvs1UXM_20n
239
239
  nat/front_ends/cron/__init__.py,sha256=Xs1JQ16L9btwreh4pdGKwskffAw1YFO48jKrU4ib_7c,685
240
240
  nat/front_ends/fastapi/__init__.py,sha256=Xs1JQ16L9btwreh4pdGKwskffAw1YFO48jKrU4ib_7c,685
241
241
  nat/front_ends/fastapi/dask_client_mixin.py,sha256=N_tw4yxA7EKIFTKp5_C2ZksIZucWxRYkFjmZszkAkXc,2072
242
- nat/front_ends/fastapi/fastapi_front_end_config.py,sha256=BcuzrVlA5b7yYyQKNvQgEanDBtKEHdpC8TAd-O7lfF0,11992
242
+ nat/front_ends/fastapi/fastapi_front_end_config.py,sha256=O_iRpGS3Vpht7ZNr1bYpvoldDEb9Z6f7tFPazVP5i9U,12383
243
243
  nat/front_ends/fastapi/fastapi_front_end_controller.py,sha256=ei-34KCMpyaeAgeAN4gVvSGFjewjjRhHZPN0FqAfhDY,2548
244
- nat/front_ends/fastapi/fastapi_front_end_plugin.py,sha256=e33YkMcLzvm4OUG34bhl-WYiBTqkR-_wJYKG4GODkGM,11169
245
- nat/front_ends/fastapi/fastapi_front_end_plugin_worker.py,sha256=T6uslFdkHl_r0U54_7cRRKLnWYP2tTMcD7snx9Gv1xs,60547
244
+ nat/front_ends/fastapi/fastapi_front_end_plugin.py,sha256=5akdWipe8onOTdSqrbGq9KO71y0_BNQQJ3JAFj6LmFY,11575
245
+ nat/front_ends/fastapi/fastapi_front_end_plugin_worker.py,sha256=lCqeymuhw_ARtkniFKaEtlbqGFUgBPYtC3iihPCGn5E,60388
246
246
  nat/front_ends/fastapi/intermediate_steps_subscriber.py,sha256=kbyWlBVpyvyQQjeUnFG9nsR4RaqqNkx567ZSVwwl2RU,3104
247
247
  nat/front_ends/fastapi/job_store.py,sha256=cWIBnIgRdkGL7qbBunEKzTYzdPp3l3QCDHMP-qTZJpc,22743
248
248
  nat/front_ends/fastapi/main.py,sha256=s8gXCy61rJjK1aywMRpgPvzlkMGsCS-kI_0EIy4JjBM,2445
@@ -259,11 +259,11 @@ nat/front_ends/fastapi/html_snippets/__init__.py,sha256=GUJrgGtpvyMUCjUBvR3faAdv
259
259
  nat/front_ends/fastapi/html_snippets/auth_code_grant_success.py,sha256=BNpWwzmA58UM0GK4kZXG4PHJy_5K9ihaVHu8SgCs5JA,1131
260
260
  nat/front_ends/mcp/__init__.py,sha256=Xs1JQ16L9btwreh4pdGKwskffAw1YFO48jKrU4ib_7c,685
261
261
  nat/front_ends/mcp/introspection_token_verifier.py,sha256=s7Q4Q6rWZJ0ZVujSxxpvVI6Bnhkg1LJQ3RLkvhzFIGE,2836
262
- nat/front_ends/mcp/mcp_front_end_config.py,sha256=m6z5qSz8YGnFnfu8hRID69suvO1YT_L6sxy1Ki64Ufw,4042
262
+ nat/front_ends/mcp/mcp_front_end_config.py,sha256=KP13AKCyqOwXqebMmYrPwx1Ogq5iPo7R9uzLNSIHySY,4089
263
263
  nat/front_ends/mcp/mcp_front_end_plugin.py,sha256=4u_kpen_T-_Uh62V5M7dfW9KyzbqXI7tGBG4AxJXWm0,5231
264
- nat/front_ends/mcp/mcp_front_end_plugin_worker.py,sha256=jMclC0qEd910oTGCqd1kQ8WjP3WPdQKTl854-2bU_KI,10200
264
+ nat/front_ends/mcp/mcp_front_end_plugin_worker.py,sha256=rOF5Z4o-4n3ky57ncNm8XCdBsbRazGeOeiw55nKbZF4,10618
265
265
  nat/front_ends/mcp/register.py,sha256=3aJtgG5VaiqujoeU1-Eq7Hl5pWslIlIwGFU2ASLTXgM,1173
266
- nat/front_ends/mcp/tool_converter.py,sha256=jyH6tFKUDXSfRBKkv8WjvJsQt05zk3FJBTCwnIuUh5M,11547
266
+ nat/front_ends/mcp/tool_converter.py,sha256=q3GgouOAQ8CGeX8nYtpwHUDEm3BWJgstgZHfKSg2m80,12470
267
267
  nat/front_ends/simple_base/__init__.py,sha256=Xs1JQ16L9btwreh4pdGKwskffAw1YFO48jKrU4ib_7c,685
268
268
  nat/front_ends/simple_base/simple_front_end_plugin_base.py,sha256=py_yA9XAw-yHfK5cQJLM8ElnubEEM2ac8M0bvz-ScWs,1801
269
269
  nat/llm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -367,7 +367,7 @@ nat/profiler/inference_optimization/experimental/prefix_span_analysis.py,sha256=
367
367
  nat/profiler/parameter_optimization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
368
368
  nat/profiler/parameter_optimization/optimizable_utils.py,sha256=93Pl8A14Zq_f3XsxSH-yFnEJ6B7W5hp7doPnPoLlRB4,3714
369
369
  nat/profiler/parameter_optimization/optimizer_runtime.py,sha256=rXmCOq81o7ZorQOUYociVjuO3NO9CIjFBbwql2u_4H4,2715
370
- nat/profiler/parameter_optimization/parameter_optimizer.py,sha256=LA2gTBTuezWg5tdqyPA2VjIPkXylgwU9EHpVyaQqBxM,6951
370
+ nat/profiler/parameter_optimization/parameter_optimizer.py,sha256=61U2jW0md2oqaN8cy7cmF8oMTbHlgyzdlAXhG5DqfI8,6962
371
371
  nat/profiler/parameter_optimization/parameter_selection.py,sha256=pfnNQIx1evNICgChsOJXIFQHoL1R_kmh_vNDsVMC9kg,3982
372
372
  nat/profiler/parameter_optimization/pareto_visualizer.py,sha256=QclLZmmsWINIAh4n0XAKmnIZOqGHTMr-iggZS0kxj-Y,17055
373
373
  nat/profiler/parameter_optimization/prompt_optimizer.py,sha256=_AmdeB1jRamd93qR5UqRy5LweYR3bjnD7zoLxzXYE0k,17658
@@ -470,10 +470,10 @@ nat/utils/reactive/base/observer_base.py,sha256=6BiQfx26EMumotJ3KoVcdmFBYR_fnAss
470
470
  nat/utils/reactive/base/subject_base.py,sha256=UQOxlkZTIeeyYmG5qLtDpNf_63Y7p-doEeUA08_R8ME,2521
471
471
  nat/utils/settings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
472
472
  nat/utils/settings/global_settings.py,sha256=9JaO6pxKT_Pjw6rxJRsRlFCXdVKCl_xUKU2QHZQWWNM,7294
473
- nvidia_nat-1.3.0a20251023.dist-info/licenses/LICENSE-3rd-party.txt,sha256=fOk5jMmCX9YoKWyYzTtfgl-SUy477audFC5hNY4oP7Q,284609
474
- nvidia_nat-1.3.0a20251023.dist-info/licenses/LICENSE.md,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
475
- nvidia_nat-1.3.0a20251023.dist-info/METADATA,sha256=xHYZgZtONL4mtI41dGyXTqLJq2leSs15jqI0aWj20YU,10248
476
- nvidia_nat-1.3.0a20251023.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
477
- nvidia_nat-1.3.0a20251023.dist-info/entry_points.txt,sha256=4jCqjyETMpyoWbCBf4GalZU8I_wbstpzwQNezdAVbbo,698
478
- nvidia_nat-1.3.0a20251023.dist-info/top_level.txt,sha256=lgJWLkigiVZuZ_O1nxVnD_ziYBwgpE2OStdaCduMEGc,8
479
- nvidia_nat-1.3.0a20251023.dist-info/RECORD,,
473
+ nvidia_nat-1.3.0a20251031.dist-info/licenses/LICENSE-3rd-party.txt,sha256=fOk5jMmCX9YoKWyYzTtfgl-SUy477audFC5hNY4oP7Q,284609
474
+ nvidia_nat-1.3.0a20251031.dist-info/licenses/LICENSE.md,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
475
+ nvidia_nat-1.3.0a20251031.dist-info/METADATA,sha256=k2pPqrwMO3RC_Ey6hNKBPQ6E-D7iD4smojmXaXHXb4g,10248
476
+ nvidia_nat-1.3.0a20251031.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
477
+ nvidia_nat-1.3.0a20251031.dist-info/entry_points.txt,sha256=4jCqjyETMpyoWbCBf4GalZU8I_wbstpzwQNezdAVbbo,698
478
+ nvidia_nat-1.3.0a20251031.dist-info/top_level.txt,sha256=lgJWLkigiVZuZ_O1nxVnD_ziYBwgpE2OStdaCduMEGc,8
479
+ nvidia_nat-1.3.0a20251031.dist-info/RECORD,,