llm_batch_helper 0.3.1__tar.gz → 0.3.3__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,20 +1,20 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: llm_batch_helper
3
- Version: 0.3.1
3
+ Version: 0.3.3
4
4
  Summary: A Python package that enables batch submission of prompts to LLM APIs, with simplified interface and built-in async capabilities handled implicitly.
5
5
  License: MIT
6
6
  Keywords: llm,openai,together,openrouter,batch,async,ai,nlp,api
7
7
  Author: Tianyi Peng
8
8
  Author-email: tianyipeng95@gmail.com
9
- Requires-Python: >=3.11,<4.0
9
+ Requires-Python: >=3.10,<4.0
10
10
  Classifier: Development Status :: 4 - Beta
11
11
  Classifier: Intended Audience :: Developers
12
12
  Classifier: License :: OSI Approved :: MIT License
13
13
  Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
14
15
  Classifier: Programming Language :: Python :: 3.11
15
16
  Classifier: Programming Language :: Python :: 3.12
16
17
  Classifier: Programming Language :: Python :: 3.13
17
- Classifier: Programming Language :: Python :: 3.10
18
18
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
19
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
20
  Requires-Dist: httpx (>=0.24.0,<2.0.0)
@@ -39,9 +39,9 @@ A Python package that enables batch submission of prompts to LLM APIs, with buil
39
39
 
40
40
  ## Why we designed this package
41
41
 
42
- Calling LLM APIs has become increasingly common, but several pain points exist in practice:
42
+ Imagine you have 5000 prompts you need to send to an LLM. Running them sequentially can be painfully slow—sometimes taking hours or even days. Worse, if the process fails midway, you’re forced to start all over again. We’ve struggled with this exact frustration, which is why we built this package, to directly tackle these pain points:
43
43
 
44
- 1. **Efficient Batch Processing**: How do you run LLM calls in batches efficiently? Our async implementation is 3X-100X faster than multi-thread/multi-process approaches.
44
+ 1. **Efficient Batch Processing**: How do you run LLM calls in batches efficiently? Our async implementation is 3X-100X faster than multi-thread/multi-process approaches. In my own experience, it reduces the time from 24 hours to 10min.
45
45
 
46
46
  2. **API Reliability**: LLM APIs can be unstable, so we need robust retry mechanisms when calls get interrupted.
47
47
 
@@ -53,46 +53,31 @@ This package is designed to solve these exact pain points with async processing,
53
53
 
54
54
  ## Features
55
55
 
