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
lm_deluge/tool.py CHANGED
@@ -1,7 +1,17 @@
1
1
  import asyncio
2
2
  import inspect
3
3
  from concurrent.futures import ThreadPoolExecutor
4
- from typing import Any, Callable, Coroutine, Literal, TypedDict, get_type_hints
4
+ from typing import (
5
+ Any,
6
+ Callable,
7
+ Coroutine,
8
+ Literal,
9
+ Type,
10
+ TypedDict,
11
+ get_args,
12
+ get_origin,
13
+ get_type_hints,
14
+ )
5
15
 
6
16
  from fastmcp import Client # pip install fastmcp >= 2.0
7
17
  from mcp.types import Tool as MCPTool
@@ -11,6 +21,196 @@ from lm_deluge.image import Image
11
21
  from lm_deluge.prompt import Text, ToolResultPart
12
22
 
13
23
 
24
+ def _python_type_to_json_schema_enhanced(python_type: Any) -> dict[str, Any]:
25
+ """
26
+ Convert Python type annotations to JSON Schema.
27
+ Handles: primitives, Optional, Literal, list[T], dict[str, T], Union.
28
+ """
29
+ # Get origin and args for generic types
30
+ origin = get_origin(python_type)
31
+ args = get_args(python_type)
32
+
33
+ # Handle Optional[T] or T | None
34
+ if origin is type(None) or python_type is type(None):
35
+ return {"type": "null"}
36
+
37
+ # Handle Union types (including Optional)
38
+ if origin is Literal:
39
+ # Literal["a", "b"] -> enum
40
+ return {"type": "string", "enum": list(args)}
41
+
42
+ # Handle list[T]
43
+ if origin is list:
44
+ if args:
45
+ items_schema = _python_type_to_json_schema_enhanced(args[0])
46
+ return {"type": "array", "items": items_schema}
47
+ return {"type": "array"}
48
+
49
+ # Handle dict[str, T]
50
+ if origin is dict:
51
+ if len(args) >= 2:
52
+ # For dict[str, T], we can set additionalProperties
53
+ value_schema = _python_type_to_json_schema_enhanced(args[1])
54
+ return {"type": "object", "additionalProperties": value_schema}
55
+ return {"type": "object"}
56
+
57
+ # Handle basic types
58
+ if python_type is int:
59
+ return {"type": "integer"}
60
+ elif python_type is float:
61
+ return {"type": "number"}
62
+ elif python_type is str:
63
+ return {"type": "string"}
64
+ elif python_type is bool:
65
+ return {"type": "boolean"}
66
+ elif python_type is list:
67
+ return {"type": "array"}
68
+ elif python_type is dict:
69
+ return {"type": "object"}
70
+ else:
71
+ # Default to string for unknown types
72
+ return {"type": "string"}
73
+
74
+
75
+ class ToolParams:
76
+ """
77
+ Helper class for constructing tool parameters more easily.
78
+
79
+ Usage:
80
+ # Simple constructor with Python types
81
+ params = ToolParams({"city": str, "age": int})
82
+
83
+ # With extras (description, enum, etc)
84
+ params = ToolParams({
85
+ "operation": (str, {"enum": ["add", "sub"], "description": "Math operation"}),
86
+ "value": (int, {"description": "The value"})
87
+ })
88
+
89
+ # From Pydantic model
90
+ params = ToolParams.from_pydantic(MyModel)
91
+
92
+ # From TypedDict
93
+ params = ToolParams.from_typed_dict(MyTypedDict)
94
+
95
+ # From existing JSON Schema
96
+ params = ToolParams.from_json_schema(schema_dict, required=["field1"])
97
+ """
98
+
99
+ def __init__(self, spec: dict[str, Any]):
100
+ """
101
+ Create ToolParams from a dict mapping parameter names to types or (type, extras) tuples.
102
+
103
+ Args:
104
+ spec: Dict where values can be:
105
+ - A Python type (str, int, list[str], etc.)
106
+ - A tuple of (type, extras_dict) for additional JSON Schema properties
107
+ - An already-formed JSON Schema dict (passed through as-is)
108
+ """
109
+ self.parameters: dict[str, Any] = {}
110
+ self.required: list[str] = []
111
+
112
+ for param_name, param_spec in spec.items():
113
+ # If it's a tuple, extract (type, extras)
114
+ if isinstance(param_spec, tuple):
115
+ param_type, extras = param_spec
116
+ schema = _python_type_to_json_schema_enhanced(param_type)
117
+ schema.update(extras)
118
+ self.parameters[param_name] = schema
119
+ # Mark as required unless explicitly marked as optional
120
+ if extras.get("optional") is not True:
121
+ self.required.append(param_name)
122
+ # If it's already a dict with "type" key, use as-is
123
+ elif isinstance(param_spec, dict) and "type" in param_spec:
124
+ self.parameters[param_name] = param_spec
125
+ # Assume required unless marked optional
126
+ if param_spec.get("optional") is not True:
127
+ self.required.append(param_name)
128
+ # Otherwise treat as a Python type
129
+ else:
130
+ self.parameters[param_name] = _python_type_to_json_schema_enhanced(
131
+ param_spec
132
+ )
133
+ self.required.append(param_name)
134
+
135
+ @classmethod
136
+ def from_pydantic(cls, model: Type[BaseModel]) -> "ToolParams":
137
+ """
138
+ Create ToolParams from a Pydantic model.
139
+
140
+ Args:
141
+ model: A Pydantic BaseModel class
142
+ """
143
+ # Get the JSON schema from Pydantic
144
+ schema = model.model_json_schema()
145
+ properties = schema.get("properties", {})
146
+ required = schema.get("required", [])
147
+
148
+ return cls.from_json_schema(properties, required)
149
+
150
+ @classmethod
151
+ def from_typed_dict(cls, typed_dict: Type) -> "ToolParams":
152
+ """
153
+ Create ToolParams from a TypedDict.
154
+
155
+ Args:
156
+ typed_dict: A TypedDict class
157
+ """
158
+ hints = get_type_hints(typed_dict)
159
+
160
+ # TypedDict doesn't have a built-in way to mark optional fields,
161
+ # but we can check for Optional in the type hints
162
+ params = {}
163
+ required = []
164
+
165
+ for field_name, field_type in hints.items():
166
+ # Check if it's Optional (Union with None)
167
+ origin = get_origin(field_type)
168
+ # args = get_args(field_type)
169
+
170
+ is_optional = False
171
+ actual_type = field_type
172
+
173
+ # Check for Union types (including Optional[T] which is Union[T, None])
174
+ if origin is type(None):
175
+ is_optional = True
176
+ actual_type = type(None)
177
+
178
+ # For now, treat all TypedDict fields as required unless they're explicitly Optional
179
+ schema = _python_type_to_json_schema_enhanced(actual_type)
180
+ params[field_name] = schema
181
+
182
+ if not is_optional:
183
+ required.append(field_name)
184
+
185
+ instance = cls.__new__(cls)
186
+ instance.parameters = params
187
+ instance.required = required
188
+ return instance
189
+
190
+ @classmethod
191
+ def from_json_schema(
192
+ cls, properties: dict[str, Any], required: list[str] | None = None
193
+ ) -> "ToolParams":
194
+ """
195
+ Create ToolParams from an existing JSON Schema properties dict.
196
+
197
+ Args:
198
+ properties: The "properties" section of a JSON Schema
199
+ required: List of required field names
200
+ """
201
+ instance = cls.__new__(cls)
202
+ instance.parameters = properties
203
+ instance.required = required or []
204
+ return instance
205
+
206
+ def to_dict(self) -> dict[str, Any]:
207
+ """
208
+ Convert to a dict with 'parameters' and 'required' keys.
209
+ Useful for unpacking into Tool constructor.
210
+ """
211
+ return {"parameters": self.parameters, "required": self.required}
212
+
213
+
14
214
  async def _load_all_mcp_tools(client: Client) -> list["Tool"]:
15
215
  metas: list[MCPTool] = await client.list_tools()
16
216
 
@@ -39,6 +239,9 @@ async def _load_all_mcp_tools(client: Client) -> list["Tool"]:
39
239
 
40
240
  tools: list[Tool] = []
41
241
  for m in metas:
242
+ # Extract definitions from the schema (could be $defs or definitions)
243
+ definitions = m.inputSchema.get("$defs") or m.inputSchema.get("definitions")
244
+
42
245
  tools.append(
43
246
  Tool(
44
247
  name=m.name,
@@ -46,6 +249,7 @@ async def _load_all_mcp_tools(client: Client) -> list["Tool"]:
46
249
  parameters=m.inputSchema.get("properties", {}),
47
250
  required=m.inputSchema.get("required", []),
48
251
  additionalProperties=m.inputSchema.get("additionalProperties"),
252
+ definitions=definitions,
49
253
  run=make_runner(m.name),
50
254
  )
51
255
  )
@@ -68,6 +272,8 @@ class Tool(BaseModel):
68
272
  is_built_in: bool = False
69
273
  type: str | None = None
70
274
  built_in_args: dict[str, Any] = Field(default_factory=dict)
275
+ # JSON Schema definitions (for $ref support)
276
+ definitions: dict[str, Any] | None = None
71
277
 
