py-adtools 0.1.4__tar.gz → 0.1.8__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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: py-adtools
3
- Version: 0.1.4
3
+ Version: 0.1.8
4
4
  Summary: Useful tools for parsing and evaluating Python programs for LLM-based algorithm design.
5
5
  Home-page: https://github.com/RayZhhh/py-adtools
6
6
  Author: Rui Zhang
@@ -13,6 +13,7 @@ Requires-Python: >=3.10
13
13
  Description-Content-Type: text/markdown
14
14
  License-File: LICENSE
15
15
  Requires-Dist: psutil
16
+ Requires-Dist: openai
16
17
  Dynamic: author
17
18
  Dynamic: author-email
18
19
  Dynamic: classifier
@@ -8,10 +8,12 @@ Commercial use of this software or its derivatives requires prior written permis
8
8
  import multiprocessing
9
9
  import os
10
10
  import sys
11
+ import time
11
12
  from abc import ABC, abstractmethod
12
13
  from queue import Empty
13
- from typing import Any, Literal, Dict, Callable, List
14
+ from typing import Any, Literal, Dict, Callable, List, Tuple
14
15
  import psutil
16
+ import traceback
15
17
 
16
18
  from .py_code import PyProgram
17
19
 
@@ -21,6 +23,7 @@ class PyEvaluator(ABC):
21
23
  def __init__(
22
24
  self,
23
25
  exec_code: bool = True,
26
+ find_and_kill_children_evaluation_process: bool = False,
24
27
  debug_mode: bool = False,
25
28
  *,
26
29
  join_timeout_seconds: int = 10
@@ -32,11 +35,15 @@ class PyEvaluator(ABC):
32
35
  which will be passed to 'self.evaluate_program()'. Set this parameter to 'False' if you are going to
33
36
  evaluate a Python scripy. Note that if the parameter is set to 'False', the arguments 'callable_...'
34
37
  in 'self.evaluate_program()' will no longer be affective.
38
+ find_and_kill_children_evaluation_process: If using 'self.secure_evaluate', kill children processes
39
+ when they are terminated. Note that it is suggested to set to 'False' if the evaluation process
40
+ does not start new processes.
35
41
  debug_mode: Debug mode.
36
42
  join_timeout_seconds: Timeout in seconds to wait for the process to finish. Kill the process if timeout.
37
43
  """
38
44
  self.debug_mode = debug_mode
39
45
  self.exec_code = exec_code
46
+ self.find_and_kill_children_evaluation_process = find_and_kill_children_evaluation_process
40
47
  self.join_timeout_seconds = join_timeout_seconds
41
48
 
42
49
  @abstractmethod
@@ -65,11 +72,14 @@ class PyEvaluator(ABC):
65
72
  )
66
73
 
67
74
  def _kill_process_and_its_children(self, process: multiprocessing.Process):
68
- # Find all children processes
69
- try:
70
- parent = psutil.Process(process.pid)
71
- children_processes = parent.children(recursive=True)
72
- except psutil.NoSuchProcess:
75
+ if self.find_and_kill_children_evaluation_process:
76
+ # Find all children processes
77
+ try:
78
+ parent = psutil.Process(process.pid)
79
+ children_processes = parent.children(recursive=True)
80
+ except psutil.NoSuchProcess:
81
+ children_processes = []
82
+ else:
73
83
  children_processes = []
74
84
  # Terminate parent process
75
85
  process.terminate()
@@ -124,7 +134,7 @@ class PyEvaluator(ABC):
124
134
  return res
125
135
  except Exception as e:
126
136
  if self.debug_mode:
127
- print(e)
137
+ print(traceback.format_exc())
128
138
  return None
129
139
 
130
140
  def _evaluate_in_safe_process(
@@ -150,8 +160,9 @@ class PyEvaluator(ABC):
150
160
  timeout_seconds: int | float = None,
151
161
  redirect_to_devnull: bool = False,
152
162
  multiprocessing_start_method: Literal['default', 'auto', 'fork', 'spawn'] = 'auto',
163
+ get_evaluate_time=False,
153
164
  **kwargs
154
- ):
165
+ ) -> Any | Tuple[Any, float]:
155
166
  """Evaluate program in a new process. This enables timeout restriction and output redirection.
156
167
  Args:
157
168
  program: the program to be evaluated.
@@ -160,7 +171,11 @@ class PyEvaluator(ABC):
160
171
  multiprocessing_start_method: start a process using 'fork' or 'spawn'. If set to 'auto',
161
172
  the process will be started using 'fork' with Linux/macOS and 'spawn' with Windows.
162
173
  If set to 'default', there will be no changes to system default.
174
+ get_evaluate_time: get evaluation time for this program.
163
175
  **kwargs: additional keyword arguments to pass to 'evaluate_program'.
176
+ Returns:
177
+ Returns the evaluation results. If the 'get_evaluate_time' is True,
178
+ the return value will be (Results, Time).
164
179
  """
165
180
  if multiprocessing_start_method == 'auto':
166
181
  # Force macOS and Linux use 'fork' to generate new process
@@ -179,33 +194,43 @@ class PyEvaluator(ABC):
179
194
  args=(str(program), result_queue, redirect_to_devnull),
180
195
  kwargs=kwargs,
181
196
  )
197
+ evaluate_start_time = time.time()
182
198
  process.start()
183
199
 
184
200
  if timeout_seconds is not None:
185
201
  try:
186
202
  # Get the result in timeout seconds
187
203
  result = result_queue.get(timeout=timeout_seconds)
204
+ # Calculate the evaluate time
205
+ eval_time = time.time() - evaluate_start_time
188
206
  # After getting the result, terminate/kill the process
189
207
  self._kill_process_and_its_children(process)
190
208
  except Empty: # The queue is empty indicates a timeout
209
+ # Calculate the evaluate time
210
+ eval_time = time.time() - evaluate_start_time
191
211
  if self.debug_mode:
192
212
  print(f'DEBUG: the evaluation time exceeds {timeout_seconds}s.')
193
213
  # Terminate/kill all processes if timeout happens
194
214
  self._kill_process_and_its_children(process)
195
215
  result = None
196
216
  except Exception as e:
217
+ # Calculate the evaluate time
218
+ eval_time = time.time() - evaluate_start_time
197
219
  if self.debug_mode:
198
- print(f'DEBUG: evaluation failed with exception:\n{e}')
220
+ print(f'DEBUG: evaluation failed with exception:\n{traceback.format_exc()}')
199
221
  # Terminate/kill all processes if meet exceptions
200
222
  self._kill_process_and_its_children(process)
201
223
  result = None
202
224
  else:
203
225
  # If there is no timeout limit, wait execution to finish
204
226
  result = result_queue.get()
227
+ # Calculate the evaluate time
228
+ eval_time = time.time() - evaluate_start_time
205
229
  # Terminate/kill all processes after evaluation
206
230
  self._kill_process_and_its_children(process)
207
- return result
231
+
232
+ return (result, eval_time) if get_evaluate_time else result
208
233
  except Exception as e:
209
234
  if self.debug_mode:
210
- print(e)
235
+ print(traceback.format_exc())
211
236
  return None
@@ -62,21 +62,23 @@ class EvaluatorExecutorPool:
62
62
  program: the program to be evaluated.
63
63
  timeout_seconds: return 'None' if the execution time exceeds 'timeout_seconds'.
64
64
  redirect_to_devnull: redirect any output to '/dev/null'.
65
- multiprocessing_start_method: start a process using 'fork' or 'spawn'.
65
+ multiprocessing_start_method: start a process using 'fork' or 'spawn'. If set to 'auto',
66
+ the process will be started using 'fork' with Linux/macOS and 'spawn' with Windows.
67
+ If set to 'default', there will be no changes to system default.
68
+ return_time: get evaluation time for this program.
66
69
  **kwargs: additional keyword arguments to pass to 'evaluate_program'.
70
+ Returns:
71
+ Returns the evaluation results. If the 'get_evaluate_time' is True,
72
+ the return value will be (Results, Time).
67
73
  """
68
- start_time = time.time()
69
74
  future = self.pool.submit(
70
75
  self.evaluator.secure_evaluate,
71
76
  program,
72
77
  timeout_seconds,
73
78
  redirect_to_devnull,
74
79
  multiprocessing_start_method,
80
+ return_time,
75
81
  **kwargs
76
82
  )
77
83
  res = future.result()
78
- duration = time.time() - start_time
79
- if return_time:
80
- return res, duration
81
- else:
82
- return res
84
+ return res
@@ -0,0 +1,6 @@
1
+ try:
2
+ import openai
3
+ except ImportError:
4
+ raise ImportError('Python package "openai" is not installed.')
5
+
6
+ from .lm_base import LanguageModel
@@ -0,0 +1,39 @@
1
+ """
2
+ Copyright (c) 2025 Rui Zhang <rzhang.cs@gmail.com>
3
+
4
+ NOTICE: This code is under MIT license. This code is intended for academic/research purposes only.
5
+ Commercial use of this software or its derivatives requires prior written permission.
6
+ """
7
+
8
+ from abc import abstractmethod
9
+ from typing import List
10
+
11
+ import openai.types.chat
12
+
13
+
14
+ class LanguageModel:
15
+ """Base class for language model interface."""
16
+
17
+ @abstractmethod
18
+ def chat_completion(
19
+ self,
20
+ message: str | List[openai.types.chat.ChatCompletionMessageParam],
21
+ max_tokens: int,
22
+ timeout_seconds: float,
23
+ *args,
24
+ **kwargs
25
+ ):
26
+ """Send a chat completion query with OpenAI format to the vLLM server. Return the response content.
27
+ Args:
28
+ message: The message in str or openai format.
29
+ max_tokens: The maximum number of tokens to generate.
30
+ timeout_seconds: The timeout seconds.
31
+ """
32
+ pass
33
+
34
+ def close(self):
35
+ """Release resources (if necessary)."""
36
+ pass
37
+
38
+ def __del__(self):
39
+ self.close()
@@ -0,0 +1,72 @@
1
+ """
2
+ Copyright (c) 2025 Rui Zhang <rzhang.cs@gmail.com>
3
+
4
+ NOTICE: This code is under MIT license. This code is intended for academic/research purposes only.
5
+ Commercial use of this software or its derivatives requires prior written permission.
6
+ """
7
+
8
+ import os
9
+ from typing import List
10
+
11
+ import openai.types.chat
12
+
13
+ from .lm_base import LanguageModel
14
+
15
+
16
+ class OpenAIAPI(LanguageModel):
17
+ def __init__(
18
+ self,
19
+ model: str,
20
+ base_url: str = None,
21
+ api_key: str = None,
22
+ **openai_init_kwargs
23
+ ):
24
+ super().__init__()
25
+ # If base_url is set to None, find 'OPENAI_BASE_URL' in environment variables
26
+ if base_url is None:
27
+ if 'OPENAI_BASE_URL' not in os.environ:
28
+ raise RuntimeError('If "base_url" is None, the environment variable OPENAI_BASE_URL must be set.')
29
+ else:
30
+ base_url = os.environ['OPENAI_BASE_URL']
31
+
32
+ # If api_key is set to None, find 'OPENAI_API_KEY' in environment variables
33
+ if api_key is None:
34
+ if 'OPENAI_API_KEY' not in os.environ:
35
+ raise RuntimeError('If "api_key" is None, OPENAI_API_KEY must be set.')
36
+ else:
37
+ api_key = os.environ['OPENAI_API_KEY']
38
+
39
+ self._model = model
40
+ self._client = openai.OpenAI(
41
+ api_key=api_key,
42
+ base_url=base_url,
43
+ **openai_init_kwargs
44
+ )
45
+
46
+ def chat_completion(
47
+ self,
48
+ message: str | List[openai.types.chat.ChatCompletionMessageParam],
49
+ max_tokens: int,
50
+ timeout_seconds: float,
51
+ *args,
52
+ **kwargs
53
+ ):
54
+ """Send a chat completion query with OpenAI format to the vLLM server. Return the response content.
55
+ Args:
56
+ message: The message in str or openai format.
57
+ max_tokens: The maximum number of tokens to generate.
58
+ timeout_seconds: The timeout seconds.
59
+ """
60
+ if isinstance(message, str):
61
+ message = [{'role': 'user', 'content': message.strip()}]
62
+
63
+ response = self._client.chat.completions.create(
64
+ model=self._model,
65
+ messages=message,
66
+ stream=False,
67
+ max_tokens=max_tokens,
68
+ timeout=timeout_seconds,
69
+ *args,
70
+ **kwargs,
71
+ )
72
+ return response.choices[0].message.content
@@ -5,103 +5,27 @@ NOTICE: This code is under MIT license. This code is intended for academic/resea
5
5
  Commercial use of this software or its derivatives requires prior written permission.
6
6
  """
7
7
 
8
- from abc import abstractmethod
8
+ try:
9
+ import vllm
10
+ except ImportError:
11
+ raise ImportError('Python package "vllm" is not installed.')
12
+
13
+ try:
14
+ import requests
15
+ except ImportError:
16
+ raise ImportError('Python package "requests" is not installed.')
17
+
9
18
  from typing import Optional, List, Literal, Dict, Any
10
19
  import os
11
20
  import subprocess
12
21
  import sys
13
22
  from pathlib import Path
14
23
  import psutil
15
- import requests
16
24
  import time
17
25
 
18
26
  import openai.types.chat
19
27
 
20
- __all__ = ['LanguageModel', 'OpenAIAPI', 'VLLMServer']
21
-
22
-
23
- class LanguageModel:
24
- """Base class for language model interface."""
25
-
26
- @abstractmethod
27
- def chat_completion(
28
- self,
29
- message: str | List[openai.types.chat.ChatCompletionMessageParam],
30
- max_tokens: int,
31
- timeout_seconds: float,
32
- *args,
33
- **kwargs
34
- ):
35
- """Send a chat completion query with OpenAI format to the vLLM server. Return the response content.
36
- Args:
37
- message: The message in str or openai format.
38
- max_tokens: The maximum number of tokens to generate.
39
- timeout_seconds: The timeout seconds.
40
- """
41
- pass
42
-
43
- def close(self):
44
- """Release resources (if necessary)."""
45
- pass
46
-
47
-
48
- class OpenAIAPI(LanguageModel):
49
- def __init__(
50
- self,
51
- model: str,
52
- base_url: str = None,
53
- api_key: str = None,
54
- **openai_init_kwargs
55
- ):
56
- super().__init__()
57
- # If base_url is set to None, find 'OPENAI_BASE_URL' in environment variables
58
- if base_url is None:
59
- if 'OPENAI_BASE_URL' not in os.environ:
60
- raise RuntimeError('If "base_url" is None, the environment variable OPENAI_BASE_URL must be set.')
61
- else:
62
- base_url = os.environ['OPENAI_BASE_URL']
63
-
64
- # If api_key is set to None, find 'OPENAI_API_KEY' in environment variables
65
- if api_key is None:
66
- if 'OPENAI_API_KEY' not in os.environ:
67
- raise RuntimeError('If "api_key" is None, OPENAI_API_KEY must be set.')
68
- else:
69
- api_key = os.environ['OPENAI_API_KEY']
70
-
71
- self._model = model
72
- self._client = openai.OpenAI(
73
- api_key=api_key,
74
- base_url=base_url,
75
- **openai_init_kwargs
76
- )
77
-
78
- def chat_completion(
79
- self,
80
- message: str | List[openai.types.chat.ChatCompletionMessageParam],
81
- max_tokens: int,
82
- timeout_seconds: float,
83
- *args,
84
- **kwargs
85
- ):
86
- """Send a chat completion query with OpenAI format to the vLLM server. Return the response content.
87
- Args:
88
- message: The message in str or openai format.
89
- max_tokens: The maximum number of tokens to generate.
90
- timeout_seconds: The timeout seconds.
91
- """
92
- if isinstance(message, str):
93
- message = [{'role': 'user', 'content': message.strip()}]
94
-
95
- response = self._client.chat.completions.create(
96
- model=self._model,
97
- messages=message,
98
- stream=False,
99
- max_tokens=max_tokens,
100
- timeout=timeout_seconds,
101
- *args,
102
- **kwargs,
103
- )
104
- return response.choices[0].message.content
28
+ from .lm_base import LanguageModel
105
29
 
106
30
 
107
31
  def _print_cmd_list(cmd_list, gpus, host, port):
@@ -115,24 +39,26 @@ def _print_cmd_list(cmd_list, gpus, host, port):
115
39
  print('=' * 80 + '\n', flush=True)
116
40
 
117
41
 
118
- class VLLMServer:
119
- def __init__(self,
120
- model_path: str,
121
- port: int,
122
- gpus: int | list[int],
123
- tokenizer_path: Optional[str] = None,
124
- max_model_len: int = 16384,
125
- max_lora_rank: Optional[int] = None,
126
- host: str = '0.0.0.0',
127
- mem_util: float = 0.85,
128
- deploy_timeout_seconds: int = 600,
129
- enforce_eager: bool = False,
130
- vllm_log_level: Literal['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'] = 'INFO',
131
- silent_mode: bool = False,
132
- env_variable_dict: Optional[Dict[str, str]] = None,
133
- vllm_serve_args: Optional[List[str]] = None,
134
- vllm_serve_kwargs: Optional[Dict[str, str]] = None,
135
- chat_template_kwargs: Optional[Dict[str, Any]] = None):
42
+ class VLLMServer(LanguageModel):
43
+ def __init__(
44
+ self,
45
+ model_path: str,
46
+ port: int,
47
+ gpus: int | list[int],
48
+ tokenizer_path: Optional[str] = None,
49
+ max_model_len: int = 16384,
50
+ max_lora_rank: Optional[int] = None,
51
+ host: str = '0.0.0.0',
52
+ mem_util: float = 0.85,
53
+ deploy_timeout_seconds: int = 600,
54
+ enforce_eager: bool = False,
55
+ vllm_log_level: Literal['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'] = 'INFO',
56
+ silent_mode: bool = False,
57
+ env_variable_dict: Optional[Dict[str, str]] = None,
58
+ vllm_serve_args: Optional[List[str]] = None,
59
+ vllm_serve_kwargs: Optional[Dict[str, str]] = None,
60
+ chat_template_kwargs: Optional[Dict[str, Any]] = None
61
+ ):
136
62
  """Deploy an LLM on specified GPUs.
137
63
  Args:
138
64
  model_path: Path to the model to deploy.
@@ -234,8 +160,8 @@ class VLLMServer:
234
160
  env['CUDA_VISIBLE_DEVICES'] = gpus
235
161
  env['VLLM_LOGGING_LEVEL'] = self._vllm_log_level
236
162
 
237
- # FIXME: These code are required for my machine :-(
238
- # FIXME: This may due to the bad NCCL environment configuration :-(
163
+ # FIXME: These code are required for my machine :(
164
+ # FIXME: This may due to the bad NCCL configuration :(
239
165
  if isinstance(self._gpus, list) and len(self._gpus) > 1:
240
166
  # set NCCL environment variable
241
167
  env['NCCL_P2P_DISABLE'] = '1'
@@ -352,24 +278,26 @@ class VLLMServer:
352
278
  print(f'[vLLM] Failed to load LoRA adapter. '
353
279
  f'Status code: {response.status_code}, Response: {response.text}')
354
280
  return True
355
- except requests.exceptions.RequestException as e:
281
+ except requests.exceptions.RequestException:
356
282
  continue
357
283
 
358
- print(f'[vLLM] Error loading LoRA adapter: {str(e)}')
284
+ print(f'[vLLM] Error loading LoRA adapter.')
359
285
  return False
360
286
 
361
287
  def close(self):
362
288
  """Shut down vLLM server and kill all vLLM processes."""
363
289
  self._kill_vllm_process()
364
290
 
365
- def chat_completion(self,
366
- message: str | List[openai.types.chat.ChatCompletionMessageParam],
367
- max_tokens: Optional[int] = None,
368
- timeout_seconds: Optional[int] = None,
369
- lora_name: Optional[str] = None,
370
- temperature: float = 0.9,
371
- top_p: float = 0.9,
372
- chat_template_kwargs: Optional[Dict[str, Any]] = None) -> str:
291
+ def chat_completion(
292
+ self,
293
+ message: str | List[openai.types.chat.ChatCompletionMessageParam],
294
+ max_tokens: Optional[int] = None,
295
+ timeout_seconds: Optional[int] = None,
296
+ lora_name: Optional[str] = None,
297
+ temperature: float = 0.9,
298
+ top_p: float = 0.9,
299
+ chat_template_kwargs: Optional[Dict[str, Any]] = None
300
+ ) -> str:
373
301
  """Send a chat completion query with OpenAI format to the vLLM server. Return the response content.
374
302
  Args:
375
303
  message: The message in str or openai format.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: py-adtools
3
- Version: 0.1.4
3
+ Version: 0.1.8
4
4
  Summary: Useful tools for parsing and evaluating Python programs for LLM-based algorithm design.
5
5
  Home-page: https://github.com/RayZhhh/py-adtools
6
6
  Author: Rui Zhang
@@ -13,6 +13,7 @@ Requires-Python: >=3.10
13
13
  Description-Content-Type: text/markdown
14
14
  License-File: LICENSE
15
15
  Requires-Dist: psutil
16
+ Requires-Dist: openai
16
17
  Dynamic: author
17
18
  Dynamic: author-email
18
19
  Dynamic: classifier
@@ -4,8 +4,11 @@ setup.py
4
4
  adtools/__init__.py
5
5
  adtools/evaluator.py
6
6
  adtools/evaluator_pool.py
7
- adtools/lm_base.py
8
7
  adtools/py_code.py
8
+ adtools/lm/__init__.py
9
+ adtools/lm/lm_base.py
10
+ adtools/lm/openai_api.py
11
+ adtools/lm/vllm_server.py
9
12
  py_adtools.egg-info/PKG-INFO
10
13
  py_adtools.egg-info/SOURCES.txt
11
14
  py_adtools.egg-info/dependency_links.txt
@@ -5,14 +5,14 @@ with open('README.md', 'r', encoding='utf-8') as fh:
5
5
 
6
6
  setup(
7
7
  name='py-adtools',
8
- version='0.1.4',
8
+ version='0.1.8',
9
9
  author='Rui Zhang',
10
10
  author_email='rzhang.cs@gmail.com',
11
11
  description='Useful tools for parsing and evaluating Python programs for LLM-based 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',
15
- packages=['adtools'],
15
+ packages=find_packages(),
16
16
  classifiers=[
17
17
  'Programming Language :: Python :: 3',
18
18
  'Operating System :: OS Independent',
@@ -20,5 +20,5 @@ setup(
20
20
  'Topic :: Scientific/Engineering',
21
21
  ],
22
22
  python_requires='>=3.10',
23
- install_requires=['psutil'],
23
+ install_requires=['psutil', 'openai'],
24
24
  )
File without changes
File without changes
File without changes