NVIDIA / NVIDIA/NeMo-Agent-Toolkit
A NAT function cannot take zero arguments: FunctionInfo.from_fn rejects no-input tools
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 2.6k
- Forks
- 762
- Avg merge
- 21h 28m
- Merged PRs (30d)
- 27
Description
Version
1.8.0 (also on develop @ 2d2e342)
Which installation method(s) does this occur on?
PyPi
Describe the bug.
FunctionInfo.from_fn cannot register a function that takes no parameters. A zero-argument
function is recognised when the descriptor is built, but never wrapped, so it reaches a
validator that requires exactly one parameter and raises.
FunctionDescriptor.from_function handles the zero case explicitly
(packages/nvidia_nat_core/src/nat/builder/function_info.py, develop @ 2d2e342):
arg_count = len(sig.parameters)
if (arg_count == 0):
input_type = NoneType
is_input_typed = False
input_schema = NoneType
elif (arg_count == 1):
...
But FunctionInfo.create only wraps the multi-argument case:
if (final_single_fn_desc.arg_count > 1):
if (input_schema is not None):
logger.warning("Using provided input_schema for multi-argument function")
else:
input_schema = final_single_fn_desc.get_base_model_function_input()
...
final_single_fn = _convert_input_pydantic
Functions with more than one parameter are therefore collapsed into a single-parameter
function whose input is a generated Pydantic model. There is no corresponding
arg_count == 0 branch, so a zero-argument function passes through unchanged and hits
_validate_single_fn:
if len(sig.parameters) != 1:
raise ValueError("single_fn must have exactly one parameter")
The result is that 1 and 3 parameters both work while 0 does not, which reads as an
oversight rather than an intentional constraint - the zero case is recognised at the
descriptor level and then not carried through.
No-input tools are common - fetch the current policy, get the current time, list everything,
health check. The workaround is to add a dummy parameter the tool ignores. That parameter is
then advertised to the model, which has to invent a value for it, so the workaround is
visible in the model's tool schema rather than hidden in the adapter.
Expected: a zero-argument function registers, with an empty input schema.
Suggested approach: mirror the existing arg_count > 1 handling with an arg_count == 0
branch that wraps the function in a single-parameter shim taking an empty Pydantic model, so
the rest of the pipeline continues to see exactly one input. The decision worth making
explicitly is what that empty schema should look like on the wire for the framework adapters
that read it - which is why this is filed as an issue rather than a PR.
Sketch of that branch, mirroring the existing multi-argument one in FunctionInfo.create
(develop @ 2d2e342). Not tested - the empty-schema question below should be settled first:
if (final_single_fn_desc.arg_count == 0):
if (input_schema is None):
# An input model with no fields, so the rest of the pipeline still
# sees exactly one parameter.
input_schema = create_model(f"{final_single_fn.__name__}Input")
saved_final_single_fn = final_single_fn
async def _discard_empty_input(value: input_schema) -> final_single_fn_desc.output_type:
# Nothing to unpack - the function takes no arguments.
return await saved_final_single_fn()
final_single_fn = _discard_empty_input
# Reset the descriptor
final_single_fn_desc = FunctionDescriptor.from_function(final_single_fn)
elif (final_single_fn_desc.arg_count > 1):
... # unchanged
The same treatment would be needed on the streaming path, which has the matching
final_stream_fn_desc.arg_count > 1 branch and no zero case either.
The part that is genuinely a maintainer decision, and the reason this is an issue rather than
a PR: what an empty input schema should look like on the wire. A field-less Pydantic model
serialises to {"type": "object", "properties": {}}, which the framework adapters then hand
to their own tool-declaration builders. Whether every adapter is happy with an empty
properties object - or whether a no-input tool should be represented some other way - is a
call about NAT's public contract, not something worth guessing at from outside.
Minimum reproducible example
import asyncio
import inspect
from nat.builder.function_info import FunctionInfo
async def no_args() -> str:
"""A tool that needs no input, e.g. fetch the current policy."""
return "policy text"
async def one_arg(text: str) -> str:
"""One parameter."""
return text
async def three_args(query: str, limit: int = 5, region: str = "global") -> str:
"""Three parameters, two optional."""
return f"{query}{limit}{region}"
async def main():
for fn in (no_args, one_arg, three_args):
count = len(inspect.signature(fn).parameters)
try:
FunctionInfo.from_fn(fn, description=fn.__doc__)
print(f"{fn.__name__:<12} ({count} params): OK")
except Exception as exc:
print(f"{fn.__name__:<12} ({count} params): {type(exc).__name__}: {exc}")
asyncio.run(main())
Relevant log output
no_args (0 params): ValueError: single_fn must have exactly one parameter
one_arg (1 params): OK
three_args (3 params): OK
Other/Misc.
Traceback for the failing case:
File ".../nat/builder/function_info.py", line 621, in from_fn
return FunctionInfo.create(single_fn=single_fn, ...)
File ".../nat/builder/function_info.py", line 543, in create
return FunctionInfo(single_fn=final_single_fn, ...)
File ".../nat/builder/function_info.py", line 311, in __init__
single_input_type, single_output_type = _validate_single_fn(single_fn)
File ".../nat/builder/function_info.py", line 61, in _validate_single_fn
raise ValueError("single_fn must have exactly one parameter")
ValueError: single_fn must have exactly one parameter
Environment:
nvidia-nat-core 1.8.0
python 3.11.9
OS Windows 11
Code of Conduct
- I agree to follow the NeMo Agent Toolkit Code of Conduct
- I have searched the open bugs and have found no duplicates for this bug report
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in packages/nvidia_nat_core/src/nat/builder/function_info.py, especially FunctionInfo.create, FunctionDescriptor.from_function, and _validate_single_fn. Compare the existing multi-argument handling with the matching streaming path, then use the provided no_args reproduction to trace how the input schema reaches framework adapters. Done means zero-argument functions register successfully with an agreed empty input schema on both paths.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend-api-design
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100