72
278
  @field_validator("name")
73
279
  @classmethod
@@ -79,6 +285,24 @@ class Tool(BaseModel):
79
285
  )
80
286
  return v
81
287
 
288
+ @field_validator("parameters", mode="before")
289
+ @classmethod
290
+ def validate_parameters(cls, v: Any) -> dict[str, Any] | None:
291
+ """Accept ToolParams objects and convert to dict for backwards compatibility."""
292
+ if isinstance(v, ToolParams):
293
+ return v.parameters
294
+ return v
295
+
296
+ def model_post_init(self, __context: Any) -> None:
297
+ """
298
+ After validation, if parameters came from ToolParams, also update required list.
299
+ This is called by Pydantic after __init__.
300
+ """
301
+ # This is a bit tricky - we need to capture the required list from ToolParams
302
+ # Since Pydantic has already converted it in the validator, we can't access it here
303
+ # Instead, we'll handle this differently in the convenience constructors
304
+ pass
305
+
82
306
  def _is_async(self) -> bool:
83
307
  return inspect.iscoroutinefunction(self.run)
84
308
 
@@ -143,7 +367,7 @@ class Tool(BaseModel):
143
367
  param_type = type_hints.get(param_name, str)
144
368
 
145
369
  # Convert Python types to JSON Schema types
146
- json_type = cls._python_type_to_json_schema(param_type)
370
+ json_type = _python_type_to_json_schema_enhanced(param_type)
147
371
 
148
372
  parameters[param_name] = json_type
149
373
 
@@ -209,6 +433,119 @@ class Tool(BaseModel):
209
433
  return t
210
434
  raise ValueError(f"Tool '{tool_name}' not found on that server")
211
435
 
