nvidia-nat 1.3.0a20250923__py3-none-any.whl → 1.3.0a20250925__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 (44) hide show
  1. nat/agent/react_agent/agent.py +5 -4
  2. nat/agent/react_agent/register.py +12 -1
  3. nat/agent/reasoning_agent/reasoning_agent.py +2 -2
  4. nat/agent/rewoo_agent/register.py +12 -1
  5. nat/agent/tool_calling_agent/register.py +28 -8
  6. nat/builder/builder.py +33 -24
  7. nat/builder/component_utils.py +1 -1
  8. nat/builder/eval_builder.py +14 -9
  9. nat/builder/framework_enum.py +1 -0
  10. nat/builder/function.py +108 -52
  11. nat/builder/workflow_builder.py +89 -79
  12. nat/cli/commands/info/info.py +16 -6
  13. nat/cli/commands/mcp/__init__.py +14 -0
  14. nat/cli/commands/mcp/mcp.py +786 -0
  15. nat/cli/entrypoint.py +2 -1
  16. nat/control_flow/router_agent/register.py +1 -1
  17. nat/control_flow/sequential_executor.py +6 -7
  18. nat/eval/evaluate.py +2 -1
  19. nat/eval/trajectory_evaluator/register.py +1 -1
  20. nat/experimental/decorators/experimental_warning_decorator.py +26 -5
  21. nat/experimental/test_time_compute/functions/plan_select_execute_function.py +2 -2
  22. nat/experimental/test_time_compute/functions/ttc_tool_orchestration_function.py +1 -1
  23. nat/experimental/test_time_compute/functions/ttc_tool_wrapper_function.py +1 -1
  24. nat/experimental/test_time_compute/models/strategy_base.py +2 -2
  25. nat/front_ends/console/console_front_end_plugin.py +4 -3
  26. nat/front_ends/fastapi/fastapi_front_end_plugin_worker.py +3 -3
  27. nat/front_ends/mcp/mcp_front_end_plugin_worker.py +4 -4
  28. nat/front_ends/simple_base/simple_front_end_plugin_base.py +3 -1
  29. nat/llm/litellm_llm.py +69 -0
  30. nat/llm/register.py +4 -0
  31. nat/profiler/decorators/framework_wrapper.py +52 -3
  32. nat/profiler/decorators/function_tracking.py +33 -1
  33. nat/profiler/parameter_optimization/prompt_optimizer.py +2 -2
  34. nat/runtime/loader.py +1 -1
  35. nat/utils/type_converter.py +4 -3
  36. nat/utils/type_utils.py +1 -1
  37. {nvidia_nat-1.3.0a20250923.dist-info → nvidia_nat-1.3.0a20250925.dist-info}/METADATA +6 -3
  38. {nvidia_nat-1.3.0a20250923.dist-info → nvidia_nat-1.3.0a20250925.dist-info}/RECORD +43 -41
  39. nat/cli/commands/info/list_mcp.py +0 -461
  40. {nvidia_nat-1.3.0a20250923.dist-info → nvidia_nat-1.3.0a20250925.dist-info}/WHEEL +0 -0
  41. {nvidia_nat-1.3.0a20250923.dist-info → nvidia_nat-1.3.0a20250925.dist-info}/entry_points.txt +0 -0
  42. {nvidia_nat-1.3.0a20250923.dist-info → nvidia_nat-1.3.0a20250925.dist-info}/licenses/LICENSE-3rd-party.txt +0 -0
  43. {nvidia_nat-1.3.0a20250923.dist-info → nvidia_nat-1.3.0a20250925.dist-info}/licenses/LICENSE.md +0 -0
  44. {nvidia_nat-1.3.0a20250923.dist-info → nvidia_nat-1.3.0a20250925.dist-info}/top_level.txt +0 -0
