nvidia-nat 1.3.0a20250910__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.
Files changed (213) hide show
  1. nat/agent/base.py +13 -8
  2. nat/agent/prompt_optimizer/prompt.py +68 -0
  3. nat/agent/prompt_optimizer/register.py +149 -0
  4. nat/agent/react_agent/agent.py +6 -5
  5. nat/agent/react_agent/register.py +49 -39
  6. nat/agent/reasoning_agent/reasoning_agent.py +17 -15
  7. nat/agent/register.py +2 -0
  8. nat/agent/responses_api_agent/__init__.py +14 -0
  9. nat/agent/responses_api_agent/register.py +126 -0
  10. nat/agent/rewoo_agent/agent.py +304 -117
  11. nat/agent/rewoo_agent/prompt.py +19 -22
  12. nat/agent/rewoo_agent/register.py +51 -38
  13. nat/agent/tool_calling_agent/agent.py +75 -17
  14. nat/agent/tool_calling_agent/register.py +46 -23
  15. nat/authentication/api_key/api_key_auth_provider.py +6 -11
  16. nat/authentication/api_key/api_key_auth_provider_config.py +8 -5
  17. nat/authentication/credential_validator/__init__.py +14 -0
  18. nat/authentication/credential_validator/bearer_token_validator.py +557 -0
  19. nat/authentication/http_basic_auth/http_basic_auth_provider.py +1 -1
  20. nat/authentication/interfaces.py +5 -2
  21. nat/authentication/oauth2/oauth2_auth_code_flow_provider.py +69 -36
  22. nat/authentication/oauth2/oauth2_auth_code_flow_provider_config.py +2 -1
  23. nat/authentication/oauth2/oauth2_resource_server_config.py +125 -0
  24. nat/builder/builder.py +55 -23
  25. nat/builder/component_utils.py +9 -5
  26. nat/builder/context.py +54 -15
  27. nat/builder/eval_builder.py +14 -9
  28. nat/builder/framework_enum.py +1 -0
  29. nat/builder/front_end.py +1 -1
  30. nat/builder/function.py +370 -0
  31. nat/builder/function_info.py +1 -1
  32. nat/builder/intermediate_step_manager.py +38 -2
  33. nat/builder/workflow.py +5 -0
  34. nat/builder/workflow_builder.py +306 -54
  35. nat/cli/cli_utils/config_override.py +1 -1
  36. nat/cli/commands/info/info.py +16 -6
  37. nat/cli/commands/mcp/__init__.py +14 -0
  38. nat/cli/commands/mcp/mcp.py +986 -0
  39. nat/cli/commands/optimize.py +90 -0
  40. nat/cli/commands/start.py +1 -1
  41. nat/cli/commands/workflow/templates/config.yml.j2 +14 -13
  42. nat/cli/commands/workflow/templates/register.py.j2 +2 -2
  43. nat/cli/commands/workflow/templates/workflow.py.j2 +35 -21
  44. nat/cli/commands/workflow/workflow_commands.py +60 -18
  45. nat/cli/entrypoint.py +15 -11
  46. nat/cli/main.py +3 -0
  47. nat/cli/register_workflow.py +38 -4
  48. nat/cli/type_registry.py +72 -1
  49. nat/control_flow/__init__.py +0 -0
  50. nat/control_flow/register.py +20 -0
  51. nat/control_flow/router_agent/__init__.py +0 -0
  52. nat/control_flow/router_agent/agent.py +329 -0
  53. nat/control_flow/router_agent/prompt.py +48 -0
  54. nat/control_flow/router_agent/register.py +91 -0
  55. nat/control_flow/sequential_executor.py +166 -0
  56. nat/data_models/agent.py +34 -0
  57. nat/data_models/api_server.py +199 -69
  58. nat/data_models/authentication.py +23 -9
  59. nat/data_models/common.py +47 -0
  60. nat/data_models/component.py +2 -0
  61. nat/data_models/component_ref.py +11 -0
  62. nat/data_models/config.py +41 -17
  63. nat/data_models/dataset_handler.py +4 -3
  64. nat/data_models/function.py +34 -0
  65. nat/data_models/function_dependencies.py +8 -0
  66. nat/data_models/intermediate_step.py +9 -1
  67. nat/data_models/llm.py +15 -1
  68. nat/data_models/openai_mcp.py +46 -0
  69. nat/data_models/optimizable.py +208 -0
  70. nat/data_models/optimizer.py +161 -0
  71. nat/data_models/span.py +41 -3
  72. nat/data_models/thinking_mixin.py +2 -2
  73. nat/embedder/azure_openai_embedder.py +2 -1
  74. nat/embedder/nim_embedder.py +3 -2
  75. nat/embedder/openai_embedder.py +3 -2
  76. nat/eval/config.py +1 -1
  77. nat/eval/dataset_handler/dataset_downloader.py +3 -2
  78. nat/eval/dataset_handler/dataset_filter.py +34 -2
  79. nat/eval/evaluate.py +10 -3
  80. nat/eval/evaluator/base_evaluator.py +1 -1
  81. nat/eval/rag_evaluator/evaluate.py +7 -4
  82. nat/eval/register.py +4 -0
  83. nat/eval/runtime_evaluator/__init__.py +14 -0
  84. nat/eval/runtime_evaluator/evaluate.py +123 -0
  85. nat/eval/runtime_evaluator/register.py +100 -0
  86. nat/eval/swe_bench_evaluator/evaluate.py +1 -1
  87. nat/eval/trajectory_evaluator/register.py +1 -1
  88. nat/eval/tunable_rag_evaluator/evaluate.py +1 -1
  89. nat/eval/usage_stats.py +2 -0
  90. nat/eval/utils/output_uploader.py +3 -2
  91. nat/eval/utils/weave_eval.py +17 -3
  92. nat/experimental/decorators/experimental_warning_decorator.py +27 -7
  93. nat/experimental/test_time_compute/functions/execute_score_select_function.py +1 -1
  94. nat/experimental/test_time_compute/functions/plan_select_execute_function.py +7 -3
  95. nat/experimental/test_time_compute/functions/ttc_tool_orchestration_function.py +1 -1
  96. nat/experimental/test_time_compute/functions/ttc_tool_wrapper_function.py +3 -3
  97. nat/experimental/test_time_compute/models/strategy_base.py +2 -2
  98. nat/experimental/test_time_compute/selection/llm_based_output_merging_selector.py +1 -1
  99. nat/front_ends/console/authentication_flow_handler.py +82 -30
  100. nat/front_ends/console/console_front_end_plugin.py +19 -7
  101. nat/front_ends/fastapi/auth_flow_handlers/http_flow_handler.py +1 -1
  102. nat/front_ends/fastapi/auth_flow_handlers/websocket_flow_handler.py +52 -17
  103. nat/front_ends/fastapi/dask_client_mixin.py +65 -0
  104. nat/front_ends/fastapi/fastapi_front_end_config.py +25 -3
  105. nat/front_ends/fastapi/fastapi_front_end_plugin.py +140 -3
  106. nat/front_ends/fastapi/fastapi_front_end_plugin_worker.py +445 -265
  107. nat/front_ends/fastapi/job_store.py +518 -99
  108. nat/front_ends/fastapi/main.py +11 -19
  109. nat/front_ends/fastapi/message_handler.py +69 -44
  110. nat/front_ends/fastapi/message_validator.py +8 -7
  111. nat/front_ends/fastapi/utils.py +57 -0
  112. nat/front_ends/mcp/introspection_token_verifier.py +73 -0
  113. nat/front_ends/mcp/mcp_front_end_config.py +71 -3
  114. nat/front_ends/mcp/mcp_front_end_plugin.py +85 -21
  115. nat/front_ends/mcp/mcp_front_end_plugin_worker.py +248 -29
  116. nat/front_ends/mcp/memory_profiler.py +320 -0
  117. nat/front_ends/mcp/tool_converter.py +78 -25
  118. nat/front_ends/simple_base/simple_front_end_plugin_base.py +3 -1
  119. nat/llm/aws_bedrock_llm.py +21 -8
  120. nat/llm/azure_openai_llm.py +14 -5
  121. nat/llm/litellm_llm.py +80 -0
  122. nat/llm/nim_llm.py +23 -9
  123. nat/llm/openai_llm.py +19 -7
  124. nat/llm/register.py +4 -0
  125. nat/llm/utils/thinking.py +1 -1
  126. nat/observability/exporter/base_exporter.py +1 -1
  127. nat/observability/exporter/processing_exporter.py +29 -55
  128. nat/observability/exporter/span_exporter.py +43 -15
  129. nat/observability/exporter_manager.py +2 -2
  130. nat/observability/mixin/redaction_config_mixin.py +5 -4
  131. nat/observability/mixin/tagging_config_mixin.py +26 -14
  132. nat/observability/mixin/type_introspection_mixin.py +420 -107
  133. nat/observability/processor/batching_processor.py +1 -1
  134. nat/observability/processor/processor.py +3 -0
  135. nat/observability/processor/redaction/__init__.py +24 -0
  136. nat/observability/processor/redaction/contextual_redaction_processor.py +125 -0
  137. nat/observability/processor/redaction/contextual_span_redaction_processor.py +66 -0
  138. nat/observability/processor/redaction/redaction_processor.py +177 -0
  139. nat/observability/processor/redaction/span_header_redaction_processor.py +92 -0
  140. nat/observability/processor/span_tagging_processor.py +21 -14
  141. nat/observability/register.py +16 -0
  142. nat/profiler/callbacks/langchain_callback_handler.py +32 -7
  143. nat/profiler/callbacks/llama_index_callback_handler.py +36 -2
  144. nat/profiler/callbacks/token_usage_base_model.py +2 -0
  145. nat/profiler/decorators/framework_wrapper.py +61 -9
  146. nat/profiler/decorators/function_tracking.py +35 -3
  147. nat/profiler/forecasting/models/linear_model.py +1 -1
  148. nat/profiler/forecasting/models/random_forest_regressor.py +1 -1
  149. nat/profiler/inference_optimization/bottleneck_analysis/nested_stack_analysis.py +1 -1
  150. nat/profiler/inference_optimization/experimental/prefix_span_analysis.py +1 -1
  151. nat/profiler/parameter_optimization/__init__.py +0 -0
  152. nat/profiler/parameter_optimization/optimizable_utils.py +93 -0
  153. nat/profiler/parameter_optimization/optimizer_runtime.py +67 -0
  154. nat/profiler/parameter_optimization/parameter_optimizer.py +189 -0
  155. nat/profiler/parameter_optimization/parameter_selection.py +107 -0
  156. nat/profiler/parameter_optimization/pareto_visualizer.py +460 -0
  157. nat/profiler/parameter_optimization/prompt_optimizer.py +384 -0
  158. nat/profiler/parameter_optimization/update_helpers.py +66 -0
  159. nat/profiler/utils.py +3 -1
  160. nat/registry_handlers/pypi/register_pypi.py +5 -3
  161. nat/registry_handlers/rest/register_rest.py +5 -3
  162. nat/retriever/milvus/retriever.py +1 -1
  163. nat/retriever/nemo_retriever/register.py +2 -1
  164. nat/runtime/loader.py +1 -1
  165. nat/runtime/runner.py +111 -6
  166. nat/runtime/session.py +49 -3
  167. nat/settings/global_settings.py +2 -2
  168. nat/tool/chat_completion.py +4 -1
  169. nat/tool/code_execution/code_sandbox.py +3 -6
  170. nat/tool/code_execution/local_sandbox/Dockerfile.sandbox +19 -32
  171. nat/tool/code_execution/local_sandbox/local_sandbox_server.py +6 -1
  172. nat/tool/code_execution/local_sandbox/sandbox.requirements.txt +2 -0
  173. nat/tool/code_execution/local_sandbox/start_local_sandbox.sh +10 -4
  174. nat/tool/datetime_tools.py +1 -1
  175. nat/tool/github_tools.py +450 -0
  176. nat/tool/memory_tools/add_memory_tool.py +3 -3
  177. nat/tool/memory_tools/delete_memory_tool.py +3 -4
  178. nat/tool/memory_tools/get_memory_tool.py +4 -4
  179. nat/tool/register.py +2 -7
  180. nat/tool/server_tools.py +15 -2
  181. nat/utils/__init__.py +76 -0
  182. nat/utils/callable_utils.py +70 -0
  183. nat/utils/data_models/schema_validator.py +1 -1
  184. nat/utils/decorators.py +210 -0
  185. nat/utils/exception_handlers/automatic_retries.py +278 -72
  186. nat/utils/io/yaml_tools.py +73 -3
  187. nat/utils/log_levels.py +25 -0
  188. nat/utils/responses_api.py +26 -0
  189. nat/utils/string_utils.py +16 -0
  190. nat/utils/type_converter.py +12 -3
  191. nat/utils/type_utils.py +6 -2
  192. nvidia_nat-1.4.0a20251112.dist-info/METADATA +197 -0
  193. {nvidia_nat-1.3.0a20250910.dist-info → nvidia_nat-1.4.0a20251112.dist-info}/RECORD +199 -165
  194. {nvidia_nat-1.3.0a20250910.dist-info → nvidia_nat-1.4.0a20251112.dist-info}/entry_points.txt +1 -0
  195. nat/cli/commands/info/list_mcp.py +0 -461
  196. nat/data_models/temperature_mixin.py +0 -43
  197. nat/data_models/top_p_mixin.py +0 -43
  198. nat/observability/processor/header_redaction_processor.py +0 -123
  199. nat/observability/processor/redaction_processor.py +0 -77
  200. nat/tool/code_execution/test_code_execution_sandbox.py +0 -414
  201. nat/tool/github_tools/create_github_commit.py +0 -133
  202. nat/tool/github_tools/create_github_issue.py +0 -87
  203. nat/tool/github_tools/create_github_pr.py +0 -106
  204. nat/tool/github_tools/get_github_file.py +0 -106
  205. nat/tool/github_tools/get_github_issue.py +0 -166
  206. nat/tool/github_tools/get_github_pr.py +0 -256
  207. nat/tool/github_tools/update_github_issue.py +0 -100
  208. nvidia_nat-1.3.0a20250910.dist-info/METADATA +0 -373
  209. /nat/{tool/github_tools → agent/prompt_optimizer}/__init__.py +0 -0
  210. {nvidia_nat-1.3.0a20250910.dist-info → nvidia_nat-1.4.0a20251112.dist-info}/WHEEL +0 -0
  211. {nvidia_nat-1.3.0a20250910.dist-info → nvidia_nat-1.4.0a20251112.dist-info}/licenses/LICENSE-3rd-party.txt +0 -0
  212. {nvidia_nat-1.3.0a20250910.dist-info → nvidia_nat-1.4.0a20251112.dist-info}/licenses/LICENSE.md +0 -0
  213. {nvidia_nat-1.3.0a20250910.dist-info → nvidia_nat-1.4.0a20251112.dist-info}/top_level.txt +0 -0