436
+ @classmethod
437
+ def from_params(
438
+ cls,
439
+ name: str,
440
+ params: ToolParams,
441
+ *,
442
+ description: str | None = None,
443
+ run: Callable | None = None,
444
+ **kwargs,
445
+ ) -> "Tool":
446
+ """
447
+ Create a Tool from a ToolParams object.
448
+
449
+ Args:
450
+ name: Tool name
451
+ params: ToolParams object defining the parameter schema
452
+ description: Optional description
453
+ run: Optional callable to execute the tool
454
+ **kwargs: Additional Tool arguments
455
+
456
+ Example:
457
+ params = ToolParams({"city": str, "age": int})
458
+ tool = Tool.from_params("get_user", params, run=my_function)
459
+ """
460
+ return cls(
461
+ name=name,
462
+ description=description,
463
+ parameters=params.parameters,
464
+ required=params.required,
465
+ run=run,
466
+ **kwargs,
467
+ )
468
+
469
+ @classmethod
470
+ def from_pydantic(
471
+ cls,
472
+ name: str,
473
+ model: Type[BaseModel],
474
+ *,
475
+ description: str | None = None,
476
+ run: Callable | None = None,
477
+ **kwargs,
478
+ ) -> "Tool":
479
+ """
480
+ Create a Tool from a Pydantic model.
481
+
482
+ Args:
483
+ name: Tool name
484
+ model: Pydantic BaseModel class
485
+ description: Optional description (defaults to model docstring)
486
+ run: Optional callable to execute the tool
487
+ **kwargs: Additional Tool arguments
488
+
489
+ Example:
490
+ class UserQuery(BaseModel):
491
+ city: str
492
+ age: int
493
+
494
+ tool = Tool.from_pydantic("get_user", UserQuery, run=my_function)
495
+ """
496
+ params = ToolParams.from_pydantic(model)
497
+
498
+ # Use model docstring as default description if not provided
499
+ if description is None and model.__doc__:
500
+ description = model.__doc__.strip()
501
+
502
+ return cls(
503
+ name=name,
504
+ description=description,
505
+ parameters=params.parameters,
506
+ required=params.required,
507
+ run=run,
508
+ **kwargs,
509
+ )
510
+
511
+ @classmethod
512
+ def from_typed_dict(
513
+ cls,
514
+ name: str,
515
+ typed_dict: Type,
516
+ *,
517
+ description: str | None = None,
518
+ run: Callable | None = None,
519
+ **kwargs,
520
+ ) -> "Tool":
521
+ """
522
+ Create a Tool from a TypedDict.
523
+
524
+ Args:
525
+ name: Tool name
526
+ typed_dict: TypedDict class
527
+ description: Optional description
528
+ run: Optional callable to execute the tool
529
+ **kwargs: Additional Tool arguments
530
+
531
+ Example:
532
+ class UserQuery(TypedDict):
533
+ city: str
534
+ age: int
535
+
536
+ tool = Tool.from_typed_dict("get_user", UserQuery, run=my_function)
537
+ """
538
+ params = ToolParams.from_typed_dict(typed_dict)
539
+
540
+ return cls(
541
+ name=name,
542
+ description=description,
543
+ parameters=params.parameters,
544
+ required=params.required,
545
+ run=run,
546
+ **kwargs,
547
+ )
548
+
212
549
  @staticmethod
213
550
  def _tool_from_meta(meta: dict[str, Any], runner) -> "Tool":
214
551
  props = meta["inputSchema"].get("properties", {})
@@ -225,22 +562,39 @@ class Tool(BaseModel):
225
562
 
226
563
  @staticmethod
227
564
  def _python_type_to_json_schema(python_type) -> dict[str, Any]:
228
- """Convert Python type to JSON Schema type definition."""
229
- if python_type is int:
230
- return {"type": "integer"}
231
- elif python_type is float:
232
- return {"type": "number"}
233
- elif python_type is str:
234
- return {"type": "string"}
235
- elif python_type is bool:
236
- return {"type": "boolean"}
237
- elif python_type is list:
238
- return {"type": "array"}
239
- elif python_type is dict:
240
- return {"type": "object"}
241
- else:
242
- # Default to string for unknown types
243
- return {"type": "string"}
565
+ """
566
+ Convert Python type to JSON Schema type definition.
567
+ Now delegates to enhanced version for better type support.
568
+ """
569
+ return _python_type_to_json_schema_enhanced(python_type)
570
+
571
+ def _is_strict_mode_compatible(self) -> bool:
572
+ """
573
+ Check if this tool's schema is compatible with OpenAI strict mode.
574
+ Strict mode requires all objects to have defined properties.
575
+ """
576
+
577
+ def has_undefined_objects(schema: dict | list | Any) -> bool:
578
+ """Recursively check for objects without defined properties."""
579
+ if isinstance(schema, dict):
580
+ # Check if this is an object type without properties
581
+ if schema.get("type") == "object":
582
+ # If additionalProperties is True or properties is missing/empty
583
+ if schema.get("additionalProperties") is True:
584
+ return True
585
+ if "properties" not in schema or not schema["properties"]:
586
+ return True
587
+ # Recursively check nested schemas
588
+ for value in schema.values():
589
+ if has_undefined_objects(value):
590
+ return True
591
+ elif isinstance(schema, list):
592
+ for item in schema:
593
+ if has_undefined_objects(item):
594
+ return True
595
+ return False
596
+
597
+ return not has_undefined_objects(self.parameters or {})
244
598
 
