lm-deluge 0.0.56__py3-none-any.whl → 0.0.69__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.
Files changed (38) hide show
  1. lm_deluge/__init__.py +12 -1
  2. lm_deluge/api_requests/anthropic.py +12 -1
  3. lm_deluge/api_requests/base.py +87 -5
  4. lm_deluge/api_requests/bedrock.py +3 -4
  5. lm_deluge/api_requests/chat_reasoning.py +4 -0
  6. lm_deluge/api_requests/gemini.py +7 -6
  7. lm_deluge/api_requests/mistral.py +8 -9
  8. lm_deluge/api_requests/openai.py +179 -124
  9. lm_deluge/batches.py +25 -9
  10. lm_deluge/client.py +280 -67
  11. lm_deluge/config.py +1 -1
  12. lm_deluge/file.py +382 -13
  13. lm_deluge/mock_openai.py +482 -0
  14. lm_deluge/models/__init__.py +12 -8
  15. lm_deluge/models/anthropic.py +12 -20
  16. lm_deluge/models/bedrock.py +0 -14
  17. lm_deluge/models/cohere.py +0 -16
  18. lm_deluge/models/google.py +0 -20
  19. lm_deluge/models/grok.py +48 -4
  20. lm_deluge/models/groq.py +2 -2
  21. lm_deluge/models/kimi.py +34 -0
  22. lm_deluge/models/meta.py +0 -8
  23. lm_deluge/models/minimax.py +10 -0
  24. lm_deluge/models/openai.py +28 -34
  25. lm_deluge/models/openrouter.py +64 -1
  26. lm_deluge/models/together.py +0 -16
  27. lm_deluge/prompt.py +138 -29
  28. lm_deluge/request_context.py +9 -11
  29. lm_deluge/tool.py +395 -19
  30. lm_deluge/tracker.py +11 -5
  31. lm_deluge/warnings.py +46 -0
  32. {lm_deluge-0.0.56.dist-info → lm_deluge-0.0.69.dist-info}/METADATA +3 -1
  33. {lm_deluge-0.0.56.dist-info → lm_deluge-0.0.69.dist-info}/RECORD +36 -33
  34. lm_deluge/agent.py +0 -0
  35. lm_deluge/gemini_limits.py +0 -65
  36. {lm_deluge-0.0.56.dist-info → lm_deluge-0.0.69.dist-info}/WHEEL +0 -0
  37. {lm_deluge-0.0.56.dist-info → lm_deluge-0.0.69.dist-info}/licenses/LICENSE +0 -0
  38. {lm_deluge-0.0.56.dist-info → lm_deluge-0.0.69.dist-info}/top_level.txt +0 -0
@@ -20,8 +20,6 @@ TOGETHER_MODELS = {
20
20
  "api_spec": "openai",
21
21
  "input_cost": 3.0,
22
22
  "output_cost": 7.0,
23
- "requests_per_minute": None,
24
- "tokens_per_minute": None,
25
23
  },
