NVIDIA / NVIDIA/NeMo-Agent-Toolkit
DecomposedType.is_instance fails for union types, so functions with an optional single input cannot be invoked with a value
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 2.6k
- Forks
- 762
- Avg merge
- 21h 28m
- Merged PRs (30d)
- 27
Description
Version
develop (013255a), also reproduces on 1.9.0
Which installation method(s) does this occur on?
Source, PyPI
Describe the bug.
DecomposedType.is_instance (packages/nvidia_nat_core/src/nat/utils/type_utils.py, around line 380) takes the union's root type and passes it to isinstance. For str | None the root is types.UnionType, so the check is always False; for typing.Optional[str] the root is typing.Union, and isinstance raises TypeError: typing.Union cannot be used with isinstance().
is_instance is what Function._convert_input, _needs_conversion and TypeConverter use to decide whether a value already matches the declared input type, so a function whose single input is annotated str | None or Optional[str] cannot be invoked with a plain value at all. Passing None works, passing "hi" fails. The same happens when requesting an optional output via to_type.
Minimum reproducible example
import asyncio
import typing
from nat.utils.type_utils import DecomposedType
print(DecomposedType(str | None).is_instance("hi")) # False, expected True
print(DecomposedType(typing.Optional[str]).is_instance("hi")) # TypeError, expected True
# End to end, through a registered function
from nat.builder.builder import Builder
from nat.builder.workflow_builder import WorkflowBuilder
from nat.cli.register_workflow import register_function
from nat.data_models.function import FunctionBaseConfig
class DummyConfig(FunctionBaseConfig, name="dummy_optional"):
pass
@register_function(config_type=DummyConfig)
async def _register(config: DummyConfig, b: Builder):
async def _inner(message: str | None) -> str:
return f"got {message!r}"
yield _inner
async def main():
async with WorkflowBuilder() as builder:
fn = await builder.add_function(name="f", config=DummyConfig())
print(await fn.ainvoke(None)) # got None
print(await fn.ainvoke("hi")) # TypeError, expected "got 'hi'"
asyncio.run(main())
Relevant log output
TypeError: typing.Union cannot be used with isinstance()
(for the str | None spelling the failure surfaces as a conversion TypeError from Function.ainvoke because is_instance returned False and no converter exists)
Other/Misc.
Proposed fix: when the base type is a union, check the instance against each member (any(DecomposedType(arg).is_instance(instance) for arg in args)), which also handles NoneType and typing.Any members. I have this implemented with unit tests and will open a PR. Report prepared with AI assistance (Claude Code); reproduced locally on develop.
Code of Conduct
- I agree to follow this project's Code of Conduct
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/utils/type_utils.py around DecomposedType.is_instance, then trace its use from Function._convert_input, _needs_conversion, and TypeConverter. Reproduce the str | None and typing.Optional[str] cases from the issue, and run or extend the unit tests so plain values and None are accepted for optional inputs and outputs.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100