1+ import os
12from abc import ABC , abstractmethod
23from contextlib import asynccontextmanager
3- from typing import Any , AsyncContextManager , AsyncGenerator , Literal , cast
4+ from typing import Any , AsyncGenerator , Literal , cast
45
56import async_lru
67import structlog .stdlib
910
1011import chz
1112from alcatraz .clusters .local import BaseAlcatrazCluster , ClusterConfig , LocalConfig
12- from nanoeval_alcatraz .task_to_alcatraz_config import task_to_alcatraz_config
1313from alcatraz .utils .cmds import run_command_streaming
1414from nanoeval .solvers .computer_tasks .code_execution_interface import (
1515 ComputerConfiguration ,
16+ ComputerInterface ,
1617 ComputerRuntime ,
1718 ExecutionResult ,
1819 JupyterComputerInterface ,
1920 JupyterExecutionResult ,
2021)
22+ from nanoeval_alcatraz .task_to_alcatraz_config import (
23+ SUPPORTED_NETWORK_MODES ,
24+ task_to_alcatraz_config ,
25+ )
2126
2227logger = structlog .stdlib .get_logger (component = __name__ )
2328
29+ ALCATRAZ_TIMEOUT = int (os .getenv ("ALCATRAZ_TIMEOUT" , 120 ))
30+
2431
2532class Python3ExceptionDict (BaseModel ):
2633 """A pydantic model for serializing a Python 3.x exception.
@@ -38,10 +45,14 @@ class Python3ExceptionDict(BaseModel):
3845 notes : list [str ]
3946
4047
41- class BaseAlcatrazComputerInterface (JupyterComputerInterface , ABC ):
48+ class BaseAlcatrazComputerInterfaceNoJupyter (ComputerInterface , ABC ):
49+ """
50+ Override this class to create a custom AlcatrazComputerInterface without Jupyter support.
51+ """
52+
4253 @property
4354 @abstractmethod
44- def cluster (self ) -> BaseAlcatrazCluster :
55+ def _cluster (self ) -> BaseAlcatrazCluster :
4556 pass
4657
4758 async def disable_internet (self ) -> None :
@@ -52,29 +63,44 @@ async def disable_internet(self) -> None:
5263
5364 # Verify
5465 logger .info ("Post-setup network access disabled" )
55- logger .info ("Verified network access successfully disabled" )
66+ try :
67+ from alcatraz .utils .network import assert_internet_disabled # type: ignore
68+
69+ await assert_internet_disabled (self ._cluster )
70+ logger .info ("Verified network access successfully disabled" )
71+ except ImportError :
72+ pass
5673
5774 async def upload (self , file : bytes , destination : str ) -> None :
58- return await self .cluster .upload (file , destination )
75+ return await self ._cluster .upload (file , destination )
5976
6077 async def download (self , file : str ) -> bytes :
61- return await self .cluster .download (file )
78+ return await self ._cluster .download (file )
6279
63- async def send_shell_command (self , cmd : str , idempotent : bool = False ) -> ExecutionResult :
64- res = await run_command_streaming (self .cluster , cmd )
80+ async def send_shell_command (self , cmd : str , * , idempotent : bool = False ) -> ExecutionResult :
81+ res = await run_command_streaming (self ._cluster , cmd )
6582 return ExecutionResult (output = res ["result" ], exit_code = res ["exit_code" ])
6683
6784 async def fetch_container_names (self ) -> list [str ]:
68- return await self .cluster .fetch_container_names ()
85+ return await self ._cluster .fetch_container_names ()
6986
7087 async def stop (self ) -> None :
71- await self .cluster ._stop ()
88+ await self ._cluster ._stop ()
89+
90+
91+ @chz .chz
92+ class BaseAlcatrazComputerInterface (
93+ BaseAlcatrazComputerInterfaceNoJupyter , JupyterComputerInterface
94+ ):
95+ """
96+ Override this class to add Jupyter-specific functionality to the AlcatrazComputerInterface.
97+ """
7298
7399 @override
74- async def execute (self , code : str , timeout : int = 120 ) -> JupyterExecutionResult :
75- await self ._start_cluster_once ()
100+ async def execute (self , code : str , timeout : int = ALCATRAZ_TIMEOUT ) -> JupyterExecutionResult :
101+ await self ._start_jupyter_kernel_once ()
76102
77- messages = await self .cluster .send_kernel_command (code , timeout = timeout )
103+ messages = await self ._cluster .send_kernel_command (code , timeout = timeout )
78104
79105 # Parse the messages into a final execution result
80106 # TODO(kevinliu) - this may not be a perfect parsing, but it should only really be used for setup and grade so hopefully it's good enough
@@ -108,42 +134,89 @@ async def execute(self, code: str, timeout: int = 120) -> JupyterExecutionResult
108134 )
109135
110136 @async_lru .alru_cache (maxsize = 1 )
111- async def _start_cluster_once (self ) -> None :
112- if not await self .cluster .is_kernel_started ():
113- await self .cluster .create_kernel_on_machine ()
137+ async def _start_jupyter_kernel_once (self ) -> None :
138+ if not await self ._cluster .is_kernel_started ():
139+ await self ._cluster .create_kernel_on_machine ()
114140
115141
116142@chz .chz
117- class AlcatrazComputerInterface ( BaseAlcatrazComputerInterface ):
143+ class AlcatrazComputerInterfaceNoJupyter ( BaseAlcatrazComputerInterfaceNoJupyter ):
118144 cluster_value : BaseAlcatrazCluster
119145
120146 @property
121- def cluster (self ) -> BaseAlcatrazCluster :
147+ def _cluster (self ) -> BaseAlcatrazCluster :
122148 return self .cluster_value
123149
124150
125151@chz .chz
126- class AlcatrazComputerRuntime (ComputerRuntime ):
152+ class AlcatrazComputerInterface (AlcatrazComputerInterfaceNoJupyter , BaseAlcatrazComputerInterface ):
153+ pass
154+
155+
156+ @chz .chz
157+ class AlcatrazComputerRuntimeNoJupyter (ComputerRuntime ):
127158 env : ClusterConfig = chz .field (default_factory = LocalConfig )
128159
160+ async def _do_runtime_setup (
161+ self , task : ComputerConfiguration , computer : ComputerInterface
162+ ) -> None :
163+ assert isinstance (computer , BaseAlcatrazComputerInterfaceNoJupyter )
164+ # Alcatraz must do this dynamically since it doesn't have the ability to remove
165+ # a container's internet access at creation time.
166+ # Other runtimes should do this by configuring the container object itself.
167+ if not task .allow_internet :
168+ logger .info ("Disabling internet (since allow_internet is False)" )
169+ await computer .disable_internet ()
170+
171+ if task .network_mode not in SUPPORTED_NETWORK_MODES :
172+ raise ValueError (
173+ f"The { task .network_mode } network mode is not supported on Alcatraz, and will never be. Please change it, or stop using Alcatraz."
174+ )
175+
129176 @asynccontextmanager
130- async def run (
177+ async def _start_computer (
131178 self , task : ComputerConfiguration
132- ) -> AsyncGenerator [AlcatrazComputerInterface , None ]:
133- async with task_to_alcatraz_config (task , self .env ).build () as cluster :
134- computer = AlcatrazComputerInterface (cluster_value = cluster )
179+ ) -> AsyncGenerator [AlcatrazComputerInterfaceNoJupyter , None ]:
180+ async with task_to_alcatraz_config (task , self .env ).build () as _cluster :
181+ computer = AlcatrazComputerInterfaceNoJupyter (cluster_value = _cluster )
135182 yield computer
136183
137- @override
184+
185+ @chz .chz
186+ class AlcatrazComputerRuntime (ComputerRuntime ):
187+ env : ClusterConfig = chz .field (default_factory = LocalConfig )
188+
138189 async def _do_runtime_setup (
139- self , task : ComputerConfiguration , computer : AlcatrazComputerInterface
190+ self , task : ComputerConfiguration , computer : ComputerInterface
140191 ) -> None :
141- """No-op, we don't use this but need to implement the abstract method."""
142- return
192+ assert isinstance (computer , BaseAlcatrazComputerInterface )
193+
194+ # Run a jupyter command; this will force Jupyter to start up and be installed.
195+ # This is an Alcatraz only feature. It must be done before Internet access is disabled, because
196+ # Alcatraz supports live-installing Jupyter.
197+ # TODO(kevinliu) we should catalog which evals rely on this and remove it.
198+ logger .info ("Running initial Jupyter command to ensure Jupyter is installed" )
199+ await computer .execute ("print('hi')" )
200+ logger .info ("Jupyter working!" )
201+
202+ # Alcatraz must do this dynamically since it doesn't have the ability to remove
203+ # a container's internet access at creation time.
204+ # Other runtimes should do this by configuring the container object itself.
205+ if not task .allow_internet :
206+ logger .info ("Disabling internet (since allow_internet is False)" )
207+ await computer .disable_internet ()
208+
209+ if task .network_mode not in SUPPORTED_NETWORK_MODES :
210+ raise ValueError (
211+ f"The { task .network_mode } network mode is not supported on Alcatraz, and will never be. Please change it, or stop using Alcatraz."
212+ )
143213
144- @override
145- def _start_computer (
214+ @asynccontextmanager
215+ async def _start_computer (
146216 self , task : ComputerConfiguration
147- ) -> AsyncContextManager [AlcatrazComputerInterface ]:
148- """No-op, we don't use this but need to implement the abstract method."""
149- return
217+ ) -> AsyncGenerator [AlcatrazComputerInterface , None ]:
218+ async with task_to_alcatraz_config (task , self .env ).build () as _cluster :
219+ computer = AlcatrazComputerInterface (cluster_value = _cluster )
220+ yield computer
221+
222+
0 commit comments