py-adtools 0.1.0__tar.gz → 0.1.2__tar.gz

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.

Potentially problematic release.


This version of py-adtools might be problematic. Click here for more details.

@@ -0,0 +1,213 @@
1
+ Metadata-Version: 2.4
2
+ Name: py-adtools
3
+ Version: 0.1.2
4
+ Summary: Useful tools for parsing and evaluating Python programs for algorithm design.
5
+ Home-page: https://github.com/RayZhhh/py-adtools
6
+ Author: Rui Zhang
7
+ Author-email: rzhang.cs@gmail.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Topic :: Scientific/Engineering
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: psutil
16
+ Dynamic: author
17
+ Dynamic: author-email
18
+ Dynamic: classifier
19
+ Dynamic: description
20
+ Dynamic: description-content-type
21
+ Dynamic: home-page
22
+ Dynamic: license-file
23
+ Dynamic: requires-dist
24
+ Dynamic: requires-python
25
+ Dynamic: summary
26
+
27
+ # Useful tools for parsing and evaluating Python programs for algorithm design
28
+
29
+ ------
30
+
31
+ > This repo aims to help develop more powerful [Large Language Models for Algorithm Design (LLM4AD)](https://github.com/Optima-CityU/llm4ad) applications.
32
+ >
33
+ > More tools will be provided soon.
34
+
35
+ ------
36
+
37
+ The figure demonstrates how a Python program is parsed into `PyScript`, `PyFunction`, `PyClass,` and `PyProgram` via `adtools`.
38
+
39
+ ![pycode](./assets/pycode.png)
40
+
41
+ ------
42
+
43
+ ## Installation
44
+
45
+ > [!TIP]
46
+ >
47
+ > It is recommended to use Python >= 3.10.
48
+
49
+ Run the following instructions to install adtools.
50
+
51
+ ```shell
52
+ pip install git+https://github.com/RayZhhh/py-adtools.git
53
+ ```
54
+
55
+ Or install via pip:
56
+
57
+ ```shell
58
+ pip install py-adtools
59
+ ```
60
+
61
+ ## Usage
62
+
63
+ ### Parser for a Python program
64
+
65
+ Parse your code (in string) into Python code instances, so that you can check each component and modify it.
66
+
67
+ ```python
68
+ from adtools import PyProgram
69
+
70
+ code = r'''
71
+ import ast, numba # This part will be parsed into PyScript
72
+ import numpy as np
73
+
74
+ @numba.jit() # This part will be parsed into PyFunction
75
+ def function(arg1, arg2=True):
76
+ if arg2:
77
+ return arg1 * 2
78
+ else:
79
+ return arg1 * 4
80
+
81
+ @some.decorators() # This part will be parsed into PyClass
82
+ class PythonClass(BaseClass):
83
+ class_var1 = 1 # This part will be parsed into PyScript
84
+ class_varb = 2 # and placed in PyClass.class_vars_and_code
85
+
86
+ def __init__(self, x): # This part will be parsed into PyFunction
87
+ self.x = x # and placed in PyClass.functions
88
+
89
+ def method1(self):
90
+ return self.x * 10
91
+
92
+ @some.decorators()
93
+ def method2(self, x, y):
94
+ return x + y + self.method1(x)
95
+
96
+ class InnerClass: # This part will be parsed into PyScript
97
+ def __init__(self): # and placed in PyClass.class_vars_and_code
98
+ ...
99
+
100
+ if __name__ == '__main__': # This part will be parsed into PyScript
101
+ res = function(1)
102
+ print(res)
103
+ res = PythonClass().method2(1, 2)
104
+ '''
105
+
106
+ p = PyProgram.from_text(code)
107
+ print(p)
108
+ print(f'-------------------------------------')
109
+ print(p.classes[0].functions[2].decorator)
110
+ print(f'-------------------------------------')
111
+ print(p.functions[0].name)
112
+ ```
113
+
114
+ ### Evaluate Python programs
115
+
116
+ Evaluate Python programs in a secure process to avoid the abortation of the main process. Two steps:
117
+
118
+ - Extend the `PyEvaluator` class and override the `evaluate_program` method.
119
+ - Evaluate the program (in str) by calling the `evaluate` (directly evaluate without executing in a sandbox process) or the `secure_evaluate` (evaluate in a sandbox process) methods.
120
+
121
+ ```python
122
+ import time
123
+ from typing import Dict, Callable, List, Any
124
+
125
+ from adtools import PyEvaluator
126
+
127
+
128
+ class SortAlgorithmEvaluator(PyEvaluator):
129
+ def evaluate_program(
130
+ self,
131
+ program_str: str,
132
+ callable_functions_dict: Dict[str, Callable] | None,
133
+ callable_functions_list: List[Callable] | None,
134
+ callable_classes_dict: Dict[str, Callable] | None,
135
+ callable_classes_list: List[Callable] | None,
136
+ **kwargs
137
+ ) -> Any | None:
138
+ """Evaluate a given sort algorithm program.
139
+ Args:
140
+ program_str : The raw program text.
141
+ callable_functions_dict: A dict maps function name to callable function.
142
+ callable_functions_list: A list of callable functions.
143
+ callable_classes_dict : A dict maps class name to callable class.
144
+ callable_classes_list : A list of callable classes.
145
+ Return:
146
+ Returns the evaluation result.
147
+ """
148
+ # Get the sort algorithm
149
+ sort_algo: Callable = callable_functions_dict['merge_sort']
150
+ # Test data
151
+ input = [10, 2, 4, 76, 19, 29, 3, 5, 1]
152
+ # Compute execution time
153
+ start = time.time()
154
+ res = sort_algo(input)
155
+ duration = time.time() - start
156
+ if res == sorted(input): # If the result is correct
157
+ return duration # Return the execution time as the score of the algorithm
158
+ else:
159
+ return None # Return None as the algorithm is incorrect
160
+
161
+
162
+ code_generated_by_llm = '''
163
+ def merge_sort(arr):
164
+ if len(arr) <= 1:
165
+ return arr
166
+
167
+ mid = len(arr) // 2
168
+ left = merge_sort(arr[:mid])
169
+ right = merge_sort(arr[mid:])
170
+
171
+ return merge(left, right)
172
+
173
+ def merge(left, right):
174
+ result = []
175
+ i = j = 0
176
+
177
+ while i < len(left) and j < len(right):
178
+ if left[i] < right[j]:
179
+ result.append(left[i])
180
+ i += 1
181
+ else:
182
+ result.append(right[j])
183
+ j += 1
184
+
185
+ result.extend(left[i:])
186
+ result.extend(right[j:])
187
+
188
+ return result
189
+ '''
190
+
191
+ harmful_code_generated_by_llm = '''
192
+ def merge_sort(arr):
193
+ while True:
194
+ pass
195
+ '''
196
+
197
+ if __name__ == '__main__':
198
+ evaluator = SortAlgorithmEvaluator()
199
+
200
+ # Evaluate
201
+ score = evaluator.evaluate(code_generated_by_llm)
202
+ print(f'Score: {score}')
203
+
204
+ # Secure evaluate (the evaluation is executed in a sandbox process)
205
+ score = evaluator.secure_evaluate(code_generated_by_llm, timeout_seconds=10)
206
+ print(f'Score: {score}')
207
+
208
+ # Evaluate a harmful code, the evaluation will be terminated within 10 seconds
209
+ # We will obtain a score of `None` due to the violation of time restriction
210
+ score = evaluator.secure_evaluate(harmful_code_generated_by_llm, timeout_seconds=10)
211
+ print(f'Score: {score}')
212
+ ```
213
+
@@ -0,0 +1,187 @@
1
+ # Useful tools for parsing and evaluating Python programs for algorithm design
2
+
3
+ ------
4
+
5
+ > This repo aims to help develop more powerful [Large Language Models for Algorithm Design (LLM4AD)](https://github.com/Optima-CityU/llm4ad) applications.
6
+ >
7
+ > More tools will be provided soon.
8
+
9
+ ------
10
+
11
+ The figure demonstrates how a Python program is parsed into `PyScript`, `PyFunction`, `PyClass,` and `PyProgram` via `adtools`.
12
+
13
+ ![pycode](./assets/pycode.png)
14
+
15
+ ------
16
+
17
+ ## Installation
18
+
19
+ > [!TIP]
20
+ >
21
+ > It is recommended to use Python >= 3.10.
22
+
23
+ Run the following instructions to install adtools.
24
+
25
+ ```shell
26
+ pip install git+https://github.com/RayZhhh/py-adtools.git
27
+ ```
28
+
29
+ Or install via pip:
30
+
31
+ ```shell
32
+ pip install py-adtools
33
+ ```
34
+
35
+ ## Usage
36
+
37
+ ### Parser for a Python program
38
+
39
+ Parse your code (in string) into Python code instances, so that you can check each component and modify it.
40
+
41
+ ```python
42
+ from adtools import PyProgram
43
+
44
+ code = r'''
45
+ import ast, numba # This part will be parsed into PyScript
46
+ import numpy as np
47
+
48
+ @numba.jit() # This part will be parsed into PyFunction
49
+ def function(arg1, arg2=True):
50
+ if arg2:
51
+ return arg1 * 2
52
+ else:
53
+ return arg1 * 4
54
+
55
+ @some.decorators() # This part will be parsed into PyClass
56
+ class PythonClass(BaseClass):
57
+ class_var1 = 1 # This part will be parsed into PyScript
58
+ class_varb = 2 # and placed in PyClass.class_vars_and_code
59
+
60
+ def __init__(self, x): # This part will be parsed into PyFunction
61
+ self.x = x # and placed in PyClass.functions
62
+
63
+ def method1(self):
64
+ return self.x * 10
65
+
66
+ @some.decorators()
67
+ def method2(self, x, y):
68
+ return x + y + self.method1(x)
69
+
70
+ class InnerClass: # This part will be parsed into PyScript
71
+ def __init__(self): # and placed in PyClass.class_vars_and_code
72
+ ...
73
+
74
+ if __name__ == '__main__': # This part will be parsed into PyScript
75
+ res = function(1)
76
+ print(res)
77
+ res = PythonClass().method2(1, 2)
78
+ '''
79
+
80
+ p = PyProgram.from_text(code)
81
+ print(p)
82
+ print(f'-------------------------------------')
83
+ print(p.classes[0].functions[2].decorator)
84
+ print(f'-------------------------------------')
85
+ print(p.functions[0].name)
86
+ ```
87
+
88
+ ### Evaluate Python programs
89
+
90
+ Evaluate Python programs in a secure process to avoid the abortation of the main process. Two steps:
91
+
92
+ - Extend the `PyEvaluator` class and override the `evaluate_program` method.
93
+ - Evaluate the program (in str) by calling the `evaluate` (directly evaluate without executing in a sandbox process) or the `secure_evaluate` (evaluate in a sandbox process) methods.
94
+
95
+ ```python
96
+ import time
97
+ from typing import Dict, Callable, List, Any
98
+
99
+ from adtools import PyEvaluator
100
+
101
+
102
+ class SortAlgorithmEvaluator(PyEvaluator):
103
+ def evaluate_program(
104
+ self,
105
+ program_str: str,
106
+ callable_functions_dict: Dict[str, Callable] | None,
107
+ callable_functions_list: List[Callable] | None,
108
+ callable_classes_dict: Dict[str, Callable] | None,
109
+ callable_classes_list: List[Callable] | None,
110
+ **kwargs
111
+ ) -> Any | None:
112
+ """Evaluate a given sort algorithm program.
113
+ Args:
114
+ program_str : The raw program text.
115
+ callable_functions_dict: A dict maps function name to callable function.
116
+ callable_functions_list: A list of callable functions.
117
+ callable_classes_dict : A dict maps class name to callable class.
118
+ callable_classes_list : A list of callable classes.
119
+ Return:
120
+ Returns the evaluation result.
121
+ """
122
+ # Get the sort algorithm
123
+ sort_algo: Callable = callable_functions_dict['merge_sort']
124
+ # Test data
125
+ input = [10, 2, 4, 76, 19, 29, 3, 5, 1]
126
+ # Compute execution time
127
+ start = time.time()
128
+ res = sort_algo(input)
129
+ duration = time.time() - start
130
+ if res == sorted(input): # If the result is correct
131
+ return duration # Return the execution time as the score of the algorithm
132
+ else:
133
+ return None # Return None as the algorithm is incorrect
134
+
135
+
136
+ code_generated_by_llm = '''
137
+ def merge_sort(arr):
138
+ if len(arr) <= 1:
139
+ return arr
140
+
141
+ mid = len(arr) // 2
142
+ left = merge_sort(arr[:mid])
143
+ right = merge_sort(arr[mid:])
144
+
145
+ return merge(left, right)
146
+
147
+ def merge(left, right):
148
+ result = []
149
+ i = j = 0
150
+
151
+ while i < len(left) and j < len(right):
152
+ if left[i] < right[j]:
153
+ result.append(left[i])
154
+ i += 1
155
+ else:
156
+ result.append(right[j])
157
+ j += 1
158
+
159
+ result.extend(left[i:])
160
+ result.extend(right[j:])
161
+
162
+ return result
163
+ '''
164
+
165
+ harmful_code_generated_by_llm = '''
166
+ def merge_sort(arr):
167
+ while True:
168
+ pass
169
+ '''
170
+
171
+ if __name__ == '__main__':
172
+ evaluator = SortAlgorithmEvaluator()
173
+
174
+ # Evaluate
175
+ score = evaluator.evaluate(code_generated_by_llm)
176
+ print(f'Score: {score}')
177
+
178
+ # Secure evaluate (the evaluation is executed in a sandbox process)
179
+ score = evaluator.secure_evaluate(code_generated_by_llm, timeout_seconds=10)
180
+ print(f'Score: {score}')
181
+
182
+ # Evaluate a harmful code, the evaluation will be terminated within 10 seconds
183
+ # We will obtain a score of `None` due to the violation of time restriction
184
+ score = evaluator.secure_evaluate(harmful_code_generated_by_llm, timeout_seconds=10)
185
+ print(f'Score: {score}')
186
+ ```
187
+
@@ -1 +1,2 @@
1
1
  from .py_code import PyScript, PyFunction, PyClass, PyProgram
2
+ from .evaluator import PyEvaluator
@@ -0,0 +1,177 @@
1
+ import multiprocessing
2
+ import os
3
+ import sys
4
+ import time
5
+ from abc import ABC, abstractmethod
6
+ from queue import Empty
7
+ from typing import Any, Literal, Dict, Callable, List
8
+ import psutil
9
+
10
+ from .py_code import PyProgram
11
+
12
+
13
+ class PyEvaluator(ABC):
14
+
15
+ def __init__(self, debug_mode: bool = False, *, exec_code: bool = True):
16
+ """Evaluator interface for evaluating the Python algorithm program.
17
+ Args:
18
+ debug_mode: Debug mode.
19
+ exec_code : Using 'exec()' to compile the code and provide the callable function.
20
+ """
21
+ self._debug_mode = debug_mode
22
+ self._exec_code = exec_code
23
+ self._JOIN_TIMEOUT_SECONDS = 5
24
+
25
+ @abstractmethod
26
+ def evaluate_program(
27
+ self,
28
+ program_str: str,
29
+ callable_functions_dict: Dict[str, Callable] | None,
30
+ callable_functions_list: List[Callable] | None,
31
+ callable_classes_dict: Dict[str, Callable] | None,
32
+ callable_classes_list: List[Callable] | None,
33
+ **kwargs
34
+ ) -> Any | None:
35
+ """Evaluate a given program.
36
+ Args:
37
+ program_str : The raw program text.
38
+ callable_functions_dict: A dict maps function name to callable function.
39
+ callable_functions_list: A list of callable functions.
40
+ callable_classes_dict : A dict maps class name to callable class.
41
+ callable_classes_list : A list of callable classes.
42
+ Return:
43
+ Returns the evaluation result.
44
+ """
45
+ raise NotImplementedError('Must provide an evaluator for a python program. '
46
+ 'Override this method in a subclass.')
47
+
48
+ def _kill_process_and_its_children(self, process: multiprocessing.Process):
49
+ # Find all children processes
50
+ try:
51
+ parent = psutil.Process(process.pid)
52
+ children_processes = parent.children(recursive=True)
53
+ except psutil.NoSuchProcess:
54
+ children_processes = []
55
+ # Terminate parent process
56
+ process.terminate()
57
+ process.join(timeout=self._JOIN_TIMEOUT_SECONDS)
58
+ if process.is_alive():
59
+ process.kill()
60
+ process.join()
61
+ # Kill all children processes
62
+ for child in children_processes:
63
+ if self._debug_mode:
64
+ print(f"Killing process {process.pid}'s children process {child.pid}")
65
+ child.terminate()
66
+
67
+ def evaluate(self, program_str: str, **kwargs):
68
+ try:
69
+ # Parse to program instance
70
+ program = PyProgram.from_text(program_str)
71
+ function_names = [f.name for f in program.functions]
72
+ class_names = [c.name for c in program.classes]
73
+ if self._exec_code:
74
+ # Compile the program, and maps the global func/var/class name to its address
75
+ all_globals_namespace = {}
76
+ # Execute the program, map func/var/class to global namespace
77
+ exec(program_str, all_globals_namespace)
78
+ # Get callable functions
79
+ callable_functions_list = [all_globals_namespace[f_name] for f_name in function_names]
80
+ callable_functions_dict = dict(zip(function_names, callable_functions_list))
81
+ # Get callable classes
82
+ callable_classes_list = [all_globals_namespace[c_name] for c_name in class_names]
83
+ callable_classes_dict = dict(zip(class_names, callable_classes_list))
84
+ else:
85
+ callable_functions_list = None
86
+ callable_functions_dict = None
87
+ callable_classes_list = None
88
+ callable_classes_dict = None
89
+
90
+ # Get evaluate result
91
+ res = self.evaluate_program(
92
+ program_str,
93
+ callable_functions_dict,
94
+ callable_functions_list,
95
+ callable_classes_dict,
96
+ callable_classes_list,
97
+ **kwargs
98
+ )
99
+ return res
100
+ except Exception as e:
101
+ if self._debug_mode:
102
+ print(e)
103
+ return None
104
+
105
+ def _evaluate_in_safe_process(
106
+ self,
107
+ program_str: str,
108
+ result_queue: multiprocessing.Queue,
109
+ redirect_to_devnull: bool,
110
+ **kwargs
111
+ ):
112
+ if redirect_to_devnull:
113
+ with open('/dev/null', 'w') as devnull:
114
+ os.dup2(devnull.fileno(), sys.stdout.fileno())
115
+ os.dup2(devnull.fileno(), sys.stderr.fileno())
116
+ res = self.evaluate(program_str, **kwargs)
117
+ result_queue.put(res)
118
+
119
+ def secure_evaluate(
120
+ self,
121
+ program: str | PyProgram,
122
+ timeout_seconds: int | float = None,
123
+ redirect_to_devnull: bool = True,
124
+ multiprocessing_start_method=Literal['auto', 'fork', 'spawn'],
125
+ **kwargs
126
+ ):
127
+ """
128
+ Args:
129
+ program: the program to be evaluated.
130
+ timeout_seconds: return 'None' if the execution time exceeds 'timeout_seconds'.
131
+ redirect_to_devnull: redirect any output to '/dev/null'.
132
+ multiprocessing_start_method: start a process using 'fork' or 'spawn'.
133
+ """
134
+ if multiprocessing_start_method == 'auto':
135
+ # Force MacOS and Linux use 'fork' to generate new process
136
+ if sys.platform.startswith('darwin') or sys.platform.startswith('linux'):
137
+ multiprocessing.set_start_method('fork', force=True)
138
+ elif multiprocessing_start_method == 'fork':
139
+ multiprocessing.set_start_method('fork', force=True)
140
+ else:
141
+ multiprocessing.set_start_method('spawn', force=True)
142
+
143
+ try:
144
+ # Start evaluation process
145
+ result_queue = multiprocessing.Queue()
146
+ process = multiprocessing.Process(
147
+ target=self._evaluate_in_safe_process,
148
+ args=(str(program), result_queue, redirect_to_devnull),
149
+ kwargs=kwargs,
150
+ )
151
+ process.start()
152
+
153
+ if timeout_seconds is not None:
154
+ try:
155
+ # Get the result in timeout seconds
156
+ result = result_queue.get(timeout=timeout_seconds)
157
+ # After getting the result, terminate/kill the process
158
+ self._kill_process_and_its_children(process)
159
+ except Empty:
160
+ # Timeout
161
+ if self._debug_mode:
162
+ print(f'DEBUG: the evaluation time exceeds {timeout_seconds}s.')
163
+ self._kill_process_and_its_children(process)
164
+ result = None
165
+ except Exception as e:
166
+ if self._debug_mode:
167
+ print(f'DEBUG: evaluation failed with exception:\n{e}')
168
+ self._kill_process_and_its_children(process)
169
+ result = None
170
+ else:
171
+ result = result_queue.get()
172
+ self._kill_process_and_its_children(process)
173
+ return result
174
+ except Exception as e:
175
+ if self._debug_mode:
176
+ print(e)
177
+ return None
@@ -44,7 +44,7 @@ class PyFunction:
44
44
  # Here, we assume the indentation is always four spaces.
45
45
  new_line = '\n' if self.body else ''
46
46
  function += f' """{self.docstring}"""{new_line}'
47
- # self.body is already indented.
47
+ # The self.body is already indented.
48
48
  function += self.body + '\n\n'
49
49
  return function
50
50
 
@@ -109,7 +109,7 @@ class PyClass:
109
109
  # Ensure there aren't leading & trailing new lines in `body`
110
110
  if name == 'body':
111
111
  value = value.strip('\n')
112
- # ensure there aren't leading & trailing quotes in `docstring`
112
+ # Ensure there aren't leading & trailing quotes in `docstring`
113
113
  if name == 'docstring' and value is not None:
114
114
  if '"""' in value:
115
115
  value = value.strip()
@@ -189,7 +189,7 @@ class _ProgramVisitor(ast.NodeVisitor):
189
189
  if has_decorators:
190
190
  # Find the minimum line number and retain the code above
191
191
  decorator_start_line = min(decorator.lineno for decorator in node.decorator_list)
192
- decorator = '\n'.join(self._codelines[decorator_start_line - 1: node.lineno - 1])
192
+ decorator = '\n'.join(self._codelines[decorator_start_line - 1: node.lineno - 1]).strip()
193
193
  # Update script end line
194
194
  script_end_line = decorator_start_line - 1
195
195
  else:
@@ -262,10 +262,10 @@ class _ProgramVisitor(ast.NodeVisitor):
262
262
  if has_decorators:
263
263
  # Find the minimum line number and retain the code above
264
264
  decorator_start_line = min(decorator.lineno for decorator in item.decorator_list)
265
- # Dedent decorator code for 4 spaces
265
+ # Dedent decorator code
266
266
  decorator = []
267
267
  for line in range(decorator_start_line - 1, item.lineno - 1):
268
- dedented_decorator = self._codelines[line][4:]
268
+ dedented_decorator = self._codelines[line].strip()
269
269
  decorator.append(dedented_decorator)
270
270
  decorator = '\n'.join(decorator)
271
271
  else:
@@ -0,0 +1,213 @@
1
+ Metadata-Version: 2.4
2
+ Name: py-adtools
3
+ Version: 0.1.2
4
+ Summary: Useful tools for parsing and evaluating Python programs for algorithm design.
5
+ Home-page: https://github.com/RayZhhh/py-adtools
6
+ Author: Rui Zhang
7
+ Author-email: rzhang.cs@gmail.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Topic :: Scientific/Engineering
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: psutil
16
+ Dynamic: author
17
+ Dynamic: author-email
18
+ Dynamic: classifier
19
+ Dynamic: description
20
+ Dynamic: description-content-type
21
+ Dynamic: home-page
22
+ Dynamic: license-file
23
+ Dynamic: requires-dist
24
+ Dynamic: requires-python
25
+ Dynamic: summary
26
+
27
+ # Useful tools for parsing and evaluating Python programs for algorithm design
28
+
29
+ ------
30
+
31
+ > This repo aims to help develop more powerful [Large Language Models for Algorithm Design (LLM4AD)](https://github.com/Optima-CityU/llm4ad) applications.
32
+ >
33
+ > More tools will be provided soon.
34
+
35
+ ------
36
+
37
+ The figure demonstrates how a Python program is parsed into `PyScript`, `PyFunction`, `PyClass,` and `PyProgram` via `adtools`.
38
+
39
+ ![pycode](./assets/pycode.png)
40
+
41
+ ------
42
+
43
+ ## Installation
44
+
45
+ > [!TIP]
46
+ >
47
+ > It is recommended to use Python >= 3.10.
48
+
49
+ Run the following instructions to install adtools.
50
+
51
+ ```shell
52
+ pip install git+https://github.com/RayZhhh/py-adtools.git
53
+ ```
54
+
55
+ Or install via pip:
56
+
57
+ ```shell
58
+ pip install py-adtools
59
+ ```
60
+
61
+ ## Usage
62
+
63
+ ### Parser for a Python program
64
+
65
+ Parse your code (in string) into Python code instances, so that you can check each component and modify it.
66
+
67
+ ```python
68
+ from adtools import PyProgram
69
+
70
+ code = r'''
71
+ import ast, numba # This part will be parsed into PyScript
72
+ import numpy as np
73
+
74
+ @numba.jit() # This part will be parsed into PyFunction
75
+ def function(arg1, arg2=True):
76
+ if arg2:
77
+ return arg1 * 2
78
+ else:
79
+ return arg1 * 4
80
+
81
+ @some.decorators() # This part will be parsed into PyClass
82
+ class PythonClass(BaseClass):
83
+ class_var1 = 1 # This part will be parsed into PyScript
84
+ class_varb = 2 # and placed in PyClass.class_vars_and_code
85
+
86
+ def __init__(self, x): # This part will be parsed into PyFunction
87
+ self.x = x # and placed in PyClass.functions
88
+
89
+ def method1(self):
90
+ return self.x * 10
91
+
92
+ @some.decorators()
93
+ def method2(self, x, y):
94
+ return x + y + self.method1(x)
95
+
96
+ class InnerClass: # This part will be parsed into PyScript
97
+ def __init__(self): # and placed in PyClass.class_vars_and_code
98
+ ...
99
+
100
+ if __name__ == '__main__': # This part will be parsed into PyScript
101
+ res = function(1)
102
+ print(res)
103
+ res = PythonClass().method2(1, 2)
104
+ '''
105
+
106
+ p = PyProgram.from_text(code)
107
+ print(p)
108
+ print(f'-------------------------------------')
109
+ print(p.classes[0].functions[2].decorator)
110
+ print(f'-------------------------------------')
111
+ print(p.functions[0].name)
112
+ ```
113
+
114
+ ### Evaluate Python programs
115
+
116
+ Evaluate Python programs in a secure process to avoid the abortation of the main process. Two steps:
117
+
118
+ - Extend the `PyEvaluator` class and override the `evaluate_program` method.
119
+ - Evaluate the program (in str) by calling the `evaluate` (directly evaluate without executing in a sandbox process) or the `secure_evaluate` (evaluate in a sandbox process) methods.
120
+
121
+ ```python
122
+ import time
123
+ from typing import Dict, Callable, List, Any
124
+
125
+ from adtools import PyEvaluator
126
+
127
+
128
+ class SortAlgorithmEvaluator(PyEvaluator):
129
+ def evaluate_program(
130
+ self,
131
+ program_str: str,
132
+ callable_functions_dict: Dict[str, Callable] | None,
133
+ callable_functions_list: List[Callable] | None,
134
+ callable_classes_dict: Dict[str, Callable] | None,
135
+ callable_classes_list: List[Callable] | None,
136
+ **kwargs
137
+ ) -> Any | None:
138
+ """Evaluate a given sort algorithm program.
139
+ Args:
140
+ program_str : The raw program text.
141
+ callable_functions_dict: A dict maps function name to callable function.
142
+ callable_functions_list: A list of callable functions.
143
+ callable_classes_dict : A dict maps class name to callable class.
144
+ callable_classes_list : A list of callable classes.
145
+ Return:
146
+ Returns the evaluation result.
147
+ """
148
+ # Get the sort algorithm
149
+ sort_algo: Callable = callable_functions_dict['merge_sort']
150
+ # Test data
151
+ input = [10, 2, 4, 76, 19, 29, 3, 5, 1]
152
+ # Compute execution time
153
+ start = time.time()
154
+ res = sort_algo(input)
155
+ duration = time.time() - start
156
+ if res == sorted(input): # If the result is correct
157
+ return duration # Return the execution time as the score of the algorithm
158
+ else:
159
+ return None # Return None as the algorithm is incorrect
160
+
161
+
162
+ code_generated_by_llm = '''
163
+ def merge_sort(arr):
164
+ if len(arr) <= 1:
165
+ return arr
166
+
167
+ mid = len(arr) // 2
168
+ left = merge_sort(arr[:mid])
169
+ right = merge_sort(arr[mid:])
170
+
171
+ return merge(left, right)
172
+
173
+ def merge(left, right):
174
+ result = []
175
+ i = j = 0
176
+
177
+ while i < len(left) and j < len(right):
178
+ if left[i] < right[j]:
179
+ result.append(left[i])
180
+ i += 1
181
+ else:
182
+ result.append(right[j])
183
+ j += 1
184
+
185
+ result.extend(left[i:])
186
+ result.extend(right[j:])
187
+
188
+ return result
189
+ '''
190
+
191
+ harmful_code_generated_by_llm = '''
192
+ def merge_sort(arr):
193
+ while True:
194
+ pass
195
+ '''
196
+
197
+ if __name__ == '__main__':
198
+ evaluator = SortAlgorithmEvaluator()
199
+
200
+ # Evaluate
201
+ score = evaluator.evaluate(code_generated_by_llm)
202
+ print(f'Score: {score}')
203
+
204
+ # Secure evaluate (the evaluation is executed in a sandbox process)
205
+ score = evaluator.secure_evaluate(code_generated_by_llm, timeout_seconds=10)
206
+ print(f'Score: {score}')
207
+
208
+ # Evaluate a harmful code, the evaluation will be terminated within 10 seconds
209
+ # We will obtain a score of `None` due to the violation of time restriction
210
+ score = evaluator.secure_evaluate(harmful_code_generated_by_llm, timeout_seconds=10)
211
+ print(f'Score: {score}')
212
+ ```
213
+
@@ -2,8 +2,10 @@ LICENSE
2
2
  README.md
3
3
  setup.py
4
4
  adtools/__init__.py
5
+ adtools/evaluator.py
5
6
  adtools/py_code.py
6
7
  py_adtools.egg-info/PKG-INFO
7
8
  py_adtools.egg-info/SOURCES.txt
8
9
  py_adtools.egg-info/dependency_links.txt
10
+ py_adtools.egg-info/requires.txt
9
11
  py_adtools.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ psutil
@@ -5,10 +5,10 @@ with open('README.md', 'r', encoding='utf-8') as fh:
5
5
 
6
6
  setup(
7
7
  name='py-adtools',
8
- version='0.1.0',
8
+ version='0.1.2',
9
9
  author='Rui Zhang',
10
10
  author_email='rzhang.cs@gmail.com',
11
- description='Useful tools for parsing Python programs for algorithm design.',
11
+ description='Useful tools for parsing and evaluating Python programs for algorithm design.',
12
12
  long_description=long_description,
13
13
  long_description_content_type='text/markdown',
14
14
  url='https://github.com/RayZhhh/py-adtools',
@@ -20,5 +20,5 @@ setup(
20
20
  'Topic :: Scientific/Engineering',
21
21
  ],
22
22
  python_requires='>=3.10',
23
- install_requires=[],
23
+ install_requires=['psutil'],
24
24
  )
py_adtools-0.1.0/PKG-INFO DELETED
@@ -1,89 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: py-adtools
3
- Version: 0.1.0
4
- Summary: Useful tools for parsing Python programs for algorithm design.
5
- Home-page: https://github.com/RayZhhh/py-adtools
6
- Author: Rui Zhang
7
- Author-email: rzhang.cs@gmail.com
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: Operating System :: OS Independent
10
- Classifier: Intended Audience :: Developers
11
- Classifier: Topic :: Scientific/Engineering
12
- Requires-Python: >=3.10
13
- Description-Content-Type: text/markdown
14
- License-File: LICENSE
15
- Dynamic: author
16
- Dynamic: author-email
17
- Dynamic: classifier
18
- Dynamic: description
19
- Dynamic: description-content-type
20
- Dynamic: home-page
21
- Dynamic: license-file
22
- Dynamic: requires-python
23
- Dynamic: summary
24
-
25
- # Useful tools for parsing Python programs for algorithm design
26
-
27
- ------
28
-
29
- > This repo aims to help develop more powerful [Large Language Models for Algorithm Design (LLM4AD)](https://github.com/Optima-CityU/llm4ad) applications.
30
- >
31
- > More tools will be provided soon.
32
-
33
- ------
34
-
35
- The figure demonstrates how a Python program is parsed into `PyScript`, `PyFunction`, `PyClass,` and `PyProgram` via `adtools`.
36
-
37
- ![pycode](./assets/pycode.png)
38
-
39
- ------
40
-
41
- ## Installation
42
-
43
- > [!TIP]
44
- >
45
- > It is recommended to use Python >= 3.10.
46
-
47
- Run the following instructions to install adtools.
48
-
49
- ```shell
50
- pip install git+https://github.com/RayZhhh/adtool.git
51
- ```
52
-
53
- ## Usage
54
-
55
- Parse your code (in string) into Python code instances.
56
-
57
- ```python
58
- from adtools import PyProgram
59
-
60
- code = r'''
61
- import ast
62
- import numpy as np
63
-
64
- def func():
65
- a = 5
66
- return a + a
67
-
68
- class A(B):
69
- a=1
70
-
71
- @yes()
72
- @deco()
73
- def __init__(self):
74
- pass
75
-
76
- def method(self):
77
- pass
78
-
79
- b=2
80
- '''
81
-
82
- p = PyProgram.from_text(code)
83
- print(p)
84
- print(f'-------------------------------------')
85
- print(p.classes[0].functions[0].decorator)
86
- print(f'-------------------------------------')
87
- print(p.functions[0].name)
88
- ```
89
-
@@ -1,65 +0,0 @@
1
- # Useful tools for parsing Python programs for algorithm design
2
-
3
- ------
4
-
5
- > This repo aims to help develop more powerful [Large Language Models for Algorithm Design (LLM4AD)](https://github.com/Optima-CityU/llm4ad) applications.
6
- >
7
- > More tools will be provided soon.
8
-
9
- ------
10
-
11
- The figure demonstrates how a Python program is parsed into `PyScript`, `PyFunction`, `PyClass,` and `PyProgram` via `adtools`.
12
-
13
- ![pycode](./assets/pycode.png)
14
-
15
- ------
16
-
17
- ## Installation
18
-
19
- > [!TIP]
20
- >
21
- > It is recommended to use Python >= 3.10.
22
-
23
- Run the following instructions to install adtools.
24
-
25
- ```shell
26
- pip install git+https://github.com/RayZhhh/adtool.git
27
- ```
28
-
29
- ## Usage
30
-
31
- Parse your code (in string) into Python code instances.
32
-
33
- ```python
34
- from adtools import PyProgram
35
-
36
- code = r'''
37
- import ast
38
- import numpy as np
39
-
40
- def func():
41
- a = 5
42
- return a + a
43
-
44
- class A(B):
45
- a=1
46
-
47
- @yes()
48
- @deco()
49
- def __init__(self):
50
- pass
51
-
52
- def method(self):
53
- pass
54
-
55
- b=2
56
- '''
57
-
58
- p = PyProgram.from_text(code)
59
- print(p)
60
- print(f'-------------------------------------')
61
- print(p.classes[0].functions[0].decorator)
62
- print(f'-------------------------------------')
63
- print(p.functions[0].name)
64
- ```
65
-
@@ -1,89 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: py-adtools
3
- Version: 0.1.0
4
- Summary: Useful tools for parsing Python programs for algorithm design.
5
- Home-page: https://github.com/RayZhhh/py-adtools
6
- Author: Rui Zhang
7
- Author-email: rzhang.cs@gmail.com
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: Operating System :: OS Independent
10
- Classifier: Intended Audience :: Developers
11
- Classifier: Topic :: Scientific/Engineering
12
- Requires-Python: >=3.10
13
- Description-Content-Type: text/markdown
14
- License-File: LICENSE
15
- Dynamic: author
16
- Dynamic: author-email
17
- Dynamic: classifier
18
- Dynamic: description
19
- Dynamic: description-content-type
20
- Dynamic: home-page
21
- Dynamic: license-file
22
- Dynamic: requires-python
23
- Dynamic: summary
24
-
25
- # Useful tools for parsing Python programs for algorithm design
26
-
27
- ------
28
-
29
- > This repo aims to help develop more powerful [Large Language Models for Algorithm Design (LLM4AD)](https://github.com/Optima-CityU/llm4ad) applications.
30
- >
31
- > More tools will be provided soon.
32
-
33
- ------
34
-
35
- The figure demonstrates how a Python program is parsed into `PyScript`, `PyFunction`, `PyClass,` and `PyProgram` via `adtools`.
36
-
37
- ![pycode](./assets/pycode.png)
38
-
39
- ------
40
-
41
- ## Installation
42
-
43
- > [!TIP]
44
- >
45
- > It is recommended to use Python >= 3.10.
46
-
47
- Run the following instructions to install adtools.
48
-
49
- ```shell
50
- pip install git+https://github.com/RayZhhh/adtool.git
51
- ```
52
-
53
- ## Usage
54
-
55
- Parse your code (in string) into Python code instances.
56
-
57
- ```python
58
- from adtools import PyProgram
59
-
60
- code = r'''
61
- import ast
62
- import numpy as np
63
-
64
- def func():
65
- a = 5
66
- return a + a
67
-
68
- class A(B):
69
- a=1
70
-
71
- @yes()
72
- @deco()
73
- def __init__(self):
74
- pass
75
-
76
- def method(self):
77
- pass
78
-
79
- b=2
80
- '''
81
-
82
- p = PyProgram.from_text(code)
83
- print(p)
84
- print(f'-------------------------------------')
85
- print(p.classes[0].functions[0].decorator)
86
- print(f'-------------------------------------')
87
- print(p.functions[0].name)
88
- ```
89
-
File without changes
File without changes