nat/runtime/runner.py CHANGED
@@ -15,11 +15,16 @@
15
15
 
16
16
  import logging
17
17
  import typing
18
+ import uuid
18
19
  from enum import Enum
19
20
 
20
21
  from nat.builder.context import Context
21
22
  from nat.builder.context import ContextState
22
23
  from nat.builder.function import Function
24
+ from nat.data_models.intermediate_step import IntermediateStepPayload
25
+ from nat.data_models.intermediate_step import IntermediateStepType
26
+ from nat.data_models.intermediate_step import StreamEventData
27
+ from nat.data_models.intermediate_step import TraceMetadata
23
28
  from nat.data_models.invocation_node import InvocationNode
24
29
  from nat.observability.exporter_manager import ExporterManager
25
30
  from nat.utils.reactive.subject import Subject
@@ -130,17 +135,60 @@ class Runner:
130
135
  if (self._state != RunnerState.INITIALIZED):
131
136
  raise ValueError("Cannot run the workflow without entering the context")
132
137
 
138
+ token_run_id = None
139
+ token_trace_id = None
133
140
  try:
134
141
  self._state = RunnerState.RUNNING
135
142
 
136
143
  if (not self._entry_fn.has_single_output):
137
144
  raise ValueError("Workflow does not support single output")
