lm-deluge 0.0.12__py3-none-any.whl → 0.0.14__py3-none-any.whl

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.

@@ -0,0 +1,75 @@
1
+ from typing import Literal
2
+
3
+ ToolVersion = Literal["2024-10-22", "2025-01-24", "2025-04-29"]
4
+ ToolType = Literal["bash", "computer", "editor"]
5
+
6
+
7
+ def model_to_version(model: str) -> ToolVersion:
8
+ if "opus" not in model and "sonnet" not in model:
9
+ raise ValueError("cannot use computer tools with incompatible model")
10
+ if "claude-4" in model:
11
+ return "2025-04-29"
12
+ elif "3.7" in model:
13
+ return "2025-01-24"
14
+ else:
15
+ return "2024-10-22"
16
+
17
+
18
+ def get_anthropic_cu_tools(
19
+ model: str,
20
+ display_width: int,
21
+ display_height: int,
22
+ exclude_tools: list[ToolType] | None = None,
23
+ ):
24
+ version = model_to_version(model)
25
+ if version == "2024-10-22":
26
+ result = [
27
+ {
28
+ "name": "computer",
29
+ "type": "computer_20241022",
30
+ "display_width_px": display_width,
31
+ "display_height_px": display_height,
32
+ "display_number": None,
33
+ },
34
+ {"name": "str_replace_editor", "type": "text_editor_20250429"},
35
+ {"type": "bash_20250124", "name": "bash"},
36
+ ]
37
+ elif version == "2025-01-24":
38
+ result = [
39
+ {
40
+ "name": "computer",
41
+ "type": "computer_20250124",
42
+ "display_width_px": display_width,
43
+ "display_height_px": display_height,
44
+ "display_number": None,
45
+ },
46
+ {"name": "str_replace_editor", "type": "text_editor_20250124"},
47
+ {"type": "bash_20250124", "name": "bash"},
48
+ ]
49
+ elif version == "2025-04-29":
50
+ result = [
51
+ {
52
+ "name": "computer",
53
+ "type": "computer_20250124",
54
+ "display_width_px": display_width,
55
+ "display_height_px": display_height,
56
+ "display_number": None,
57
+ },
58
+ {"name": "str_replace_based_edit_tool", "type": "text_editor_20250429"},
59
+ {
60
+ "name": "bash",
61
+ "type": "bash_20250124",
62
+ },
63
+ ]
64
+ else:
65
+ raise ValueError("invalid tool version")
66
+
67
+ if exclude_tools is None:
68
+ return result
69
+ if "bash" in exclude_tools:
70
+ result = [x for x in result if x["name"] != "bash"]
71
+ if "editor" in exclude_tools:
72
+ result = [x for x in result if "edit" not in x["name"]]
73
+ if "computer" in exclude_tools:
74
+ result = [x for x in result if "computer" not in x["name"]]
75
+ return result
@@ -1,14 +1,16 @@
1
- from dataclasses import dataclass
2
1
  from typing import Literal
3
2
 
3
+ from pydantic import BaseModel
4
4
 
5
- @dataclass
6
- class SamplingParams:
5
+
6
+ class SamplingParams(BaseModel):
7
7
  temperature: float = 0.0
8
8
  top_p: float = 1.0
9
9
  json_mode: bool = False
10
10
  max_new_tokens: int = 512
11
- reasoning_effort: Literal["low", "medium", "high", None] = None
11
+ reasoning_effort: Literal["low", "medium", "high", "none", None] = None
12
+ logprobs: bool = False
13
+ top_logprobs: int | None = None
12
14
 
13
15
  def to_vllm(self):
14
16
  try:
@@ -23,3 +25,9 @@ class SamplingParams:
23
25
  top_p=self.top_p,
24
26
  max_tokens=self.max_new_tokens,
25
27
  )
28
+
29
+
30
+ class ComputerUseParams(BaseModel):
31
+ enabled: bool = False
32
+ display_width: int = 1024
33
+ display_height: int = 768
lm_deluge/embed.py CHANGED
@@ -1,12 +1,14 @@
1
1
  ### specific utility for cohere rerank api
