lm-deluge 0.0.39__tar.gz → 0.0.41__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 lm-deluge might be problematic. Click here for more details.

Files changed (78) hide show
  1. {lm_deluge-0.0.39/src/lm_deluge.egg-info → lm_deluge-0.0.41}/PKG-INFO +7 -4
  2. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/README.md +6 -3
  3. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/pyproject.toml +1 -1
  4. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/batches.py +96 -10
  5. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/client.py +49 -12
  6. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/tracker.py +1 -1
  7. {lm_deluge-0.0.39 → lm_deluge-0.0.41/src/lm_deluge.egg-info}/PKG-INFO +7 -4
  8. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/LICENSE +0 -0
  9. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/setup.cfg +0 -0
  10. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/__init__.py +0 -0
  11. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/agent.py +0 -0
  12. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/api_requests/__init__.py +0 -0
  13. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/api_requests/anthropic.py +0 -0
  14. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/api_requests/base.py +0 -0
  15. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/api_requests/bedrock.py +0 -0
  16. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/api_requests/common.py +0 -0
  17. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/api_requests/deprecated/bedrock.py +0 -0
  18. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/api_requests/deprecated/cohere.py +0 -0
  19. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/api_requests/deprecated/deepseek.py +0 -0
  20. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/api_requests/deprecated/mistral.py +0 -0
  21. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/api_requests/deprecated/vertex.py +0 -0
  22. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/api_requests/gemini.py +0 -0
  23. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/api_requests/mistral.py +0 -0
  24. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/api_requests/openai.py +0 -0
  25. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/api_requests/response.py +0 -0
  26. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/built_in_tools/anthropic/__init__.py +0 -0
  27. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/built_in_tools/anthropic/bash.py +0 -0
  28. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/built_in_tools/anthropic/computer_use.py +0 -0
  29. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/built_in_tools/anthropic/editor.py +0 -0
  30. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/built_in_tools/base.py +0 -0
  31. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/built_in_tools/openai.py +0 -0
  32. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/cache.py +0 -0
  33. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/cli.py +0 -0
  34. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/config.py +0 -0
  35. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/embed.py +0 -0
  36. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/errors.py +0 -0
  37. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/file.py +0 -0
  38. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/gemini_limits.py +0 -0
  39. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/image.py +0 -0
  40. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/llm_tools/__init__.py +0 -0
  41. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/llm_tools/classify.py +0 -0
  42. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/llm_tools/extract.py +0 -0
  43. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/llm_tools/locate.py +0 -0
  44. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/llm_tools/ocr.py +0 -0
  45. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/llm_tools/score.py +0 -0
  46. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/llm_tools/translate.py +0 -0
  47. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/models/__init__.py +0 -0
  48. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/models/anthropic.py +0 -0
  49. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/models/bedrock.py +0 -0
  50. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/models/cerebras.py +0 -0
  51. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/models/cohere.py +0 -0
  52. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/models/deepseek.py +0 -0
  53. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/models/fireworks.py +0 -0
  54. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/models/google.py +0 -0
  55. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/models/grok.py +0 -0
  56. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/models/groq.py +0 -0
  57. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/models/meta.py +0 -0
  58. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/models/mistral.py +0 -0
  59. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/models/openai.py +0 -0
  60. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/models/openrouter.py +0 -0
  61. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/models/together.py +0 -0
  62. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/prompt.py +0 -0
  63. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/request_context.py +0 -0
  64. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/rerank.py +0 -0
  65. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/tool.py +0 -0
  66. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/usage.py +0 -0
  67. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/util/harmony.py +0 -0
  68. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/util/json.py +0 -0
  69. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/util/logprobs.py +0 -0
  70. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/util/spatial.py +0 -0
  71. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/util/validation.py +0 -0
  72. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge/util/xml.py +0 -0
  73. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge.egg-info/SOURCES.txt +0 -0
  74. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge.egg-info/dependency_links.txt +0 -0
  75. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge.egg-info/requires.txt +0 -0
  76. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/src/lm_deluge.egg-info/top_level.txt +0 -0
  77. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/tests/test_builtin_tools.py +0 -0
  78. {lm_deluge-0.0.39 → lm_deluge-0.0.41}/tests/test_native_mcp_server.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lm_deluge