26
24
  "deepseek-v3-together": {
27
25
  "id": "deepseek-v3-together",
@@ -32,8 +30,6 @@ TOGETHER_MODELS = {
32
30
  "api_spec": "openai",
33
31
  "input_cost": 1.25,
34
32
  "output_cost": 1.25,
35
- "requests_per_minute": None,
36
- "tokens_per_minute": None,
37
33
  },
38
34
  "qwen-3-235b-together": {
39
35
  "id": "qwen-3-235b-together",
@@ -44,8 +40,6 @@ TOGETHER_MODELS = {
44
40
  "api_spec": "openai",
45
41
  "input_cost": 0.2,
46
42
  "output_cost": 0.6,
47
- "requests_per_minute": None,
48
- "tokens_per_minute": None,
49
43
  },
50
44
  "qwen-2.5-vl-together": {
51
45
  "id": "qwen-2.5-vl-together",
@@ -56,8 +50,6 @@ TOGETHER_MODELS = {
56
50
  "api_spec": "openai",
57
51
  "input_cost": 1.95,
58
52
  "output_cost": 8.0,
59
- "requests_per_minute": None,
60
- "tokens_per_minute": None,
61
53
  },
62
54
  "llama-4-maverick-together": {
63
55
  "id": "llama-4-maverick-together",
@@ -68,8 +60,6 @@ TOGETHER_MODELS = {
68
60
  "api_spec": "openai",
69
61
  "input_cost": 0.27,
70
62
  "output_cost": 0.85,
71
- "requests_per_minute": None,
72
- "tokens_per_minute": None,
73
63
  },
74
64
  "llama-4-scout-together": {
75
65
  "id": "llama-4-scout-together",
@@ -80,8 +70,6 @@ TOGETHER_MODELS = {
80
70
  "api_spec": "openai",
81
71
  "input_cost": 0.18,
82
72
  "output_cost": 0.59,
83
- "requests_per_minute": None,
84
- "tokens_per_minute": None,
85
73
  },
86
74
  "gpt-oss-120b-together": {
87
75
  "id": "gpt-oss-120b-together",
@@ -92,8 +80,6 @@ TOGETHER_MODELS = {
92
80
  "api_spec": "openai",
93
81
  "input_cost": 0.18,
94
82
  "output_cost": 0.59,
95
- "requests_per_minute": None,
96
- "tokens_per_minute": None,
97
83
  "reasoning_model": True,
98
84
  },
99
85
  "gpt-oss-20b-together": {
@@ -105,8 +91,6 @@ TOGETHER_MODELS = {
105
91
  "api_spec": "openai",
106
92
  "input_cost": 0.18,
107
93
  "output_cost": 0.59,
108
- "requests_per_minute": None,
109
- "tokens_per_minute": None,
110
94
  "reasoning_model": True,
111
95
  },
112
96
  }
lm_deluge/prompt.py CHANGED
@@ -2,13 +2,14 @@ import io
2
2
  import json
3
3
  from dataclasses import dataclass, field
4
4
  from pathlib import Path
5
- from typing import Literal, Sequence
5
+ from typing import Literal, Sequence, TypeAlias
6
6
 
7
7
  import tiktoken
8
8
  import xxhash
9
9
 
10
10
  from lm_deluge.file import File
11
11
  from lm_deluge.image import Image, MediaType
12
+ from lm_deluge.warnings import deprecated
12
13
 
13
14
  CachePattern = Literal[
14
15
  "tools_only",
@@ -144,8 +145,8 @@ class ToolResult:
144
145
  def oa_chat(
145
146
  self,
146
147
  ) -> dict: # OpenAI Chat Completions - tool results are separate messages
147
- print("serializing toolresult with oa_chat...")
148
- print("typeof self.result:", type(self.result))
148
+ # print("serializing toolresult with oa_chat...")
149
+ # print("typeof self.result:", type(self.result))
149
150
  if isinstance(self.result, str):
150
151
  return {
151
152
  "role": "tool",
@@ -174,8 +175,7 @@ class ToolResult:
174
175
  raise ValueError("result type not supported")
175
176
 
176
177
  def oa_resp(self) -> dict: # OpenAI Responses
177
- print("serializing toolresult with oa_chat...")
178
- print("typeof self.result:", type(self.result))
178
+ # print("typeof self.result:", type(self.result))
179
179
  # if normal (not built-in just return the regular output
180
180
  if not self.built_in:
181
181
  result = (
@@ -329,6 +329,18 @@ class Message:
329
329
  """Get all thinking parts with proper typing."""
330
330
  return [part for part in self.parts if part.type == "thinking"] # type: ignore
331
331
 
332
+ # @staticmethod
333
+ # def dump_part(part: Part):
334
+ # if isinstance(value, Text):
335
+ # return {"type": "text", "text": value.text}
336
+ # if isinstance(value, Image):
337
+ # w, h = value.size
338
+ # return {"type": "image", "tag": f"<Image ({w}×{h})>"}
339
+ # if isinstance(value, File):
340
+ # size = value.size
341
+ # return {"type": "file", "tag": f"<File ({size} bytes)>"}
342
+ # return repr(value)
343
+
332
344
  def to_log(self) -> dict:
333
345
  """
334
346
  Return a JSON-serialisable dict that fully captures the message.
@@ -416,12 +428,17 @@ class Message:
416
428
 
417
429
  return cls(role, parts)
418
430
 
419
- def add_text(self, content: str) -> "Message":
431
+ def with_text(self, content: str) -> "Message":
420
432
  """Append a text block and return self for chaining."""
421
433
  self.parts.append(Text(content))
422
434
  return self
423
435
 
424
- def add_image(
436
+ @deprecated("with_text")
437
+ def add_text(self, content: str) -> "Message":
438
+ """Append a text block and return self for chaining."""
439
+ return self.with_text(content)
440
+
441
+ def with_image(
425
442
  self,
426
443
  data: bytes | str | Path | io.BytesIO | Image,
427
444
  *,
@@ -447,9 +464,50 @@ class Message:
447
464
  self.parts.append(img)
448
465
  return self
449
466
 
467
+ @deprecated("with_image")
468
+ def add_image(
469
+ self,
470
+ data: bytes | str | Path | io.BytesIO | Image,
471
+ *,
472
+ media_type: MediaType | None = None,
473
+ detail: Literal["low", "high", "auto"] = "auto",
474
+ max_size: int | None = None,
475
+ ) -> "Message":
476
+ """
477
+ Append an image block and return self for chaining.
478
+
479
+ If max_size is provided, the image will be resized so that its longer
480
+ dimension equals max_size, but only if the longer dimension is currently
481
+ larger than max_size.
482
+ """
483
+ return self.with_image(
484
+ data=data, media_type=media_type, detail=detail, max_size=max_size
485
+ )
486
+
487
+ def with_file(
488
+ self,
489
+ data: bytes | str | Path | io.BytesIO | File,
490
+ *,
491
+ media_type: str | None = None,
492
+ filename: str | None = None,
493
+ # remote: bool = False,
494
+ # provider: Literal["openai", "anthropic", "google"] | None = None,
495
+ ) -> "Message":
496
+ """
497
+ Append a file block and return self for chaining.
498
+ """
499
+ if not isinstance(data, File):
500
+ file = File(data, media_type=media_type, filename=filename)
501
+ else:
502
+ file = data
503
+
504
+ self.parts.append(file)
505
+ return self
506
+
507
+ @deprecated("with_file")
450
508
  def add_file(
451
509
  self,
452
- data: bytes | str | Path | io.BytesIO,
510
+ data: bytes | str | Path | io.BytesIO | File,
453
511
  *,
454
512
  media_type: str | None = None,
455
513
  filename: str | None = None,
@@ -457,27 +515,66 @@ class Message:
457
515
  """
458
516
  Append a file block and return self for chaining.
459
517
  """
460
- file = File(data, media_type=media_type, filename=filename)
518
+ return self.with_file(data, media_type=media_type, filename=filename)
519
+
520
+ async def with_remote_file(
521
+ self,
522
+ data: bytes | str | Path | io.BytesIO | File,
523
+ *,
524
+ media_type: str | None = None,
525
+ filename: str | None = None,
526
+ provider: Literal["openai", "anthropic", "google"] = "openai",
527
+ ):
528
+ if not isinstance(data, File):
529
+ file = File(data, media_type=media_type, filename=filename)
530
+ else:
531
+ file = data
532
+
533
+ if not file.is_remote:
534
+ file = await file.as_remote(provider=provider)
535
+ else:
536
+ if file.remote_provider != provider:
537
+ raise ValueError(
538
+ f"File is already remote with provider {file.remote_provider}, cannot change provider"
539
+ )
540
+
461
541
  self.parts.append(file)
462
542
  return self
463
543
 
464
- def add_tool_call(self, id: str, name: str, arguments: dict) -> "Message":
544
+ def with_tool_call(self, id: str, name: str, arguments: dict) -> "Message":
465
545
  """Append a tool call block and return self for chaining."""
466
546
  self.parts.append(ToolCall(id=id, name=name, arguments=arguments))
467
547
  return self
468
548
 
469
- def add_tool_result(
549
+ @deprecated("with_tool_call")
550
+ def add_tool_call(self, id: str, name: str, arguments: dict) -> "Message":
551
+ """Append a tool call block and return self for chaining."""
552
+ return self.with_tool_call(id, name, arguments)
553
+
554
+ def with_tool_result(
470
555
  self, tool_call_id: str, result: str | list[ToolResultPart]
471
556
  ) -> "Message":
472
557
  """Append a tool result block and return self for chaining."""
473
558
  self.parts.append(ToolResult(tool_call_id=tool_call_id, result=result))
474
559
  return self
475
560
 
476
- def add_thinking(self, content: str) -> "Message":
561
+ @deprecated("with_tool_result")
562
+ def add_tool_result(
563
+ self, tool_call_id: str, result: str | list[ToolResultPart]
564
+ ) -> "Message":
565
+ """Append a tool result block and return self for chaining."""
566
+ return self.with_tool_result(tool_call_id, result)
567
+
568
+ def with_thinking(self, content: str) -> "Message":
477
569
  """Append a thinking block and return self for chaining."""
478
570
  self.parts.append(Thinking(content=content))
479
571
  return self
480
572
 
573
+ @deprecated("with_thinking")
574
+ def add_thinking(self, content: str) -> "Message":
575
+ """Append a thinking block and return self for chaining."""
576
+ return self.with_thinking(content)
577
+
481
578
  # -------- convenient constructors --------
482
579
  @classmethod
483
580
  def user(
@@ -489,25 +586,25 @@ class Message:
489
586
  ) -> "Message":
490
587
  res = cls("user", [])
491
588
  if text is not None:
492
- res.add_text(text)
589
+ res.with_text(text)
493
590
  if image is not None:
494
- res.add_image(image)
591
+ res.with_image(image)
495
592
  if file is not None:
496
- res.add_file(file)
593
+ res.with_file(file)
497
594
  return res
498
595
 
499
596
  @classmethod
500
597
  def system(cls, text: str | None = None) -> "Message":
501
598
  res = cls("system", [])
502
599
  if text is not None:
503
- res.add_text(text)
600
+ res.with_text(text)
504
601
  return res
505
602
 
506
603
  @classmethod
507
604
  def ai(cls, text: str | None = None) -> "Message":
508
605
  res = cls("assistant", [])
509
606
  if text is not None:
510
- res.add_text(text)
607
+ res.with_text(text)
511
608
  return res
512
609
 
513
610
  # ──── provider-specific constructors ───
@@ -699,9 +796,9 @@ class Conversation:
699
796
  ) -> "Conversation":
700
797
  msg = Message.user(text)
701
798
  if image is not None:
702
- msg.add_image(image)
799
+ msg.with_image(image)
703
800
  if file is not None:
704
- msg.add_file(file)
801
+ msg.with_file(file)
705
802
  return cls([msg])
706
803
 
707
804
  @classmethod
@@ -1189,11 +1286,11 @@ class Conversation:
1189
1286
  """
1190
1287
  if self.messages and self.messages[-1].role == "tool":
1191
1288
  # Append to existing tool message (parallel tool calls)
1192
- self.messages[-1].add_tool_result(tool_call_id, result)
1289
+ self.messages[-1].with_tool_result(tool_call_id, result)
1193
1290
  else:
1194
1291
  # Create new tool message
1195
1292
  tool_msg = Message("tool", [])
1196
- tool_msg.add_tool_result(tool_call_id, result)
1293
+ tool_msg.with_tool_result(tool_call_id, result)
1197
1294
  self.messages.append(tool_msg)
1198
1295
  return self
1199
1296
 
@@ -1212,11 +1309,11 @@ class Conversation:
1212
1309
  for i, tool_result in enumerate(m.tool_results):
1213
1310
  images = tool_result.get_images()
1214
1311
  if len(images) > 0:
1215
- user_msg.add_text(
1312
+ user_msg.with_text(
1216
1313
  f"[Images for Tool Call {tool_result.tool_call_id}]"
1217
1314
  )
1218
1315
  for img in images:
1219
- user_msg.add_image(img)
1316
+ user_msg.with_image(img)
1220
1317
 
1221
1318
  else:
1222
1319
  result.append(m.oa_chat())
@@ -1496,9 +1593,21 @@ class Conversation:
1496
1593
  return cls(msgs)
1497
1594
 
1498
1595
 
1499
- def prompts_to_conversations(prompts: Sequence[str | list[dict] | Conversation]):
1500
- if any(isinstance(x, list) for x in prompts):
1501
- raise ValueError("can't convert list[dict] to conversation yet")
1502
- return [ # type: ignore
1503
- Conversation.user(p) if isinstance(p, str) else p for p in prompts
1504
- ]
1596
+ Prompt: TypeAlias = str | list[dict] | Message | Conversation
1597
+
1598
+
1599
+ def prompts_to_conversations(prompts: Sequence[Prompt]) -> Sequence[Prompt]:
1600
+ converted = []
1601
+ for prompt in prompts:
1602
+ if isinstance(prompt, Conversation):
1603
+ converted.append(prompt)
1604
+ elif isinstance(prompt, Message):
1605
+ converted.append(Conversation([prompt]))
1606
+ elif isinstance(prompt, str):
1607
+ converted.append(Conversation.user(prompt))
1608
+ elif isinstance(prompt, list):
1609
+ conv, provider = Conversation.from_unknown(prompt)
1610
+ converted.append(conv)
1611
+ else:
1612
+ raise ValueError(f"Unknown prompt type {type(prompt)}")
1613
+ return converted
@@ -26,28 +26,22 @@ class RequestContext:
26
26
 
27
27
  # Infrastructure
28
28
  status_tracker: StatusTracker | None = None
29
- results_arr: list[Any] | None = (
30
- None # list["APIRequestBase"] but avoiding circular import
31
- )
29
+ # avoiding circular import
30
+ results_arr: list[Any] | None = None # list["APIRequestBase"]
32
31
  callback: Callable | None = None
33
32
 
34
33
  # Optional features
35
34
  tools: list | None = None
36
35
  cache: CachePattern | None = None
37
36
  use_responses_api: bool = False
37
+ background: bool = False
38
+ service_tier: str | None = None
38
39
  extra_headers: dict[str, str] | None = None
40
+ extra_body: dict[str, Any] | None = None
39
41
  force_local_mcp: bool = False
40
42
 
41
43
  # Computed properties
42
44
  cache_key: str = field(init=False)
43
- # num_tokens: int = field(init=False)
44
-
45
- # def __post_init__(self):
46
- # # Compute cache key from prompt fingerprint
47
- # # self.cache_key = self.prompt.fingerprint
48
-
49
- # # Compute token count
50
- # self.num_tokens =
51
45
 
52
46
  @cached_property
53
47
  def num_tokens(self):
@@ -74,6 +68,10 @@ class RequestContext:
74
68
  "tools": self.tools,
75
69
  "cache": self.cache,
76
70
  "use_responses_api": self.use_responses_api,
71
+ "background": self.background,
72
+ "service_tier": self.service_tier,
73
+ "extra_headers": self.extra_headers,
74
+ "extra_body": self.extra_body,
77
75
  "force_local_mcp": self.force_local_mcp,
78
76
  }
79
77