138
145
 
146
+ # Establish workflow run and trace identifiers
147
+ existing_run_id = self._context_state.workflow_run_id.get()
148
+ existing_trace_id = self._context_state.workflow_trace_id.get()
149
+
150
+ workflow_run_id = existing_run_id or str(uuid.uuid4())
151
+
152
+ workflow_trace_id = existing_trace_id or uuid.uuid4().int
153
+
154
+ token_run_id = self._context_state.workflow_run_id.set(workflow_run_id)
155
+ token_trace_id = self._context_state.workflow_trace_id.set(workflow_trace_id)
156
+
157
+ # Prepare workflow-level intermediate step identifiers
158
+ workflow_step_uuid = str(uuid.uuid4())
159
+ workflow_name = getattr(self._entry_fn, 'instance_name', None) or "workflow"
160
+
139
161
  async with self._exporter_manager.start(context_state=self._context_state):
140
- # Run the workflow
141
- result = await self._entry_fn.ainvoke(self._input_message, to_type=to_type)
162
+ # Emit WORKFLOW_START
163
+ start_metadata = TraceMetadata(
164
+ provided_metadata={
165
+ "workflow_run_id": workflow_run_id,
166
+ "workflow_trace_id": f"{workflow_trace_id:032x}",
167
+ "conversation_id": self._context_state.conversation_id.get(),
168
+ })
169
+ self._context.intermediate_step_manager.push_intermediate_step(
170
+ IntermediateStepPayload(UUID=workflow_step_uuid,
171
+ event_type=IntermediateStepType.WORKFLOW_START,
172
+ name=workflow_name,
173
+ metadata=start_metadata,
174
+ data=StreamEventData(input=self._input_message)))
175
+
176
+ result = await self._entry_fn.ainvoke(self._input_message, to_type=to_type) # type: ignore
177
+
178
+ # Emit WORKFLOW_END with output
179
+ end_metadata = TraceMetadata(
180
+ provided_metadata={
181
+ "workflow_run_id": workflow_run_id,
182
+ "workflow_trace_id": f"{workflow_trace_id:032x}",
183
+ "conversation_id": self._context_state.conversation_id.get(),
184
+ })
185
+ self._context.intermediate_step_manager.push_intermediate_step(
186
+ IntermediateStepPayload(UUID=workflow_step_uuid,
187
+ event_type=IntermediateStepType.WORKFLOW_END,
188
+ name=workflow_name,
189
+ metadata=end_metadata,
190
+ data=StreamEventData(output=result)))
142
191
 