3
- Version: 0.0.39
3
+ Version: 0.0.41
4
4
  Summary: Python utility for using LLM API models.
5
5
  Author-email: Benjamin Anderson <ben@trytaylor.ai>
6
6
  Requires-Python: >=3.10
@@ -111,14 +111,17 @@ await client.process_prompts_async(
111
111
 
112
112
  ### Queueing individual prompts
113
113
 
114
- You can queue prompts one at a time and track progress explicitly:
114
+ You can queue prompts one at a time and track progress explicitly. Iterate over
115
+ results as they finish with `as_completed` (or gather them all at once with
116
+ `wait_for_all`):
115
117
 
116
118
  ```python
117
119
  client = LLMClient("gpt-4.1-mini", progress="tqdm")
118
120
  client.open()
119
- task_id = client.start_nowait("hello there")
121
+ client.start_nowait("hello there")
120
122
  # ... queue more tasks ...
121
- results = await client.wait_for_all()
123
+ async for task_id, result in client.as_completed():
124
+ print(task_id, result.completion)
122
125
  client.close()
123
126
  ```
124
127
 
@@ -84,14 +84,17 @@ await client.process_prompts_async(
84
84
 
85
85
  ### Queueing individual prompts
86
86
 
87
- You can queue prompts one at a time and track progress explicitly:
87
+ You can queue prompts one at a time and track progress explicitly. Iterate over
88
+ results as they finish with `as_completed` (or gather them all at once with
89
+ `wait_for_all`):
88
90
 
89
91
  ```python
90
92
  client = LLMClient("gpt-4.1-mini", progress="tqdm")
91
93
  client.open()
92
- task_id = client.start_nowait("hello there")
94
+ client.start_nowait("hello there")
93
95
  # ... queue more tasks ...
94
- results = await client.wait_for_all()
96
+ async for task_id, result in client.as_completed():
97
+ print(task_id, result.completion)
95
98
  client.close()
96
99
  ```
97
100
 
@@ -3,7 +3,7 @@ requires = ["setuptools", "wheel"]
3
3
 
4
4
  [project]
5
5
  name = "lm_deluge"
6
- version = "0.0.39"
6
+ version = "0.0.41"
7
7
  authors = [{ name = "Benjamin Anderson", email = "ben@trytaylor.ai" }]
8
8
  description = "Python utility for using LLM API models."
9
9
  readme = "README.md"
@@ -1,21 +1,22 @@
1
- import os
1
+ import asyncio
2
2
  import json
3
+ import os
4
+ import tempfile
3
5
  import time
4
- import asyncio
6
+ from typing import Literal, Sequence
7
+
5
8
  import aiohttp
6
- import tempfile
7
- from lm_deluge.prompt import CachePattern, Conversation, prompts_to_conversations
8
- from lm_deluge.config import SamplingParams
9
- from lm_deluge.models import APIModel
10
- from typing import Sequence, Literal
11
- from lm_deluge.api_requests.openai import _build_oa_chat_request
12
- from lm_deluge.api_requests.anthropic import _build_anthropic_request
13
9
  from rich.console import Console
14
10
  from rich.live import Live
15
11
  from rich.spinner import Spinner
16
12
  from rich.table import Table
17
13
  from rich.text import Text
18
- from lm_deluge.models import registry
14
+
15
+ from lm_deluge.api_requests.anthropic import _build_anthropic_request
16
+ from lm_deluge.api_requests.openai import _build_oa_chat_request
17
+ from lm_deluge.config import SamplingParams
18
+ from lm_deluge.models import APIModel, registry
19
+ from lm_deluge.prompt import CachePattern, Conversation, prompts_to_conversations
19
20
  from lm_deluge.request_context import RequestContext
20
21
 
21
22
 
@@ -162,6 +163,91 @@ async def _submit_anthropic_batch(file_path: str, headers: dict, model: str):
162
163
  return batch_id
163
164
 
164
165
 
166
+ async def create_batch_files_oa(
167
+ model: str,
168
+ sampling_params: SamplingParams,
169
+ prompts: Sequence[str | list[dict] | Conversation],
170
+ batch_size: int = 50_000,
171
+ destination: str | None = None, # if none provided, temp files
172
+ ):
173
+ MAX_BATCH_SIZE_BYTES = 200 * 1024 * 1024 # 200MB
174
+ MAX_BATCH_SIZE_ITEMS = batch_size
175
+
176
+ prompts = prompts_to_conversations(prompts)
177
+ if any(p is None for p in prompts):
178
+ raise ValueError("All prompts must be valid.")
179
+
180
+ model_obj = APIModel.from_registry(model)
181
+
182
+ current_batch = []
183
+ current_batch_size = 0
184
+ file_paths = []
185
+
186
+ for idx, prompt in enumerate(prompts):
187
+ assert isinstance(prompt, Conversation)
188
+ context = RequestContext(
189
+ task_id=idx,
190
+ model_name=model,
191
+ prompt=prompt,
192
+ sampling_params=sampling_params,
193
+ )
194
+ request = {
195
+ "custom_id": str(idx),
196
+ "method": "POST",
197
+ "url": "/v1/chat/completions",
198
+ "body": await _build_oa_chat_request(model_obj, context),
199
+ }
200
+
201
+ # Calculate size of this request
202
+ request_json = json.dumps(request) + "\n"
203
+ request_size = len(request_json.encode("utf-8"))
204
+
205
+ # Check if adding this request would exceed limits
206
+ would_exceed_size = current_batch_size + request_size > MAX_BATCH_SIZE_BYTES
207
+ would_exceed_items = len(current_batch) >= MAX_BATCH_SIZE_ITEMS
208
+
209
+ if current_batch and (would_exceed_size or would_exceed_items):
210
+ # Submit current batch
211
+ def write_batch_file():
212
+ with tempfile.NamedTemporaryFile(
213
+ mode="w+", suffix=".jsonl", delete=False
214
+ ) as f:
215
+ for batch_request in current_batch:
216
+ json.dump(batch_request, f)
217
+ f.write("\n")
218
+ print("wrote", len(current_batch), "items")
219
+ return f.name
220
+
221
+ file_path = await asyncio.to_thread(write_batch_file)
222
+ file_paths.append(file_path)
223
+ # Start new batch
224
+ current_batch = []
225
+ current_batch_size = 0
226
+ # current_batch_start_idx = idx
227
+
228
+ # Add request to current batch
229
+ current_batch.append(request)
230
+ current_batch_size += request_size
231
+
232
+ # Submit final batch if it has items
233
+ if current_batch:
234
+
235
+ def write_final_batch_file():
236
+ with tempfile.NamedTemporaryFile(
237
+ mode="w+", suffix=".jsonl", delete=False
238
+ ) as f:
239
+ for batch_request in current_batch:
240
+ json.dump(batch_request, f)
241
+ f.write("\n")
242
+ print("wrote", len(current_batch), "items")
243
+ return f.name
244
+
245
+ file_path = await asyncio.to_thread(write_final_batch_file)
246
+ file_paths.append(file_path)
247
+
248
+ return file_paths
249
+
250
+
165
251
  async def submit_batches_oa(
166
252
  model: str,
167
253
  sampling_params: SamplingParams,
@@ -1,6 +1,6 @@
1
1
  import asyncio
2
2
  import random
3
- from typing import Any, Callable, Literal, Self, Sequence, overload
3
+ from typing import Any, AsyncGenerator, Callable, Literal, Self, Sequence, overload
4
4
 
5
5
  import numpy as np
6
6
  import yaml
@@ -356,16 +356,16 @@ class _LLMClient(BaseModel):
356
356
  prompts = prompts_to_conversations(prompts)
357
357
  ids = list(range(len(prompts)))
358
358
  results: list[APIResponse | None] = [None for _ in range(len(prompts))]
359
-
360
- # Create StatusTracker
361
- tracker = StatusTracker(
362
- max_requests_per_minute=self.max_requests_per_minute,
363
- max_tokens_per_minute=self.max_tokens_per_minute,
364
- max_concurrent_requests=self.max_concurrent_requests,
365
- progress_style=self.progress,
366
- use_progress_bar=show_progress,
367
- )
368
- tracker.init_progress_bar(total=len(prompts), disable=not show_progress)
359
+ # Use existing tracker if client has been opened; otherwise open/close automatically
360
+ tracker: StatusTracker
361
+ tracker_preopened = self._tracker is not None
362
+ if tracker_preopened:
363
+ tracker = self._tracker # type: ignore[assignment]
364
+ tracker.add_to_total(len(prompts))
365
+ else:
366
+ self.open(total=len(prompts), show_progress=show_progress)
367
+ tracker = self._tracker # type: ignore[assignment]
368
+ assert tracker is not None
369
369
 
370
370
  # Create retry queue for failed requests
371
371
  retry_queue: asyncio.Queue[RequestContext] = asyncio.Queue()
@@ -458,7 +458,8 @@ class _LLMClient(BaseModel):
458
458
  # Sleep - original logic
459
459
  await asyncio.sleep(seconds_to_sleep_each_loop + tracker.seconds_to_pause)
460
460
 
461
- tracker.log_final_status()
461
+ if not tracker_preopened:
462
+ self.close()
462
463
 
463
464
  if return_completions_only:
464
465
  return [r.completion if r is not None else None for r in results]
@@ -557,6 +558,42 @@ class _LLMClient(BaseModel):
557
558
  task_ids = list(self._tasks.keys())
558
559
  return [await self.wait_for(tid) for tid in task_ids]
559
560
 
561
+ async def as_completed(
562
+ self, task_ids: Sequence[int] | None = None
563
+ ) -> AsyncGenerator[tuple[int, APIResponse | None], None]:
564
+ """Yield ``(task_id, result)`` pairs as tasks complete.
565
+
566
+ Args:
567
+ task_ids: Optional sequence of task IDs to wait on. If ``None``,
568
+ all queued tasks are watched.
569
+
570
+ Yields:
571
+ Tuples of task ID and ``APIResponse`` as each task finishes.
572
+ """
573
+
574
+ if task_ids is None:
575
+ tasks_map: dict[asyncio.Task, int] = {
576
+ task: tid for tid, task in self._tasks.items()
577
+ }
578
+ else:
579
+ tasks_map = {
580
+ self._tasks[tid]: tid for tid in task_ids if tid in self._tasks
581
+ }
582
+
583
+ # Yield any tasks that have already completed
584
+ for task in list(tasks_map.keys()):
585
+ if task.done():
586
+ tid = tasks_map.pop(task)
587
+ yield tid, self._results.get(tid, await task)
588
+
589
+ while tasks_map:
590
+ done, _ = await asyncio.wait(
591
+ set(tasks_map.keys()), return_when=asyncio.FIRST_COMPLETED
592
+ )
593
+ for task in done:
594
+ tid = tasks_map.pop(task)
595
+ yield tid, self._results.get(tid, await task)
596
+
560
597
  async def stream(
561
598
  self,
562
599
  prompt: str | Conversation,
@@ -14,7 +14,7 @@ from rich.progress import (
14
14
  TextColumn,
15
15
  )
16
16
  from rich.text import Text
17
- from tqdm import tqdm
17
+ from tqdm.auto import tqdm
18
18
 
19
19
  SECONDS_TO_PAUSE_AFTER_RATE_LIMIT_ERROR = 5
20
20
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lm_deluge
3
- Version: 0.0.39
3
+ Version: 0.0.41
4
4
  Summary: Python utility for using LLM API models.
5
5
  Author-email: Benjamin Anderson <ben@trytaylor.ai>
6
6
  Requires-Python: >=3.10
@@ -111,14 +111,17 @@ await client.process_prompts_async(
111
111
 
112
112
  ### Queueing individual prompts
113
113
 
114
- You can queue prompts one at a time and track progress explicitly:
114
+ You can queue prompts one at a time and track progress explicitly. Iterate over
115
+ results as they finish with `as_completed` (or gather them all at once with
116
+ `wait_for_all`):
115
117
 
116
118
  ```python
117
119
  client = LLMClient("gpt-4.1-mini", progress="tqdm")
118
120
  client.open()
119
- task_id = client.start_nowait("hello there")
121
+ client.start_nowait("hello there")
120
122
  # ... queue more tasks ...
121
- results = await client.wait_for_all()
123
+ async for task_id, result in client.as_completed():
124
+ print(task_id, result.completion)
122
125
  client.close()
123
126
  ```
124
127
 
File without changes
File without changes