@@ -1,461 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
- # SPDX-License-Identifier: Apache-2.0
3
- #
4
- # Licensed under the Apache License, Version 2.0 (the "License");
5
- # you may not use this file except in compliance with the License.
6
- # You may obtain a copy of the License at
7
- #
8
- # http://www.apache.org/licenses/LICENSE-2.0
9
- #
10
- # Unless required by applicable law or agreed to in writing, software
11
- # distributed under the License is distributed on an "AS IS" BASIS,
12
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- # See the License for the specific language governing permissions and
14
- # limitations under the License.
15
-
16
- import asyncio
17
- import json
18
- import logging
19
- import time
20
- from typing import Any
21
-
22
- import click
23
- from pydantic import BaseModel
24
-
25
- try:
26
- from nat.plugins.mcp.exception_handler import format_mcp_error
27
- from nat.plugins.mcp.exceptions import MCPError
28
- except ImportError:
29
- # Fallback for when MCP client package is not installed
30
- MCPError = Exception
31
-
32
- def format_mcp_error(error, include_traceback=False):
33
- click.echo(f"Error: {error}", err=True)
34
-
35
-
36
- # Suppress verbose logs from mcp.client.sse and httpx
37
- logging.getLogger("mcp.client.sse").setLevel(logging.WARNING)
38
- logging.getLogger("httpx").setLevel(logging.WARNING)
39
-
40
-
41
- def validate_transport_cli_args(transport: str, command: str | None, args: str | None, env: str | None) -> bool:
42
- """
43
- Validate transport and parameter combinations, returning False if invalid.
44
-
45
- Args:
46
- transport: The transport type ('sse', 'stdio', or 'streamable-http')
47
- command: Command for stdio transport
48
- args: Arguments for stdio transport
49
- env: Environment variables for stdio transport
50
-
51
- Returns:
52
- bool: True if valid, False if invalid (error message already displayed)
53
- """
54
- if transport == 'stdio':
55
- if not command:
56
- click.echo("--command is required when using stdio client type", err=True)
57
- return False
58
- elif transport in ['sse', 'streamable-http']:
59
- if command or args or env:
60
- click.echo("--command, --args, and --env are not allowed when using sse or streamable-http client type",
61
- err=True)
62
- return False
63
- return True
64
-
65
-
66
- class MCPPingResult(BaseModel):
67
- """Result of an MCP server ping request.
68
-
69
- Attributes:
70
- url (str): The MCP server URL that was pinged
71
- status (str): Health status - 'healthy', 'unhealthy', or 'unknown'
72
- response_time_ms (float | None): Response time in milliseconds, None if request failed completely
73
- error (str | None): Error message if the ping failed, None if successful
74
- """
75
- url: str
76
- status: str
77
- response_time_ms: float | None
78
- error: str | None
79
-
80
-
81
- def format_tool(tool: Any) -> dict[str, str | None]:
82
- """Format an MCP tool into a dictionary for display.
83
-
84
- Extracts name, description, and input schema from various MCP tool object types
85
- and normalizes them into a consistent dictionary format for CLI display.
86
-
87
- Args:
88
- tool (Any): MCPToolClient or raw MCP Tool object (uses Any due to different types)
89
-
90
- Returns:
91
- dict[str, str | None]: Dictionary with name, description, and input_schema as keys
92
- """
93
- name = getattr(tool, 'name', None)
94
- description = getattr(tool, 'description', '')
95
- input_schema = getattr(tool, 'input_schema', None) or getattr(tool, 'inputSchema', None)
96
-
97
- # Normalize schema to JSON string
98
- if input_schema is None:
99
- return {
100
- "name": name,
101
- "description": description,
102
- "input_schema": None,
103
- }
104
- elif hasattr(input_schema, "schema_json"):
105
- schema_str = input_schema.schema_json(indent=2)
106
- elif isinstance(input_schema, dict):
107
- schema_str = json.dumps(input_schema, indent=2)
108
- else:
109
- # Final fallback: attempt to dump stringified version wrapped as JSON string
110
- schema_str = json.dumps({"raw": str(input_schema)}, indent=2)
111
-
112
- return {
113
- "name": name,
114
- "description": description,
115
- "input_schema": schema_str,
116
- }
117
-
118
-
119
- def print_tool(tool_dict: dict[str, str | None], detail: bool = False) -> None:
120
- """Print a formatted tool to the console with optional detailed information.
121
-
122
- Outputs tool information in a user-friendly format to stdout. When detail=True
123
- or when description/schema are available, shows full information with separator.
124
-
125
- Args:
126
- tool_dict (dict[str, str | None]): Dictionary containing tool information with name, description, and
127
- input_schema as keys
128
- detail (bool, optional): Whether to force detailed output. Defaults to False.
129
- """
130
- click.echo(f"Tool: {tool_dict.get('name', 'Unknown')}")
131
- if detail or tool_dict.get('input_schema') or tool_dict.get('description'):
132
- click.echo(f"Description: {tool_dict.get('description', 'No description available')}")
133
- if tool_dict.get("input_schema"):
134
- click.echo("Input Schema:")
135
- click.echo(tool_dict.get("input_schema"))
136
- else:
137
- click.echo("Input Schema: None")
138
- click.echo("-" * 60)
139
-
140
-
141
- async def list_tools_and_schemas(command, url, tool_name=None, transport='sse', args=None, env=None):
142
- """List MCP tools using NAT MCPClient with structured exception handling.
143
-
144
- Args:
145
- url (str): MCP server URL to connect to
146
- tool_name (str | None, optional): Specific tool name to retrieve.
147
- If None, retrieves all available tools. Defaults to None.
148
-
149
- Returns:
150
- list[dict[str, str | None]]: List of formatted tool dictionaries, each containing name, description, and
151
- input_schema as keys
152
-
153
- Raises:
154
- MCPError: Caught internally and logged, returns empty list instead
155
- """
156
- try:
157
- from nat.plugins.mcp.client_base import MCPSSEClient
158
- from nat.plugins.mcp.client_base import MCPStdioClient
159
- from nat.plugins.mcp.client_base import MCPStreamableHTTPClient
160
- except ImportError:
161
- click.echo(
162
- "MCP client functionality requires nvidia-nat-mcp package. Install with: uv pip install nvidia-nat-mcp",
163
- err=True)
164
- return []
165
-
166
- if args is None:
167
- args = []
168
-
169
- try:
170
- if transport == 'stdio':
171
- client = MCPStdioClient(command=command, args=args, env=env)
172
- elif transport == 'streamable-http':
173
- client = MCPStreamableHTTPClient(url=url)
174
- else: # sse
175
- client = MCPSSEClient(url=url)
176
-
177
- async with client:
178
- if tool_name:
179
- tool = await client.get_tool(tool_name)
180
- return [format_tool(tool)]
181
- else:
182
- tools = await client.get_tools()
183
- return [format_tool(tool) for tool in tools.values()]
184
- except MCPError as e:
185
- format_mcp_error(e, include_traceback=False)
186
- return []
187
-
188
-
189
- async def list_tools_direct(command, url, tool_name=None, transport='sse', args=None, env=None):
190
- """List MCP tools using direct MCP protocol with structured exception handling.
191
-
192
- Bypasses MCPBuilder and uses raw MCP ClientSession and SSE client directly.
193
- Converts raw exceptions to structured MCPErrors for consistent user experience.
194
- Used when --direct flag is specified in CLI.
195
-
196
- Args:
197
- url (str): MCP server URL to connect to
198
- tool_name (str | None, optional): Specific tool name to retrieve.
199
- If None, retrieves all available tools. Defaults to None.
200
-
201
- Returns:
202
- list[dict[str, str | None]]: List of formatted tool dictionaries, each containing name, description, and
203
- input_schema as keys
204
-
205
- Note:
206
- This function handles ExceptionGroup by extracting the most relevant exception
207
- and converting it to MCPError for consistent error reporting.
208
- """
209
- if args is None:
210
- args = []
211
- from mcp import ClientSession
212
- from mcp.client.sse import sse_client
213
- from mcp.client.stdio import StdioServerParameters
214
- from mcp.client.stdio import stdio_client
215
- from mcp.client.streamable_http import streamablehttp_client
216
-
217
- try:
218
- if transport == 'stdio':
219
-
220
- def get_stdio_client():
221
- return stdio_client(server=StdioServerParameters(command=command, args=args, env=env))
222
-
223
- client = get_stdio_client
224
- elif transport == 'streamable-http':
225
-
226
- def get_streamable_http_client():
227
- return streamablehttp_client(url=url)
228
-
229
- client = get_streamable_http_client
230
- else:
231
-
232
- def get_sse_client():
233
- return sse_client(url=url)
234
-
235
- client = get_sse_client
236
-
237
- async with client() as ctx:
238
- read, write = (ctx[0], ctx[1]) if isinstance(ctx, tuple) else ctx
239
- async with ClientSession(read, write) as session:
240
- await session.initialize()
241
- response = await session.list_tools()
242
-
243
- tools = []
244
- for tool in response.tools:
245
- if tool_name:
246
- if tool.name == tool_name:
247
- tools.append(format_tool(tool))
248
- else:
249
- tools.append(format_tool(tool))
250
-
251
- if tool_name and not tools:
252
- click.echo(f"[INFO] Tool '{tool_name}' not found.")
253
- return tools
254
- except Exception as e:
255
- # Convert raw exceptions to structured MCPError for consistency
256
- try:
257
- from nat.plugins.mcp.exception_handler import convert_to_mcp_error
258
- from nat.plugins.mcp.exception_handler import extract_primary_exception
259
- except ImportError:
260
- # Fallback when MCP client package is not installed
261
- def convert_to_mcp_error(exception, url):
262
- return Exception(f"Error connecting to {url}: {exception}")
263
-
264
- def extract_primary_exception(exceptions):
265
- return exceptions[0] if exceptions else Exception("Unknown error")
266
-
267
- if isinstance(e, ExceptionGroup): # noqa: F821
268
- primary_exception = extract_primary_exception(list(e.exceptions))
269
- mcp_error = convert_to_mcp_error(primary_exception, url)
270
- else:
271
- mcp_error = convert_to_mcp_error(e, url)
272
-
273
- format_mcp_error(mcp_error, include_traceback=False)
274
- return []
275
-
276
-
277
- async def ping_mcp_server(url: str,
278
- timeout: int,
279
- transport: str = 'streamable-http',
280
- command: str | None = None,
281
- args: list[str] | None = None,
282
- env: dict[str, str] | None = None) -> MCPPingResult:
283
- """Ping an MCP server to check if it's responsive.
284
-
285
- Args:
286
- url (str): MCP server URL to ping
287
- timeout (int): Timeout in seconds for the ping request
288
-
289
- Returns:
290
- MCPPingResult: Structured result with status, response_time, and any error info
291
- """
292
- from mcp.client.session import ClientSession
293
- from mcp.client.sse import sse_client
294
- from mcp.client.stdio import StdioServerParameters
295
- from mcp.client.stdio import stdio_client
296
- from mcp.client.streamable_http import streamablehttp_client
297
-
298
- async def _ping_operation():
299
- # Select transport
300
- if transport == 'stdio':
301
- stdio_args_local: list[str] = args or []
302
- if not command:
303
- raise RuntimeError("--command is required for stdio transport")
304
- client_ctx = stdio_client(server=StdioServerParameters(command=command, args=stdio_args_local, env=env))
305
- elif transport == 'sse':
306
- client_ctx = sse_client(url)
307
- else: # streamable-http
308
- client_ctx = streamablehttp_client(url=url)
309
-
310
- async with client_ctx as ctx:
311
- read, write = (ctx[0], ctx[1]) if isinstance(ctx, tuple) else ctx
312
- async with ClientSession(read, write) as session:
313
- await session.initialize()
314
-
315
- start_time = time.time()
316
- await session.send_ping()
317
- end_time = time.time()
318
- response_time_ms = round((end_time - start_time) * 1000, 2)
319
-
320
- return MCPPingResult(url=url, status="healthy", response_time_ms=response_time_ms, error=None)
321
-
322
- try:
323
- # Apply timeout to the entire ping operation
324
- return await asyncio.wait_for(_ping_operation(), timeout=timeout)
325
-
326
- except asyncio.TimeoutError:
327
- return MCPPingResult(url=url,
328
- status="unreachable",
329
- response_time_ms=None,
330
- error=f"Timeout after {timeout} seconds")
331
-
332
- except Exception as e:
333
- return MCPPingResult(url=url, status="unhealthy", response_time_ms=None, error=str(e))
334
-
335
-
336
- @click.group(invoke_without_command=True, help="List tool names (default), or show details with --detail or --tool.")
337
- @click.option('--direct', is_flag=True, help='Bypass MCPBuilder and use direct MCP protocol')
338
- @click.option(
339
- '--url',
340
- default='http://localhost:9901/mcp',
341
- show_default=True,
342
- help='MCP server URL (e.g. http://localhost:8080/mcp for streamable-http, http://localhost:8080/sse for sse)')
343
- @click.option('--transport',
344
- type=click.Choice(['sse', 'stdio', 'streamable-http']),
345
- default='streamable-http',
346
- show_default=True,
347
- help='Type of client to use (default: streamable-http, backwards compatible with sse)')
348
- @click.option('--command', help='For stdio: The command to run (e.g. mcp-server)')
349
- @click.option('--args', help='For stdio: Additional arguments for the command (space-separated)')
350
- @click.option('--env', help='For stdio: Environment variables in KEY=VALUE format (space-separated)')
351
- @click.option('--tool', default=None, help='Get details for a specific tool by name')
352
- @click.option('--detail', is_flag=True, help='Show full details for all tools')
353
- @click.option('--json-output', is_flag=True, help='Output tool metadata in JSON format')
354
- @click.pass_context
355
- def list_mcp(ctx, direct, url, transport, command, args, env, tool, detail, json_output):
356
- """List MCP tool names (default) or show detailed tool information.
357
-
358
- Use --detail for full output including descriptions and input schemas.
359
- If --tool is provided, always shows full output for that specific tool.
360
- Use --direct to bypass MCPBuilder and use raw MCP protocol.
361
- Use --json-output to get structured JSON data instead of formatted text.
362
-
363
- Args:
364
- ctx (click.Context): Click context object for command invocation
365
- direct (bool): Whether to bypass MCPBuilder and use direct MCP protocol
366
- url (str): MCP server URL to connect to (default: http://localhost:9901/mcp)
367
- tool (str | None): Optional specific tool name to retrieve detailed info for
368
- detail (bool): Whether to show full details (description + schema) for all tools
369
- json_output (bool): Whether to output tool metadata in JSON format instead of text
370
-
371
- Examples:
372
- nat info mcp # List tool names only
373
- nat info mcp --detail # Show all tools with full details
374
- nat info mcp --tool my_tool # Show details for specific tool
375
- nat info mcp --json-output # Get JSON format output
376
- nat info mcp --direct --url http://... # Use direct protocol with custom URL
377
- """
378
- if ctx.invoked_subcommand is not None:
379
- return
380
-
381
- if not validate_transport_cli_args(transport, command, args, env):
382
- return
383
-
384
- if transport in ['sse', 'streamable-http']:
385
- if not url:
386
- click.echo("[ERROR] --url is required when using sse or streamable-http client type", err=True)
387
- return
388
-
389
- stdio_args = args.split() if args else []
390
- stdio_env = dict(var.split('=', 1) for var in env.split()) if env else None
391
-
392
- fetcher = list_tools_direct if direct else list_tools_and_schemas
393
- tools = asyncio.run(fetcher(command, url, tool, transport, stdio_args, stdio_env))
394
-
395
- if json_output:
396
- click.echo(json.dumps(tools, indent=2))
397
- elif tool:
398
- for tool_dict in (tools or []):
399
- print_tool(tool_dict, detail=True)
400
- elif detail:
401
- for tool_dict in (tools or []):
402
- print_tool(tool_dict, detail=True)
403
- else:
404
- for tool_dict in (tools or []):
405
- click.echo(tool_dict.get('name', 'Unknown tool'))
406
-
407
-
408
- @list_mcp.command()
409
- @click.option(
410
- '--url',
411
- default='http://localhost:9901/mcp',
412
- show_default=True,
413
- help='MCP server URL (e.g. http://localhost:8080/mcp for streamable-http, http://localhost:8080/sse for sse)')
414
- @click.option('--transport',
415
- type=click.Choice(['sse', 'stdio', 'streamable-http']),
416
- default='streamable-http',
417
- show_default=True,
418
- help='Type of client to use for ping')
419
- @click.option('--command', help='For stdio: The command to run (e.g. mcp-server)')
420
- @click.option('--args', help='For stdio: Additional arguments for the command (space-separated)')
421
- @click.option('--env', help='For stdio: Environment variables in KEY=VALUE format (space-separated)')
422
- @click.option('--timeout', default=60, show_default=True, help='Timeout in seconds for ping request')
423
- @click.option('--json-output', is_flag=True, help='Output ping result in JSON format')
424
- def ping(url: str,
425
- transport: str,
426
- command: str | None,
427
- args: str | None,
428
- env: str | None,
429
- timeout: int,
430
- json_output: bool) -> None:
431
- """Ping an MCP server to check if it's responsive.
432
-
433
- This command sends a ping request to the MCP server and measures the response time.
434
- It's useful for health checks and monitoring server availability.
435
-
436
- Args:
437
- url (str): MCP server URL to ping (default: http://localhost:9901/mcp)
438
- timeout (int): Timeout in seconds for the ping request (default: 60)
439
- json_output (bool): Whether to output the result in JSON format
440
-
441
- Examples:
442
- nat info mcp ping # Ping default server
443
- nat info mcp ping --url http://custom-server:9901/mcp # Ping custom server
444
- nat info mcp ping --timeout 10 # Use 10 second timeout
445
- nat info mcp ping --json-output # Get JSON format output
446
- """
447
- # Validate combinations similar to parent command
448
- if not validate_transport_cli_args(transport, command, args, env):
449
- return
450
-
451
- stdio_args = args.split() if args else []
452
- stdio_env = dict(var.split('=', 1) for var in env.split()) if env else None
453
-
454
- result = asyncio.run(ping_mcp_server(url, timeout, transport, command, stdio_args, stdio_env))
455
-
456
- if json_output:
457
- click.echo(result.model_dump_json(indent=2))
458
- elif result.status == "healthy":
459
- click.echo(f"Server at {result.url} is healthy (response time: {result.response_time_ms}ms)")
460
- else:
461
- click.echo(f"Server at {result.url} {result.status}: {result.error}")