143
- # Close the intermediate stream
144
192
  event_stream = self._context_state.event_stream.get()
145
193
  if event_stream:
146
194
  event_stream.on_complete()
@@ -154,25 +202,78 @@ class Runner:
154
202
  if event_stream:
155
203
  event_stream.on_complete()
156
204
  self._state = RunnerState.FAILED
157
-
158
205
  raise
206
+ finally:
207
+ if token_run_id is not None:
208
+ self._context_state.workflow_run_id.reset(token_run_id)
209
+ if token_trace_id is not None:
210
+ self._context_state.workflow_trace_id.reset(token_trace_id)
159
211
 
160
212
  async def result_stream(self, to_type: type | None = None):
161
213
 
162
214
  if (self._state != RunnerState.INITIALIZED):
163
215
  raise ValueError("Cannot run the workflow without entering the context")
164
216
 
217
+ token_run_id = None
218
+ token_trace_id = None
165
219
  try:
166
220
  self._state = RunnerState.RUNNING
167
221
 
168
222
  if (not self._entry_fn.has_streaming_output):
169
223
  raise ValueError("Workflow does not support streaming output")
170
224
 
225
+ # Establish workflow run and trace identifiers
226
+ existing_run_id = self._context_state.workflow_run_id.get()
227
+ existing_trace_id = self._context_state.workflow_trace_id.get()
228
+
229
+ workflow_run_id = existing_run_id or str(uuid.uuid4())
230
+
231
+ workflow_trace_id = existing_trace_id or uuid.uuid4().int
232
+
233
+ token_run_id = self._context_state.workflow_run_id.set(workflow_run_id)
234
+ token_trace_id = self._context_state.workflow_trace_id.set(workflow_trace_id)
235
+
236
+ # Prepare workflow-level intermediate step identifiers
237
+ workflow_step_uuid = str(uuid.uuid4())
238
+ workflow_name = getattr(self._entry_fn, 'instance_name', None) or "workflow"
239
+
171
240
  # Run the workflow