56
- - **Async Processing**: Submit multiple prompts concurrently for faster processing
57
- - **Response Caching**: Automatically cache responses to avoid redundant API calls
58
- - **Multiple Input Formats**: Support for both file-based and list-based prompts
59
- - **Provider Support**: Works with OpenAI (all models including GPT-5), OpenRouter (100+ models), and Together.ai APIs
60
- - **Retry Logic**: Built-in retry mechanism with exponential backoff and detailed logging
61
- - **Verification Callbacks**: Custom verification for response quality
62
- - **Progress Tracking**: Real-time progress bars for batch operations
63
- - **Simplified API**: Async operations handled implicitly - no async/await needed (v0.3.0+)
64
- - **Detailed Error Logging**: See exactly what happens during retries with timestamps and error details
56
+ - **🚀 Dramatic Speed Improvements**: **10-100x faster** than sequential processing ([see demo](https://github.com/TianyiPeng/LLM_batch_helper/blob/main/tutorials/performance_comparison_tutorial.ipynb))
57
+ - **⚡ Async Processing**: Submit multiple prompts concurrently for maximum throughput
58
+ - **💾 Smart Caching**: Automatically cache responses and resume interrupted work seamlessly
59
+ - **📝 Multiple Input Formats**: Support for strings, tuples, dictionaries, and file-based prompts
60
+ - **🌐 Multi-Provider Support**: Works with OpenAI (all models), OpenRouter (100+ models), and Together.ai
61
+ - **🔄 Intelligent Retry Logic**: Built-in retry mechanism with exponential backoff and detailed logging
62
+ - **✅ Quality Control**: Custom verification callbacks for response validation
63
+ - **📊 Progress Tracking**: Real-time progress bars and comprehensive statistics
64
+ - **🎯 Simplified API**: No async/await complexity - works seamlessly in Jupyter notebooks (v0.3.0+)
65
+ - **🔧 Tunable Performance**: Adjust concurrency on-the-fly for optimal speed vs rate limits
65
66
 
66
67
  ## Installation
67
68
 
68
- ### For Users (Recommended)
69
-
70
69
  ```bash
71
70
  # Install from PyPI
72
71
  pip install llm_batch_helper
73
72
  ```
74
73
 
75
- ### For Development
76
-
77
- ```bash
78
- # Clone the repository
79
- git clone https://github.com/TianyiPeng/LLM_batch_helper.git
80
- cd llm_batch_helper
81
-
82
- # Install with Poetry
83
- poetry install
84
-
85
- # Activate the virtual environment
86
- poetry shell
87
- ```
88
-
89
74
  ## Quick Start
90
75
 
91
76
  ### 1. Set up environment variables
92
77
 
93
78
  **Option A: Environment Variables**
94
79
  ```bash
95
- # For OpenAI (all models including GPT-5)
80
+ # For OpenAI (all OpenAI models including GPT-5)
96
81
  export OPENAI_API_KEY="your-openai-api-key"
97
82
 
98
83
  # For OpenRouter (100+ models - Recommended)
@@ -103,6 +88,11 @@ export TOGETHER_API_KEY="your-together-api-key"
103
88
  ```
104
89
 
105
90
  **Option B: .env File (Recommended for Development)**
91
+ Create a `.env` file in your project:
92
+ ```
93
+ OPENAI_API_KEY=your-openai-api-key
94
+ ```
95
+
106
96
  ```python
107
97
  # In your script, before importing llm_batch_helper
108
98
  from dotenv import load_dotenv
@@ -112,17 +102,17 @@ load_dotenv() # Load from .env file
112
102
  from llm_batch_helper import LLMConfig, process_prompts_batch
113
103
  ```
114
104
 
115
- Create a `.env` file in your project:
116
- ```
117
- OPENAI_API_KEY=your-openai-api-key
118
- TOGETHER_API_KEY=your-together-api-key
119
- ```
120
-
121
- ### 2. Interactive Tutorial (Recommended)
105
+ ### 2. Interactive Tutorials (Recommended)
122
106
 
123
- Check out the comprehensive Jupyter notebook [tutorial](https://github.com/TianyiPeng/LLM_batch_helper/blob/main/tutorials/llm_batch_helper_tutorial.ipynb).
107
+ **🎯 NEW: Performance Comparison Tutorial**
108
+ See the dramatic speed improvements! Our [Performance Comparison Tutorial](https://github.com/TianyiPeng/LLM_batch_helper/blob/main/tutorials/performance_comparison_tutorial.ipynb) demonstrates:
109
+ - **10-100x speedup** vs naive sequential processing
110
+ - Processing **5,000 prompts** in minutes instead of hours
111
+ - **Smart caching** that lets you resume interrupted work
112
+ - **Tunable concurrency** for optimal performance
124
113
 
125
- The tutorial covers all features with interactive examples!
114
+ **📚 Complete Feature Tutorial**
115
+ Check out the comprehensive [main tutorial](https://github.com/TianyiPeng/LLM_batch_helper/blob/main/tutorials/llm_batch_helper_tutorial.ipynb) covering all features with interactive examples!
126
116
 
127
117
  ### 3. Basic usage
128
118
 
@@ -138,10 +128,10 @@ config = LLMConfig(
138
128
  model_name="gpt-4o-mini",
139
129
  temperature=1.0,
140
130
  max_completion_tokens=100,
141
- max_concurrent_requests=30 # number of concurrent requests with asyncIO
131
+ max_concurrent_requests=100 # number of concurrent requests with asyncIO, this number decides how fast your pipeline can run. We suggest a number that is as large as possible (e.g., 300) while making sure you are not over the rate limit constrained by the LLM APIs.
142
132
  )
143
133
 
144
- # Process prompts - no async/await needed!
134
+ # Process prompts
145
135
  prompts = [
146
136
  "What is the capital of France?",
147
137
  "What is 2+2?",
@@ -162,6 +152,49 @@ for prompt_id, response in results.items():
162
152
 
163
153
  **🎉 New in v0.3.0**: `process_prompts_batch` now handles async operations **implicitly** - no more async/await syntax needed! Works seamlessly in Jupyter notebooks.
164
154
 
155
+ ### 4. Multiple Input Formats
156
+
157
+ The package supports three different input formats for maximum flexibility:
158
+
159
+ ```python
160
+ from llm_batch_helper import LLMConfig, process_prompts_batch
161
+
162
+ config = LLMConfig(
163
+ model_name="gpt-4o-mini",
164
+ temperature=1.0,
165
+ max_completion_tokens=100
166
+ )
167
+
168
+ # Mix different input formats in the same list
169
+ prompts = [
170
+ # String format - ID will be auto-generated from hash
171
+ "What is the capital of France?",
172
+
173
+ # Tuple format - (custom_id, prompt_text)
174
+ ("custom_id_1", "What is 2+2?"),
175
+
176
+ # Dictionary format - {"id": custom_id, "text": prompt_text}
177
+ {"id": "shakespeare_q", "text": "Who wrote 'Hamlet'?"},
178
+ {"id": "science_q", "text": "Explain photosynthesis briefly."}
179
+ ]
180
+
181
+ results = process_prompts_batch(
182
+ config=config,
183
+ provider="openai",
184
+ prompts=prompts,
185
+ cache_dir="cache"
186
+ )
187
+
188
+ # Print results with custom IDs
189
+ for prompt_id, response in results.items():
190
+ print(f"{prompt_id}: {response['response_text']}")
191
+ ```
192
+
193
+ **Input Format Requirements:**
194
+ - **String**: Plain text prompt (ID auto-generated)
195
+ - **Tuple**: `(prompt_id, prompt_text)` - both elements required
196
+ - **Dictionary**: `{"id": "prompt_id", "text": "prompt_text"}` - both keys required
197
+
165
198
  ### 🔄 Backward Compatibility
166
199
 
167
200
  For users who prefer the async version or have existing code, the async API is still available:
@@ -352,7 +385,8 @@ llm_batch_helper/
352
385
  │ ├── prompts/ # Sample prompt files
353
386
  │ └── llm_cache/ # Example cache directory
354
387
  └── tutorials/ # Interactive tutorials
355
- └── llm_batch_helper_tutorial.ipynb # Comprehensive Jupyter notebook tutorial
388
+ ├── llm_batch_helper_tutorial.ipynb # Comprehensive feature tutorial
389
+ └── performance_comparison_tutorial.ipynb # Performance demo (NEW!)
356
390
  ```
357
391
 
358
392
  ## Supported Models
@@ -12,9 +12,9 @@ A Python package that enables batch submission of prompts to LLM APIs, with buil
12
12
 
13
13
  ## Why we designed this package
14
14
 
15
- Calling LLM APIs has become increasingly common, but several pain points exist in practice:
15
+ Imagine you have 5000 prompts you need to send to an LLM. Running them sequentially can be painfully slow—sometimes taking hours or even days. Worse, if the process fails midway, you’re forced to start all over again. We’ve struggled with this exact frustration, which is why we built this package, to directly tackle these pain points:
16
16
 
17
- 1. **Efficient Batch Processing**: How do you run LLM calls in batches efficiently? Our async implementation is 3X-100X faster than multi-thread/multi-process approaches.
17
+ 1. **Efficient Batch Processing**: How do you run LLM calls in batches efficiently? Our async implementation is 3X-100X faster than multi-thread/multi-process approaches. In my own experience, it reduces the time from 24 hours to 10min.
18
18
 
19
19
  2. **API Reliability**: LLM APIs can be unstable, so we need robust retry mechanisms when calls get interrupted.
20
20
 
@@ -26,46 +26,31 @@ This package is designed to solve these exact pain points with async processing,
26
26
 
27
27
  ## Features
28
28
 
29
- - **Async Processing**: Submit multiple prompts concurrently for faster processing
30
- - **Response Caching**: Automatically cache responses to avoid redundant API calls
31
- - **Multiple Input Formats**: Support for both file-based and list-based prompts
32
- - **Provider Support**: Works with OpenAI (all models including GPT-5), OpenRouter (100+ models), and Together.ai APIs
33
- - **Retry Logic**: Built-in retry mechanism with exponential backoff and detailed logging
34
- - **Verification Callbacks**: Custom verification for response quality
35
- - **Progress Tracking**: Real-time progress bars for batch operations
36
- - **Simplified API**: Async operations handled implicitly - no async/await needed (v0.3.0+)
37
- - **Detailed Error Logging**: See exactly what happens during retries with timestamps and error details
29
+ - **🚀 Dramatic Speed Improvements**: **10-100x faster** than sequential processing ([see demo](https://github.com/TianyiPeng/LLM_batch_helper/blob/main/tutorials/performance_comparison_tutorial.ipynb))
30
+ - **⚡ Async Processing**: Submit multiple prompts concurrently for maximum throughput
31
+ - **💾 Smart Caching**: Automatically cache responses and resume interrupted work seamlessly
32
+ - **📝 Multiple Input Formats**: Support for strings, tuples, dictionaries, and file-based prompts
33
+ - **🌐 Multi-Provider Support**: Works with OpenAI (all models), OpenRouter (100+ models), and Together.ai
34
+ - **🔄 Intelligent Retry Logic**: Built-in retry mechanism with exponential backoff and detailed logging
35
+ - **✅ Quality Control**: Custom verification callbacks for response validation
36
+ - **📊 Progress Tracking**: Real-time progress bars and comprehensive statistics
37
+ - **🎯 Simplified API**: No async/await complexity - works seamlessly in Jupyter notebooks (v0.3.0+)
38
+ - **🔧 Tunable Performance**: Adjust concurrency on-the-fly for optimal speed vs rate limits
38
39
 
39
40
  ## Installation
40
41
 
41
- ### For Users (Recommended)
42
-
43
42
  ```bash
44
43
  # Install from PyPI
45
44
  pip install llm_batch_helper
46
45
  ```
47
46
 
48
- ### For Development
49
-
50
- ```bash
51
- # Clone the repository
52
- git clone https://github.com/TianyiPeng/LLM_batch_helper.git
53
- cd llm_batch_helper
54
-
55
- # Install with Poetry
56
- poetry install
57
-
58
- # Activate the virtual environment
59
- poetry shell
60
- ```
61
-
62
47
  ## Quick Start
63
48
 
64
49
  ### 1. Set up environment variables
65
50
 
66
51
  **Option A: Environment Variables**
67
52
  ```bash
68
- # For OpenAI (all models including GPT-5)
53
+ # For OpenAI (all OpenAI models including GPT-5)
69
54
  export OPENAI_API_KEY="your-openai-api-key"
70
55
 
71
56
  # For OpenRouter (100+ models - Recommended)
@@ -76,6 +61,11 @@ export TOGETHER_API_KEY="your-together-api-key"
76
61
  ```
77
62
 
78
63
  **Option B: .env File (Recommended for Development)**
64
+ Create a `.env` file in your project:
65
+ ```
66
+ OPENAI_API_KEY=your-openai-api-key
67
+ ```
68
+
79
69
  ```python
80
70
  # In your script, before importing llm_batch_helper
81
71
  from dotenv import load_dotenv
@@ -85,17 +75,17 @@ load_dotenv() # Load from .env file
85
75
  from llm_batch_helper import LLMConfig, process_prompts_batch
86
76
  ```
87
77
 
88
- Create a `.env` file in your project:
89
- ```
90
- OPENAI_API_KEY=your-openai-api-key
91
- TOGETHER_API_KEY=your-together-api-key
92
- ```
93
-
94
- ### 2. Interactive Tutorial (Recommended)
78
+ ### 2. Interactive Tutorials (Recommended)
95
79
 
96
- Check out the comprehensive Jupyter notebook [tutorial](https://github.com/TianyiPeng/LLM_batch_helper/blob/main/tutorials/llm_batch_helper_tutorial.ipynb).
80
+ **🎯 NEW: Performance Comparison Tutorial**
81
+ See the dramatic speed improvements! Our [Performance Comparison Tutorial](https://github.com/TianyiPeng/LLM_batch_helper/blob/main/tutorials/performance_comparison_tutorial.ipynb) demonstrates:
82
+ - **10-100x speedup** vs naive sequential processing
83
+ - Processing **5,000 prompts** in minutes instead of hours
84
+ - **Smart caching** that lets you resume interrupted work
85
+ - **Tunable concurrency** for optimal performance
97
86
 
98
- The tutorial covers all features with interactive examples!
87
+ **📚 Complete Feature Tutorial**
88
+ Check out the comprehensive [main tutorial](https://github.com/TianyiPeng/LLM_batch_helper/blob/main/tutorials/llm_batch_helper_tutorial.ipynb) covering all features with interactive examples!
99
89
 
100
90
  ### 3. Basic usage
101
91
 
@@ -111,10 +101,10 @@ config = LLMConfig(
111
101
  model_name="gpt-4o-mini",
112
102
  temperature=1.0,
113
103
  max_completion_tokens=100,
114
- max_concurrent_requests=30 # number of concurrent requests with asyncIO
104
+ max_concurrent_requests=100 # number of concurrent requests with asyncIO, this number decides how fast your pipeline can run. We suggest a number that is as large as possible (e.g., 300) while making sure you are not over the rate limit constrained by the LLM APIs.
115
105
  )
116
106
 
117
- # Process prompts - no async/await needed!
107
+ # Process prompts
118
108
  prompts = [
119
109
  "What is the capital of France?",
120
110
  "What is 2+2?",
@@ -135,6 +125,49 @@ for prompt_id, response in results.items():
135
125
 
136
126
  **🎉 New in v0.3.0**: `process_prompts_batch` now handles async operations **implicitly** - no more async/await syntax needed! Works seamlessly in Jupyter notebooks.
137
127
 
128
+ ### 4. Multiple Input Formats
129
+
130
+ The package supports three different input formats for maximum flexibility:
131
+
132
+ ```python
133
+ from llm_batch_helper import LLMConfig, process_prompts_batch
134
+
135
+ config = LLMConfig(
136
+ model_name="gpt-4o-mini",
137
+ temperature=1.0,
138
+ max_completion_tokens=100
139
+ )
140
+
141
+ # Mix different input formats in the same list
142
+ prompts = [
143
+ # String format - ID will be auto-generated from hash
144
+ "What is the capital of France?",
145
+
146
+ # Tuple format - (custom_id, prompt_text)
147
+ ("custom_id_1", "What is 2+2?"),
148
+
149
+ # Dictionary format - {"id": custom_id, "text": prompt_text}
150
+ {"id": "shakespeare_q", "text": "Who wrote 'Hamlet'?"},
151
+ {"id": "science_q", "text": "Explain photosynthesis briefly."}
152
+ ]
153
+
154
+ results = process_prompts_batch(
155
+ config=config,
156
+ provider="openai",
157
+ prompts=prompts,
158
+ cache_dir="cache"
159
+ )
160
+
161
+ # Print results with custom IDs
162
+ for prompt_id, response in results.items():
163
+ print(f"{prompt_id}: {response['response_text']}")
164
+ ```
165
+
166
+ **Input Format Requirements:**
167
+ - **String**: Plain text prompt (ID auto-generated)
168
+ - **Tuple**: `(prompt_id, prompt_text)` - both elements required
169
+ - **Dictionary**: `{"id": "prompt_id", "text": "prompt_text"}` - both keys required
170
+
138
171
  ### 🔄 Backward Compatibility
139
172
 
140
173
  For users who prefer the async version or have existing code, the async API is still available:
@@ -325,7 +358,8 @@ llm_batch_helper/
325
358
  │ ├── prompts/ # Sample prompt files
326
359
  │ └── llm_cache/ # Example cache directory
327
360
  └── tutorials/ # Interactive tutorials
328
- └── llm_batch_helper_tutorial.ipynb # Comprehensive Jupyter notebook tutorial
361
+ ├── llm_batch_helper_tutorial.ipynb # Comprehensive feature tutorial
362
+ └── performance_comparison_tutorial.ipynb # Performance demo (NEW!)
329
363
  ```
330
364
 
331
365
  ## Supported Models
@@ -1,13 +1,16 @@
1
1
  from .cache import LLMCache
2
2
  from .config import LLMConfig
3
+ from .exceptions import InvalidPromptFormatError, VerificationFailedError
3
4
  from .input_handlers import get_prompts, read_prompt_files, read_prompt_list
4
5
  from .providers import process_prompts_batch, process_prompts_batch_async
5
6
 
6
- __version__ = "0.3.1"
7
+ __version__ = "0.3.3"
7
8
 
8
9
  __all__ = [
9
10
  "LLMCache",
10
11
  "LLMConfig",
12
+ "InvalidPromptFormatError",
13
+ "VerificationFailedError",
11
14
  "get_prompts",
12
15
  "process_prompts_batch",
13
16
  "process_prompts_batch_async", # For backward compatibility
@@ -5,3 +5,11 @@ class VerificationFailedError(Exception):
5
5
  super().__init__(message)
6
6
  self.prompt_id = prompt_id
7
7
  self.llm_response_data = llm_response_data
8
+
9
+
10
+ class InvalidPromptFormatError(Exception):
11
+ """Custom exception for invalid prompt format."""
12
+
13
+ def __init__(self, message, invalid_item=None):
14
+ super().__init__(message)
15
+ self.invalid_item = invalid_item
@@ -2,6 +2,8 @@ import hashlib
2
2
  from pathlib import Path
3
3
  from typing import Any, Dict, List, Tuple, Union
4
4
 
5
+ from .exceptions import InvalidPromptFormatError
6
+
5
7
 
6
8
  def read_prompt_files(input_dir: str) -> List[Tuple[str, str]]:
7
9
  """Read all text files from input directory and return as (filename, content) pairs.
@@ -53,12 +55,34 @@ def read_prompt_list(
53
55
  elif isinstance(item, tuple) and len(item) == 2:
54
56
  # Tuple format: (prompt_id, prompt_text)
55
57
  prompt_id, prompt_text = item
56
- elif isinstance(item, dict) and "id" in item and "text" in item:
57
- # Dict format: {"id": prompt_id, "text": prompt_text}
58
+ elif isinstance(item, dict):
59
+ # Dict format: must have both "id" and "text" keys
60
+ if "id" not in item:
61
+ raise InvalidPromptFormatError(
62
+ f"Dictionary prompt is missing required 'id' key. "
63
+ f"Dictionary format must be: {{'id': 'prompt_id', 'text': 'prompt_text'}}. "
64
+ f"Got: {item}",
65
+ invalid_item=item
66
+ )
67
+ if "text" not in item:
68
+ raise InvalidPromptFormatError(
69
+ f"Dictionary prompt is missing required 'text' key. "
70
+ f"Dictionary format must be: {{'id': 'prompt_id', 'text': 'prompt_text'}}. "
71
+ f"Got: {item}",
72
+ invalid_item=item
73
+ )
58
74
  prompt_id = item["id"]
59
75
  prompt_text = item["text"]
60
76
  else:
61
- raise ValueError(f"Invalid prompt format: {item}")
77
+ raise InvalidPromptFormatError(
78
+ f"Invalid prompt format. Expected str, tuple, or dict, got {type(item).__name__}. "
79
+ f"Valid formats: "
80
+ f"- str: 'prompt text' "
81
+ f"- tuple: ('prompt_id', 'prompt_text') "
82
+ f"- dict: {{'id': 'prompt_id', 'text': 'prompt_text'}}. "
83
+ f"Got: {item}",
84
+ invalid_item=item
85
+ )
62
86
  prompts.append((prompt_id, prompt_text))
63
87
  return prompts
64
88
 
@@ -288,10 +288,11 @@ async def process_prompts_batch_async(
288
288
  force: If True, force regeneration even if cached response exists
289
289
 
290
290
  Returns:
291
- Dict mapping prompt IDs to their responses
291
+ Dict mapping prompt IDs to their responses, ordered by input sequence
292
292
 
293
293
  Note:
294
294
  Either prompts or input_dir must be provided, but not both.
295
+ Results are returned in the same order as the input prompts.
295
296
  """
296
297
  if prompts is None and input_dir is None:
297
298
  raise ValueError("Either prompts or input_dir must be provided")
@@ -309,6 +310,9 @@ async def process_prompts_batch_async(
309
310
 
310
311
  # Process prompts
311
312
  results = {}
313
+ # Keep track of original order for sorting results
314
+ prompt_order = {prompt_id: idx for idx, (prompt_id, _) in enumerate(prompts)}
315
+
312
316
  tasks = [
313
317
  _process_single_prompt_attempt_with_verification(
314
318
  prompt_id, prompt_text, config, provider, semaphore, cache_dir, force
@@ -320,7 +324,14 @@ async def process_prompts_batch_async(
320
324
  prompt_id, response_data = await future
321
325
  results[prompt_id] = response_data
322
326
 
323
- return results
327
+ # Sort results by original input order to maintain input sequence
328
+ # Note: Python 3.7+ guarantees dict insertion order, we explicitly sort
329
+ # to ensure results match the original prompt order regardless of completion order
330
+ ordered_results = {}
331
+ for prompt_id in sorted(results.keys(), key=lambda pid: prompt_order[pid]):
332
+ ordered_results[prompt_id] = results[prompt_id]
333
+
334
+ return ordered_results
324
335
 
325
336
 
326
337
  def process_prompts_batch(
@@ -348,10 +359,11 @@ def process_prompts_batch(
348
359
  force: If True, force regeneration even if cached response exists
349
360
 
350
361
  Returns:
351
- Dict mapping prompt IDs to their responses
362
+ Dict mapping prompt IDs to their responses, ordered by input sequence
352
363
 
353
364
  Note:
354
365
  Either prompts or input_dir must be provided, but not both.
366
+ Results are returned in the same order as the input prompts.
355
367
 
356
368
  Example:
357
369
  >>> from llm_batch_helper import LLMConfig, process_prompts_batch
@@ -361,6 +373,7 @@ def process_prompts_batch(
361
373
  ... config=config,
362
374
  ... provider="openai"
363
375
  ... )
376
+ >>> # Results will be in the same order as input prompts
364
377
  """
365
378
  return _run_async_function(
366
379
  process_prompts_batch_async,
@@ -390,18 +403,22 @@ async def _process_single_prompt_attempt_with_verification(
390
403
  cache = LLMCache(cache_dir)
391
404
  cached_response = cache.get_cached_response(prompt_id)
392
405
  if cached_response is not None:
393
- # Verify response if callback provided
394
406
  cached_response_data = cached_response["llm_response"]
395
- if config.verification_callback:
396
- verified = await asyncio.to_thread(
397
- config.verification_callback,
398
- prompt_id,
399
- cached_response_data,
400
- prompt_text,
401
- **config.verification_callback_args,
402
- )
403
- if verified:
404
- return prompt_id, {**cached_response_data, "from_cache": True}
407
+
408
+ # If no verification callback, use cached response directly
409
+ if config.verification_callback is None:
410
+ return prompt_id, {**cached_response_data, "from_cache": True}
411
+
412
+ # Verify response if callback provided
413
+ verified = await asyncio.to_thread(
414
+ config.verification_callback,
415
+ prompt_id,
416
+ cached_response_data,
417
+ prompt_text,
418
+ **config.verification_callback_args,
419
+ )
420
+ if verified:
421
+ return prompt_id, {**cached_response_data, "from_cache": True}
405
422
 
406
423
  # Process the prompt
407
424
  last_exception_details = None
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "llm_batch_helper"
3
- version = "0.3.1"
3
+ version = "0.3.3"
4
4
  description = "A Python package that enables batch submission of prompts to LLM APIs, with simplified interface and built-in async capabilities handled implicitly."
5
5
  authors = ["Tianyi Peng <tianyipeng95@gmail.com>"]
6
6
  readme = "README.md"
@@ -22,7 +22,7 @@ classifiers = [
22
22
  packages = [{include = "llm_batch_helper"}]
23
23
 
24
24
  [tool.poetry.dependencies]
25
- python = "^3.11"
25
+ python = "^3.10"
26
26
  httpx = ">=0.24.0,<2.0.0"
27
27
  openai = "^1.0.0"
28
28
  tenacity = "^8.0.0"