gpt-batch 0.1.6__tar.gz → 0.1.9__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: gpt_batch
3
- Version: 0.1.6
3
+ Version: 0.1.9
4
4
  Summary: A package for batch processing with OpenAI API.
5
5
  Home-page: https://github.com/fengsxy/gpt_batch
6
6
  Author: Ted Yu
@@ -9,7 +9,6 @@ License: UNKNOWN
9
9
  Platform: UNKNOWN
10
10
  Description-Content-Type: text/markdown
11
11
 
12
- Certainly! Here's a clean and comprehensive README for your `GPTBatcher` tool, formatted in Markdown:
13
12
 
14
13
  ```markdown
15
14
  # GPT Batcher
@@ -60,6 +59,22 @@ print(result)
60
59
  # Expected output: ["embedding_1", "embedding_2", "embedding_3", "embedding_4"]
61
60
  ```
62
61
 
62
+ ### Handling Message Lists with different API
63
+
64
+ This example demonstrates how to send a list of questions and receive answers with different api:
65
+
66
+ ```python
67
+ from gpt_batch.batcher import GPTBatcher
68
+
69
+ # Initialize the batcher
70
+ batcher = GPTBatcher(api_key='sk-', model_name='deepseek-chat',api_base_url="https://api.deepseek.com/v1")
71
+
72
+
73
+ # Send a list of messages and receive answers
74
+ result = batcher.handle_message_list(['question_1', 'question_2', 'question_3', 'question_4'])
75
+
76
+ # Expected output: ["answer_1", "answer_2", "answer_3", "answer_4"]
77
+ ```
63
78
  ## Configuration
64
79
 
65
80
  The `GPTBatcher` class can be customized with several parameters to adjust its performance and behavior:
@@ -1,4 +1,3 @@
1
- Certainly! Here's a clean and comprehensive README for your `GPTBatcher` tool, formatted in Markdown:
2
1
 
3
2
  ```markdown
4
3
  # GPT Batcher
@@ -49,6 +48,22 @@ print(result)
49
48
  # Expected output: ["embedding_1", "embedding_2", "embedding_3", "embedding_4"]
50
49
  ```
51
50
 
51
+ ### Handling Message Lists with different API
52
+
53
+ This example demonstrates how to send a list of questions and receive answers with different api:
54
+
55
+ ```python
56
+ from gpt_batch.batcher import GPTBatcher
57
+
58
+ # Initialize the batcher
59
+ batcher = GPTBatcher(api_key='sk-', model_name='deepseek-chat',api_base_url="https://api.deepseek.com/v1")
60
+
61
+
62
+ # Send a list of messages and receive answers
63
+ result = batcher.handle_message_list(['question_1', 'question_2', 'question_3', 'question_4'])
64
+
65
+ # Expected output: ["answer_1", "answer_2", "answer_3", "answer_4"]
66
+ ```
52
67
  ## Configuration
53
68
 
54
69
  The `GPTBatcher` class can be customized with several parameters to adjust its performance and behavior:
@@ -0,0 +1,203 @@
1
+ from openai import OpenAI
2
+ import anthropic
3
+ from concurrent.futures import ThreadPoolExecutor, wait
4
+ from functools import partial
5
+ from tqdm import tqdm
6
+ import re
7
+
8
+ class GPTBatcher:
9
+ """
10
+ A class to handle batching and sending requests to the OpenAI GPT model and Anthropic Claude models efficiently.
11
+
12
+ Attributes:
13
+ client: The client instance to communicate with the API (OpenAI or Anthropic).
14
+ is_claude (bool): Flag to indicate if using a Claude model.
15
+ model_name (str): The name of the model to be used. Default is 'gpt-3.5-turbo-0125'.
16
+ system_prompt (str): Initial prompt or context to be used with the model. Default is an empty string.
17
+ temperature (float): Controls the randomness of the model's responses. Higher values lead to more diverse outputs. Default is 1.
18
+ num_workers (int): Number of worker threads used for handling concurrent requests. Default is 64.
19
+ timeout_duration (int): Maximum time (in seconds) to wait for a response from the API before timing out. Default is 60 seconds.
20
+ retry_attempts (int): Number of retries if a request fails. Default is 2.
21
+ miss_index (list): Tracks the indices of requests that failed to process correctly.
22
+ """
23
+ def __init__(self, api_key, model_name="gpt-3.5-turbo-0125", system_prompt="",temperature=1,num_workers=64,timeout_duration=60,retry_attempts=2,api_base_url=None,**kwargs):
24
+
25
+ self.is_claude = bool(re.search(r'claude', model_name, re.IGNORECASE))
26
+
27
+ if self.is_claude:
28
+ self.client = anthropic.Anthropic(api_key=api_key)
29
+ # Anthropic doesn't support custom base URL the same way
30
+ # If needed, this could be implemented differently
31
+ else:
32
+ self.client = OpenAI(api_key=api_key)
33
+ if api_base_url:
34
+ self.client.base_url = api_base_url
35
+
36
+ self.model_name = model_name
37
+ self.system_prompt = system_prompt
38
+ self.temperature = temperature
39
+ self.num_workers = num_workers
40
+ self.timeout_duration = timeout_duration
41
+ self.retry_attempts = retry_attempts
42
+ self.miss_index = []
43
+ self.extra_params = kwargs
44
+
45
+ def get_attitude(self, ask_text):
46
+ index, ask_text = ask_text
47
+ try:
48
+ if self.is_claude:
49
+ # Use the Anthropic Claude API
50
+ message = self.client.messages.create(
51
+ model=self.model_name,
52
+ max_tokens=1024, # You can make this configurable if needed
53
+ messages=[
54
+ {"role": "user", "content": ask_text}
55
+ ],
56
+ system=self.system_prompt if self.system_prompt else None,
57
+ temperature=self.temperature,
58
+ **self.extra_params
59
+ )
60
+ return (index, message.content[0].text)
61
+ else:
62
+ # Use the OpenAI API as before
63
+ completion = self.client.chat.completions.create(
64
+ model=self.model_name,
65
+ messages=[
66
+ {"role": "system", "content": self.system_prompt},
67
+ {"role": "user", "content": ask_text}
68
+ ],
69
+ temperature=self.temperature,
70
+ **self.extra_params
71
+ )
72
+ return (index, completion.choices[0].message.content)
73
+ except Exception as e:
74
+ print(f"Error occurred: {e}")
75
+ self.miss_index.append(index)
76
+ return (index, None)
77
+
78
+ def process_attitude(self, message_list):
79
+ new_list = []
80
+ num_workers = self.num_workers
81
+ timeout_duration = self.timeout_duration
82
+ retry_attempts = self.retry_attempts
83
+
84
+ executor = ThreadPoolExecutor(max_workers=num_workers)
85
+ message_chunks = list(self.chunk_list(message_list, num_workers))
86
+ try:
87
+ for chunk in tqdm(message_chunks, desc="Processing messages"):
88
+ future_to_message = {executor.submit(self.get_attitude, message): message for message in chunk}
89
+ for _ in range(retry_attempts):
90
+ done, not_done = wait(future_to_message.keys(), timeout=timeout_duration)
91
+ for future in not_done:
92
+ future.cancel()
93
+ new_list.extend(future.result() for future in done if future.done())
94
+ if len(not_done) == 0:
95
+ break
96
+ future_to_message = {executor.submit(self.get_attitude, future_to_message[future]): future_to_message[future] for future in not_done}
97
+ except Exception as e:
98
+ print(f"Error occurred: {e}")
99
+ finally:
100
+ executor.shutdown(wait=False)
101
+ return new_list
102
+
103
+ def complete_attitude_list(self, attitude_list, max_length):
104
+ completed_list = []
105
+ current_index = 0
106
+ for item in attitude_list:
107
+ index, value = item
108
+ # Fill in missing indices
109
+ while current_index < index:
110
+ completed_list.append((current_index, None))
111
+ current_index += 1
112
+ # Add the current element from the list
113
+ completed_list.append(item)
114
+ current_index = index + 1
115
+ while current_index < max_length:
116
+ print("Filling in missing index", current_index)
117
+ self.miss_index.append(current_index)
118
+ completed_list.append((current_index, None))
119
+ current_index += 1
120
+ return completed_list
121
+
122
+ def chunk_list(self, lst, n):
123
+ """Yield successive n-sized chunks from lst."""
124
+ for i in range(0, len(lst), n):
125
+ yield lst[i:i + n]
126
+
127
+ def handle_message_list(self, message_list):
128
+ indexed_list = [(index, data) for index, data in enumerate(message_list)]
129
+ max_length = len(indexed_list)
130
+ attitude_list = self.process_attitude(indexed_list)
131
+ attitude_list.sort(key=lambda x: x[0])
132
+ attitude_list = self.complete_attitude_list(attitude_list, max_length)
133
+ attitude_list = [x[1] for x in attitude_list]
134
+ return attitude_list
135
+
136
+ def get_embedding(self, text):
137
+ index, text = text
138
+ try:
139
+ if self.is_claude:
140
+ # Use Anthropic's embedding API if available
141
+ # Note: As of March 2025, make sure to check Anthropic's latest API
142
+ # for embeddings, as the format might have changed
143
+ response = self.client.embeddings.create(
144
+ model=self.model_name,
145
+ input=text
146
+ )
147
+ return (index, response.embedding)
148
+ else:
149
+ # Use OpenAI's embedding API
150
+ response = self.client.embeddings.create(
151
+ input=text,
152
+ model=self.model_name
153
+ )
154
+ return (index, response.data[0].embedding)
155
+ except Exception as e:
156
+ print(f"Error getting embedding: {e}")
157
+ self.miss_index.append(index)
158
+ return (index, None)
159
+
160
+ def process_embedding(self, message_list):
161
+ new_list = []
162
+ executor = ThreadPoolExecutor(max_workers=self.num_workers)
163
+ # Split message_list into chunks
164
+ message_chunks = list(self.chunk_list(message_list, self.num_workers))
165
+ fixed_get_embedding = partial(self.get_embedding)
166
+ for chunk in tqdm(message_chunks, desc="Processing messages"):
167
+ future_to_message = {executor.submit(fixed_get_embedding, message): message for message in chunk}
168
+ for i in range(self.retry_attempts):
169
+ done, not_done = wait(future_to_message.keys(), timeout=self.timeout_duration)
170
+ for future in not_done:
171
+ future.cancel()
172
+ new_list.extend(future.result() for future in done if future.done())
173
+ if len(not_done) == 0:
174
+ break
175
+ future_to_message = {executor.submit(fixed_get_embedding, future_to_message[future]): future_to_message[future] for future in not_done}
176
+ executor.shutdown(wait=False)
177
+ return new_list
178
+
179
+ def handle_embedding_list(self, message_list):
180
+ indexed_list = [(index, data) for index, data in enumerate(message_list)]
181
+ max_length = len(indexed_list)
182
+ attitude_list = self.process_embedding(indexed_list)
183
+ attitude_list.sort(key=lambda x: x[0])
184
+ attitude_list = self.complete_attitude_list(attitude_list, max_length)
185
+ attitude_list = [x[1] for x in attitude_list]
186
+ return attitude_list
187
+
188
+ def get_miss_index(self):
189
+ return self.miss_index
190
+
191
+ # Example usage:
192
+ if __name__ == "__main__":
193
+ # For OpenAI
194
+ openai_batcher = GPTBatcher(
195
+ api_key="your_openai_api_key",
196
+ model_name="gpt-4-turbo"
197
+ )
198
+
199
+ # For Claude
200
+ claude_batcher = GPTBatcher(
201
+ api_key="your_anthropic_api_key",
202
+ model_name="claude-3-7-sonnet-20250219"
203
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: gpt-batch
3
- Version: 0.1.6
3
+ Version: 0.1.9
4
4
  Summary: A package for batch processing with OpenAI API.
5
5
  Home-page: https://github.com/fengsxy/gpt_batch
6
6
  Author: Ted Yu
@@ -9,7 +9,6 @@ License: UNKNOWN
9
9
  Platform: UNKNOWN
10
10
  Description-Content-Type: text/markdown
11
11
 
12
- Certainly! Here's a clean and comprehensive README for your `GPTBatcher` tool, formatted in Markdown:
13
12
 
14
13
  ```markdown
15
14
  # GPT Batcher
@@ -60,6 +59,22 @@ print(result)
60
59
  # Expected output: ["embedding_1", "embedding_2", "embedding_3", "embedding_4"]
61
60
  ```
62
61
 
62
+ ### Handling Message Lists with different API
63
+
64
+ This example demonstrates how to send a list of questions and receive answers with different api:
65
+
66
+ ```python
67
+ from gpt_batch.batcher import GPTBatcher
68
+
69
+ # Initialize the batcher
70
+ batcher = GPTBatcher(api_key='sk-', model_name='deepseek-chat',api_base_url="https://api.deepseek.com/v1")
71
+
72
+
73
+ # Send a list of messages and receive answers
74
+ result = batcher.handle_message_list(['question_1', 'question_2', 'question_3', 'question_4'])
75
+
76
+ # Expected output: ["answer_1", "answer_2", "answer_3", "answer_4"]
77
+ ```
63
78
  ## Configuration
64
79
 
65
80
  The `GPTBatcher` class can be customized with several parameters to adjust its performance and behavior:
@@ -1,2 +1,3 @@
1
1
  openai
2
2
  tqdm
3
+ anthropic
@@ -2,10 +2,10 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name='gpt_batch',
5
- version='0.1.6',
5
+ version='0.1.9',
6
6
  packages=find_packages(),
7
7
  install_requires=[
8
- 'openai', 'tqdm'
8
+ 'openai', 'tqdm','anthropic'
9
9
  ],
10
10
  author='Ted Yu',
11
11
  author_email='liddlerain@gmail.com',
@@ -18,6 +18,27 @@ def test_handle_message_list():
18
18
  assert len(results) == 2, "There should be two results, one for each message"
19
19
  assert all(len(result) >= 2 for result in results), "Each result should be at least two elements"
20
20
 
21
+
22
+ def test_json_format():
23
+ import json
24
+ # Initialize the GPTBatcher with hypothetical valid credentials
25
+ #api_key = #get from system environment
26
+ api_key = os.getenv('TEST_KEY')
27
+ if not api_key:
28
+ raise ValueError("API key must be set in the environment variables")
29
+ batcher = GPTBatcher(api_key=api_key, model_name='gpt-3.5-turbo-1106', system_prompt="Your task is to discuss privacy questions and provide persuasive answers with supporting reasons.",response_format={ "type": "json_object" })
30
+ message_list = ["return me a random json object", "return me a random json object"]
31
+
32
+ # Call the method under test
33
+ results = batcher.handle_message_list(message_list)
34
+ # Assertions to verify the length of the results and the structure of each item
35
+ assert len(results) == 2, "There should be two results, one for each message"
36
+ assert all(len(result) >= 2 for result in results), "Each result should be at least two elements"
37
+ #assert all(isinstance(result, dict) and 'json' in result for result in results), "Each result should be a JSON object with 'json' key"
38
+ assert all(isinstance(json.loads(result), dict) for result in results), "Each result should be a JSON object with 'json' key"
39
+
40
+
41
+
21
42
  def test_handle_embedding_list():
22
43
  # Initialize the GPTBatcher with hypothetical valid credentials
23
44
  #api_key = #get from system environment
@@ -51,5 +72,27 @@ def test_get_miss_index():
51
72
  miss_index = batcher.get_miss_index()
52
73
  assert miss_index == [], "The miss index should be empty"
53
74
  # Optionally, you can add a test configuration if you have specific needs
75
+
76
+
77
+ def test_claude_handle_message_list():
78
+ # Initialize the GPTBatcher with Claude model
79
+ api_key = os.getenv('ANTHROPIC_API_KEY')
80
+ if not api_key:
81
+ raise ValueError("Anthropic API key must be set in the environment variables as ANTHROPIC_API_KEY")
82
+
83
+ batcher = GPTBatcher(
84
+ api_key=api_key,
85
+ model_name='claude-3-7-sonnet-20250219',
86
+ system_prompt="Your task is to discuss privacy questions and provide persuasive answers with supporting reasons."
87
+ )
88
+ message_list = ["I think privacy is important", "I don't think privacy is important"]
89
+
90
+ # Call the method under test
91
+ results = batcher.handle_message_list(message_list)
92
+
93
+ # Assertions to verify the length of the results and the structure of each item
94
+ assert len(results) == 2, "There should be two results, one for each message"
95
+ assert all(isinstance(result, str) and len(result) > 0 for result in results if result is not None), "Each result should be a non-empty string if not None"
96
+ assert batcher.is_claude, "Should recognize model as Claude"
54
97
  if __name__ == "__main__":
55
98
  pytest.main()
@@ -1,156 +0,0 @@
1
- from openai import OpenAI
2
- from concurrent.futures import ThreadPoolExecutor, wait
3
- from functools import partial
4
- from tqdm import tqdm
5
-
6
- class GPTBatcher:
7
- """
8
- A class to handle batching and sending requests to the OpenAI GPT model efficiently.
9
-
10
- Attributes:
11
- client (OpenAI): The client instance to communicate with the OpenAI API using the provided API key.
12
- model_name (str): The name of the GPT model to be used. Default is 'gpt-3.5-turbo-0125'.
13
- system_prompt (str): Initial prompt or context to be used with the model. Default is an empty string.
14
- temperature (float): Controls the randomness of the model's responses. Higher values lead to more diverse outputs. Default is 1.
15
- num_workers (int): Number of worker threads used for handling concurrent requests. Default is 64.
16
- timeout_duration (int): Maximum time (in seconds) to wait for a response from the API before timing out. Default is 60 seconds.
17
- retry_attempts (int): Number of retries if a request fails. Default is 2.
18
- miss_index (list): Tracks the indices of requests that failed to process correctly.
19
-
20
- Parameters:
21
- api_key (str): API key for authenticating requests to the OpenAI API.
22
- model_name (str, optional): Specifies the GPT model version. Default is 'gpt-3.5-turbo-0125'.
23
- system_prompt (str, optional): Initial text or question to seed the model with. Default is empty.
24
- temperature (float, optional): Sets the creativity of the responses. Default is 1.
25
- num_workers (int, optional): Number of parallel workers for request handling. Default is 64.
26
- timeout_duration (int, optional): Timeout for API responses in seconds. Default is 60.
27
- retry_attempts (int, optional): How many times to retry a failed request. Default is 2.
28
- """
29
-
30
- def __init__(self, api_key, model_name="gpt-3.5-turbo-0125", system_prompt="",temperature=1,num_workers=64,timeout_duration=60,retry_attempts=2,api_base_url=None):
31
-
32
- self.client = OpenAI(api_key=api_key)
33
- self.model_name = model_name
34
- self.system_prompt = system_prompt
35
- self.temperature = temperature
36
- self.num_workers = num_workers
37
- self.timeout_duration = timeout_duration
38
- self.retry_attempts = retry_attempts
39
- self.miss_index =[]
40
- if api_base_url:
41
- self.client.base_url = api_base_url
42
-
43
- def get_attitude(self, ask_text):
44
- index, ask_text = ask_text
45
- try:
46
- completion = self.client.chat.completions.create(
47
- model=self.model_name,
48
- messages=[
49
- {"role": "system", "content": self.system_prompt},
50
- {"role": "user", "content": ask_text}
51
- ],
52
- temperature=self.temperature,
53
- )
54
- return (index, completion.choices[0].message.content)
55
- except Exception as e:
56
- print(f"Error occurred: {e}")
57
- self.miss_index.append(index)
58
- return (index, None)
59
-
60
- def process_attitude(self, message_list):
61
- new_list = []
62
- num_workers = self.num_workers
63
- timeout_duration = self.timeout_duration
64
- retry_attempts = 2
65
-
66
- executor = ThreadPoolExecutor(max_workers=num_workers)
67
- message_chunks = list(self.chunk_list(message_list, num_workers))
68
- try:
69
- for chunk in tqdm(message_chunks, desc="Processing messages"):
70
- future_to_message = {executor.submit(self.get_attitude, message): message for message in chunk}
71
- for _ in range(retry_attempts):
72
- done, not_done = wait(future_to_message.keys(), timeout=timeout_duration)
73
- for future in not_done:
74
- future.cancel()
75
- new_list.extend(future.result() for future in done if future.done())
76
- if len(not_done) == 0:
77
- break
78
- future_to_message = {executor.submit(self.get_attitude, future_to_message[future]): future for future in not_done}
79
- except Exception as e:
80
- print(f"Error occurred: {e}")
81
- finally:
82
- executor.shutdown(wait=False)
83
- return new_list
84
-
85
- def complete_attitude_list(self,attitude_list, max_length):
86
- completed_list = []
87
- current_index = 0
88
- for item in attitude_list:
89
- index, value = item
90
- # Fill in missing indices
91
- while current_index < index:
92
- completed_list.append((current_index, None))
93
- current_index += 1
94
- # Add the current element from the list
95
- completed_list.append(item)
96
- current_index = index + 1
97
- while current_index < max_length:
98
- print("Filling in missing index", current_index)
99
- self.miss_index.append(current_index)
100
- completed_list.append((current_index, None))
101
- current_index += 1
102
- return completed_list
103
-
104
- def chunk_list(self, lst, n):
105
- """Yield successive n-sized chunks from lst."""
106
- for i in range(0, len(lst), n):
107
- yield lst[i:i + n]
108
-
109
- def handle_message_list(self,message_list):
110
- indexed_list = [(index, data) for index, data in enumerate(message_list)]
111
- max_length = len(indexed_list)
112
- attitude_list = self.process_attitude(indexed_list)
113
- attitude_list.sort(key=lambda x: x[0])
114
- attitude_list = self.complete_attitude_list(attitude_list, max_length)
115
- attitude_list = [x[1] for x in attitude_list]
116
- return attitude_list
117
-
118
- def process_embedding(self,message_list):
119
- new_list = []
120
- executor = ThreadPoolExecutor(max_workers=self.num_workers)
121
- # Split message_list into chunks
122
- message_chunks = list(self.chunk_list(message_list, self.num_workers))
123
- fixed_get_embedding = partial(self.get_embedding)
124
- for chunk in tqdm(message_chunks, desc="Processing messages"):
125
- future_to_message = {executor.submit(fixed_get_embedding, message): message for message in chunk}
126
- for i in range(self.retry_attempts):
127
- done, not_done = wait(future_to_message.keys(), timeout=self.timeout_duration)
128
- for future in not_done:
129
- future.cancel()
130
- new_list.extend(future.result() for future in done if future.done())
131
- if len(not_done) == 0:
132
- break
133
- future_to_message = {executor.submit(fixed_get_embedding, future_to_message[future]): future_to_message[future] for future in not_done}
134
- executor.shutdown(wait=False)
135
- return new_list
136
- def get_embedding(self,text):
137
- index,text = text
138
- response = self.client.embeddings.create(
139
- input=text,
140
- model=self.model_name)
141
- return (index,response.data[0].embedding)
142
-
143
- def handle_embedding_list(self,message_list):
144
- indexed_list = [(index, data) for index, data in enumerate(message_list)]
145
- max_length = len(indexed_list)
146
- attitude_list = self.process_embedding(indexed_list)
147
- attitude_list.sort(key=lambda x: x[0])
148
- attitude_list = self.complete_attitude_list(attitude_list, max_length)
149
- attitude_list = [x[1] for x in attitude_list]
150
- return attitude_list
151
-
152
- def get_miss_index(self):
153
- return self.miss_index
154
-
155
- # Add other necessary methods similar to the above, refactored to fit within this class structure.
156
-
File without changes
File without changes