172
241
  async with self._exporter_manager.start(context_state=self._context_state):
173
- async for m in self._entry_fn.astream(self._input_message, to_type=to_type):
242
+ # Emit WORKFLOW_START
243
+ start_metadata = TraceMetadata(
244
+ provided_metadata={
245
+ "workflow_run_id": workflow_run_id,
246
+ "workflow_trace_id": f"{workflow_trace_id:032x}",
247
+ "conversation_id": self._context_state.conversation_id.get(),
248
+ })
249
+ self._context.intermediate_step_manager.push_intermediate_step(
250
+ IntermediateStepPayload(UUID=workflow_step_uuid,
251
+ event_type=IntermediateStepType.WORKFLOW_START,
252
+ name=workflow_name,
253
+ metadata=start_metadata,
254
+ data=StreamEventData(input=self._input_message)))
255
+
256
+ # Collect preview of streaming results for the WORKFLOW_END event
257
+ output_preview = []
258
+
259
+ async for m in self._entry_fn.astream(self._input_message, to_type=to_type): # type: ignore
260
+ if len(output_preview) < 50:
261
+ output_preview.append(m)
174
262
  yield m
175
263
 
264
+ # Emit WORKFLOW_END
265
+ end_metadata = TraceMetadata(
266
+ provided_metadata={
267
+ "workflow_run_id": workflow_run_id,
268
+ "workflow_trace_id": f"{workflow_trace_id:032x}",
269
+ "conversation_id": self._context_state.conversation_id.get(),
270
+ })
271
+ self._context.intermediate_step_manager.push_intermediate_step(
272
+ IntermediateStepPayload(UUID=workflow_step_uuid,
273
+ event_type=IntermediateStepType.WORKFLOW_END,
274
+ name=workflow_name,
275
+ metadata=end_metadata,
276
+ data=StreamEventData(output=output_preview)))
176
277
  self._state = RunnerState.COMPLETED
