nvidia-nat 1.4.0a20251028__py3-none-any.whl → 1.4.0a20251030__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.
- nat/authentication/api_key/api_key_auth_provider.py +5 -10
- nat/authentication/api_key/api_key_auth_provider_config.py +8 -5
- nat/authentication/oauth2/oauth2_auth_code_flow_provider_config.py +2 -1
- nat/authentication/oauth2/oauth2_resource_server_config.py +2 -1
- nat/data_models/api_server.py +3 -2
- nat/data_models/common.py +47 -0
- nat/data_models/dataset_handler.py +3 -2
- nat/embedder/azure_openai_embedder.py +2 -1
- nat/embedder/nim_embedder.py +2 -1
- nat/embedder/openai_embedder.py +2 -1
- nat/eval/dataset_handler/dataset_downloader.py +3 -2
- nat/eval/utils/output_uploader.py +3 -2
- nat/front_ends/console/authentication_flow_handler.py +1 -1
- nat/front_ends/mcp/mcp_front_end_config.py +16 -0
- nat/front_ends/mcp/mcp_front_end_plugin.py +76 -41
- nat/front_ends/mcp/mcp_front_end_plugin_worker.py +128 -38
- nat/front_ends/mcp/tool_converter.py +53 -22
- nat/llm/azure_openai_llm.py +2 -1
- nat/llm/litellm_llm.py +2 -1
- nat/llm/nim_llm.py +2 -1
- nat/llm/openai_llm.py +2 -1
- nat/registry_handlers/pypi/register_pypi.py +5 -3
- nat/registry_handlers/rest/register_rest.py +5 -3
- nat/retriever/nemo_retriever/register.py +2 -1
- {nvidia_nat-1.4.0a20251028.dist-info → nvidia_nat-1.4.0a20251030.dist-info}/METADATA +2 -2
- {nvidia_nat-1.4.0a20251028.dist-info → nvidia_nat-1.4.0a20251030.dist-info}/RECORD +31 -31
- {nvidia_nat-1.4.0a20251028.dist-info → nvidia_nat-1.4.0a20251030.dist-info}/WHEEL +0 -0
- {nvidia_nat-1.4.0a20251028.dist-info → nvidia_nat-1.4.0a20251030.dist-info}/entry_points.txt +0 -0
- {nvidia_nat-1.4.0a20251028.dist-info → nvidia_nat-1.4.0a20251030.dist-info}/licenses/LICENSE-3rd-party.txt +0 -0
- {nvidia_nat-1.4.0a20251028.dist-info → nvidia_nat-1.4.0a20251030.dist-info}/licenses/LICENSE.md +0 -0
- {nvidia_nat-1.4.0a20251028.dist-info → nvidia_nat-1.4.0a20251030.dist-info}/top_level.txt +0 -0
|
@@ -15,8 +15,6 @@
|
|
|
15
15
|
|
|
16
16
|
import logging
|
|
17
17
|
|
|
18
|
-
from pydantic import SecretStr
|
|
19
|
-
|
|
20
18
|
from nat.authentication.api_key.api_key_auth_provider_config import APIKeyAuthProviderConfig
|
|
21
19
|
from nat.authentication.interfaces import AuthProviderBase
|
|
22
20
|
from nat.data_models.authentication import AuthResult
|
|
@@ -29,11 +27,10 @@ logger = logging.getLogger(__name__)
|
|
|
29
27
|
class APIKeyAuthProvider(AuthProviderBase[APIKeyAuthProviderConfig]):
|
|
30
28
|
|
|
31
29
|
# fmt: off
|
|
32
|
-
def __init__(self,
|
|
33
|
-
config: APIKeyAuthProviderConfig,
|
|
34
|
-
config_name: str | None = None) -> None:
|
|
30
|
+
def __init__(self, config: APIKeyAuthProviderConfig, config_name: str | None = None) -> None:
|
|
35
31
|
assert isinstance(config, APIKeyAuthProviderConfig), ("Config is not APIKeyAuthProviderConfig")
|
|
36
32
|
super().__init__(config)
|
|
33
|
+
|
|
37
34
|
# fmt: on
|
|
38
35
|
|
|
39
36
|
async def _construct_authentication_header(self) -> BearerTokenCred:
|
|
@@ -58,14 +55,12 @@ class APIKeyAuthProvider(AuthProviderBase[APIKeyAuthProviderConfig]):
|
|
|
58
55
|
header_auth_scheme = config.auth_scheme
|
|
59
56
|
|
|
60
57
|
if header_auth_scheme == HeaderAuthScheme.BEARER:
|
|
61
|
-
return BearerTokenCred(token=
|
|
58
|
+
return BearerTokenCred(token=config.raw_key,
|
|
62
59
|
scheme=HeaderAuthScheme.BEARER.value,
|
|
63
60
|
header_name=AUTHORIZATION_HEADER)
|
|
64
61
|
|
|
65
62
|
if header_auth_scheme == HeaderAuthScheme.X_API_KEY:
|
|
66
|
-
return BearerTokenCred(token=
|
|
67
|
-
scheme=HeaderAuthScheme.X_API_KEY.value,
|
|
68
|
-
header_name='')
|
|
63
|
+
return BearerTokenCred(token=config.raw_key, scheme=HeaderAuthScheme.X_API_KEY.value, header_name='')
|
|
69
64
|
|
|
70
65
|
if header_auth_scheme == HeaderAuthScheme.CUSTOM:
|
|
71
66
|
if not config.custom_header_name:
|
|
@@ -74,7 +69,7 @@ class APIKeyAuthProvider(AuthProviderBase[APIKeyAuthProviderConfig]):
|
|
|
74
69
|
if not config.custom_header_prefix:
|
|
75
70
|
raise ValueError('custom_header_prefix required when using header_auth_scheme=CUSTOM')
|
|
76
71
|
|
|
77
|
-
return BearerTokenCred(token=
|
|
72
|
+
return BearerTokenCred(token=config.raw_key,
|
|
78
73
|
scheme=config.custom_header_prefix,
|
|
79
74
|
header_name=config.custom_header_name)
|
|
80
75
|
|
|
@@ -25,6 +25,7 @@ from nat.authentication.exceptions.api_key_exceptions import HeaderNameFieldErro
|
|
|
25
25
|
from nat.authentication.exceptions.api_key_exceptions import HeaderPrefixFieldError
|
|
26
26
|
from nat.data_models.authentication import AuthProviderBaseConfig
|
|
27
27
|
from nat.data_models.authentication import HeaderAuthScheme
|
|
28
|
+
from nat.data_models.common import SerializableSecretStr
|
|
28
29
|
|
|
29
30
|
logger = logging.getLogger(__name__)
|
|
30
31
|
|
|
@@ -37,8 +38,9 @@ class APIKeyAuthProviderConfig(AuthProviderBaseConfig, name="api_key"):
|
|
|
37
38
|
API Key authentication configuration model.
|
|
38
39
|
"""
|
|
39
40
|
|
|
40
|
-
raw_key:
|
|
41
|
-
|
|
41
|
+
raw_key: SerializableSecretStr = Field(
|
|
42
|
+
description=("Raw API token or credential to be injected into the request parameter. "
|
|
43
|
+
"Used for 'bearer','x-api-key','custom', and other schemes. "))
|
|
42
44
|
|
|
43
45
|
auth_scheme: HeaderAuthScheme = Field(default=HeaderAuthScheme.BEARER,
|
|
44
46
|
description=("The HTTP authentication scheme to use. "
|
|
@@ -53,7 +55,7 @@ class APIKeyAuthProviderConfig(AuthProviderBaseConfig, name="api_key"):
|
|
|
53
55
|
|
|
54
56
|
@field_validator('raw_key')
|
|
55
57
|
@classmethod
|
|
56
|
-
def validate_raw_key(cls, value:
|
|
58
|
+
def validate_raw_key(cls, value: SerializableSecretStr) -> SerializableSecretStr:
|
|
57
59
|
if not value:
|
|
58
60
|
raise APIKeyFieldError('value_missing', 'raw_key field value is required.')
|
|
59
61
|
|
|
@@ -63,11 +65,12 @@ class APIKeyAuthProviderConfig(AuthProviderBaseConfig, name="api_key"):
|
|
|
63
65
|
'raw_key field value must be at least 8 characters long for security. '
|
|
64
66
|
f'Got: {len(value)} characters.')
|
|
65
67
|
|
|
66
|
-
|
|
68
|
+
str_value = value.get_secret_value()
|
|
69
|
+
if len(str_value.strip()) != len(value):
|
|
67
70
|
raise APIKeyFieldError('whitespace_found',
|
|
68
71
|
'raw_key field value cannot have leading or trailing whitespace.')
|
|
69
72
|
|
|
70
|
-
if any(c in string.whitespace for c in
|
|
73
|
+
if any(c in string.whitespace for c in str_value):
|
|
71
74
|
raise APIKeyFieldError('contains_whitespace', 'raw_key must not contain any '
|
|
72
75
|
'whitespace characters.')
|
|
73
76
|
|
|
@@ -16,12 +16,13 @@
|
|
|
16
16
|
from pydantic import Field
|
|
17
17
|
|
|
18
18
|
from nat.data_models.authentication import AuthProviderBaseConfig
|
|
19
|
+
from nat.data_models.common import SerializableSecretStr
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
class OAuth2AuthCodeFlowProviderConfig(AuthProviderBaseConfig, name="oauth2_auth_code_flow"):
|
|
22
23
|
|
|
23
24
|
client_id: str = Field(description="The client ID for OAuth 2.0 authentication.")
|
|
24
|
-
client_secret:
|
|
25
|
+
client_secret: SerializableSecretStr = Field(description="The secret associated with the client_id.")
|
|
25
26
|
authorization_url: str = Field(description="The authorization URL for OAuth 2.0 authentication.")
|
|
26
27
|
token_url: str = Field(description="The token URL for OAuth 2.0 authentication.")
|
|
27
28
|
token_endpoint_auth_method: str | None = Field(
|
|
@@ -20,6 +20,7 @@ from pydantic import field_validator
|
|
|
20
20
|
from pydantic import model_validator
|
|
21
21
|
|
|
22
22
|
from nat.data_models.authentication import AuthProviderBaseConfig
|
|
23
|
+
from nat.data_models.common import OptionalSecretStr
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
class OAuth2ResourceServerConfig(AuthProviderBaseConfig, name="oauth2_resource_server"):
|
|
@@ -66,7 +67,7 @@ class OAuth2ResourceServerConfig(AuthProviderBaseConfig, name="oauth2_resource_s
|
|
|
66
67
|
default=None,
|
|
67
68
|
description="OAuth2 client ID for authenticating to the introspection endpoint (opaque token validation).",
|
|
68
69
|
)
|
|
69
|
-
client_secret:
|
|
70
|
+
client_secret: OptionalSecretStr = Field(
|
|
70
71
|
default=None,
|
|
71
72
|
description="OAuth2 client secret for authenticating to the introspection endpoint (opaque token validation).",
|
|
72
73
|
)
|
nat/data_models/api_server.py
CHANGED
|
@@ -31,6 +31,7 @@ from pydantic import field_validator
|
|
|
31
31
|
from pydantic import model_validator
|
|
32
32
|
from pydantic_core.core_schema import ValidationInfo
|
|
33
33
|
|
|
34
|
+
from nat.data_models.common import SerializableSecretStr
|
|
34
35
|
from nat.data_models.interactive import HumanPrompt
|
|
35
36
|
from nat.utils.type_converter import GlobalTypeConverter
|
|
36
37
|
|
|
@@ -109,8 +110,8 @@ class TextContent(BaseModel):
|
|
|
109
110
|
class Security(BaseModel):
|
|
110
111
|
model_config = ConfigDict(extra="forbid")
|
|
111
112
|
|
|
112
|
-
api_key:
|
|
113
|
-
token:
|
|
113
|
+
api_key: SerializableSecretStr = Field(default="default")
|
|
114
|
+
token: SerializableSecretStr = Field(default="default")
|
|
114
115
|
|
|
115
116
|
|
|
116
117
|
UserContent = typing.Annotated[TextContent | ImageContent | AudioContent, Discriminator("type")]
|
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)]
|
|
@@ -26,6 +26,7 @@ from pydantic import FilePath
|
|
|
26
26
|
from pydantic import Tag
|
|
27
27
|
|
|
28
28
|
from nat.data_models.common import BaseModelRegistryTag
|
|
29
|
+
from nat.data_models.common import SerializableSecretStr
|
|
29
30
|
from nat.data_models.common import TypedBaseModel
|
|
30
31
|
|
|
31
32
|
|
|
@@ -34,8 +35,8 @@ class EvalS3Config(BaseModel):
|
|
|
34
35
|
endpoint_url: str | None = None
|
|
35
36
|
region_name: str | None = None
|
|
36
37
|
bucket: str
|
|
37
|
-
access_key:
|
|
38
|
-
secret_key:
|
|
38
|
+
access_key: SerializableSecretStr
|
|
39
|
+
secret_key: SerializableSecretStr
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
class EvalFilterEntryConfig(BaseModel):
|
|
@@ -20,6 +20,7 @@ from pydantic import Field
|
|
|
20
20
|
from nat.builder.builder import Builder
|
|
21
21
|
from nat.builder.embedder import EmbedderProviderInfo
|
|
22
22
|
from nat.cli.register_workflow import register_embedder_provider
|
|
23
|
+
from nat.data_models.common import OptionalSecretStr
|
|
23
24
|
from nat.data_models.embedder import EmbedderBaseConfig
|
|
24
25
|
from nat.data_models.retry_mixin import RetryMixin
|
|
25
26
|
|
|
@@ -29,7 +30,7 @@ class AzureOpenAIEmbedderModelConfig(EmbedderBaseConfig, RetryMixin, name="azure
|
|
|
29
30
|
|
|
30
31
|
model_config = ConfigDict(protected_namespaces=(), extra="allow")
|
|
31
32
|
|
|
32
|
-
api_key:
|
|
33
|
+
api_key: OptionalSecretStr = Field(default=None, description="Azure OpenAI API key to interact with hosted model.")
|
|
33
34
|
api_version: str = Field(default="2025-04-01-preview", description="Azure OpenAI API version.")
|
|
34
35
|
azure_endpoint: str | None = Field(validation_alias=AliasChoices("azure_endpoint", "base_url"),
|
|
35
36
|
serialization_alias="azure_endpoint",
|
nat/embedder/nim_embedder.py
CHANGED
|
@@ -23,6 +23,7 @@ from pydantic import Field
|
|
|
23
23
|
from nat.builder.builder import Builder
|
|
24
24
|
from nat.builder.embedder import EmbedderProviderInfo
|
|
25
25
|
from nat.cli.register_workflow import register_embedder_provider
|
|
26
|
+
from nat.data_models.common import OptionalSecretStr
|
|
26
27
|
from nat.data_models.embedder import EmbedderBaseConfig
|
|
27
28
|
from nat.data_models.retry_mixin import RetryMixin
|
|
28
29
|
|
|
@@ -41,7 +42,7 @@ TruncationOption = typing.Annotated[str, AfterValidator(option_in_allowed_values
|
|
|
41
42
|
class NIMEmbedderModelConfig(EmbedderBaseConfig, RetryMixin, name="nim"):
|
|
42
43
|
"""A NVIDIA Inference Microservice (NIM) embedder provider to be used with an embedder client."""
|
|
43
44
|
|
|
44
|
-
api_key:
|
|
45
|
+
api_key: OptionalSecretStr = Field(default=None, description="NVIDIA API key to interact with hosted NIM.")
|
|
45
46
|
base_url: str | None = Field(default=None, description="Base url to the hosted NIM.")
|
|
46
47
|
model_name: str = Field(validation_alias=AliasChoices("model_name", "model"),
|
|
47
48
|
serialization_alias="model",
|
nat/embedder/openai_embedder.py
CHANGED
|
@@ -20,6 +20,7 @@ from pydantic import Field
|
|
|
20
20
|
from nat.builder.builder import Builder
|
|
21
21
|
from nat.builder.embedder import EmbedderProviderInfo
|
|
22
22
|
from nat.cli.register_workflow import register_embedder_provider
|
|
23
|
+
from nat.data_models.common import OptionalSecretStr
|
|
23
24
|
from nat.data_models.embedder import EmbedderBaseConfig
|
|
24
25
|
from nat.data_models.retry_mixin import RetryMixin
|
|
25
26
|
|
|
@@ -29,7 +30,7 @@ class OpenAIEmbedderModelConfig(EmbedderBaseConfig, RetryMixin, name="openai"):
|
|
|
29
30
|
|
|
30
31
|
model_config = ConfigDict(protected_namespaces=(), extra="allow")
|
|
31
32
|
|
|
32
|
-
api_key:
|
|
33
|
+
api_key: OptionalSecretStr = Field(default=None, description="OpenAI API key to interact with hosted model.")
|
|
33
34
|
base_url: str | None = Field(default=None, description="Base url to the hosted model.")
|
|
34
35
|
model_name: str = Field(validation_alias=AliasChoices("model_name", "model"),
|
|
35
36
|
serialization_alias="model",
|
|
@@ -19,6 +19,7 @@ import boto3
|
|
|
19
19
|
import requests
|
|
20
20
|
from botocore.exceptions import NoCredentialsError
|
|
21
21
|
|
|
22
|
+
from nat.data_models.common import get_secret_value
|
|
22
23
|
from nat.data_models.dataset_handler import EvalDatasetConfig
|
|
23
24
|
|
|
24
25
|
logger = logging.getLogger(__name__)
|
|
@@ -46,8 +47,8 @@ class DatasetDownloader:
|
|
|
46
47
|
try:
|
|
47
48
|
self._s3_client = boto3.client("s3",
|
|
48
49
|
endpoint_url=self.s3_config.endpoint_url,
|
|
49
|
-
aws_access_key_id=self.s3_config.access_key,
|
|
50
|
-
aws_secret_access_key=self.s3_config.secret_key)
|
|
50
|
+
aws_access_key_id=get_secret_value(self.s3_config.access_key),
|
|
51
|
+
aws_secret_access_key=get_secret_value(self.s3_config.secret_key))
|
|
51
52
|
except NoCredentialsError as e:
|
|
52
53
|
logger.error("AWS credentials not available: %s", e)
|
|
53
54
|
raise
|
|
@@ -24,6 +24,7 @@ import aioboto3
|
|
|
24
24
|
from botocore.exceptions import NoCredentialsError
|
|
25
25
|
from tqdm import tqdm
|
|
26
26
|
|
|
27
|
+
from nat.data_models.common import get_secret_value
|
|
27
28
|
from nat.data_models.evaluate import EvalOutputConfig
|
|
28
29
|
|
|
29
30
|
logger = logging.getLogger(__name__)
|
|
@@ -90,8 +91,8 @@ class OutputUploader:
|
|
|
90
91
|
"s3",
|
|
91
92
|
endpoint_url=endpoint_url,
|
|
92
93
|
region_name=region_name,
|
|
93
|
-
aws_access_key_id=self.s3_config.access_key,
|
|
94
|
-
aws_secret_access_key=self.s3_config.secret_key,
|
|
94
|
+
aws_access_key_id=get_secret_value(self.s3_config.access_key),
|
|
95
|
+
aws_secret_access_key=get_secret_value(self.s3_config.secret_key),
|
|
95
96
|
) as s3_client:
|
|
96
97
|
with tqdm(total=len(file_entries), desc="Uploading files to S3") as pbar:
|
|
97
98
|
upload_tasks = [
|
|
@@ -95,7 +95,7 @@ class ConsoleAuthenticationFlowHandler(FlowHandlerBase):
|
|
|
95
95
|
try:
|
|
96
96
|
client = AsyncOAuth2Client(
|
|
97
97
|
client_id=cfg.client_id,
|
|
98
|
-
client_secret=cfg.client_secret,
|
|
98
|
+
client_secret=cfg.client_secret.get_secret_value(),
|
|
99
99
|
redirect_uri=cfg.redirect_uri,
|
|
100
100
|
scope=" ".join(cfg.scopes) if cfg.scopes else None,
|
|
101
101
|
token_endpoint=cfg.token_url,
|
|
@@ -17,6 +17,7 @@ import logging
|
|
|
17
17
|
from typing import Literal
|
|
18
18
|
|
|
19
19
|
from pydantic import Field
|
|
20
|
+
from pydantic import field_validator
|
|
20
21
|
from pydantic import model_validator
|
|
21
22
|
|
|
22
23
|
from nat.authentication.oauth2.oauth2_resource_server_config import OAuth2ResourceServerConfig
|
|
@@ -47,10 +48,25 @@ class MCPFrontEndConfig(FrontEndBaseConfig, name="mcp"):
|
|
|
47
48
|
description="Transport type for the MCP server (default: streamable-http, backwards compatible with sse)")
|
|
48
49
|
runner_class: str | None = Field(
|
|
49
50
|
default=None, description="Custom worker class for handling MCP routes (default: built-in worker)")
|
|
51
|
+
base_path: str | None = Field(default=None,
|
|
52
|
+
description="Base path to mount the MCP server at (e.g., '/api/v1'). "
|
|
53
|
+
"If specified, the server will be accessible at http://host:port{base_path}/mcp. "
|
|
54
|
+
"If None, server runs at root path /mcp.")
|
|
50
55
|
|
|
51
56
|
server_auth: OAuth2ResourceServerConfig | None = Field(
|
|
52
57
|
default=None, description=("OAuth 2.0 Resource Server configuration for token verification."))
|
|
53
58
|
|
|
59
|
+
@field_validator('base_path')
|
|
60
|
+
@classmethod
|
|
61
|
+
def validate_base_path(cls, v: str | None) -> str | None:
|
|
62
|
+
"""Validate that base_path starts with '/' and doesn't end with '/'."""
|
|
63
|
+
if v is not None:
|
|
64
|
+
if not v.startswith('/'):
|
|
65
|
+
raise ValueError("base_path must start with '/'")
|
|
66
|
+
if v.endswith('/'):
|
|
67
|
+
raise ValueError("base_path must not end with '/'")
|
|
68
|
+
return v
|
|
69
|
+
|
|
54
70
|
# Memory profiling configuration
|
|
55
71
|
enable_memory_profiling: bool = Field(default=False,
|
|
56
72
|
description="Enable memory profiling and diagnostics (default: False)")
|
|
@@ -16,12 +16,14 @@
|
|
|
16
16
|
import logging
|
|
17
17
|
import typing
|
|
18
18
|
|
|
19
|
-
from nat.authentication.oauth2.oauth2_resource_server_config import OAuth2ResourceServerConfig
|
|
20
19
|
from nat.builder.front_end import FrontEndBase
|
|
21
20
|
from nat.builder.workflow_builder import WorkflowBuilder
|
|
22
21
|
from nat.front_ends.mcp.mcp_front_end_config import MCPFrontEndConfig
|
|
23
22
|
from nat.front_ends.mcp.mcp_front_end_plugin_worker import MCPFrontEndPluginWorkerBase
|
|
24
23
|
|
|
24
|
+
if typing.TYPE_CHECKING:
|
|
25
|
+
from mcp.server.fastmcp import FastMCP
|
|
26
|
+
|
|
25
27
|
logger = logging.getLogger(__name__)
|
|
26
28
|
|
|
27
29
|
|
|
@@ -43,7 +45,7 @@ class MCPFrontEndPlugin(FrontEndBase[MCPFrontEndConfig]):
|
|
|
43
45
|
worker_class = self.get_worker_class()
|
|
44
46
|
return f"{worker_class.__module__}.{worker_class.__qualname__}"
|
|
45
47
|
|
|
46
|
-
def _get_worker_instance(self)
|
|
48
|
+
def _get_worker_instance(self):
|
|
47
49
|
"""Get an instance of the worker class."""
|
|
48
50
|
# Import the worker class dynamically if specified in config
|
|
49
51
|
if self.front_end_config.runner_class:
|
|
@@ -56,61 +58,94 @@ class MCPFrontEndPlugin(FrontEndBase[MCPFrontEndConfig]):
|
|
|
56
58
|
|
|
57
59
|
return worker_class(self.full_config)
|
|
58
60
|
|
|
59
|
-
async def _create_token_verifier(self, token_verifier_config: OAuth2ResourceServerConfig):
|
|
60
|
-
"""Create a token verifier based on configuration."""
|
|
61
|
-
from nat.front_ends.mcp.introspection_token_verifier import IntrospectionTokenVerifier
|
|
62
|
-
|
|
63
|
-
if not self.front_end_config.server_auth:
|
|
64
|
-
return None
|
|
65
|
-
|
|
66
|
-
return IntrospectionTokenVerifier(token_verifier_config)
|
|
67
|
-
|
|
68
61
|
async def run(self) -> None:
|
|
69
62
|
"""Run the MCP server."""
|
|
70
|
-
# Import FastMCP
|
|
71
|
-
from mcp.server.fastmcp import FastMCP
|
|
72
|
-
|
|
73
|
-
# Create auth settings and token verifier if auth is required
|
|
74
|
-
auth_settings = None
|
|
75
|
-
token_verifier = None
|
|
76
|
-
|
|
77
63
|
# Build the workflow and add routes using the worker
|
|
78
64
|
async with WorkflowBuilder.from_config(config=self.full_config) as builder:
|
|
79
65
|
|
|
80
|
-
|
|
81
|
-
from mcp.server.auth.settings import AuthSettings
|
|
82
|
-
from pydantic import AnyHttpUrl
|
|
83
|
-
|
|
84
|
-
server_url = f"http://{self.front_end_config.host}:{self.front_end_config.port}"
|
|
85
|
-
|
|
86
|
-
auth_settings = AuthSettings(issuer_url=AnyHttpUrl(self.front_end_config.server_auth.issuer_url),
|
|
87
|
-
required_scopes=self.front_end_config.server_auth.scopes,
|
|
88
|
-
resource_server_url=AnyHttpUrl(server_url))
|
|
89
|
-
|
|
90
|
-
token_verifier = await self._create_token_verifier(self.front_end_config.server_auth)
|
|
91
|
-
|
|
92
|
-
# Create an MCP server with the configured parameters
|
|
93
|
-
mcp = FastMCP(name=self.front_end_config.name,
|
|
94
|
-
host=self.front_end_config.host,
|
|
95
|
-
port=self.front_end_config.port,
|
|
96
|
-
debug=self.front_end_config.debug,
|
|
97
|
-
auth=auth_settings,
|
|
98
|
-
token_verifier=token_verifier)
|
|
99
|
-
|
|
100
|
-
# Get the worker instance and set up routes
|
|
66
|
+
# Get the worker instance
|
|
101
67
|
worker = self._get_worker_instance()
|
|
102
68
|
|
|
69
|
+
# Let the worker create the MCP server (allows plugins to customize)
|
|
70
|
+
mcp = await worker.create_mcp_server()
|
|
71
|
+
|
|
103
72
|
# Add routes through the worker (includes health endpoint and function registration)
|
|
104
73
|
await worker.add_routes(mcp, builder)
|
|
105
74
|
|
|
106
75
|
# Start the MCP server with configurable transport
|
|
107
76
|
# streamable-http is the default, but users can choose sse if preferred
|
|
108
77
|
try:
|
|
109
|
-
|
|
78
|
+
# If base_path is configured, mount server at sub-path using FastAPI wrapper
|
|
79
|
+
if self.front_end_config.base_path:
|
|
80
|
+
if self.front_end_config.transport == "sse":
|
|
81
|
+
logger.warning(
|
|
82
|
+
"base_path is configured but SSE transport does not support mounting at sub-paths. "
|
|
83
|
+
"Use streamable-http transport for base_path support.")
|
|
84
|
+
logger.info("Starting MCP server with SSE endpoint at /sse")
|
|
85
|
+
await mcp.run_sse_async()
|
|
86
|
+
else:
|
|
87
|
+
full_url = f"http://{self.front_end_config.host}:{self.front_end_config.port}{self.front_end_config.base_path}/mcp"
|
|
88
|
+
logger.info(
|
|
89
|
+
"Mounting MCP server at %s/mcp on %s:%s",
|
|
90
|
+
self.front_end_config.base_path,
|
|
91
|
+
self.front_end_config.host,
|
|
92
|
+
self.front_end_config.port,
|
|
93
|
+
)
|
|
94
|
+
logger.info("MCP server URL: %s", full_url)
|
|
95
|
+
await self._run_with_mount(mcp)
|
|
96
|
+
# Standard behavior - run at root path
|
|
97
|
+
elif self.front_end_config.transport == "sse":
|
|
110
98
|
logger.info("Starting MCP server with SSE endpoint at /sse")
|
|
111
99
|
await mcp.run_sse_async()
|
|
112
100
|
else: # streamable-http
|
|
113
|
-
|
|
101
|
+
full_url = f"http://{self.front_end_config.host}:{self.front_end_config.port}/mcp"
|
|
102
|
+
logger.info("MCP server URL: %s", full_url)
|
|
114
103
|
await mcp.run_streamable_http_async()
|
|
115
104
|
except KeyboardInterrupt:
|
|
116
105
|
logger.info("MCP server shutdown requested (Ctrl+C). Shutting down gracefully.")
|
|
106
|
+
|
|
107
|
+
async def _run_with_mount(self, mcp: "FastMCP") -> None:
|
|
108
|
+
"""Run MCP server mounted at configured base_path using FastAPI wrapper.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
mcp: The FastMCP server instance to mount
|
|
112
|
+
"""
|
|
113
|
+
import contextlib
|
|
114
|
+
|
|
115
|
+
import uvicorn
|
|
116
|
+
from fastapi import FastAPI
|
|
117
|
+
|
|
118
|
+
@contextlib.asynccontextmanager
|
|
119
|
+
async def lifespan(_app: FastAPI):
|
|
120
|
+
"""Manage MCP server session lifecycle."""
|
|
121
|
+
logger.info("Starting MCP server session manager...")
|
|
122
|
+
async with contextlib.AsyncExitStack() as stack:
|
|
123
|
+
try:
|
|
124
|
+
# Initialize the MCP server's session manager
|
|
125
|
+
await stack.enter_async_context(mcp.session_manager.run())
|
|
126
|
+
logger.info("MCP server session manager started successfully")
|
|
127
|
+
yield
|
|
128
|
+
except Exception as e:
|
|
129
|
+
logger.error("Failed to start MCP server session manager: %s", e)
|
|
130
|
+
raise
|
|
131
|
+
logger.info("MCP server session manager stopped")
|
|
132
|
+
|
|
133
|
+
# Create a FastAPI wrapper app with lifespan management
|
|
134
|
+
app = FastAPI(
|
|
135
|
+
title=self.front_end_config.name,
|
|
136
|
+
description="MCP server mounted at custom base path",
|
|
137
|
+
lifespan=lifespan,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Mount the MCP server's ASGI app at the configured base_path
|
|
141
|
+
app.mount(self.front_end_config.base_path, mcp.streamable_http_app())
|
|
142
|
+
|
|
143
|
+
# Configure and start uvicorn server
|
|
144
|
+
config = uvicorn.Config(
|
|
145
|
+
app,
|
|
146
|
+
host=self.front_end_config.host,
|
|
147
|
+
port=self.front_end_config.port,
|
|
148
|
+
log_level=self.front_end_config.log_level.lower(),
|
|
149
|
+
)
|
|
150
|
+
server = uvicorn.Server(config)
|
|
151
|
+
await server.serve()
|
|
@@ -35,7 +35,12 @@ logger = logging.getLogger(__name__)
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
class MCPFrontEndPluginWorkerBase(ABC):
|
|
38
|
-
"""Base class for MCP front end plugin workers.
|
|
38
|
+
"""Base class for MCP front end plugin workers.
|
|
39
|
+
|
|
40
|
+
This abstract base class provides shared utilities and defines the contract
|
|
41
|
+
for MCP worker implementations. Most users should inherit from
|
|
42
|
+
MCPFrontEndPluginWorker instead of this class directly.
|
|
43
|
+
"""
|
|
39
44
|
|
|
40
45
|
def __init__(self, config: Config):
|
|
41
46
|
"""Initialize the MCP worker with configuration.
|
|
@@ -83,15 +88,86 @@ class MCPFrontEndPluginWorkerBase(ABC):
|
|
|
83
88
|
},
|
|
84
89
|
status_code=503)
|
|
85
90
|
|
|
91
|
+
@abstractmethod
|
|
92
|
+
async def create_mcp_server(self) -> FastMCP:
|
|
93
|
+
"""Create and configure the MCP server instance.
|
|
94
|
+
|
|
95
|
+
This is the main extension point. Plugins can return FastMCP or any subclass
|
|
96
|
+
to customize server behavior (for example, add authentication, custom transports).
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
FastMCP instance or a subclass with custom behavior
|
|
100
|
+
"""
|
|
101
|
+
...
|
|
102
|
+
|
|
86
103
|
@abstractmethod
|
|
87
104
|
async def add_routes(self, mcp: FastMCP, builder: WorkflowBuilder):
|
|
88
105
|
"""Add routes to the MCP server.
|
|
89
106
|
|
|
107
|
+
Plugins must implement this method. Most plugins can call
|
|
108
|
+
_default_add_routes() for standard behavior and then add
|
|
109
|
+
custom enhancements.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
mcp: The FastMCP server instance
|
|
113
|
+
builder: The workflow builder instance
|
|
114
|
+
"""
|
|
115
|
+
...
|
|
116
|
+
|
|
117
|
+
async def _default_add_routes(self, mcp: FastMCP, builder: WorkflowBuilder):
|
|
118
|
+
"""Default route registration logic - reusable by subclasses.
|
|
119
|
+
|
|
120
|
+
This is a protected helper method that plugins can call to get
|
|
121
|
+
standard route registration behavior. Plugins typically call this
|
|
122
|
+
from their add_routes() implementation and then add custom features.
|
|
123
|
+
|
|
124
|
+
This method:
|
|
125
|
+
- Sets up the health endpoint
|
|
126
|
+
- Builds the workflow and extracts all functions
|
|
127
|
+
- Filters functions based on tool_names config
|
|
128
|
+
- Registers each function as an MCP tool
|
|
129
|
+
- Sets up debug endpoints for tool introspection
|
|
130
|
+
|
|
90
131
|
Args:
|
|
91
132
|
mcp: The FastMCP server instance
|
|
92
|
-
builder
|
|
133
|
+
builder: The workflow builder instance
|
|
93
134
|
"""
|
|
94
|
-
|
|
135
|
+
from nat.front_ends.mcp.tool_converter import register_function_with_mcp
|
|
136
|
+
|
|
137
|
+
# Set up the health endpoint
|
|
138
|
+
self._setup_health_endpoint(mcp)
|
|
139
|
+
|
|
140
|
+
# Build the workflow and register all functions with MCP
|
|
141
|
+
workflow = await builder.build()
|
|
142
|
+
|
|
143
|
+
# Get all functions from the workflow
|
|
144
|
+
functions = await self._get_all_functions(workflow)
|
|
145
|
+
|
|
146
|
+
# Filter functions based on tool_names if provided
|
|
147
|
+
if self.front_end_config.tool_names:
|
|
148
|
+
logger.info("Filtering functions based on tool_names: %s", self.front_end_config.tool_names)
|
|
149
|
+
filtered_functions: dict[str, Function] = {}
|
|
150
|
+
for function_name, function in functions.items():
|
|
151
|
+
if function_name in self.front_end_config.tool_names:
|
|
152
|
+
# Treat current tool_names as function names, so check if the function name is in the list
|
|
153
|
+
filtered_functions[function_name] = function
|
|
154
|
+
elif any(function_name.startswith(f"{group_name}.") for group_name in self.front_end_config.tool_names):
|
|
155
|
+
# Treat tool_names as function group names, so check if the function name starts with the group name
|
|
156
|
+
filtered_functions[function_name] = function
|
|
157
|
+
else:
|
|
158
|
+
logger.debug("Skipping function %s as it's not in tool_names", function_name)
|
|
159
|
+
functions = filtered_functions
|
|
160
|
+
|
|
161
|
+
# Register each function with MCP, passing workflow context for observability
|
|
162
|
+
for function_name, function in functions.items():
|
|
163
|
+
register_function_with_mcp(mcp, function_name, function, workflow, self.memory_profiler)
|
|
164
|
+
|
|
165
|
+
# Add a simple fallback function if no functions were found
|
|
166
|
+
if not functions:
|
|
167
|
+
raise RuntimeError("No functions found in workflow. Please check your configuration.")
|
|
168
|
+
|
|
169
|
+
# After registration, expose debug endpoints for tool/schema inspection
|
|
170
|
+
self._setup_debug_endpoints(mcp, functions)
|
|
95
171
|
|
|
96
172
|
async def _get_all_functions(self, workflow: Workflow) -> dict[str, Function]:
|
|
97
173
|
"""Get all functions from the workflow.
|
|
@@ -225,48 +301,62 @@ class MCPFrontEndPluginWorkerBase(ABC):
|
|
|
225
301
|
|
|
226
302
|
|
|
227
303
|
class MCPFrontEndPluginWorker(MCPFrontEndPluginWorkerBase):
|
|
228
|
-
"""Default MCP
|
|
304
|
+
"""Default MCP server worker implementation.
|
|
229
305
|
|
|
230
|
-
|
|
231
|
-
|
|
306
|
+
Inherit from this class to create custom MCP workers that extend or modify
|
|
307
|
+
server behavior. Override create_mcp_server() to use a different server type,
|
|
308
|
+
and override add_routes() to add custom functionality.
|
|
232
309
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
310
|
+
Example:
|
|
311
|
+
class CustomWorker(MCPFrontEndPluginWorker):
|
|
312
|
+
async def create_mcp_server(self):
|
|
313
|
+
# Return custom MCP server instance
|
|
314
|
+
return MyCustomFastMCP(...)
|
|
315
|
+
|
|
316
|
+
async def add_routes(self, mcp, builder):
|
|
317
|
+
# Get default routes
|
|
318
|
+
await super().add_routes(mcp, builder)
|
|
319
|
+
# Add custom features
|
|
320
|
+
self._add_my_custom_features(mcp)
|
|
321
|
+
"""
|
|
322
|
+
|
|
323
|
+
async def create_mcp_server(self) -> FastMCP:
|
|
324
|
+
"""Create default MCP server with optional authentication.
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
FastMCP instance configured with settings from NAT config
|
|
236
328
|
"""
|
|
237
|
-
|
|
329
|
+
# Handle auth if configured
|
|
330
|
+
auth_settings = None
|
|
331
|
+
token_verifier = None
|
|
238
332
|
|
|
239
|
-
|
|
240
|
-
|
|
333
|
+
if self.front_end_config.server_auth:
|
|
334
|
+
from mcp.server.auth.settings import AuthSettings
|
|
335
|
+
from pydantic import AnyHttpUrl
|
|
241
336
|
|
|
242
|
-
|
|
243
|
-
|
|
337
|
+
server_url = f"http://{self.front_end_config.host}:{self.front_end_config.port}"
|
|
338
|
+
auth_settings = AuthSettings(issuer_url=AnyHttpUrl(self.front_end_config.server_auth.issuer_url),
|
|
339
|
+
required_scopes=self.front_end_config.server_auth.scopes,
|
|
340
|
+
resource_server_url=AnyHttpUrl(server_url))
|
|
244
341
|
|
|
245
|
-
|
|
246
|
-
|
|
342
|
+
# Create token verifier
|
|
343
|
+
from nat.front_ends.mcp.introspection_token_verifier import IntrospectionTokenVerifier
|
|
247
344
|
|
|
248
|
-
|
|
249
|
-
if self.front_end_config.tool_names:
|
|
250
|
-
logger.info("Filtering functions based on tool_names: %s", self.front_end_config.tool_names)
|
|
251
|
-
filtered_functions: dict[str, Function] = {}
|
|
252
|
-
for function_name, function in functions.items():
|
|
253
|
-
if function_name in self.front_end_config.tool_names:
|
|
254
|
-
# Treat current tool_names as function names, so check if the function name is in the list
|
|
255
|
-
filtered_functions[function_name] = function
|
|
256
|
-
elif any(function_name.startswith(f"{group_name}.") for group_name in self.front_end_config.tool_names):
|
|
257
|
-
# Treat tool_names as function group names, so check if the function name starts with the group name
|
|
258
|
-
filtered_functions[function_name] = function
|
|
259
|
-
else:
|
|
260
|
-
logger.debug("Skipping function %s as it's not in tool_names", function_name)
|
|
261
|
-
functions = filtered_functions
|
|
345
|
+
token_verifier = IntrospectionTokenVerifier(self.front_end_config.server_auth)
|
|
262
346
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
347
|
+
return FastMCP(name=self.front_end_config.name,
|
|
348
|
+
host=self.front_end_config.host,
|
|
349
|
+
port=self.front_end_config.port,
|
|
350
|
+
debug=self.front_end_config.debug,
|
|
351
|
+
auth=auth_settings,
|
|
352
|
+
token_verifier=token_verifier)
|
|
266
353
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
raise RuntimeError("No functions found in workflow. Please check your configuration.")
|
|
354
|
+
async def add_routes(self, mcp: FastMCP, builder: WorkflowBuilder):
|
|
355
|
+
"""Add default routes to the MCP server.
|
|
270
356
|
|
|
271
|
-
|
|
272
|
-
|
|
357
|
+
Args:
|
|
358
|
+
mcp: The FastMCP server instance
|
|
359
|
+
builder: The workflow builder instance
|
|
360
|
+
"""
|
|
361
|
+
# Use the default implementation from base class to add the tools to the MCP server
|
|
362
|
+
await self._default_add_routes(mcp, builder)
|
|
@@ -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
|
|
@@ -32,6 +35,41 @@ if TYPE_CHECKING:
|
|
|
32
35
|
|
|
33
36
|
logger = logging.getLogger(__name__)
|
|
34
37
|
|
|
38
|
+
# Sentinel: marks "optional; let Pydantic supply default/factory"
|
|
39
|
+
_USE_PYDANTIC_DEFAULT = object()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def is_field_optional(field: FieldInfo) -> tuple[bool, Any]:
|
|
43
|
+
"""Determine if a Pydantic field is optional and extract its default value for MCP signatures.
|
|
44
|
+
|
|
45
|
+
For MCP tool signatures, we need to distinguish:
|
|
46
|
+
- Required fields: marked with Parameter.empty
|
|
47
|
+
- Optional with concrete default: use that default
|
|
48
|
+
- Optional with factory: use sentinel so Pydantic can apply the factory later
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
field: The Pydantic FieldInfo to check
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
A tuple of (is_optional, default_value):
|
|
55
|
+
- (False, Parameter.empty) for required fields
|
|
56
|
+
- (True, actual_default) for optional fields with explicit defaults
|
|
57
|
+
- (True, _USE_PYDANTIC_DEFAULT) for optional fields with default_factory
|
|
58
|
+
"""
|
|
59
|
+
if field.is_required():
|
|
60
|
+
return False, Parameter.empty
|
|
61
|
+
|
|
62
|
+
# Field is optional - has either default or factory
|
|
63
|
+
if field.default is not PydanticUndefined:
|
|
64
|
+
return True, field.default
|
|
65
|
+
|
|
66
|
+
# Factory case: mark optional in signature but don't fabricate a value
|
|
67
|
+
if field.default_factory is not None:
|
|
68
|
+
return True, _USE_PYDANTIC_DEFAULT
|
|
69
|
+
|
|
70
|
+
# Rare corner case: non-required yet no default surfaced
|
|
71
|
+
return True, _USE_PYDANTIC_DEFAULT
|
|
72
|
+
|
|
35
73
|
|
|
36
74
|
def create_function_wrapper(
|
|
37
75
|
function_name: str,
|
|
@@ -79,12 +117,15 @@ def create_function_wrapper(
|
|
|
79
117
|
# Get the field type and convert to appropriate Python type
|
|
80
118
|
field_type = field.annotation
|
|
81
119
|
|
|
120
|
+
# Check if field is optional and get its default value
|
|
121
|
+
_is_optional, param_default = is_field_optional(field)
|
|
122
|
+
|
|
82
123
|
# Add the parameter to our list
|
|
83
124
|
parameters.append(
|
|
84
125
|
Parameter(
|
|
85
126
|
name=name,
|
|
86
127
|
kind=Parameter.KEYWORD_ONLY,
|
|
87
|
-
default=
|
|
128
|
+
default=param_default,
|
|
88
129
|
annotation=field_type,
|
|
89
130
|
))
|
|
90
131
|
|
|
@@ -143,33 +184,23 @@ def create_function_wrapper(
|
|
|
143
184
|
result = await call_with_observability(lambda: function.ainvoke(chat_request, to_type=str))
|
|
144
185
|
else:
|
|
145
186
|
# Regular handling
|
|
146
|
-
#
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
# If it's a pydantic model, we need to create an instance
|
|
154
|
-
if field_type and hasattr(field_type, "model_validate"):
|
|
155
|
-
# Create the nested object
|
|
156
|
-
nested_obj = field_type.model_validate(kwargs)
|
|
157
|
-
# Call with the nested object
|
|
158
|
-
kwargs = {field_name: nested_obj}
|
|
187
|
+
# Strip sentinel values so Pydantic can apply defaults/factories
|
|
188
|
+
cleaned_kwargs = {k: v for k, v in kwargs.items() if v is not _USE_PYDANTIC_DEFAULT}
|
|
189
|
+
|
|
190
|
+
# Always validate with the declared schema
|
|
191
|
+
# This handles defaults, factories, nested models, validators, etc.
|
|
192
|
+
model_input = schema.model_validate(cleaned_kwargs)
|
|
159
193
|
|
|
160
194
|
# Call the NAT function with the parameters - special handling for Workflow
|
|
161
195
|
if is_workflow:
|
|
162
|
-
#
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
# Workflows have a run method that is an async context manager
|
|
166
|
-
# that returns a Runner
|
|
167
|
-
async with function.run(input_value) as runner:
|
|
196
|
+
# Workflows expect the model instance directly
|
|
197
|
+
async with function.run(model_input) as runner:
|
|
168
198
|
# Get the result from the runner
|
|
169
199
|
result = await runner.result(to_type=str)
|
|
170
200
|
else:
|
|
171
|
-
# Regular function call
|
|
172
|
-
result = await call_with_observability(lambda: function.acall_invoke(**
|
|
201
|
+
# Regular function call - unpack the validated model
|
|
202
|
+
result = await call_with_observability(lambda: function.acall_invoke(**model_input.model_dump())
|
|
203
|
+
)
|
|
173
204
|
|
|
174
205
|
# Report completion
|
|
175
206
|
if ctx:
|
nat/llm/azure_openai_llm.py
CHANGED
|
@@ -20,6 +20,7 @@ from pydantic import Field
|
|
|
20
20
|
from nat.builder.builder import Builder
|
|
21
21
|
from nat.builder.llm import LLMProviderInfo
|
|
22
22
|
from nat.cli.register_workflow import register_llm_provider
|
|
23
|
+
from nat.data_models.common import OptionalSecretStr
|
|
23
24
|
from nat.data_models.llm import LLMBaseConfig
|
|
24
25
|
from nat.data_models.retry_mixin import RetryMixin
|
|
25
26
|
from nat.data_models.temperature_mixin import TemperatureMixin
|
|
@@ -39,7 +40,7 @@ class AzureOpenAIModelConfig(
|
|
|
39
40
|
|
|
40
41
|
model_config = ConfigDict(protected_namespaces=(), extra="allow")
|
|
41
42
|
|
|
42
|
-
api_key:
|
|
43
|
+
api_key: OptionalSecretStr = Field(default=None, description="Azure OpenAI API key to interact with hosted model.")
|
|
43
44
|
api_version: str = Field(default="2025-04-01-preview", description="Azure OpenAI API version.")
|
|
44
45
|
azure_endpoint: str | None = Field(validation_alias=AliasChoices("azure_endpoint", "base_url"),
|
|
45
46
|
serialization_alias="azure_endpoint",
|
nat/llm/litellm_llm.py
CHANGED
|
@@ -22,6 +22,7 @@ from pydantic import Field
|
|
|
22
22
|
from nat.builder.builder import Builder
|
|
23
23
|
from nat.builder.llm import LLMProviderInfo
|
|
24
24
|
from nat.cli.register_workflow import register_llm_provider
|
|
25
|
+
from nat.data_models.common import OptionalSecretStr
|
|
25
26
|
from nat.data_models.llm import LLMBaseConfig
|
|
26
27
|
from nat.data_models.optimizable import OptimizableField
|
|
27
28
|
from nat.data_models.optimizable import OptimizableMixin
|
|
@@ -44,7 +45,7 @@ class LiteLlmModelConfig(
|
|
|
44
45
|
|
|
45
46
|
model_config = ConfigDict(protected_namespaces=(), extra="allow")
|
|
46
47
|
|
|
47
|
-
api_key:
|
|
48
|
+
api_key: OptionalSecretStr = Field(default=None, description="API key to interact with hosted model.")
|
|
48
49
|
base_url: str | None = Field(default=None,
|
|
49
50
|
description="Base url to the hosted model.",
|
|
50
51
|
validation_alias=AliasChoices("base_url", "api_base"),
|
nat/llm/nim_llm.py
CHANGED
|
@@ -21,6 +21,7 @@ from pydantic import PositiveInt
|
|
|
21
21
|
from nat.builder.builder import Builder
|
|
22
22
|
from nat.builder.llm import LLMProviderInfo
|
|
23
23
|
from nat.cli.register_workflow import register_llm_provider
|
|
24
|
+
from nat.data_models.common import OptionalSecretStr
|
|
24
25
|
from nat.data_models.llm import LLMBaseConfig
|
|
25
26
|
from nat.data_models.optimizable import OptimizableField
|
|
26
27
|
from nat.data_models.optimizable import OptimizableMixin
|
|
@@ -42,7 +43,7 @@ class NIMModelConfig(LLMBaseConfig,
|
|
|
42
43
|
|
|
43
44
|
model_config = ConfigDict(protected_namespaces=(), extra="allow")
|
|
44
45
|
|
|
45
|
-
api_key:
|
|
46
|
+
api_key: OptionalSecretStr = Field(default=None, description="NVIDIA API key to interact with hosted NIM.")
|
|
46
47
|
base_url: str | None = Field(default=None, description="Base url to the hosted NIM.")
|
|
47
48
|
model_name: str = OptimizableField(validation_alias=AliasChoices("model_name", "model"),
|
|
48
49
|
serialization_alias="model",
|
nat/llm/openai_llm.py
CHANGED
|
@@ -20,6 +20,7 @@ from pydantic import Field
|
|
|
20
20
|
from nat.builder.builder import Builder
|
|
21
21
|
from nat.builder.llm import LLMProviderInfo
|
|
22
22
|
from nat.cli.register_workflow import register_llm_provider
|
|
23
|
+
from nat.data_models.common import OptionalSecretStr
|
|
23
24
|
from nat.data_models.llm import LLMBaseConfig
|
|
24
25
|
from nat.data_models.optimizable import OptimizableField
|
|
25
26
|
from nat.data_models.optimizable import OptimizableMixin
|
|
@@ -40,7 +41,7 @@ class OpenAIModelConfig(LLMBaseConfig,
|
|
|
40
41
|
|
|
41
42
|
model_config = ConfigDict(protected_namespaces=(), extra="allow")
|
|
42
43
|
|
|
43
|
-
api_key:
|
|
44
|
+
api_key: OptionalSecretStr = Field(default=None, description="OpenAI API key to interact with hosted model.")
|
|
44
45
|
base_url: str | None = Field(default=None, description="Base url to the hosted model.")
|
|
45
46
|
model_name: str = OptimizableField(validation_alias=AliasChoices("model_name", "model"),
|
|
46
47
|
serialization_alias="model",
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
from pydantic import Field
|
|
17
17
|
|
|
18
18
|
from nat.cli.register_workflow import register_registry_handler
|
|
19
|
+
from nat.data_models.common import OptionalSecretStr
|
|
20
|
+
from nat.data_models.common import get_secret_value
|
|
19
21
|
from nat.data_models.registry_handler import RegistryHandlerBaseConfig
|
|
20
22
|
|
|
21
23
|
|
|
@@ -23,8 +25,8 @@ class PypiRegistryHandlerConfig(RegistryHandlerBaseConfig, name="pypi"):
|
|
|
23
25
|
"""Registry handler for interacting with a remote PyPI registry index."""
|
|
24
26
|
|
|
25
27
|
endpoint: str = Field(description="A string representing the remote endpoint.")
|
|
26
|
-
token:
|
|
27
|
-
|
|
28
|
+
token: OptionalSecretStr = Field(default=None,
|
|
29
|
+
description="The authentication token to use when interacting with the registry.")
|
|
28
30
|
publish_route: str = Field(description="The route to the NAT publish service.")
|
|
29
31
|
pull_route: str = Field(description="The route to the NAT pull service.")
|
|
30
32
|
search_route: str = Field(default="simple", description="The route to the NAT search service.")
|
|
@@ -35,6 +37,6 @@ async def pypi_publish_registry_handler(config: PypiRegistryHandlerConfig):
|
|
|
35
37
|
|
|
36
38
|
from nat.registry_handlers.pypi.pypi_handler import PypiRegistryHandler
|
|
37
39
|
|
|
38
|
-
registry_handler = PypiRegistryHandler(endpoint=config.endpoint, token=config.token)
|
|
40
|
+
registry_handler = PypiRegistryHandler(endpoint=config.endpoint, token=get_secret_value(config.token))
|
|
39
41
|
|
|
40
42
|
yield registry_handler
|
|
@@ -18,6 +18,8 @@ import os
|
|
|
18
18
|
from pydantic import Field
|
|
19
19
|
|
|
20
20
|
from nat.cli.register_workflow import register_registry_handler
|
|
21
|
+
from nat.data_models.common import OptionalSecretStr
|
|
22
|
+
from nat.data_models.common import get_secret_value
|
|
21
23
|
from nat.data_models.registry_handler import RegistryHandlerBaseConfig
|
|
22
24
|
|
|
23
25
|
|
|
@@ -25,8 +27,8 @@ class RestRegistryHandlerConfig(RegistryHandlerBaseConfig, name="rest"):
|
|
|
25
27
|
"""Registry handler for interacting with a remote REST registry."""
|
|
26
28
|
|
|
27
29
|
endpoint: str = Field(description="A string representing the remote endpoint.")
|
|
28
|
-
token:
|
|
29
|
-
|
|
30
|
+
token: OptionalSecretStr = Field(default=None,
|
|
31
|
+
description="The authentication token to use when interacting with the registry.")
|
|
30
32
|
publish_route: str = Field(default="", description="The route to the NAT publish service.")
|
|
31
33
|
pull_route: str = Field(default="", description="The route to the NAT pull service.")
|
|
32
34
|
search_route: str = Field(default="", description="The route to the NAT search service")
|
|
@@ -44,7 +46,7 @@ async def rest_search_handler(config: RestRegistryHandlerConfig):
|
|
|
44
46
|
if (registry_token is None):
|
|
45
47
|
raise ValueError("Please supply registry token.")
|
|
46
48
|
else:
|
|
47
|
-
registry_token = config.token
|
|
49
|
+
registry_token = get_secret_value(config.token)
|
|
48
50
|
|
|
49
51
|
registry_handler = RestRegistryHandler(token=registry_token,
|
|
50
52
|
endpoint=config.endpoint,
|
|
@@ -20,6 +20,7 @@ from nat.builder.builder import Builder
|
|
|
20
20
|
from nat.builder.retriever import RetrieverProviderInfo
|
|
21
21
|
from nat.cli.register_workflow import register_retriever_client
|
|
22
22
|
from nat.cli.register_workflow import register_retriever_provider
|
|
23
|
+
from nat.data_models.common import OptionalSecretStr
|
|
23
24
|
from nat.data_models.retriever import RetrieverBaseConfig
|
|
24
25
|
|
|
25
26
|
|
|
@@ -34,7 +35,7 @@ class NemoRetrieverConfig(RetrieverBaseConfig, name="nemo_retriever"):
|
|
|
34
35
|
default=None,
|
|
35
36
|
description="A list of fields to return from the datastore. If 'None', all fields but the vector are returned.")
|
|
36
37
|
timeout: int = Field(default=60, description="Maximum time to wait for results to be returned from the service.")
|
|
37
|
-
nvidia_api_key:
|
|
38
|
+
nvidia_api_key: OptionalSecretStr = Field(
|
|
38
39
|
description="API key used to authenticate with the service. If 'None', will use ENV Variable 'NVIDIA_API_KEY'",
|
|
39
40
|
default=None,
|
|
40
41
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nvidia-nat
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.0a20251030
|
|
4
4
|
Summary: NVIDIA NeMo Agent toolkit
|
|
5
5
|
Author: NVIDIA Corporation
|
|
6
6
|
Maintainer: NVIDIA Corporation
|
|
@@ -22,7 +22,7 @@ Requires-Dist: click~=8.1
|
|
|
22
22
|
Requires-Dist: colorama~=0.4.6
|
|
23
23
|
Requires-Dist: datasets~=4.0
|
|
24
24
|
Requires-Dist: expandvars~=1.0
|
|
25
|
-
Requires-Dist: fastapi~=0.
|
|
25
|
+
Requires-Dist: fastapi~=0.120.1
|
|
26
26
|
Requires-Dist: httpx~=0.27
|
|
27
27
|
Requires-Dist: jinja2~=3.1
|
|
28
28
|
Requires-Dist: jsonpath-ng~=1.7
|
|
@@ -26,8 +26,8 @@ nat/authentication/__init__.py,sha256=Xs1JQ16L9btwreh4pdGKwskffAw1YFO48jKrU4ib_7
|
|
|
26
26
|
nat/authentication/interfaces.py,sha256=1J2CWEJ_n6CLA3_HD3XV28CSbyfxrPAHzr7Q4kKDFdc,3511
|
|
27
27
|
nat/authentication/register.py,sha256=lFhswYUk9iZ53mq33fClR9UfjJPdjGIivGGNHQeWiYo,915
|
|
28
28
|
nat/authentication/api_key/__init__.py,sha256=GUJrgGtpvyMUCjUBvR3faAdv-tZzbU9W-izgx9aMEQg,680
|
|
29
|
-
nat/authentication/api_key/api_key_auth_provider.py,sha256=
|
|
30
|
-
nat/authentication/api_key/api_key_auth_provider_config.py,sha256=
|
|
29
|
+
nat/authentication/api_key/api_key_auth_provider.py,sha256=J6WNarzdLIT1dRLM0wbYlcgypddNNGhgo9ExfQodM1I,3849
|
|
30
|
+
nat/authentication/api_key/api_key_auth_provider_config.py,sha256=RLywkXGxfQlKnTW0Rz3a5qGgqyHV1B8ec8w2_nv3Dl8,5628
|
|
31
31
|
nat/authentication/api_key/register.py,sha256=Mhv3WyZ9H7C2JN8VuPvwlsJEZrwXJCLXCIokkN9RrP0,1147
|
|
32
32
|
nat/authentication/credential_validator/__init__.py,sha256=GUJrgGtpvyMUCjUBvR3faAdv-tZzbU9W-izgx9aMEQg,680
|
|
33
33
|
nat/authentication/credential_validator/bearer_token_validator.py,sha256=cwGENd_bp-u2Y_JCRbEPxFkulk_vREpLog5c83h1nRU,21250
|
|
@@ -38,8 +38,8 @@ nat/authentication/http_basic_auth/http_basic_auth_provider.py,sha256=OXr5TV87Si
|
|
|
38
38
|
nat/authentication/http_basic_auth/register.py,sha256=N2VD0vw7cYABsLxsGXl5yw0htc8adkrB0Y_EMxKwFfk,1235
|
|
39
39
|
nat/authentication/oauth2/__init__.py,sha256=Xs1JQ16L9btwreh4pdGKwskffAw1YFO48jKrU4ib_7c,685
|
|
40
40
|
nat/authentication/oauth2/oauth2_auth_code_flow_provider.py,sha256=NXsVATFxQ10Gg_nrW7Ljft2VXlAj460TeoXL-ww4WZc,5804
|
|
41
|
-
nat/authentication/oauth2/oauth2_auth_code_flow_provider_config.py,sha256=
|
|
42
|
-
nat/authentication/oauth2/oauth2_resource_server_config.py,sha256=
|
|
41
|
+
nat/authentication/oauth2/oauth2_auth_code_flow_provider_config.py,sha256=R261a2QU2ZUiyY2GjMc3xJpPYqPzydSKVSGhgyj7Do0,2279
|
|
42
|
+
nat/authentication/oauth2/oauth2_resource_server_config.py,sha256=WtqFMsJ-FzIjP7tjqs-tdYN4Pck0wxvdSyKIObNtU_8,5374
|
|
43
43
|
nat/authentication/oauth2/register.py,sha256=7rXhf-ilgSS_bUJsd9pOOCotL1FM8dKUt3ke1TllKkQ,1228
|
|
44
44
|
nat/builder/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
45
45
|
nat/builder/builder.py,sha256=okI3Y101hwF63AwazzxiahQx-W9eFZ_SNdFXzDuoftU,11608
|
|
@@ -114,13 +114,13 @@ nat/control_flow/router_agent/prompt.py,sha256=fIAiNsAs1zXRAatButR76zSpHJNxSkXXK
|
|
|
114
114
|
nat/control_flow/router_agent/register.py,sha256=4RGmS9sy-QtIMmvh8mfMcR1VqxFPLpG4RckWCIExh40,4144
|
|
115
115
|
nat/data_models/__init__.py,sha256=Xs1JQ16L9btwreh4pdGKwskffAw1YFO48jKrU4ib_7c,685
|
|
116
116
|
nat/data_models/agent.py,sha256=IwDyb9Zc3R4Zd5rFeqt7q0EQswczAl5focxV9KozIzs,1625
|
|
117
|
-
nat/data_models/api_server.py,sha256=
|
|
117
|
+
nat/data_models/api_server.py,sha256=sX_faprmycij1Zy_PQqEMtAcbvGD8PG1kWKLAyNQx6M,30775
|
|
118
118
|
nat/data_models/authentication.py,sha256=XPu9W8nh4XRSuxPv3HxO-FMQ_JtTEoK6Y02JwnzDwTg,8457
|
|
119
|
-
nat/data_models/common.py,sha256=
|
|
119
|
+
nat/data_models/common.py,sha256=dOtZI6g9AvFplu40nTsUDnahafVa9c2VITq19V_cb50,7302
|
|
120
120
|
nat/data_models/component.py,sha256=b_hXOA8Gm5UNvlFkAhsR6kEvf33ST50MKtr5kWf75Ao,1894
|
|
121
121
|
nat/data_models/component_ref.py,sha256=KFDWFVCcvJCfBBcXTh9f3R802EVHBtHXh9OdbRqFmdM,4747
|
|
122
122
|
nat/data_models/config.py,sha256=P0JJmjqvUHUkpZ3Yc0IrMPoA2qP8HkmOjl7CwNq-nQQ,18833
|
|
123
|
-
nat/data_models/dataset_handler.py,sha256=
|
|
123
|
+
nat/data_models/dataset_handler.py,sha256=1zz0456WGcGdLA9bodbMd1EMtQC8pns8TpvjNkk27No,5611
|
|
124
124
|
nat/data_models/discovery_metadata.py,sha256=_l97iQsqp_ihba8CbMBQ73mH1sipTQ19GiJDdzQYQGY,13432
|
|
125
125
|
nat/data_models/embedder.py,sha256=nPhthEQDtzAMGd8gFRB1ZfJpN5M9DJvv0h28ohHnTmI,1002
|
|
126
126
|
nat/data_models/evaluate.py,sha256=L0GdNh_c8jii-MiK8oHW9sUUsGO3l1FMsprr-UazT5c,4836
|
|
@@ -153,9 +153,9 @@ nat/data_models/thinking_mixin.py,sha256=VRDUJZ8XP_Vv0gW2FRZUf8O9-kVgNEdZCEZ8oEm
|
|
|
153
153
|
nat/data_models/top_p_mixin.py,sha256=mu0DLnCAiwNzpSFR8FOW4kQBUpodSrvUR4MsLrNtbgA,1599
|
|
154
154
|
nat/data_models/ttc_strategy.py,sha256=tAkKWcyEBmBOOYtHMtQTgeCbHxFTk5SEkmFunNVnfyE,1114
|
|
155
155
|
nat/embedder/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
156
|
-
nat/embedder/azure_openai_embedder.py,sha256=
|
|
157
|
-
nat/embedder/nim_embedder.py,sha256=
|
|
158
|
-
nat/embedder/openai_embedder.py,sha256=
|
|
156
|
+
nat/embedder/azure_openai_embedder.py,sha256=yMHOA6lWZl15Pvd9Gpp_rHy5q2qmBiRjbFesFBGuC_U,2441
|
|
157
|
+
nat/embedder/nim_embedder.py,sha256=BDwqixfzToXoOg4vkHcLAhoCGzwfJjixcDAGYGM8Sok,2592
|
|
158
|
+
nat/embedder/openai_embedder.py,sha256=To7aCg8UyWPwSoA0MAHanH_MAKFDi3EcZxgLU1xYE9M,1997
|
|
159
159
|
nat/embedder/register.py,sha256=TM_LKuSlJr3tEceNVuHfAx_yrCzf1sryD5Ycep5rNGo,883
|
|
160
160
|
nat/eval/__init__.py,sha256=Xs1JQ16L9btwreh4pdGKwskffAw1YFO48jKrU4ib_7c,685
|
|
161
161
|
nat/eval/config.py,sha256=G0LE4JpZaQy3PvERldVATFpQCiDQcVJGUFChgorqzNo,2377
|
|
@@ -166,7 +166,7 @@ nat/eval/remote_workflow.py,sha256=JAAbD0s753AOjo9baT4OqcB5dVEDmN34jPe0Uk13LcU,6
|
|
|
166
166
|
nat/eval/runtime_event_subscriber.py,sha256=9MVgZLKvpnThHINaPbszPYDWnJ61sntS09YZtDhIRE4,1900
|
|
167
167
|
nat/eval/usage_stats.py,sha256=r8Zr3bIy2qXU1BgVKXr_4mr7bG0hte3BPNQLSgZvg8M,1376
|
|
168
168
|
nat/eval/dataset_handler/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
169
|
-
nat/eval/dataset_handler/dataset_downloader.py,sha256=
|
|
169
|
+
nat/eval/dataset_handler/dataset_downloader.py,sha256=ZaNohbRSvoHPWIj0C_FqyJnhQKoTBk_uZF7ijjfk6vE,4641
|
|
170
170
|
nat/eval/dataset_handler/dataset_filter.py,sha256=HrK0m3SQnPX6zOcKwJwYMrD9O-f6aOw8Vo3j9RKszqE,2115
|
|
171
171
|
nat/eval/dataset_handler/dataset_handler.py,sha256=my28rKvQiiRN_h2TJz6fdKeMOjP3LC3_e2aJNnPPYhE,18159
|
|
172
172
|
nat/eval/evaluator/__init__.py,sha256=GUJrgGtpvyMUCjUBvR3faAdv-tZzbU9W-izgx9aMEQg,680
|
|
@@ -192,7 +192,7 @@ nat/eval/tunable_rag_evaluator/evaluate.py,sha256=SzZYwE0juuNv6o9Pbddxi4edxRXlo8
|
|
|
192
192
|
nat/eval/tunable_rag_evaluator/register.py,sha256=jYhPz8Xwsxyb7E0xpkAcQFT20xjSoYd_4qOJ7ltvUzU,2558
|
|
193
193
|
nat/eval/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
194
194
|
nat/eval/utils/eval_trace_ctx.py,sha256=hN0YZ0wMOPzh9I-iSav-cGdxY3RWQWoE_tk5BxUf1mc,3264
|
|
195
|
-
nat/eval/utils/output_uploader.py,sha256=
|
|
195
|
+
nat/eval/utils/output_uploader.py,sha256=wtAaxvrJrjQPFt8mTLSz31sWDs09KmLkmyelMQOLDws,5667
|
|
196
196
|
nat/eval/utils/tqdm_position_registry.py,sha256=9CtpCk1wtYCSyieHPaSp8nlZu6EcNUOaUz2RTqfekrA,1286
|
|
197
197
|
nat/eval/utils/weave_eval.py,sha256=fma5x9JbWpWrfQbfMHcjMovlRVR0v35yfNt1Avt6Vro,7719
|
|
198
198
|
nat/experimental/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -235,7 +235,7 @@ nat/experimental/test_time_compute/selection/threshold_selector.py,sha256=9E__TM
|
|
|
235
235
|
nat/front_ends/__init__.py,sha256=Xs1JQ16L9btwreh4pdGKwskffAw1YFO48jKrU4ib_7c,685
|
|
236
236
|
nat/front_ends/register.py,sha256=_C6AFpsQ8hUXavKHaBMy0g137fOcLfEjyU0EAuYqtao,857
|
|
237
237
|
nat/front_ends/console/__init__.py,sha256=Xs1JQ16L9btwreh4pdGKwskffAw1YFO48jKrU4ib_7c,685
|
|
238
|
-
nat/front_ends/console/authentication_flow_handler.py,sha256=
|
|
238
|
+
nat/front_ends/console/authentication_flow_handler.py,sha256=ikZdzQH4uLJ_eWLDHcCAcXhOk_2gmnjVTjykuISeVQE,12166
|
|
239
239
|
nat/front_ends/console/console_front_end_config.py,sha256=wkMXk-RCdlEj3303kB1gh47UKJnubX2R-vzBzhedpS4,1318
|
|
240
240
|
nat/front_ends/console/console_front_end_plugin.py,sha256=rlh8rL8iJCczVJngBFMckNFB7ERqJGtX1QJr-iNKGyA,4670
|
|
241
241
|
nat/front_ends/console/register.py,sha256=2Kf6Mthx6jzWzU8YdhYIR1iABmZDvs1UXM_20npXWXs,1153
|
|
@@ -262,20 +262,20 @@ nat/front_ends/fastapi/html_snippets/__init__.py,sha256=GUJrgGtpvyMUCjUBvR3faAdv
|
|
|
262
262
|
nat/front_ends/fastapi/html_snippets/auth_code_grant_success.py,sha256=BNpWwzmA58UM0GK4kZXG4PHJy_5K9ihaVHu8SgCs5JA,1131
|
|
263
263
|
nat/front_ends/mcp/__init__.py,sha256=Xs1JQ16L9btwreh4pdGKwskffAw1YFO48jKrU4ib_7c,685
|
|
264
264
|
nat/front_ends/mcp/introspection_token_verifier.py,sha256=s7Q4Q6rWZJ0ZVujSxxpvVI6Bnhkg1LJQ3RLkvhzFIGE,2836
|
|
265
|
-
nat/front_ends/mcp/mcp_front_end_config.py,sha256=
|
|
266
|
-
nat/front_ends/mcp/mcp_front_end_plugin.py,sha256=
|
|
267
|
-
nat/front_ends/mcp/mcp_front_end_plugin_worker.py,sha256=
|
|
265
|
+
nat/front_ends/mcp/mcp_front_end_config.py,sha256=QHmz0OdB6pdUU9TH65NjLk7JsAnR-F6xisel5Bv2Po4,5744
|
|
266
|
+
nat/front_ends/mcp/mcp_front_end_plugin.py,sha256=MVYJBCOhZAzUPlnXest6CYP3Gf0Ef1lbURaezgHpoyg,6701
|
|
267
|
+
nat/front_ends/mcp/mcp_front_end_plugin_worker.py,sha256=qoRbYLC_HWqSH_jSNb-w7R_qwOmLyXaUA5JK0SX33GA,15362
|
|
268
268
|
nat/front_ends/mcp/memory_profiler.py,sha256=OpcpLBAGCdQwYSFZbtAqdfncrnGYVjDcMpWydB71hjY,12811
|
|
269
269
|
nat/front_ends/mcp/register.py,sha256=3aJtgG5VaiqujoeU1-Eq7Hl5pWslIlIwGFU2ASLTXgM,1173
|
|
270
|
-
nat/front_ends/mcp/tool_converter.py,sha256=
|
|
270
|
+
nat/front_ends/mcp/tool_converter.py,sha256=IOHb8UoW_TVvRoiML2yi6nlbx13KgcmUsuYOGS3xYe0,13349
|
|
271
271
|
nat/front_ends/simple_base/__init__.py,sha256=Xs1JQ16L9btwreh4pdGKwskffAw1YFO48jKrU4ib_7c,685
|
|
272
272
|
nat/front_ends/simple_base/simple_front_end_plugin_base.py,sha256=py_yA9XAw-yHfK5cQJLM8ElnubEEM2ac8M0bvz-ScWs,1801
|
|
273
273
|
nat/llm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
274
274
|
nat/llm/aws_bedrock_llm.py,sha256=Qo6-7MUrh4qGzUQMsEPTyKEBp5D7DwU6e8LHoHwzQIs,3252
|
|
275
|
-
nat/llm/azure_openai_llm.py,sha256=
|
|
276
|
-
nat/llm/litellm_llm.py,sha256=
|
|
277
|
-
nat/llm/nim_llm.py,sha256=
|
|
278
|
-
nat/llm/openai_llm.py,sha256=
|
|
275
|
+
nat/llm/azure_openai_llm.py,sha256=IJ_o6W_NtR2rzaCowznyJUA8-5F4CG0scy3Nw2egoPs,2709
|
|
276
|
+
nat/llm/litellm_llm.py,sha256=060odQkgTR087LmFrgnpy_yP0nvUoHI25DahS5VUbgA,3003
|
|
277
|
+
nat/llm/nim_llm.py,sha256=2v5QC16Fk-Nczx2p9OP7hfPM7CsylbAEZlkJrkvLgKI,2789
|
|
278
|
+
nat/llm/openai_llm.py,sha256=vg4K05IUamsRz9SVs1GUJrabr38cs8JNa_jd52048cw,2637
|
|
279
279
|
nat/llm/register.py,sha256=7xDYdK4w4opAwIjzDM5x7moJXT3QeEGaGGc_nDfY0i4,1090
|
|
280
280
|
nat/llm/utils/__init__.py,sha256=GUJrgGtpvyMUCjUBvR3faAdv-tZzbU9W-izgx9aMEQg,680
|
|
281
281
|
nat/llm/utils/env_config_value.py,sha256=kBVsv0pEokIAfDQx5omR7_FevFv_5fTPswcbnvhVT2c,3548
|
|
@@ -386,9 +386,9 @@ nat/registry_handlers/local/local_handler.py,sha256=Dz-jRccF3NbSM9ddEzI7qckD-Ngj
|
|
|
386
386
|
nat/registry_handlers/local/register_local.py,sha256=EMcQ3tvDDic4YeViz3jNIQyrUiD0foriP11CuuEmrr0,1341
|
|
387
387
|
nat/registry_handlers/pypi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
388
388
|
nat/registry_handlers/pypi/pypi_handler.py,sha256=ec6oJjMO4msWTqahpP0L72O-iVckOFvrgcnpZuUlrw4,10070
|
|
389
|
-
nat/registry_handlers/pypi/register_pypi.py,sha256=
|
|
389
|
+
nat/registry_handlers/pypi/register_pypi.py,sha256=4TY6RLab99yIeQvfysZKFdsCWzTU81Dkxyb0yMONys0,1977
|
|
390
390
|
nat/registry_handlers/rest/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
391
|
-
nat/registry_handlers/rest/register_rest.py,sha256=
|
|
391
|
+
nat/registry_handlers/rest/register_rest.py,sha256=anIc20Tkupnm0T7wFfo5g_9tvJwBJjtaCo3nfhZUAmI,2666
|
|
392
392
|
nat/registry_handlers/rest/rest_handler.py,sha256=Ez-seJyUqVYrR_kLrDFrA1kuzWIKeiJYY0GTrV9-qnA,9371
|
|
393
393
|
nat/registry_handlers/schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
394
394
|
nat/registry_handlers/schemas/headers.py,sha256=LnVfCMNc3vRr-lRdFB8Cuv9Db5Ct-ACTapjWLaRg2os,1549
|
|
@@ -406,7 +406,7 @@ nat/retriever/milvus/__init__.py,sha256=GUJrgGtpvyMUCjUBvR3faAdv-tZzbU9W-izgx9aM
|
|
|
406
406
|
nat/retriever/milvus/register.py,sha256=FaWvUFj4rU6qcui-G459Z-bQV-QAVR3PNONT1qu7jxs,4027
|
|
407
407
|
nat/retriever/milvus/retriever.py,sha256=wfWi-Ck17ZXbrCJE3MiEVD4DuVeeAkgifdAkoISqNa0,9485
|
|
408
408
|
nat/retriever/nemo_retriever/__init__.py,sha256=GUJrgGtpvyMUCjUBvR3faAdv-tZzbU9W-izgx9aMEQg,680
|
|
409
|
-
nat/retriever/nemo_retriever/register.py,sha256=
|
|
409
|
+
nat/retriever/nemo_retriever/register.py,sha256=j0K5wz4jS9LbSXMknKUjkZ5bnqLGqrkcKGKTQNSg0ro,2953
|
|
410
410
|
nat/retriever/nemo_retriever/retriever.py,sha256=gi3_qJFqE-iqRh3of_cmJg-SwzaQ3z24zA9LwY_MSLY,6930
|
|
411
411
|
nat/runtime/__init__.py,sha256=Xs1JQ16L9btwreh4pdGKwskffAw1YFO48jKrU4ib_7c,685
|
|
412
412
|
nat/runtime/loader.py,sha256=obUdAgZVYCPGC0R8u3wcoKFJzzSPQgJvrbU4OWygtog,7953
|
|
@@ -475,10 +475,10 @@ nat/utils/reactive/base/observer_base.py,sha256=6BiQfx26EMumotJ3KoVcdmFBYR_fnAss
|
|
|
475
475
|
nat/utils/reactive/base/subject_base.py,sha256=UQOxlkZTIeeyYmG5qLtDpNf_63Y7p-doEeUA08_R8ME,2521
|
|
476
476
|
nat/utils/settings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
477
477
|
nat/utils/settings/global_settings.py,sha256=9JaO6pxKT_Pjw6rxJRsRlFCXdVKCl_xUKU2QHZQWWNM,7294
|
|
478
|
-
nvidia_nat-1.4.
|
|
479
|
-
nvidia_nat-1.4.
|
|
480
|
-
nvidia_nat-1.4.
|
|
481
|
-
nvidia_nat-1.4.
|
|
482
|
-
nvidia_nat-1.4.
|
|
483
|
-
nvidia_nat-1.4.
|
|
484
|
-
nvidia_nat-1.4.
|
|
478
|
+
nvidia_nat-1.4.0a20251030.dist-info/licenses/LICENSE-3rd-party.txt,sha256=fOk5jMmCX9YoKWyYzTtfgl-SUy477audFC5hNY4oP7Q,284609
|
|
479
|
+
nvidia_nat-1.4.0a20251030.dist-info/licenses/LICENSE.md,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
|
|
480
|
+
nvidia_nat-1.4.0a20251030.dist-info/METADATA,sha256=EWbdokoCV8bvnCxqEIw-AssdqH85mzpC4KkEy8gC_3s,10248
|
|
481
|
+
nvidia_nat-1.4.0a20251030.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
482
|
+
nvidia_nat-1.4.0a20251030.dist-info/entry_points.txt,sha256=4jCqjyETMpyoWbCBf4GalZU8I_wbstpzwQNezdAVbbo,698
|
|
483
|
+
nvidia_nat-1.4.0a20251030.dist-info/top_level.txt,sha256=lgJWLkigiVZuZ_O1nxVnD_ziYBwgpE2OStdaCduMEGc,8
|
|
484
|
+
nvidia_nat-1.4.0a20251030.dist-info/RECORD,,
|
|
File without changes
|
{nvidia_nat-1.4.0a20251028.dist-info → nvidia_nat-1.4.0a20251030.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
{nvidia_nat-1.4.0a20251028.dist-info → nvidia_nat-1.4.0a20251030.dist-info}/licenses/LICENSE.md
RENAMED
|
File without changes
|
|
File without changes
|