2
- import os
3
- import numpy as np
4
- import aiohttp
5
- from tqdm.auto import tqdm
6
2
  import asyncio
3
+ import os
7
4
  import time
8
- from typing import Any
9
5
  from dataclasses import dataclass
6
+ from typing import Any
7
+
8
+ import aiohttp
9
+ import numpy as np
10
+ from tqdm.auto import tqdm
11
+
10
12
  from .tracker import StatusTracker
11
13
 
12
14
  registry = {
@@ -56,7 +58,6 @@ class EmbeddingRequest:
56
58
  texts: list[str],
57
59
  attempts_left: int,
58
60
  status_tracker: StatusTracker,
59
- retry_queue: asyncio.Queue,
60
61
  request_timeout: int,
61
62
  pbar: tqdm | None = None,
62
63
  **kwargs, # openai or cohere specific params
@@ -66,7 +67,6 @@ class EmbeddingRequest:
66
67
  self.texts = texts
67
68
  self.attempts_left = attempts_left
68
69
  self.status_tracker = status_tracker
69
- self.retry_queue = retry_queue
70
70
  self.request_timeout = request_timeout
71
71
  self.pbar = pbar
72
72
  self.result = []
@@ -89,7 +89,8 @@ class EmbeddingRequest:
89
89
  print(error_to_print)
90
90
  if self.attempts_left > 0:
91
91
  self.attempts_left -= 1
92
- self.retry_queue.put_nowait(self)
92
+ assert self.status_tracker.retry_queue
93
+ self.status_tracker.retry_queue.put_nowait(self)
93
94
  return
94
95
  else:
95
96
  print(f"Task {self.task_id} out of tries.")
@@ -243,7 +244,11 @@ async def embed_parallel_async(
243
244
 
244
245
  # initialize trackers
245
246
  retry_queue = asyncio.Queue()
246
- status_tracker = StatusTracker()
247
+ status_tracker = StatusTracker(
248
+ max_tokens_per_minute=10_000_000,
249
+ max_requests_per_minute=max_requests_per_minute,
250
+ max_concurrent_requests=1_000,
251
+ )
247
252
  next_request = None # variable to hold the next request to call
248
253
 
249
254
  # initialize available capacity counts
@@ -262,7 +267,8 @@ async def embed_parallel_async(
262
267
  while True:
263
268
  # get next request (if one is not already waiting for capacity)
264
269
  if next_request is None:
265
- if not retry_queue.empty():
270
+ assert status_tracker.retry_queue
271
+ if not status_tracker.retry_queue.empty():
266
272
  next_request = retry_queue.get_nowait()
267
273
  print(f"Retrying request {next_request.task_id}.")
268
274
  elif prompts_not_finished:
@@ -285,7 +291,7 @@ async def embed_parallel_async(
285
291
 
286
292
  except StopIteration:
287
293
  prompts_not_finished = False
288
- print("API requests finished, only retries remain.")
294
+ # print("API requests finished, only retries remain.")
289
295
 
290
296
  # update available capacity
291
297
  current_time = time.time()
lm_deluge/file.py ADDED
@@ -0,0 +1,149 @@
1
+ import os
2
+ import io
3
+ import requests
4
+ import base64
5
+ import mimetypes
6
+ import xxhash
7
+ from dataclasses import dataclass, field
8
+ from pathlib import Path
9
+
10
+
11
+ @dataclass(slots=True)
12
+ class File:
13
+ # raw bytes, pathlike, http url, base64 data url, or file_id
14
+ data: bytes | io.BytesIO | Path | str
15
+ media_type: str | None = None # inferred if None
16
+ filename: str | None = None # optional filename for uploads
17
+ file_id: str | None = None # for OpenAI file uploads or Anthropic file API
18
+ type: str = field(init=False, default="file")
19
+
20
+ # helpers -----------------------------------------------------------------
21
+ def _bytes(self) -> bytes:
22
+ if isinstance(self.data, bytes):
23
+ return self.data
24
+ elif isinstance(self.data, io.BytesIO):
25
+ return self.data.getvalue()
26
+ elif isinstance(self.data, str) and self.data.startswith("http"):
27
+ res = requests.get(self.data)
28
+ res.raise_for_status()
29
+ return res.content
30
+ elif isinstance(self.data, str) and os.path.exists(self.data):
31
+ with open(self.data, "rb") as f:
32
+ return f.read()
33
+ elif isinstance(self.data, Path) and self.data.exists():
34
+ return Path(self.data).read_bytes()
35
+ elif isinstance(self.data, str) and self.data.startswith("data:"):
36
+ header, encoded = self.data.split(",", 1)
37
+ return base64.b64decode(encoded)
38
+ else:
39
+ raise ValueError("unreadable file format")
40
+
41
+ def _mime(self) -> str:
42
+ if self.media_type:
43
+ return self.media_type
44
+ if isinstance(self.data, (Path, str)):
45
+ # For URL or path, try to guess from the string
46
+ path_str = str(self.data)
47
+ guess = mimetypes.guess_type(path_str)[0]
48
+ if guess:
49
+ return guess
50
+ return "application/pdf" # default to PDF
51
+
52
+ def _filename(self) -> str:
53
+ if self.filename:
54
+ return self.filename
55
+ if isinstance(self.data, (Path, str)):
56
+ path_str = str(self.data)
57
+ if path_str.startswith("http"):
58
+ # Extract filename from URL
59
+ return path_str.split("/")[-1].split("?")[0] or "document.pdf"
60
+ else:
61
+ # Extract from local path
62
+ return os.path.basename(path_str) or "document.pdf"
63
+ return "document.pdf"
64
+
65
+ def _base64(self, include_header: bool = True) -> str:
66
+ encoded = base64.b64encode(self._bytes()).decode("utf-8")
67
+ if not include_header:
68
+ return encoded
69
+ return f"data:{self._mime()};base64,{encoded}"
70
+
71
+ @property
72
+ def fingerprint(self) -> str:
73
+ # Hash the file contents for fingerprinting
74
+ file_bytes = self._bytes()
75
+ return xxhash.xxh64(file_bytes).hexdigest()
76
+
77
+ @property
78
+ def size(self) -> int:
79
+ """Return file size in bytes."""
80
+ return len(self._bytes())
81
+
82
+ # ── provider-specific emission ────────────────────────────────────────────
83
+ def oa_chat(self) -> dict:
84
+ """For OpenAI Chat Completions - file content as base64 or file_id."""
85
+ if self.file_id:
86
+ return {
87
+ "type": "file",
88
+ "file": {
89
+ "file_id": self.file_id,
90
+ },
91
+ }
92
+ else:
93
+ return {
94
+ "type": "file",
95
+ "file": {
96
+ "filename": self._filename(),
97
+ "file_data": self._base64(),
98
+ },
99
+ }
100
+
101
+ def oa_resp(self) -> dict:
102
+ """For OpenAI Responses API - file content as base64 or file_id."""
103
+ if self.file_id:
104
+ return {
105
+ "type": "input_file",
106
+ "file_id": self.file_id,
107
+ }
108
+ else:
109
+ return {
110
+ "type": "input_file",
111
+ "filename": self._filename(),
112
+ "file_data": self._base64(),
113
+ }
114
+
115
+ def anthropic(self) -> dict:
116
+ """For Anthropic Messages API - file content as base64 or file_id."""
117
+ if self.file_id:
118
+ return {
119
+ "type": "document",
120
+ "source": {
121
+ "type": "file",
122
+ "file_id": self.file_id,
123
+ },
124
+ }
125
+ else:
126
+ b64 = base64.b64encode(self._bytes()).decode()
127
+ return {
128
+ "type": "document",
129
+ "source": {
130
+ "type": "base64",
131
+ "media_type": self._mime(),
132
+ "data": b64,
133
+ },
134
+ }
135
+
136
+ def anthropic_file_upload(self) -> tuple[str, bytes, str]:
137
+ """For Anthropic Files API - return tuple for file upload."""
138
+ filename = self._filename()
139
+ content = self._bytes()
140
+ media_type = self._mime()
141
+ return filename, content, media_type
142
+
143
+ def gemini(self) -> dict:
144
+ """For Gemini API - not yet supported."""
145
+ raise NotImplementedError("File support for Gemini is not yet implemented")
146
+
147
+ def mistral(self) -> dict:
148
+ """For Mistral API - not yet supported."""
149
+ raise NotImplementedError("File support for Mistral is not yet implemented")
lm_deluge/models.py CHANGED
@@ -178,6 +178,21 @@ registry = {
178
178
  # ░███
179
179
  # █████
180
180
  # ░░░░░
181
+ "openai-computer-use-preview": {
182
+ "id": "openai-computer-use-preview",
183
+ "name": "computer-use-preview",
184
+ "api_base": "https://api.openai.com/v1",
185
+ "api_key_env_var": "OPENAI_API_KEY",
186
+ "supports_json": True,
187
+ "supports_logprobs": False,
188
+ "supports_responses": True,
189
+ "api_spec": "openai-responses",
190
+ "input_cost": 2.0,
191
+ "output_cost": 8.0,
192
+ "requests_per_minute": 20,
193
+ "tokens_per_minute": 100_000,
194
+ "reasoning_model": False,
195
+ },
181
196
  "o3": {
182
197
  "id": "o3",
183
198
  "name": "o3-2025-04-16",
@@ -185,6 +200,7 @@ registry = {
185
200
  "api_key_env_var": "OPENAI_API_KEY",
186
201
  "supports_json": False,
187
202
  "supports_logprobs": True,
203
+ "supports_responses": True,
188
204
  "api_spec": "openai",
189
205
  "input_cost": 10.0,
190
206
  "output_cost": 40.0,
@@ -199,6 +215,7 @@ registry = {
199
215
  "api_key_env_var": "OPENAI_API_KEY",
200
216
  "supports_json": False,
201
217
  "supports_logprobs": True,
218
+ "supports_responses": True,
202
219
  "api_spec": "openai",
203
220
  "input_cost": 1.1,
204
221
  "output_cost": 4.4,
@@ -213,6 +230,7 @@ registry = {
213
230
  "api_key_env_var": "OPENAI_API_KEY",
214
231
  "supports_json": True,
215
232
  "supports_logprobs": True,
233
+ "supports_responses": True,
216
234
  "api_spec": "openai",
217
235
  "input_cost": 2.0,
218
236
  "output_cost": 8.0,
@@ -227,6 +245,7 @@ registry = {
227
245
  "api_key_env_var": "OPENAI_API_KEY",
228
246
  "supports_json": True,
229
247
  "supports_logprobs": True,
248
+ "supports_responses": True,
230
249
  "api_spec": "openai",
231
250
  "input_cost": 0.4,
232
251
  "output_cost": 1.6,
@@ -241,6 +260,7 @@ registry = {
241
260
  "api_key_env_var": "OPENAI_API_KEY",
242
261
  "supports_json": True,
243
262
  "supports_logprobs": True,
263
+ "supports_responses": True,
244
264
  "api_spec": "openai",
245
265
  "input_cost": 0.1,
246
266
  "output_cost": 0.4,
@@ -255,6 +275,7 @@ registry = {
255
275
  "api_key_env_var": "OPENAI_API_KEY",
256
276
  "supports_json": False,
257
277
  "supports_logprobs": True,
278
+ "supports_responses": True,
258
279
  "api_spec": "openai",
259
280
  "input_cost": 75.0,
260
281
  "output_cost": 150.0,
@@ -269,6 +290,7 @@ registry = {
269
290
  "api_key_env_var": "OPENAI_API_KEY",
270
291
  "supports_json": False,
271
292
  "supports_logprobs": True,
293
+ "supports_responses": True,
272
294
  "api_spec": "openai",
273
295
  "input_cost": 1.1,
274
296
  "output_cost": 4.4,
@@ -283,6 +305,7 @@ registry = {
283
305
  "api_key_env_var": "OPENAI_API_KEY",
284
306
  "supports_json": False,
285
307
  "supports_logprobs": True,
308
+ "supports_responses": True,
286
309
  "api_spec": "openai",
287
310
  "input_cost": 15.0,
288
311
  "output_cost": 60.0,
@@ -297,6 +320,7 @@ registry = {
297
320
  "api_key_env_var": "OPENAI_API_KEY",
298
321
  "supports_json": False,
299
322
  "supports_logprobs": True,
323
+ "supports_responses": True,
300
324
  "api_spec": "openai",
301
325
  "input_cost": 15.0,
302
326
  "output_cost": 60.0,
@@ -311,6 +335,7 @@ registry = {
311
335
  "api_key_env_var": "OPENAI_API_KEY",
312
336
  "supports_json": False,
313
337
  "supports_logprobs": True,
338
+ "supports_responses": True,
314
339
  "api_spec": "openai",
315
340
  "input_cost": 3.0,
316
341
  "output_cost": 15.0,
@@ -325,6 +350,7 @@ registry = {
325
350
  "api_key_env_var": "OPENAI_API_KEY",
326
351
  "supports_json": True,
327
352
  "supports_logprobs": True,
353
+ "supports_responses": True,
328
354
  "api_spec": "openai",
329
355
  "input_cost": 5.0,
330
356
  "output_cost": 15.0,
@@ -338,6 +364,7 @@ registry = {
338
364
  "api_key_env_var": "OPENAI_API_KEY",
339
365
  "supports_json": True,
340
366
  "supports_logprobs": True,
367
+ "supports_responses": True,
341
368
  "api_spec": "openai",
342
369
  "input_cost": 0.15,
343
370
  "output_cost": 0.6,
@@ -351,6 +378,7 @@ registry = {
351
378
  "api_key_env_var": "OPENAI_API_KEY",
352
379
  "supports_json": True,
353
380
  "supports_logprobs": True,
381
+ "supports_responses": True,
354
382
  "api_spec": "openai",
355
383
  "input_cost": 0.0,
356
384
  "output_cost": 0.0,
@@ -364,6 +392,7 @@ registry = {
364
392
  "api_key_env_var": "OPENAI_API_KEY",
365
393
  "supports_json": True,
366
394
  "supports_logprobs": True,
395
+ "supports_responses": True,
367
396
  "api_spec": "openai",
368
397
  "input_cost": 0.5,
369
398
  "output_cost": 1.5,
@@ -377,6 +406,7 @@ registry = {
377
406
  "api_key_env_var": "OPENAI_API_KEY",
378
407
  "supports_json": True,
379
408
  "supports_logprobs": True,
409
+ "supports_responses": True,
380
410
  "api_spec": "openai",
381
411
  "input_cost": 10.0,
382
412
  "output_cost": 30.0,
@@ -390,6 +420,7 @@ registry = {
390
420
  "api_key_env_var": "OPENAI_API_KEY",
391
421
  "supports_json": False,
392
422
  "supports_logprobs": False,
423
+ "supports_responses": True,
393
424
  "api_spec": "openai",
394
425
  "input_cost": 30.0,
395
426
  "output_cost": 60.0,
@@ -403,6 +434,7 @@ registry = {
403
434
  "api_key_env_var": "OPENAI_API_KEY",
404
435
  "supports_json": False,
405
436
  "supports_logprobs": False,
437
+ "supports_responses": True,
406
438
  "api_spec": "openai",
407
439
  "input_cost": 60.0,
408
440
  "output_cost": 120.0,
@@ -1093,6 +1125,7 @@ class APIModel:
1093
1125
  output_cost: float | None = 0 # $ per million output tokens
1094
1126
  supports_json: bool = False
1095
1127
  supports_logprobs: bool = False
1128
+ supports_responses: bool = False
1096
1129
  reasoning_model: bool = False
1097
1130
  regions: list[str] | dict[str, int] = field(default_factory=list)
1098
1131
  tokens_per_minute: int | None = None