177
278
 
178
279
  # Close the intermediate stream
@@ -186,8 +287,12 @@ class Runner:
186
287
  if event_stream:
187
288
  event_stream.on_complete()
188
289
  self._state = RunnerState.FAILED
189
-
190
290
  raise
291
+ finally:
292
+ if token_run_id is not None:
293
+ self._context_state.workflow_run_id.reset(token_run_id)
294
+ if token_trace_id is not None:
295
+ self._context_state.workflow_trace_id.reset(token_trace_id)
191
296
 
192
297
 
193
298
  # Compatibility aliases with previous releases
nat/runtime/session.py CHANGED
@@ -16,6 +16,7 @@
16
16
  import asyncio
17
17
  import contextvars
18
18
  import typing
19
+ import uuid
19
20
  from collections.abc import Awaitable
20
21
  from collections.abc import Callable
21
22
  from contextlib import asynccontextmanager
@@ -111,7 +112,7 @@ class SessionManager:
111
112
  token_user_authentication = self._context_state.user_auth_callback.set(user_authentication_callback)
112
113
 
113
114
  if isinstance(http_connection, WebSocket):
114
- self.set_metadata_from_websocket(user_message_id, conversation_id)
115
+ self.set_metadata_from_websocket(http_connection, user_message_id, conversation_id)
115
116
 
