nvidia-nat 1.4.0a20251102__py3-none-any.whl → 1.4.0a20251112__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/cli/commands/workflow/workflow_commands.py +3 -2
- nat/eval/dataset_handler/dataset_filter.py +34 -2
- nat/eval/evaluate.py +1 -1
- nat/eval/utils/weave_eval.py +17 -3
- nat/front_ends/fastapi/fastapi_front_end_config.py +7 -0
- nat/front_ends/fastapi/fastapi_front_end_plugin.py +13 -7
- nat/front_ends/fastapi/fastapi_front_end_plugin_worker.py +20 -14
- nat/llm/aws_bedrock_llm.py +11 -9
- nat/llm/azure_openai_llm.py +12 -4
- nat/llm/litellm_llm.py +11 -4
- nat/llm/nim_llm.py +11 -9
- nat/llm/openai_llm.py +12 -9
- nat/tool/code_execution/code_sandbox.py +3 -6
- nat/tool/code_execution/local_sandbox/Dockerfile.sandbox +19 -32
- nat/tool/code_execution/local_sandbox/local_sandbox_server.py +5 -0
- nat/tool/code_execution/local_sandbox/sandbox.requirements.txt +2 -0
- nat/tool/code_execution/local_sandbox/start_local_sandbox.sh +10 -4
- nat/tool/server_tools.py +15 -2
- nat/utils/__init__.py +8 -4
- nat/utils/io/yaml_tools.py +73 -3
- {nvidia_nat-1.4.0a20251102.dist-info → nvidia_nat-1.4.0a20251112.dist-info}/METADATA +3 -1
- {nvidia_nat-1.4.0a20251102.dist-info → nvidia_nat-1.4.0a20251112.dist-info}/RECORD +27 -30
- nat/data_models/temperature_mixin.py +0 -44
- nat/data_models/top_p_mixin.py +0 -44
- nat/tool/code_execution/test_code_execution_sandbox.py +0 -414
- {nvidia_nat-1.4.0a20251102.dist-info → nvidia_nat-1.4.0a20251112.dist-info}/WHEEL +0 -0
- {nvidia_nat-1.4.0a20251102.dist-info → nvidia_nat-1.4.0a20251112.dist-info}/entry_points.txt +0 -0
- {nvidia_nat-1.4.0a20251102.dist-info → nvidia_nat-1.4.0a20251112.dist-info}/licenses/LICENSE-3rd-party.txt +0 -0
- {nvidia_nat-1.4.0a20251102.dist-info → nvidia_nat-1.4.0a20251112.dist-info}/licenses/LICENSE.md +0 -0
- {nvidia_nat-1.4.0a20251102.dist-info → nvidia_nat-1.4.0a20251112.dist-info}/top_level.txt +0 -0
|
@@ -354,7 +354,8 @@ def reinstall_command(workflow_name):
|
|
|
354
354
|
|
|
355
355
|
@click.command()
|
|
356
356
|
@click.argument('workflow_name')
|
|
357
|
-
|
|
357
|
+
@click.option('-y', '--yes', "yes_flag", is_flag=True, default=False, help='Do not prompt for confirmation.')
|
|
358
|
+
def delete_command(workflow_name: str, yes_flag: bool):
|
|
358
359
|
"""
|
|
359
360
|
Delete a NAT workflow and uninstall its package.
|
|
360
361
|
|
|
@@ -362,7 +363,7 @@ def delete_command(workflow_name: str):
|
|
|
362
363
|
workflow_name (str): The name of the workflow to delete.
|
|
363
364
|
"""
|
|
364
365
|
try:
|
|
365
|
-
if not click.confirm(f"Are you sure you want to delete the workflow '{workflow_name}'?"):
|
|
366
|
+
if not yes_flag and not click.confirm(f"Are you sure you want to delete the workflow '{workflow_name}'?"):
|
|
366
367
|
click.echo("Workflow deletion cancelled.")
|
|
367
368
|
return
|
|
368
369
|
editable = get_repo_root() is not None
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
# See the License for the specific language governing permissions and
|
|
14
14
|
# limitations under the License.
|
|
15
15
|
|
|
16
|
+
import fnmatch
|
|
17
|
+
|
|
16
18
|
import pandas as pd
|
|
17
19
|
|
|
18
20
|
from nat.data_models.dataset_handler import EvalFilterConfig
|
|
@@ -24,6 +26,7 @@ class DatasetFilter:
|
|
|
24
26
|
- If a allowlist is provided, only keep rows matching the filter values.
|
|
25
27
|
- If a denylist is provided, remove rows matching the filter values.
|
|
26
28
|
- If the filter column does not exist in the DataFrame, the filtering is skipped for that column.
|
|
29
|
+
- Supports Unix shell-style wildcards (``*``, ``?``, ``[seq]``, ``[!seq]``) for string matching.
|
|
27
30
|
|
|
28
31
|
This is a utility class that is dataset agnostic and can be used to filter any DataFrame based on the provided
|
|
29
32
|
filter configuration.
|
|
@@ -33,6 +36,33 @@ class DatasetFilter:
|
|
|
33
36
|
|
|
34
37
|
self.filter_config = filter_config
|
|
35
38
|
|
|
39
|
+
@staticmethod
|
|
40
|
+
def _match_wildcard_patterns(series: pd.Series, patterns: list[str | int | float]) -> pd.Series:
|
|
41
|
+
"""
|
|
42
|
+
Match series values against wildcard patterns and exact values.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
series (pd.Series): pandas Series to match against
|
|
46
|
+
patterns (list[str | int | float]): List of patterns/values
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
pd.Series: Boolean Series indicating matches
|
|
50
|
+
"""
|
|
51
|
+
# Convert series to string for pattern matching
|
|
52
|
+
str_series = series.astype(str)
|
|
53
|
+
|
|
54
|
+
# Initialize boolean mask
|
|
55
|
+
matches = pd.Series([False] * len(series), index=series.index)
|
|
56
|
+
|
|
57
|
+
# Check each pattern using fnmatch with list comprehension to avoid lambda capture
|
|
58
|
+
for pattern in patterns:
|
|
59
|
+
pattern_str = str(pattern)
|
|
60
|
+
pattern_matches = pd.Series([fnmatch.fnmatch(val, pattern_str) for val in str_series],
|
|
61
|
+
index=str_series.index)
|
|
62
|
+
matches |= pattern_matches
|
|
63
|
+
|
|
64
|
+
return matches
|
|
65
|
+
|
|
36
66
|
def apply_filters(self, df) -> pd.DataFrame:
|
|
37
67
|
|
|
38
68
|
filtered_df = df.copy()
|
|
@@ -41,12 +71,14 @@ class DatasetFilter:
|
|
|
41
71
|
if self.filter_config.allowlist:
|
|
42
72
|
for column, values in self.filter_config.allowlist.field.items():
|
|
43
73
|
if column in filtered_df.columns:
|
|
44
|
-
|
|
74
|
+
matches = self._match_wildcard_patterns(filtered_df[column], values)
|
|
75
|
+
filtered_df = filtered_df[matches]
|
|
45
76
|
|
|
46
77
|
# Apply denylist (remove specified rows)
|
|
47
78
|
if self.filter_config.denylist:
|
|
48
79
|
for column, values in self.filter_config.denylist.field.items():
|
|
49
80
|
if column in filtered_df.columns:
|
|
50
|
-
|
|
81
|
+
matches = self._match_wildcard_patterns(filtered_df[column], values)
|
|
82
|
+
filtered_df = filtered_df[~matches]
|
|
51
83
|
|
|
52
84
|
return filtered_df
|
nat/eval/evaluate.py
CHANGED
|
@@ -514,7 +514,7 @@ class EvaluationRun:
|
|
|
514
514
|
# Run workflow and evaluate
|
|
515
515
|
async with WorkflowEvalBuilder.from_config(config=config) as eval_workflow:
|
|
516
516
|
# Initialize Weave integration
|
|
517
|
-
self.weave_eval.initialize_logger(workflow_alias, self.eval_input, config)
|
|
517
|
+
self.weave_eval.initialize_logger(workflow_alias, self.eval_input, config, job_id=job_id)
|
|
518
518
|
|
|
519
519
|
with self.eval_trace_context.evaluation_context():
|
|
520
520
|
# Run workflow
|
nat/eval/utils/weave_eval.py
CHANGED
|
@@ -82,7 +82,7 @@ class WeaveEvaluationIntegration:
|
|
|
82
82
|
"""Get the full dataset for Weave."""
|
|
83
83
|
return [item.full_dataset_entry for item in eval_input.eval_input_items]
|
|
84
84
|
|
|
85
|
-
def initialize_logger(self, workflow_alias: str, eval_input: EvalInput, config: Any):
|
|
85
|
+
def initialize_logger(self, workflow_alias: str, eval_input: EvalInput, config: Any, job_id: str | None = None):
|
|
86
86
|
"""Initialize the Weave evaluation logger."""
|
|
87
87
|
if not self.client and not self.initialize_client():
|
|
88
88
|
# lazy init the client
|
|
@@ -92,10 +92,16 @@ class WeaveEvaluationIntegration:
|
|
|
92
92
|
weave_dataset = self._get_weave_dataset(eval_input)
|
|
93
93
|
config_dict = config.model_dump(mode="json")
|
|
94
94
|
config_dict["name"] = workflow_alias
|
|
95
|
+
|
|
96
|
+
# Include job_id in eval_attributes if provided
|
|
97
|
+
eval_attributes = {}
|
|
98
|
+
if job_id:
|
|
99
|
+
eval_attributes["job_id"] = job_id
|
|
100
|
+
|
|
95
101
|
self.eval_logger = self.evaluation_logger_cls(model=config_dict,
|
|
96
102
|
dataset=weave_dataset,
|
|
97
103
|
name=workflow_alias,
|
|
98
|
-
eval_attributes=
|
|
104
|
+
eval_attributes=eval_attributes)
|
|
99
105
|
self.pred_loggers = {}
|
|
100
106
|
|
|
101
107
|
# Capture the current evaluation call for context propagation
|
|
@@ -136,9 +142,17 @@ class WeaveEvaluationIntegration:
|
|
|
136
142
|
coros = []
|
|
137
143
|
for eval_output_item in eval_output.eval_output_items:
|
|
138
144
|
if eval_output_item.id in self.pred_loggers:
|
|
145
|
+
# Structure the score as a dict and include reasoning if available
|
|
146
|
+
score_value = {
|
|
147
|
+
"score": eval_output_item.score,
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if eval_output_item.reasoning is not None:
|
|
151
|
+
score_value["reasoning"] = eval_output_item.reasoning
|
|
152
|
+
|
|
139
153
|
coros.append(self.pred_loggers[eval_output_item.id].alog_score(
|
|
140
154
|
scorer=evaluator_name,
|
|
141
|
-
score=
|
|
155
|
+
score=score_value,
|
|
142
156
|
))
|
|
143
157
|
|
|
144
158
|
# Execute all coroutines concurrently
|
|
@@ -211,6 +211,13 @@ class FastApiFrontEndConfig(FrontEndBaseConfig, name="fastapi"):
|
|
|
211
211
|
"Maximum number of async jobs to run concurrently, this controls the number of dask workers created. "
|
|
212
212
|
"This parameter is only used when scheduler_address is `None` and a Dask local cluster is created."),
|
|
213
213
|
ge=1)
|
|
214
|
+
dask_workers: typing.Literal["threads", "processes"] = Field(
|
|
215
|
+
default="processes",
|
|
216
|
+
description=(
|
|
217
|
+
"Type of Dask workers to use. Options are 'threads' for Threaded Dask workers or 'processes' for "
|
|
218
|
+
"Process based Dask workers. This parameter is only used when scheduler_address is `None` and a local Dask "
|
|
219
|
+
"cluster is created."),
|
|
220
|
+
)
|
|
214
221
|
dask_log_level: str = Field(
|
|
215
222
|
default="WARNING",
|
|
216
223
|
description="Logging level for Dask.",
|
|
@@ -120,18 +120,24 @@ class FastApiFrontEndPlugin(DaskClientMixin, FrontEndBase[FastApiFrontEndConfig]
|
|
|
120
120
|
|
|
121
121
|
from dask.distributed import LocalCluster
|
|
122
122
|
|
|
123
|
-
self.
|
|
123
|
+
use_threads = self.front_end_config.dask_workers == 'threads'
|
|
124
|
+
|
|
125
|
+
# set n_workers to max_running_async_jobs + 1 to allow for one worker to handle the cleanup task
|
|
126
|
+
self._cluster = LocalCluster(processes=not use_threads,
|
|
124
127
|
silence_logs=dask_log_level,
|
|
125
|
-
|
|
126
|
-
|
|
128
|
+
protocol="tcp",
|
|
129
|
+
n_workers=self.front_end_config.max_running_async_jobs + 1)
|
|
127
130
|
|
|
128
131
|
self._scheduler_address = self._cluster.scheduler.address
|
|
129
132
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
+
if not use_threads and sys.platform != "win32":
|
|
134
|
+
with self.blocking_client(self._scheduler_address) as client:
|
|
135
|
+
# Client.run submits a function to be run on each worker
|
|
136
|
+
client.run(self._setup_worker)
|
|
133
137
|
|
|
134
|
-
logger.info("Created local Dask cluster with scheduler at %s",
|
|
138
|
+
logger.info("Created local Dask cluster with scheduler at %s using %s workers",
|
|
139
|
+
self._scheduler_address,
|
|
140
|
+
self.front_end_config.dask_workers)
|
|
135
141
|
|
|
136
142
|
except ImportError:
|
|
137
143
|
logger.warning("Dask is not installed, async execution and evaluation will not be available.")
|
|
@@ -544,7 +544,8 @@ class FastApiFrontEndPluginWorker(FastApiFrontEndPluginWorkerBase):
|
|
|
544
544
|
GenerateStreamResponseType = workflow.streaming_output_schema
|
|
545
545
|
GenerateSingleResponseType = workflow.single_output_schema
|
|
546
546
|
|
|
547
|
-
|
|
547
|
+
# Skip async generation for custom routes (those with function_name)
|
|
548
|
+
if self._dask_available and not hasattr(endpoint, 'function_name'):
|
|
548
549
|
# Append job_id and expiry_seconds to the input schema, this effectively makes these reserved keywords
|
|
549
550
|
# Consider prefixing these with "nat_" to avoid conflicts
|
|
550
551
|
|
|
@@ -562,6 +563,10 @@ class FastApiFrontEndPluginWorker(FastApiFrontEndPluginWorkerBase):
|
|
|
562
563
|
description="Optional time (in seconds) before the job expires. "
|
|
563
564
|
"Clamped between 600 (10 min) and 86400 (24h).")
|
|
564
565
|
|
|
566
|
+
def validate_model(self):
|
|
567
|
+
# Override to ensure that the parent class validator is not called
|
|
568
|
+
return self
|
|
569
|
+
|
|
565
570
|
# Ensure that the input is in the body. POD types are treated as query parameters
|
|
566
571
|
if (not issubclass(GenerateBodyType, BaseModel)):
|
|
567
572
|
GenerateBodyType = typing.Annotated[GenerateBodyType, Body()]
|
|
@@ -760,17 +765,18 @@ class FastApiFrontEndPluginWorker(FastApiFrontEndPluginWorkerBase):
|
|
|
760
765
|
return AsyncGenerateResponse(job_id=job.job_id, status=job.status)
|
|
761
766
|
|
|
762
767
|
job_id = self._job_store.ensure_job_id(request.job_id)
|
|
763
|
-
(_, job) = await self._job_store.submit_job(
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
768
|
+
(_, job) = await self._job_store.submit_job(
|
|
769
|
+
job_id=job_id,
|
|
770
|
+
expiry_seconds=request.expiry_seconds,
|
|
771
|
+
job_fn=run_generation,
|
|
772
|
+
sync_timeout=request.sync_timeout,
|
|
773
|
+
job_args=[
|
|
774
|
+
self._scheduler_address,
|
|
775
|
+
self._db_url,
|
|
776
|
+
self._config_file_path,
|
|
777
|
+
job_id,
|
|
778
|
+
request.model_dump(mode="json", exclude=["job_id", "sync_timeout", "expiry_seconds"])
|
|
779
|
+
])
|
|
774
780
|
|
|
775
781
|
if job is not None:
|
|
776
782
|
response.status_code = 200
|
|
@@ -916,7 +922,7 @@ class FastApiFrontEndPluginWorker(FastApiFrontEndPluginWorkerBase):
|
|
|
916
922
|
responses={500: response_500},
|
|
917
923
|
)
|
|
918
924
|
|
|
919
|
-
if self._dask_available:
|
|
925
|
+
if self._dask_available and not hasattr(endpoint, 'function_name'):
|
|
920
926
|
app.add_api_route(
|
|
921
927
|
path=f"{endpoint.path}/async",
|
|
922
928
|
endpoint=post_async_generation(request_type=AsyncGenerateRequest),
|
|
@@ -930,7 +936,7 @@ class FastApiFrontEndPluginWorker(FastApiFrontEndPluginWorkerBase):
|
|
|
930
936
|
else:
|
|
931
937
|
raise ValueError(f"Unsupported method {endpoint.method}")
|
|
932
938
|
|
|
933
|
-
if self._dask_available:
|
|
939
|
+
if self._dask_available and not hasattr(endpoint, 'function_name'):
|
|
934
940
|
app.add_api_route(
|
|
935
941
|
path=f"{endpoint.path}/async/job/{{job_id}}",
|
|
936
942
|
endpoint=get_async_job_status,
|
nat/llm/aws_bedrock_llm.py
CHANGED
|
@@ -25,18 +25,10 @@ from nat.data_models.optimizable import OptimizableField
|
|
|
25
25
|
from nat.data_models.optimizable import OptimizableMixin
|
|
26
26
|
from nat.data_models.optimizable import SearchSpace
|
|
27
27
|
from nat.data_models.retry_mixin import RetryMixin
|
|
28
|
-
from nat.data_models.temperature_mixin import TemperatureMixin
|
|
29
28
|
from nat.data_models.thinking_mixin import ThinkingMixin
|
|
30
|
-
from nat.data_models.top_p_mixin import TopPMixin
|
|
31
29
|
|
|
32
30
|
|
|
33
|
-
class AWSBedrockModelConfig(LLMBaseConfig,
|
|
34
|
-
RetryMixin,
|
|
35
|
-
OptimizableMixin,
|
|
36
|
-
TemperatureMixin,
|
|
37
|
-
TopPMixin,
|
|
38
|
-
ThinkingMixin,
|
|
39
|
-
name="aws_bedrock"):
|
|
31
|
+
class AWSBedrockModelConfig(LLMBaseConfig, RetryMixin, OptimizableMixin, ThinkingMixin, name="aws_bedrock"):
|
|
40
32
|
"""An AWS Bedrock llm provider to be used with an LLM client."""
|
|
41
33
|
|
|
42
34
|
model_config = ConfigDict(protected_namespaces=(), extra="allow")
|
|
@@ -61,6 +53,16 @@ class AWSBedrockModelConfig(LLMBaseConfig,
|
|
|
61
53
|
default=None, description="Bedrock endpoint to use. Needed if you don't want to default to us-east-1 endpoint.")
|
|
62
54
|
credentials_profile_name: str | None = Field(
|
|
63
55
|
default=None, description="The name of the profile in the ~/.aws/credentials or ~/.aws/config files.")
|
|
56
|
+
temperature: float | None = OptimizableField(
|
|
57
|
+
default=None,
|
|
58
|
+
ge=0.0,
|
|
59
|
+
description="Sampling temperature to control randomness in the output.",
|
|
60
|
+
space=SearchSpace(high=0.9, low=0.1, step=0.2))
|
|
61
|
+
top_p: float | None = OptimizableField(default=None,
|
|
62
|
+
ge=0.0,
|
|
63
|
+
le=1.0,
|
|
64
|
+
description="Top-p for distribution sampling.",
|
|
65
|
+
space=SearchSpace(high=1.0, low=0.5, step=0.1))
|
|
64
66
|
|
|
65
67
|
|
|
66
68
|
@register_llm_provider(config_type=AWSBedrockModelConfig)
|
nat/llm/azure_openai_llm.py
CHANGED
|
@@ -22,17 +22,15 @@ from nat.builder.llm import LLMProviderInfo
|
|
|
22
22
|
from nat.cli.register_workflow import register_llm_provider
|
|
23
23
|
from nat.data_models.common import OptionalSecretStr
|
|
24
24
|
from nat.data_models.llm import LLMBaseConfig
|
|
25
|
+
from nat.data_models.optimizable import OptimizableField
|
|
26
|
+
from nat.data_models.optimizable import SearchSpace
|
|
25
27
|
from nat.data_models.retry_mixin import RetryMixin
|
|
26
|
-
from nat.data_models.temperature_mixin import TemperatureMixin
|
|
27
28
|
from nat.data_models.thinking_mixin import ThinkingMixin
|
|
28
|
-
from nat.data_models.top_p_mixin import TopPMixin
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
class AzureOpenAIModelConfig(
|
|
32
32
|
LLMBaseConfig,
|
|
33
33
|
RetryMixin,
|
|
34
|
-
TemperatureMixin,
|
|
35
|
-
TopPMixin,
|
|
36
34
|
ThinkingMixin,
|
|
37
35
|
name="azure_openai",
|
|
38
36
|
):
|
|
@@ -50,6 +48,16 @@ class AzureOpenAIModelConfig(
|
|
|
50
48
|
serialization_alias="azure_deployment",
|
|
51
49
|
description="The Azure OpenAI hosted model/deployment name.")
|
|
52
50
|
seed: int | None = Field(default=None, description="Random seed to set for generation.")
|
|
51
|
+
temperature: float | None = OptimizableField(
|
|
52
|
+
default=None,
|
|
53
|
+
ge=0.0,
|
|
54
|
+
description="Sampling temperature to control randomness in the output.",
|
|
55
|
+
space=SearchSpace(high=0.9, low=0.1, step=0.2))
|
|
56
|
+
top_p: float | None = OptimizableField(default=None,
|
|
57
|
+
ge=0.0,
|
|
58
|
+
le=1.0,
|
|
59
|
+
description="Top-p for distribution sampling.",
|
|
60
|
+
space=SearchSpace(high=1.0, low=0.5, step=0.1))
|
|
53
61
|
|
|
54
62
|
|
|
55
63
|
@register_llm_provider(config_type=AzureOpenAIModelConfig)
|
nat/llm/litellm_llm.py
CHANGED
|
@@ -26,18 +26,15 @@ from nat.data_models.common import OptionalSecretStr
|
|
|
26
26
|
from nat.data_models.llm import LLMBaseConfig
|
|
27
27
|
from nat.data_models.optimizable import OptimizableField
|
|
28
28
|
from nat.data_models.optimizable import OptimizableMixin
|
|
29
|
+
from nat.data_models.optimizable import SearchSpace
|
|
29
30
|
from nat.data_models.retry_mixin import RetryMixin
|
|
30
|
-
from nat.data_models.temperature_mixin import TemperatureMixin
|
|
31
31
|
from nat.data_models.thinking_mixin import ThinkingMixin
|
|
32
|
-
from nat.data_models.top_p_mixin import TopPMixin
|
|
33
32
|
|
|
34
33
|
|
|
35
34
|
class LiteLlmModelConfig(
|
|
36
35
|
LLMBaseConfig,
|
|
37
36
|
OptimizableMixin,
|
|
38
37
|
RetryMixin,
|
|
39
|
-
TemperatureMixin,
|
|
40
|
-
TopPMixin,
|
|
41
38
|
ThinkingMixin,
|
|
42
39
|
name="litellm",
|
|
43
40
|
):
|
|
@@ -54,6 +51,16 @@ class LiteLlmModelConfig(
|
|
|
54
51
|
serialization_alias="model",
|
|
55
52
|
description="The LiteLlm hosted model name.")
|
|
56
53
|
seed: int | None = Field(default=None, description="Random seed to set for generation.")
|
|
54
|
+
temperature: float | None = OptimizableField(
|
|
55
|
+
default=None,
|
|
56
|
+
ge=0.0,
|
|
57
|
+
description="Sampling temperature to control randomness in the output.",
|
|
58
|
+
space=SearchSpace(high=0.9, low=0.1, step=0.2))
|
|
59
|
+
top_p: float | None = OptimizableField(default=None,
|
|
60
|
+
ge=0.0,
|
|
61
|
+
le=1.0,
|
|
62
|
+
description="Top-p for distribution sampling.",
|
|
63
|
+
space=SearchSpace(high=1.0, low=0.5, step=0.1))
|
|
57
64
|
|
|
58
65
|
|
|
59
66
|
@register_llm_provider(config_type=LiteLlmModelConfig)
|
nat/llm/nim_llm.py
CHANGED
|
@@ -27,18 +27,10 @@ from nat.data_models.optimizable import OptimizableField
|
|
|
27
27
|
from nat.data_models.optimizable import OptimizableMixin
|
|
28
28
|
from nat.data_models.optimizable import SearchSpace
|
|
29
29
|
from nat.data_models.retry_mixin import RetryMixin
|
|
30
|
-
from nat.data_models.temperature_mixin import TemperatureMixin
|
|
31
30
|
from nat.data_models.thinking_mixin import ThinkingMixin
|
|
32
|
-
from nat.data_models.top_p_mixin import TopPMixin
|
|
33
31
|
|
|
34
32
|
|
|
35
|
-
class NIMModelConfig(LLMBaseConfig,
|
|
36
|
-
RetryMixin,
|
|
37
|
-
OptimizableMixin,
|
|
38
|
-
TemperatureMixin,
|
|
39
|
-
TopPMixin,
|
|
40
|
-
ThinkingMixin,
|
|
41
|
-
name="nim"):
|
|
33
|
+
class NIMModelConfig(LLMBaseConfig, RetryMixin, OptimizableMixin, ThinkingMixin, name="nim"):
|
|
42
34
|
"""An NVIDIA Inference Microservice (NIM) llm provider to be used with an LLM client."""
|
|
43
35
|
|
|
44
36
|
model_config = ConfigDict(protected_namespaces=(), extra="allow")
|
|
@@ -51,6 +43,16 @@ class NIMModelConfig(LLMBaseConfig,
|
|
|
51
43
|
max_tokens: PositiveInt = OptimizableField(default=300,
|
|
52
44
|
description="Maximum number of tokens to generate.",
|
|
53
45
|
space=SearchSpace(high=2176, low=128, step=512))
|
|
46
|
+
temperature: float | None = OptimizableField(
|
|
47
|
+
default=None,
|
|
48
|
+
ge=0.0,
|
|
49
|
+
description="Sampling temperature to control randomness in the output.",
|
|
50
|
+
space=SearchSpace(high=0.9, low=0.1, step=0.2))
|
|
51
|
+
top_p: float | None = OptimizableField(default=None,
|
|
52
|
+
ge=0.0,
|
|
53
|
+
le=1.0,
|
|
54
|
+
description="Top-p for distribution sampling.",
|
|
55
|
+
space=SearchSpace(high=1.0, low=0.5, step=0.1))
|
|
54
56
|
|
|
55
57
|
|
|
56
58
|
@register_llm_provider(config_type=NIMModelConfig)
|
nat/llm/openai_llm.py
CHANGED
|
@@ -24,19 +24,12 @@ from nat.data_models.common import OptionalSecretStr
|
|
|
24
24
|
from nat.data_models.llm import LLMBaseConfig
|
|
25
25
|
from nat.data_models.optimizable import OptimizableField
|
|
26
26
|
from nat.data_models.optimizable import OptimizableMixin
|
|
27
|
+
from nat.data_models.optimizable import SearchSpace
|
|
27
28
|
from nat.data_models.retry_mixin import RetryMixin
|
|
28
|
-
from nat.data_models.temperature_mixin import TemperatureMixin
|
|
29
29
|
from nat.data_models.thinking_mixin import ThinkingMixin
|
|
30
|
-
from nat.data_models.top_p_mixin import TopPMixin
|
|
31
30
|
|
|
32
31
|
|
|
33
|
-
class OpenAIModelConfig(LLMBaseConfig,
|
|
34
|
-
RetryMixin,
|
|
35
|
-
OptimizableMixin,
|
|
36
|
-
TemperatureMixin,
|
|
37
|
-
TopPMixin,
|
|
38
|
-
ThinkingMixin,
|
|
39
|
-
name="openai"):
|
|
32
|
+
class OpenAIModelConfig(LLMBaseConfig, RetryMixin, OptimizableMixin, ThinkingMixin, name="openai"):
|
|
40
33
|
"""An OpenAI LLM provider to be used with an LLM client."""
|
|
41
34
|
|
|
42
35
|
model_config = ConfigDict(protected_namespaces=(), extra="allow")
|
|
@@ -48,6 +41,16 @@ class OpenAIModelConfig(LLMBaseConfig,
|
|
|
48
41
|
description="The OpenAI hosted model name.")
|
|
49
42
|
seed: int | None = Field(default=None, description="Random seed to set for generation.")
|
|
50
43
|
max_retries: int = Field(default=10, description="The max number of retries for the request.")
|
|
44
|
+
temperature: float | None = OptimizableField(
|
|
45
|
+
default=None,
|
|
46
|
+
ge=0.0,
|
|
47
|
+
description="Sampling temperature to control randomness in the output.",
|
|
48
|
+
space=SearchSpace(high=0.9, low=0.1, step=0.2))
|
|
49
|
+
top_p: float | None = OptimizableField(default=None,
|
|
50
|
+
ge=0.0,
|
|
51
|
+
le=1.0,
|
|
52
|
+
description="Top-p for distribution sampling.",
|
|
53
|
+
space=SearchSpace(high=1.0, low=0.5, step=0.1))
|
|
51
54
|
|
|
52
55
|
|
|
53
56
|
@register_llm_provider(config_type=OpenAIModelConfig)
|
|
@@ -92,7 +92,9 @@ class Sandbox(abc.ABC):
|
|
|
92
92
|
raise ValueError(f"Language {language} not supported")
|
|
93
93
|
|
|
94
94
|
generated_code = generated_code.strip().strip("`")
|
|
95
|
-
|
|
95
|
+
# Use json.dumps to properly escape the generated_code instead of repr()
|
|
96
|
+
escaped_code = json.dumps(generated_code)
|
|
97
|
+
code_to_execute = textwrap.dedent(f"""
|
|
96
98
|
import traceback
|
|
97
99
|
import json
|
|
98
100
|
import os
|
|
@@ -101,11 +103,6 @@ class Sandbox(abc.ABC):
|
|
|
101
103
|
import io
|
|
102
104
|
warnings.filterwarnings('ignore')
|
|
103
105
|
os.environ['OPENBLAS_NUM_THREADS'] = '16'
|
|
104
|
-
""").strip()
|
|
105
|
-
|
|
106
|
-
# Use json.dumps to properly escape the generated_code instead of repr()
|
|
107
|
-
escaped_code = json.dumps(generated_code)
|
|
108
|
-
code_to_execute += textwrap.dedent(f"""
|
|
109
106
|
|
|
110
107
|
generated_code = {escaped_code}
|
|
111
108
|
|
|
@@ -12,43 +12,26 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
# Create Lean project directory and initialize a new Lean project with Mathlib4
|
|
30
|
-
RUN mkdir -p /lean4 && cd /lean4 && \
|
|
31
|
-
/root/.elan/bin/lake new my_project && \
|
|
32
|
-
cd my_project && \
|
|
33
|
-
echo 'leanprover/lean4:v4.12.0' > lean-toolchain && \
|
|
34
|
-
echo 'require mathlib from git "https://github.com/leanprover-community/mathlib4" @ "v4.12.0"' >> lakefile.lean
|
|
35
|
-
|
|
36
|
-
# Download and cache Mathlib4 to avoid recompiling, then build the project
|
|
37
|
-
RUN cd /lean4/my_project && \
|
|
38
|
-
/root/.elan/bin/lake exe cache get && \
|
|
39
|
-
/root/.elan/bin/lake build
|
|
40
|
-
|
|
41
|
-
# Set environment variables to include Lean project path
|
|
42
|
-
ENV LEAN_PATH="/lean4/my_project"
|
|
43
|
-
ENV PATH="/lean4/my_project:$PATH"
|
|
15
|
+
# UWSGI_CHEAPER sets the number of initial uWSGI worker processes
|
|
16
|
+
# UWSGI_PROCESSES sets the maximum number of uWSGI worker processes
|
|
17
|
+
ARG UWSGI_CHEAPER=5
|
|
18
|
+
ARG UWSGI_PROCESSES=10
|
|
19
|
+
|
|
20
|
+
# Use the base image with Python 3.13
|
|
21
|
+
FROM python:3.13-slim-bookworm
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
RUN apt update && \
|
|
25
|
+
apt upgrade && \
|
|
26
|
+
apt install -y --no-install-recommends libexpat1 && \
|
|
27
|
+
apt clean && \
|
|
28
|
+
rm -rf /var/lib/apt/lists/*
|
|
44
29
|
|
|
45
30
|
# Set up application code and install Python dependencies
|
|
46
31
|
COPY sandbox.requirements.txt /app/requirements.txt
|
|
47
32
|
RUN pip install --no-cache-dir -r /app/requirements.txt
|
|
48
33
|
COPY local_sandbox_server.py /app/main.py
|
|
49
|
-
|
|
50
|
-
# Set the working directory to /app
|
|
51
|
-
WORKDIR /app
|
|
34
|
+
RUN mkdir /workspace
|
|
52
35
|
|
|
53
36
|
# Set Flask app environment variables and ports
|
|
54
37
|
ARG UWSGI_CHEAPER
|
|
@@ -58,3 +41,7 @@ ARG UWSGI_PROCESSES
|
|
|
58
41
|
ENV UWSGI_PROCESSES=$UWSGI_PROCESSES
|
|
59
42
|
|
|
60
43
|
ENV LISTEN_PORT=6000
|
|
44
|
+
EXPOSE 6000
|
|
45
|
+
|
|
46
|
+
WORKDIR /app
|
|
47
|
+
CMD uwsgi --http 0.0.0.0:${LISTEN_PORT} --master -p ${UWSGI_PROCESSES} --force-cwd /workspace -w main:app
|
|
@@ -19,7 +19,11 @@
|
|
|
19
19
|
|
|
20
20
|
DOCKER_COMMAND=${DOCKER_COMMAND:-"docker"}
|
|
21
21
|
SANDBOX_NAME=${1:-'local-sandbox'}
|
|
22
|
-
|
|
22
|
+
|
|
23
|
+
# UWSGI_CHEAPER sets the number of initial uWSGI worker processes
|
|
24
|
+
# UWSGI_PROCESSES sets the maximum number of uWSGI worker processes
|
|
25
|
+
UWSGI_CHEAPER=${UWSGI_CHEAPER:-5}
|
|
26
|
+
UWSGI_PROCESSES=${UWSGI_PROCESSES:-10}
|
|
23
27
|
|
|
24
28
|
# Get the output_data directory path for mounting
|
|
25
29
|
# Priority: command line argument > environment variable > default path (current directory)
|
|
@@ -37,14 +41,16 @@ fi
|
|
|
37
41
|
# Check if the Docker image already exists
|
|
38
42
|
if ! ${DOCKER_COMMAND} images ${SANDBOX_NAME} | grep -q "${SANDBOX_NAME}"; then
|
|
39
43
|
echo "Docker image not found locally. Building ${SANDBOX_NAME}..."
|
|
40
|
-
${DOCKER_COMMAND} build --tag=${SANDBOX_NAME}
|
|
44
|
+
${DOCKER_COMMAND} build --tag=${SANDBOX_NAME} \
|
|
45
|
+
--build-arg="UWSGI_PROCESSES=${UWSGI_PROCESSES}" \
|
|
46
|
+
--build-arg="UWSGI_CHEAPER=${UWSGI_CHEAPER}" \
|
|
47
|
+
-f Dockerfile.sandbox .
|
|
41
48
|
else
|
|
42
49
|
echo "Using existing Docker image: ${SANDBOX_NAME}"
|
|
43
50
|
fi
|
|
44
51
|
|
|
45
52
|
# Mount the output_data directory directly so files created in container appear in the local directory
|
|
46
|
-
${DOCKER_COMMAND} run --rm --name=local-sandbox \
|
|
53
|
+
${DOCKER_COMMAND} run --rm -ti --name=local-sandbox \
|
|
47
54
|
--network=host \
|
|
48
55
|
-v "${OUTPUT_DATA_PATH}:/workspace" \
|
|
49
|
-
-w /workspace \
|
|
50
56
|
${SANDBOX_NAME}
|
nat/tool/server_tools.py
CHANGED
|
@@ -32,14 +32,23 @@ class RequestAttributesTool(FunctionBaseConfig, name="current_request_attributes
|
|
|
32
32
|
@register_function(config_type=RequestAttributesTool)
|
|
33
33
|
async def current_request_attributes(config: RequestAttributesTool, builder: Builder):
|
|
34
34
|
|
|
35
|
+
from pydantic import RootModel
|
|
36
|
+
from pydantic.types import JsonValue
|
|
35
37
|
from starlette.datastructures import Headers
|
|
36
38
|
from starlette.datastructures import QueryParams
|
|
37
39
|
|
|
38
|
-
|
|
40
|
+
class RequestBody(RootModel[JsonValue]):
|
|
41
|
+
"""
|
|
42
|
+
Data model that accepts a request body of any valid JSON type.
|
|
43
|
+
"""
|
|
44
|
+
root: JsonValue
|
|
45
|
+
|
|
46
|
+
async def _get_request_attributes(request_body: RequestBody) -> str:
|
|
39
47
|
|
|
40
48
|
from nat.builder.context import Context
|
|
41
49
|
nat_context = Context.get()
|
|
42
50
|
|
|
51
|
+
# Access request attributes from context
|
|
43
52
|
method: str | None = nat_context.metadata.method
|
|
44
53
|
url_path: str | None = nat_context.metadata.url_path
|
|
45
54
|
url_scheme: str | None = nat_context.metadata.url_scheme
|
|
@@ -51,6 +60,9 @@ async def current_request_attributes(config: RequestAttributesTool, builder: Bui
|
|
|
51
60
|
cookies: dict[str, str] | None = nat_context.metadata.cookies
|
|
52
61
|
conversation_id: str | None = nat_context.conversation_id
|
|
53
62
|
|
|
63
|
+
# Access the request body data - can be any valid JSON type
|
|
64
|
+
request_body_data: JsonValue = request_body.root
|
|
65
|
+
|
|
54
66
|
return (f"Method: {method}, "
|
|
55
67
|
f"URL Path: {url_path}, "
|
|
56
68
|
f"URL Scheme: {url_scheme}, "
|
|
@@ -60,7 +72,8 @@ async def current_request_attributes(config: RequestAttributesTool, builder: Bui
|
|
|
60
72
|
f"Client Host: {client_host}, "
|
|
61
73
|
f"Client Port: {client_port}, "
|
|
62
74
|
f"Cookies: {cookies}, "
|
|
63
|
-
f"Conversation Id: {conversation_id}"
|
|
75
|
+
f"Conversation Id: {conversation_id}, "
|
|
76
|
+
f"Request Body: {request_body_data}")
|
|
64
77
|
|
|
65
78
|
yield FunctionInfo.from_fn(_get_request_attributes,
|
|
66
79
|
description="Returns the acquired user defined request attributes.")
|