245
599
  def _json_schema(
246
600
  self, include_additional_properties=False, remove_defaults=False
@@ -248,7 +602,8 @@ class Tool(BaseModel):
248
602
  def _add_additional_properties_recursive(
249
603
  schema: dict | list | Any, remove_defaults: bool = False
250
604
  ) -> dict | list | Any:
251
- """Recursively add additionalProperties: false to all object-type schemas."""
605
+ """Recursively add additionalProperties: false to all object-type schemas.
606
+ In strict mode (when remove_defaults=True), also makes all properties required."""
252
607
  if isinstance(schema, dict):
253
608
  # Copy the dictionary to avoid modifying the original
254
609
  new_schema = schema.copy()
@@ -264,6 +619,10 @@ class Tool(BaseModel):
264
619
  if new_schema.get("type") == "object":
265
620
  new_schema["additionalProperties"] = False
266
621
 
622
+ # In strict mode, all properties must be required
623
+ if remove_defaults and "properties" in new_schema:
624
+ new_schema["required"] = list(new_schema["properties"].keys())
625
+
267
626
  # Remove default values if requested (for strict mode)
268
627
  if remove_defaults and "default" in new_schema:
269
628
  del new_schema["default"]
@@ -294,6 +653,14 @@ class Tool(BaseModel):
294
653
  else:
295
654
  processed_parameters = self.parameters
296
655
 
656
+ # Process definitions too
657
+ if self.definitions and include_additional_properties:
658
+ processed_definitions = _add_additional_properties_recursive(
659
+ self.definitions, remove_defaults
660
+ )
661
+ else:
662
+ processed_definitions = self.definitions
663
+
297
664
  res = {
298
665
  "type": "object",
299
666
  "properties": processed_parameters,
@@ -303,6 +670,10 @@ class Tool(BaseModel):
303
670
  if include_additional_properties:
304
671
  res["additionalProperties"] = False
305
672
 
673
+ # Include definitions if present (for $ref support)
674
+ if processed_definitions:
675
+ res["$defs"] = processed_definitions
676
+
306
677
  return res
307
678
 
308
679
  # ---------- dumpers ----------
@@ -311,6 +682,11 @@ class Tool(BaseModel):
311
682
  ) -> dict[str, Any]:
312
683
  if self.is_built_in:
313
684
  return {"type": self.type, **self.built_in_args, **kwargs}
685
+
686
+ # Check if schema is compatible with strict mode
687
+ if strict and not self._is_strict_mode_compatible():
688
+ strict = False
689
+
314
690
  if strict:
315
691
  # For strict mode, remove defaults and make all parameters required
316
692
  schema = self._json_schema(
lm_deluge/tracker.py CHANGED
@@ -171,20 +171,24 @@ class StatusTracker:
171
171
  )
172
172
 
173
173
  # Display cumulative usage stats if available
174
- if self.total_cost > 0 or self.total_input_tokens > 0 or self.total_output_tokens > 0:
174
+ if (
175
+ self.total_cost > 0
176
+ or self.total_input_tokens > 0
177
+ or self.total_output_tokens > 0
178
+ ):
175
179
  usage_parts = []
176
180
  if self.total_cost > 0:
177
- usage_parts.append(f"Cost: ${self.total_cost:.4f}")
181
+ usage_parts.append(f"💰 Cost: ${self.total_cost:.4f}")
178
182
  if self.total_input_tokens > 0 or self.total_output_tokens > 0:
179
183
  usage_parts.append(
180
- f"Tokens: {self.total_input_tokens:,} in / {self.total_output_tokens:,} out"
184
+ f"🔡 Tokens: {self.total_input_tokens:,} in / {self.total_output_tokens:,} out"
181
185
  )
182
186
  if self.total_cache_read_tokens > 0:
183
187
  usage_parts.append(f"Cache: {self.total_cache_read_tokens:,} read")
184
188
  if self.total_cache_write_tokens > 0:
185
189
  usage_parts.append(f"{self.total_cache_write_tokens:,} write")
186
190
 
187
- print(" | ".join(usage_parts))
191
+ print(" ", " ".join(usage_parts))
188
192
 
189
193
  @property
190
194
  def pbar(self) -> tqdm | None:
@@ -288,7 +292,9 @@ class StatusTracker:
288
292
  usage_text = f" [gold3]Usage:[/gold3] {' • '.join(usage_parts)}"
289
293
 
290
294
  if usage_text:
291
- display = Group(self._rich_progress, in_progress, capacity_text, usage_text)
295
+ display = Group(
296
+ self._rich_progress, in_progress, capacity_text, usage_text
297
+ )
292
298
  else:
293
299
  display = Group(self._rich_progress, in_progress, capacity_text)
294
300
  live.update(display)
lm_deluge/warnings.py ADDED
@@ -0,0 +1,46 @@
1
+ import functools
2
+ import os
3
+ import warnings
4
+
5
+ WARNINGS: dict[str, str] = {
6
+ "WARN_JSON_MODE_UNSUPPORTED": "JSON mode requested for {model_name} but response_format parameter not supported.",
7
+ "WARN_REASONING_UNSUPPORTED": "Ignoring reasoning_effort param for non-reasoning model: {model_name}.",
8
+ "WARN_CACHING_UNSUPPORTED": "Cache parameter '{cache_param}' is not supported, ignoring for {model_name}.",
9
+ "WARN_LOGPROBS_UNSUPPORTED": "Ignoring logprobs param for non-logprobs model: {model_name}",
10
+ }
11
+
12
+
13
+ def maybe_warn(warning: str, **kwargs):
14
+ if os.getenv(warning):
15
+ pass
16
+ else:
17
+ warnings.warn(WARNINGS[warning].format(**kwargs))
18
+ os.environ[warning] = "1"
19
+
20
+
21
+ def deprecated(replacement: str):
22
+ """Decorator to mark methods as deprecated and suggest replacement.
23
+
24
+ Only shows the warning once per method to avoid spam.
25
+
26
+ Args:
27
+ replacement: The name of the replacement method to suggest
28
+ """
29
+
30
+ def decorator(func):
31
+ warning_key = f"DEPRECATED_{func.__module__}_{func.__qualname__}"
32
+
33
+ @functools.wraps(func)
34
+ def wrapper(*args, **kwargs):
35
+ if not os.getenv(warning_key):
36
+ warnings.warn(
37
+ f"{func.__name__} is deprecated, use {replacement} instead",
38
+ DeprecationWarning,
39
+ stacklevel=2,
40
+ )
41
+ os.environ[warning_key] = "1"
42
+ return func(*args, **kwargs)
43
+
44
+ return wrapper
45
+
46
+ return decorator
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lm_deluge
3
- Version: 0.0.56
3
+ Version: 0.0.69
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
@@ -23,6 +23,8 @@ Requires-Dist: pdf2image
23
23
  Requires-Dist: pillow
24
24
  Requires-Dist: fastmcp>=2.4
25
25
  Requires-Dist: rich
26
+ Provides-Extra: openai
27
+ Requires-Dist: openai>=1.0.0; extra == "openai"
26
28
  Dynamic: license-file
27
29
 
28
30
  # lm-deluge