116
117
  if isinstance(http_connection, Request):
117
118
  self.set_metadata_from_http_request(http_connection)
@@ -161,11 +162,56 @@ class SessionManager:
161
162
  if request.headers.get("user-message-id"):
162
163
  self._context_state.user_message_id.set(request.headers["user-message-id"])
163
164
 
164
- def set_metadata_from_websocket(self, user_message_id: str | None, conversation_id: str | None) -> None:
165
+ # W3C Trace Context header: traceparent: 00-<trace-id>-<span-id>-<flags>
166
+ traceparent = request.headers.get("traceparent")
167
+ if traceparent:
168
+ try:
169
+ parts = traceparent.split("-")
170
+ if len(parts) >= 4:
171
+ trace_id_hex = parts[1]
172
+ if len(trace_id_hex) == 32:
173
+ trace_id_int = uuid.UUID(trace_id_hex).int
174
+ self._context_state.workflow_trace_id.set(trace_id_int)
175
+ except Exception:
176
+ pass
177
+
178
+ if not self._context_state.workflow_trace_id.get():
179
+ workflow_trace_id = request.headers.get("workflow-trace-id")
180
+ if workflow_trace_id:
181
+ try:
182
+ self._context_state.workflow_trace_id.set(uuid.UUID(workflow_trace_id).int)
183
+ except Exception:
184
+ pass
185
+
186
+ workflow_run_id = request.headers.get("workflow-run-id")
187
+ if workflow_run_id:
188
+ self._context_state.workflow_run_id.set(workflow_run_id)
189
+
190
+ def set_metadata_from_websocket(self,
191
+ websocket: WebSocket,
192
+ user_message_id: str | None,
193
+ conversation_id: str | None) -> None:
165
194
  """
166
- Extracts and sets user metadata for Websocket connections.
195
+ Extracts and sets user metadata for WebSocket connections.
167
196
  """
168
197
 
198
+ # Extract cookies from WebSocket headers (similar to HTTP request)
199
+ if websocket and hasattr(websocket, 'scope') and 'headers' in websocket.scope:
200
+ cookies = {}
201
+ for header_name, header_value in websocket.scope.get('headers', []):
202
+ if header_name == b'cookie':
203
+ cookie_header = header_value.decode('utf-8')
204
+ # Parse cookie header: "name1=value1; name2=value2"
205
+ for cookie in cookie_header.split(';'):
206
+ cookie = cookie.strip()
207
+ if '=' in cookie:
208
+ name, value = cookie.split('=', 1)
209
+ cookies[name.strip()] = value.strip()
210
+
211
+ # Set cookies in metadata (same as HTTP request)
212
+ self._context.metadata._request.cookies = cookies
213
+ self._context_state.metadata.set(self._context.metadata)
214
+
169
215
  if conversation_id is not None:
170
216
  self._context_state.conversation_id.set(conversation_id)
171
217
 
@@ -124,7 +124,7 @@ class Settings(HashableBaseModel):
124
124
  if (short_names[key.local_name] == 1):
125
125
  type_list.append((key.local_name, key.config_type))
126
126
 
127
- return typing.Union[tuple(typing.Annotated[x_type, Tag(x_id)] for x_id, x_type in type_list)]
127
+ return typing.Union[*tuple(typing.Annotated[x_type, Tag(x_id)] for x_id, x_type in type_list)]
128
128
 
129
129
  RegistryHandlerAnnotation = dict[
130
130
  str,
@@ -169,7 +169,7 @@ class Settings(HashableBaseModel):
169
169
  if (not os.path.exists(configuration_file)):
170
170
  loaded_config = {}
171
171
  else:
172
- with open(file_path, mode="r", encoding="utf-8") as f:
172
+ with open(file_path, encoding="utf-8") as f:
173
173
  try:
174
174
  loaded_config = json.load(f)
175
175
  except Exception as e:
@@ -63,7 +63,10 @@ async def register_chat_completion(config: ChatCompletionConfig, builder: Builde
63
63
  # Generate response using the LLM
64
64
  response = await llm.ainvoke(prompt)
65
65
 
66
- return response
66
+ if isinstance(response, str):
67
+ return response
68
+
69
+ return response.text()
67
70
 
68
71
  except Exception as e:
69
72
  # Fallback response if LLM call fails
@@ -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
- code_to_execute = textwrap.dedent("""
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
- # Use the base image with Python 3.10 and Flask
16
- FROM tiangolo/uwsgi-nginx-flask:python3.10
17
-
18
- # Install dependencies required for Lean 4 and other tools
19
- RUN apt-get update && \
20
- apt-get install -y curl git && \
21
- curl https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh -sSf | sh -s -- -y && \
22
- /root/.elan/bin/elan toolchain install leanprover/lean4:v4.12.0 && \
23
- /root/.elan/bin/elan default leanprover/lean4:v4.12.0 && \
24
- /root/.elan/bin/elan self update
25
-
26
- # Set environment variables to include Lean and elan/lake in the PATH
27
- ENV PATH="/root/.elan/bin:$PATH"
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
@@ -62,7 +62,7 @@ class CodeExecutionResponse(Response):
62
62
  super().__init__(status=status_code, mimetype="application/json", response=result.model_dump_json())
63
63
 
64
64
  @classmethod
65
- def with_error(cls, status_code: int, error_message: str) -> 'CodeExecutionResponse':
65
+ def with_error(cls, status_code: int, error_message: str) -> CodeExecutionResponse:
66
66
  return cls(status_code,
67
67
  CodeExecutionResult(process_status=CodeExecutionStatus.ERROR, stdout="", stderr=error_message))
68
68
 
@@ -194,5 +194,10 @@ def execute():
194
194
  return do_execute(request)
195
195
 
196
196
 
197
+ @app.route("/", methods=["GET"])
198
+ def status() -> tuple[dict[str, str], int]:
199
+ return ({"status": "ok"}, 200)
200
+
201
+
197
202
  if __name__ == '__main__':
198
203
  app.run(port=6000)
@@ -1,6 +1,8 @@
1
+ Flask==3.1
1
2
  numpy
2
3
  pandas
3
4
  scipy
4
5
  ipython
5
6
  plotly
6
7
  pydantic
8
+ pyuwsgi==2.0.*
@@ -19,7 +19,11 @@
19
19
 
20
20
  DOCKER_COMMAND=${DOCKER_COMMAND:-"docker"}
21
21
  SANDBOX_NAME=${1:-'local-sandbox'}
22
- NUM_THREADS=10
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} --build-arg="UWSGI_PROCESSES=$((${NUM_THREADS} * 10))" --build-arg="UWSGI_CHEAPER=${NUM_THREADS}" -f Dockerfile.sandbox .
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}
@@ -72,7 +72,7 @@ async def current_datetime(_config: CurrentTimeToolConfig, _builder: Builder):
72
72
  timezone_obj = _get_timezone_obj(headers)
73
73
 
74
74
  now = datetime.datetime.now(timezone_obj)
75
- now_machine_readable = now.strftime(("%Y-%m-%d %H:%M:%S %z"))
75
+ now_machine_readable = now.strftime("%Y-%m-%d %H:%M:%S %z")
76
76
 
77
77
  # Returns the current time in machine readable format with timezone offset.
78
78
  return f"The current time of day is {now_machine_readable}"