lm-deluge 0.0.17__py3-none-any.whl → 0.0.18__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.
@@ -36,7 +36,7 @@ def _build_anthropic_request(
36
36
  cache_pattern: CachePattern | None = None,
37
37
  ):
38
38
  system_message, messages = prompt.to_anthropic(cache_pattern=cache_pattern)
39
- request_header = {
39
+ base_headers = {
40
40
  "x-api-key": os.getenv(model.api_key_env_var),
41
41
  "anthropic-version": "2023-06-01",
42
42
  "content-type": "application/json",
@@ -83,13 +83,13 @@ def _build_anthropic_request(
83
83
  "text_editor_20241022",
84
84
  "bash_20241022",
85
85
  ]:
86
- _add_beta(request_header, "computer-use-2024-10-22")
86
+ _add_beta(base_headers, "computer-use-2024-10-22")
87
87
  elif tool["type"] == "computer_20250124":
88
- _add_beta(request_header, "computer-use-2025-01-24")
88
+ _add_beta(base_headers, "computer-use-2025-01-24")
89
89
  elif tool["type"] == "code_execution_20250522":
90
- _add_beta(request_header, "code-execution-2025-05-22")
90
+ _add_beta(base_headers, "code-execution-2025-05-22")
91
91
  elif isinstance(tool, MCPServer):
92
- _add_beta(request_header, "mcp-client-2025-04-04")
92
+ _add_beta(base_headers, "mcp-client-2025-04-04")
93
93
  mcp_servers.append(tool.for_anthropic())
94
94
 
95
95
  # Add cache control to last tool if tools_only caching is specified
@@ -100,7 +100,7 @@ def _build_anthropic_request(
100
100
  if len(mcp_servers) > 0:
101
101
  request_json["mcp_servers"] = mcp_servers
102
102
 
103
- return request_json, request_header
103
+ return request_json, base_headers
104
104
 
105
105
 
106
106
  class AnthropicRequest(APIRequestBase):
@@ -114,13 +114,16 @@ class AnthropicRequest(APIRequestBase):
114
114
  if self.context.cache is not None:
115
115
  self.context.prompt.lock_images_as_bytes()
116
116
 
117
- self.request_json, self.request_header = _build_anthropic_request(
117
+ self.request_json, base_headers = _build_anthropic_request(
118
118
  self.model,
119
119
  self.context.prompt,
120
120
  self.context.tools,
121
121
  self.context.sampling_params,
122
122
  self.context.cache,
123
123
  )
124
+ self.request_header = self.merge_headers(
125
+ base_headers, exclude_patterns=["openai", "gemini", "mistral"]
126
+ )
124
127
 
125
128
  async def handle_response(self, http_response: ClientResponse) -> APIResponse:
126
129
  data = None
@@ -46,6 +46,29 @@ class APIRequestBase(ABC):
46
46
  # the APIResponse in self.result includes all the information
47
47
  self.context.callback(self.result[-1], self.context.status_tracker)
48
48
 
49
+ def merge_headers(
50
+ self, base_headers: dict[str, str], exclude_patterns: list[str] | None = None
51
+ ) -> dict[str, str]:
52
+ """Merge extra_headers with base headers, giving priority to extra_headers."""
53
+ if not self.context.extra_headers:
54
+ return base_headers
55
+
56
+ # Filter out headers that match exclude patterns
57
+ filtered_extra = {}
58
+ if exclude_patterns:
59
+ for key, value in self.context.extra_headers.items():
60
+ if not any(
61
+ pattern.lower() in key.lower() for pattern in exclude_patterns
62
+ ):
63
+ filtered_extra[key] = value
64
+ else:
65
+ filtered_extra = dict(self.context.extra_headers)
66
+
67
+ # Start with base headers, then overlay filtered extra headers (extra takes precedence)
68
+ merged = dict(base_headers)
69
+ merged.update(filtered_extra)
70
+ return merged
71
+
49
72
  def handle_success(self, data):
50
73
  self.call_callback()
51
74
  if self.context.status_tracker:
@@ -85,7 +85,7 @@ def _build_anthropic_bedrock_request(
85
85
  )
86
86
 
87
87
  # Setup basic headers (AWS4Auth will add the Authorization header)
88
- request_header = {
88
+ base_headers = {
89
89
  "Content-Type": "application/json",
90
90
  }
91
91
 
@@ -115,11 +115,11 @@ def _build_anthropic_bedrock_request(
115
115
  "text_editor_20241022",
116
116
  "bash_20241022",
117
117
  ]:
118
- _add_beta(request_header, "computer-use-2024-10-22")
118
+ _add_beta(base_headers, "computer-use-2024-10-22")
119
119
  elif tool["type"] == "computer_20250124":
120
- _add_beta(request_header, "computer-use-2025-01-24")
120
+ _add_beta(base_headers, "computer-use-2025-01-24")
121
121
  elif tool["type"] == "code_execution_20250522":
122
- _add_beta(request_header, "code-execution-2025-05-22")
122
+ _add_beta(base_headers, "code-execution-2025-05-22")
123
123
  elif isinstance(tool, MCPServer):
124
124
  raise ValueError("bedrock doesn't support MCP connector right now")
125
125
  # _add_beta(request_header, "mcp-client-2025-04-04")
@@ -133,7 +133,7 @@ def _build_anthropic_bedrock_request(
133
133
  if len(mcp_servers) > 0:
134
134
  request_json["mcp_servers"] = mcp_servers
135
135
 
136
- return request_json, request_header, auth, url
136
+ return request_json, base_headers, auth, url
137
137
 
138
138
 
139
139
  class BedrockRequest(APIRequestBase):
@@ -147,7 +147,7 @@ class BedrockRequest(APIRequestBase):
147
147
  if self.context.cache is not None:
148
148
  self.context.prompt.lock_images_as_bytes()
149
149
 
150
- self.request_json, self.request_header, self.auth, self.url = (
150
+ self.request_json, base_headers, self.auth, self.url = (
151
151
  _build_anthropic_bedrock_request(
152
152
  self.model,
153
153
  context.prompt,
@@ -156,6 +156,9 @@ class BedrockRequest(APIRequestBase):
156
156
  context.cache,
157
157
  )
158
158
  )
159
+ self.request_header = self.merge_headers(
160
+ base_headers, exclude_patterns=["anthropic", "openai", "gemini", "mistral"]
161
+ )
159
162
 
160
163
  async def execute_once(self) -> APIResponse:
161
164
  """Override execute_once to handle AWS4Auth signing."""
@@ -77,9 +77,12 @@ class GeminiRequest(APIRequestBase):
77
77
  self.model = APIModel.from_registry(self.context.model_name)
78
78
  # Gemini API endpoint format: https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent
79
79
  self.url = f"{self.model.api_base}/models/{self.model.name}:generateContent"
80
- self.request_header = {
80
+ base_headers = {
81
81
  "Content-Type": "application/json",
82
82
  }
83
+ self.request_header = self.merge_headers(
84
+ base_headers, exclude_patterns=["anthropic", "openai", "mistral"]
85
+ )
83
86
 
84
87
  # Add API key as query parameter for Gemini
85
88
  api_key = os.getenv(self.model.api_key_env_var)
@@ -22,9 +22,12 @@ class MistralRequest(APIRequestBase):
22
22
  )
23
23
  self.model = APIModel.from_registry(self.context.model_name)
24
24
  self.url = f"{self.model.api_base}/chat/completions"
25
- self.request_header = {
25
+ base_headers = {
26
26
  "Authorization": f"Bearer {os.getenv(self.model.api_key_env_var)}"
27
27
  }
28
+ self.request_header = self.merge_headers(
29
+ base_headers, exclude_patterns=["anthropic", "openai", "gemini"]
30
+ )
28
31
  self.request_json = {
29
32
  "model": self.model.name,
30
33
  "messages": self.context.prompt.to_mistral(),
@@ -73,9 +73,12 @@ class OpenAIRequest(APIRequestBase):
73
73
  )
74
74
  self.model = APIModel.from_registry(self.context.model_name)
75
75
  self.url = f"{self.model.api_base}/chat/completions"
76
- self.request_header = {
76
+ base_headers = {
77
77
  "Authorization": f"Bearer {os.getenv(self.model.api_key_env_var)}"
78
78
  }
79
+ self.request_header = self.merge_headers(
80
+ base_headers, exclude_patterns=["anthropic"]
81
+ )
79
82
 
80
83
  self.request_json = _build_oa_chat_request(
81
84
  self.model,
@@ -432,6 +435,7 @@ async def stream_chat(
432
435
  sampling_params: SamplingParams = SamplingParams(),
433
436
  tools: list | None = None,
434
437
  cache: CachePattern | None = None,
438
+ extra_headers: dict[str, str] | None = None,
435
439
  ):
436
440
  if cache is not None:
437
441
  warnings.warn(
@@ -442,7 +446,16 @@ async def stream_chat(
442
446
  if model.api_spec != "openai":
443
447
  raise ValueError("streaming only supported on openai models for now")
444
448
  url = f"{model.api_base}/chat/completions"
445
- request_header = {"Authorization": f"Bearer {os.getenv(model.api_key_env_var)}"}
449
+ base_headers = {"Authorization": f"Bearer {os.getenv(model.api_key_env_var)}"}
450
+
451
+ # Merge extra headers, filtering out anthropic headers
452
+ request_header = dict(base_headers)
453
+ if extra_headers:
454
+ filtered_extra = {
455
+ k: v for k, v in extra_headers.items() if "anthropic" not in k.lower()
456
+ }
457
+ request_header.update(filtered_extra)
458
+
446
459
  request_json = _build_oa_chat_request(model, prompt, tools, sampling_params)
447
460
  request_json["stream"] = True
448
461
 
lm_deluge/client.py CHANGED
@@ -50,6 +50,7 @@ class LLMClient(BaseModel):
50
50
  max_attempts: int = 5
51
51
  request_timeout: int = 30
52
52
  cache: Any = None
53
+ extra_headers: dict[str, str] | None = None
53
54
  # sampling params - if provided, and sampling_params is not,
54
55
  # these override the defaults
55
56
  temperature: float = 0.75
@@ -364,6 +365,7 @@ class LLMClient(BaseModel):
364
365
  tools=tools,
365
366
  cache=cache,
366
367
  use_responses_api=use_responses_api,
368
+ extra_headers=self.extra_headers,
367
369
  )
368
370
  except StopIteration:
369
371
  prompts_not_finished = False
@@ -451,7 +453,9 @@ class LLMClient(BaseModel):
451
453
  model, sampling_params = self._select_model()
452
454
  if isinstance(prompt, str):
453
455
  prompt = Conversation.user(prompt)
454
- async for item in stream_chat(model, prompt, sampling_params, tools, None):
456
+ async for item in stream_chat(
457
+ model, prompt, sampling_params, tools, None, self.extra_headers
458
+ ):
455
459
  if isinstance(item, str):
456
460
  print(item, end="", flush=True)
457
461
  else:
lm_deluge/image.py CHANGED
@@ -1,20 +1,23 @@
1
- import os
2
- from contextlib import contextmanager
3
- import io
4
- import requests
5
- from PIL import Image as PILImage # type: ignore
6
1
  import base64
2
+ import io
7
3
  import mimetypes
4
+ import os
5
+ from contextlib import contextmanager
8
6
  from dataclasses import dataclass, field
9
7
  from pathlib import Path
10
8
  from typing import Literal
11
9
 
10
+ import requests
11
+ from PIL import Image as PILImage # type: ignore
12
+
13
+ MediaType = Literal["image/jpeg", "image/png", "image/gif", "image/webp"]
14
+
12
15
 
13
16
  @dataclass(slots=True)
14
17
  class Image:
15
18
  # raw bytes, pathlike, http url, or base64 data url
16
19
  data: bytes | io.BytesIO | Path | str
17
- media_type: str | None = None # inferred if None
20
+ media_type: MediaType | None = None # inferred if None
18
21
  detail: Literal["low", "high", "auto"] = "auto"
19
22
  type: str = field(init=False, default="image")
20
23
  _fingerprint_cache: str | None = field(init=False, default=None)
@@ -0,0 +1 @@
1
+ # NOT IMPLEMENTED!!!
@@ -0,0 +1,162 @@
1
+ # # utilities for locating things in images
2
+ # from dataclasses import dataclass
3
+ # from typing import Literal
4
+
5
+ # from lm_deluge.util.json import load_json
6
+ # from lm_deluge.util.spatial import Box2D, Point
7
+
8
+
9
+ # @dataclass
10
+ # class LocatePrompt:
11
+ # description: str
12
+ # task: Literal["click", "detect"] = "click"
13
+ # orientation: Literal["xy", "yx"] = "xy"
14
+ # origin: Literal["top-left", "bottom-left"] = "top-left"
15
+ # coords: Literal["absolute", "relative-1", "relative-1k"] = "absolute"
16
+ # height: int | None = None
17
+ # width: int | None = None
18
+ # output_type: Literal["point", "points", "box", "boxes"] = "point"
19
+ # output_fmt: Literal["json", "xml"] = "xml"
20
+
21
+ # def to_string(self) -> str:
22
+ # """Compose the full prompt string based on the current configuration."""
23
+ # parts: list[str] = []
24
+
25
+ # if self.task == "click":
26
+ # parts.append(
27
+ # "Given an instruction, determine where to click in the image to complete it. "
28
+ # )
29
+ # parts.append(f"\n\nINSTRUCTION: {self.description}\n\n")
30
+ # else:
31
+ # if self.output_type.endswith("s"):
32
+ # parts.append(
33
+ # "Given a description of an object to locate, find ALL instances of that object in the image."
34
+ # )
35
+ # else:
36
+ # parts.append(
37
+ # "Given a description of an object to locate, find that object in the image."
38
+ # )
39
+ # parts.append(f"\n\nDESCRIPTION: {self.description}\n\n")
40
+
41
+ # if self.output_type == "point":
42
+ # point_type = "(x, y)" if self.orientation == "xy" else "(y, x)"
43
+ # parts.append(f"Your response should be a single {point_type} point")
44
+ # if self.output_fmt == "xml":
45
+ # parts.append("enclosed in <point></point> tags.")
46
+ # else:
47
+ # parts.append('formatted as JSON, like {"point": [0, 1]}.')
48
+
49
+ # elif self.output_type == "points":
50
+ # point_type = "(x, y)" if self.orientation == "xy" else "(y, x)"
51
+ # parts.append(f"Your response should be a series of {point_type} points,")
52
+ # if self.output_fmt == "xml":
53
+ # parts.append("each one enclosed in <point></point> tags.")
54
+ # else:
55
+ # parts.append(
56
+ # 'formatted as a JSON array, like [{"point": [0, 1]}, {"point": [1, 0]}].'
57
+ # )
58
+
59
+ # elif self.output_type == "box":
60
+ # box_type = (
61
+ # "(x0, y0, x1, y1)" if self.orientation == "xy" else "(y0, x0, y1, x1)"
62
+ # )
63
+ # parts.append(f"Your response should be a {box_type} bounding box,")
64
+
65
+ # if self.output_fmt == "xml":
66
+ # parts.append("enclosed in <box></box> tags.")
67
+ # else:
68
+ # parts.append('formatted as JSON, like {"box_2d": [0, 0, 1, 1]}')
69
+
70
+ # elif self.output_type == "boxes":
71
+ # box_type = (
72
+ # "(x0, y0, x1, y1)" if self.orientation == "xy" else "(y0, x0, y1, x1)"
73
+ # )
74
+ # parts.append(
75
+ # f"Your response should be a series of {box_type} bounding boxes,"
76
+ # )
77
+ # if self.output_fmt == "xml":
78
+ # parts.append("each one enclosed in <box></box> tags.")
79
+ # else:
80
+ # parts.append(
81
+ # 'formatted as a JSON array, like [{"box_2d": [0, 0, 1, 1]}, {"box_2d": [0.5, 0.5, 1, 1]}].'
82
+ # )
83
+
84
+ # if self.coords == "absolute":
85
+ # parts.append(
86
+ # "The returned coordinates should be absolute pixel coordinates in the image. "
87
+ # f"The image has a height of {self.height} pixels and a width of {self.width} pixels. "
88
+ # )
89
+ # if self.origin == "top-left":
90
+ # parts.append("The origin (0, 0) is at the top-left of the image.")
91
+ # else:
92
+ # parts.append("The origin (0, 0) is at the bottom-left of the image.")
93
+ # elif self.coords == "relative-1":
94
+ # parts.append(
95
+ # "The returned coordinates should be relative coordinates where x are between 0 and 1. "
96
+ # )
97
+ # if self.origin == "top-left":
98
+ # parts.append(
99
+ # "The origin (0, 0) is at the top-left of the image, and (1, 1) is at the bottom-right."
100
+ # )
101
+ # else:
102
+ # parts.append(
103
+ # "The origin (0, 0) is at the bottom-left of the image, and (1, 1) is at the top-right."
104
+ # )
105
+ # elif self.coords == "relative-1k":
106
+ # parts.append(
107
+ # "The returned coordinates should be relative coordinates where x are between 0 and 1000. "
108
+ # )
109
+ # if self.origin == "top-left":
110
+ # parts.append(
111
+ # "The origin (0, 0) is at the top-left of the image, and (1000, 1000) is at the bottom-right."
112
+ # )
113
+ # else:
114
+ # parts.append(
115
+ # "The origin (0, 0) is at the bottom-left of the image, and (1000, 1000) is at the top-right."
116
+ # )
117
+
118
+ # parts.append(
119
+ # "Return JUST the structured output, no prelude or commentary needed."
120
+ # )
121
+
122
+ # result = ""
123
+ # for part in parts:
124
+ # if part.startswith("\n") or result.endswith("\n"):
125
+ # result += part
126
+ # else:
127
+ # result += " " + part
128
+
129
+ # return result.strip()
130
+
131
+ # def parse_output(self, output: str) -> Point | Box2D | list[Point] | list[Box2D]:
132
+ # if self.output_fmt == "json":
133
+ # loaded = load_json(output)
134
+ # if self.output_type == "point":
135
+ # assert isinstance(loaded, dict)
136
+ # if self.orientation == "xy":
137
+ # x, y = loaded["point"]
138
+ # else:
139
+ # y, x = loaded["point"]
140
+
141
+ # return Point(x=)
142
+
143
+ # else:
144
+ # pass
145
+
146
+ # return []
147
+
148
+
149
+ # def locate_point():
150
+ # pass
151
+
152
+
153
+ # def locate_points():
154
+ # pass
155
+
156
+
157
+ # def locate_box():
158
+ # pass
159
+
160
+
161
+ # def locate_boxes():
162
+ # pass
lm_deluge/prompt.py CHANGED
@@ -8,7 +8,7 @@ import tiktoken
8
8
  import xxhash
9
9
 
10
10
  from lm_deluge.file import File
11
- from lm_deluge.image import Image
11
+ from lm_deluge.image import Image, MediaType
12
12
 
13
13
  CachePattern = Literal[
14
14
  "tools_only",
@@ -348,7 +348,7 @@ class Message:
348
348
  self,
349
349
  data: bytes | str | Path | io.BytesIO,
350
350
  *,
351
- media_type: str | None = None,
351
+ media_type: MediaType | None = None,
352
352
  detail: Literal["low", "high", "auto"] = "auto",
353
353
  max_size: int | None = None,
354
354
  ) -> "Message":
@@ -34,6 +34,7 @@ class RequestContext:
34
34
  tools: list | None = None
35
35
  cache: CachePattern | None = None
36
36
  use_responses_api: bool = False
37
+ extra_headers: dict[str, str] | None = None
37
38
 
38
39
  # Computed properties
39
40
  cache_key: str = field(init=False)
@@ -0,0 +1,139 @@
1
+ from dataclasses import dataclass
2
+ from functools import partial
3
+
4
+ from PIL import Image, ImageDraw
5
+
6
+
7
+ @dataclass
8
+ class Point:
9
+ def __init__(self, x: float, y: float, description: str | None = None):
10
+ self.x = x
11
+ self.y = y
12
+ self.description = description
13
+
14
+ def __getitem__(self, index):
15
+ if index == 0:
16
+ return self.x
17
+ elif index == 1:
18
+ return self.y
19
+ elif index == "x":
20
+ return self.x
21
+ elif index == "y":
22
+ return self.y
23
+ else:
24
+ raise IndexError("Index out of range")
25
+
26
+ def __iter__(self):
27
+ yield self.x
28
+ yield self.y
29
+
30
+ def __str__(self):
31
+ return f"Point(x={self.x}, y={self.y})"
32
+
33
+ def __repr__(self):
34
+ return f"Point(x={self.x}, y={self.y})"
35
+
36
+ @classmethod
37
+ def from_tuple(cls, point: tuple, description: str | None = None):
38
+ return cls(point[0], point[1], description)
39
+
40
+ @classmethod
41
+ def from_dict(cls, point: dict, description: str | None = None):
42
+ return cls(point["x"], point["y"], description)
43
+
44
+
45
+ @dataclass
46
+ class Box2D:
47
+ def __init__(
48
+ self,
49
+ xmin: float,
50
+ ymin: float,
51
+ xmax: float,
52
+ ymax: float,
53
+ description: str | None = None,
54
+ ):
55
+ self.xmin = xmin
56
+ self.ymin = ymin
57
+ self.xmax = xmax
58
+ self.ymax = ymax
59
+ self.description = description
60
+
61
+ def __repr__(self):
62
+ return f"Box2D(xmin={self.xmin}, ymin={self.ymin}, xmax={self.xmax}, ymax={self.ymax})"
63
+
64
+ def __str__(self):
65
+ return f"Box2D(xmin={self.xmin}, ymin={self.ymin}, xmax={self.xmax}, ymax={self.ymax})"
66
+
67
+ @classmethod
68
+ def from_list(cls, box: list):
69
+ return cls(box[1], box[0], box[3], box[2])
70
+
71
+ def center(self):
72
+ return Point((self.xmin + self.xmax) / 2, (self.ymin + self.ymax) / 2)
73
+
74
+
75
+ def scale(
76
+ obj: Point | Box2D | tuple | dict,
77
+ src_width: int,
78
+ src_height: int,
79
+ dst_width: int,
80
+ dst_height: int,
81
+ return_integers: bool = True,
82
+ ):
83
+ if isinstance(obj, tuple):
84
+ if len(obj) == 2:
85
+ obj = Point(obj[0], obj[1])
86
+ elif len(obj) == 4:
87
+ obj = Box2D(obj[0], obj[1], obj[2], obj[3])
88
+ else:
89
+ raise ValueError("Invalid tuple length")
90
+ elif isinstance(obj, dict):
91
+ if "x" in obj and "y" in obj:
92
+ obj = Point(obj["x"], obj["y"])
93
+ elif "xmin" in obj and "ymin" in obj and "xmax" in obj and "ymax" in obj:
94
+ obj = Box2D(obj["xmin"], obj["ymin"], obj["xmax"], obj["ymax"])
95
+ else:
96
+ raise ValueError("Invalid dictionary keys")
97
+ scale_x = dst_width / src_width
98
+ scale_y = dst_height / src_height
99
+
100
+ if isinstance(obj, Point):
101
+ x = obj.x * scale_x
102
+ y = obj.y * scale_y
103
+ if return_integers:
104
+ return Point(int(x), int(y), obj.description)
105
+ return Point(x, y)
106
+ elif isinstance(obj, Box2D):
107
+ xmin = obj.xmin * scale_x
108
+ ymin = obj.ymin * scale_y
109
+ xmax = obj.xmax * scale_x
110
+ ymax = obj.ymax * scale_y
111
+ if return_integers:
112
+ return Box2D(int(xmin), int(ymin), int(xmax), int(ymax), obj.description)
113
+ return Box2D(xmin, ymin, xmax, ymax, obj.description)
114
+ else:
115
+ raise TypeError("Unsupported type")
116
+
117
+
118
+ normalize_point = partial(scale, dst_width=1, dst_height=1, return_integers=False)
119
+ denormalize_point = partial(scale, src_width=1, src_height=1, return_integers=False)
120
+ normalize_point_1k = partial(
121
+ scale, dst_width=1000, dst_height=1000, return_integers=True
122
+ )
123
+ denormalize_point_1k = partial(
124
+ scale, src_width=1000, src_height=1000, return_integers=False
125
+ )
126
+
127
+
128
+ def draw_box(image: Image.Image | str, box: Box2D):
129
+ if isinstance(image, str):
130
+ image = Image.open(image)
131
+ draw = ImageDraw.Draw(image)
132
+ draw.rectangle((box.xmin, box.ymin, box.xmax, box.ymax), outline="red", width=2)
133
+ return image
134
+
135
+
136
+ def draw_point(image: Image.Image, point: Point):
137
+ draw = ImageDraw.Draw(image)
138
+ draw.ellipse((point[0] - 2, point[1] - 2, point[0] + 2, point[1] + 2), fill="red")
139
+ return image
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lm_deluge
3
- Version: 0.0.17
3
+ Version: 0.0.18
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
@@ -2,28 +2,28 @@ lm_deluge/__init__.py,sha256=mAztMuxINmh7dGbYnT8tsmw1eryQAvd0jpY8yHzd0EE,315
2
2
  lm_deluge/agent.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  lm_deluge/batches.py,sha256=05t8UL1xCKjLRKtZLkfbexLqro6T_ufFVsaNIMk05Fw,17725
4
4
  lm_deluge/cache.py,sha256=VB1kv8rM2t5XWPR60uhszFcxLDnVKOe1oA5hYjVDjIo,4375
5
- lm_deluge/client.py,sha256=yE82ssvJnAzaYPQchd-cAsbdVFefLGMp-D29aFkZKlE,25530
5
+ lm_deluge/client.py,sha256=KFkI8uzHxwIvDS1PV4KjykHUMQ48wo6h-Yn4IJKUDbg,25682
6
6
  lm_deluge/config.py,sha256=H1tQyJDNHGFuwxqQNL5Z-CjWAC0luHSBA3iY_pxmACM,932
7
7
  lm_deluge/embed.py,sha256=CO-TOlC5kOTAM8lcnicoG4u4K664vCBwHF1vHa-nAGg,13382
8
8
  lm_deluge/errors.py,sha256=oHjt7YnxWbh-eXMScIzov4NvpJMo0-2r5J6Wh5DQ1tk,209
9
9
  lm_deluge/file.py,sha256=zQH1STMjCG9pczO7Fk9Jw0_0Pj_8CogcdIxTe4J4AJw,5414
10
10
  lm_deluge/gemini_limits.py,sha256=V9mpS9JtXYz7AY6OuKyQp5TuIMRH1BVv9YrSNmGmHNA,1569
11
- lm_deluge/image.py,sha256=SIf6vh4pZ5ccrBvWc3zB_ncsWeFw2lKuIJfP3ovo6hk,7444
11
+ lm_deluge/image.py,sha256=D8kMh2yu8sTuOchKpW9DE3XKbE6oUiFl9cRi6H1GpDc,7526
12
12
  lm_deluge/models.py,sha256=6ZCirxOpdcg_M24cKUABYbRpLK-r9dlkXxUS9aeh0UY,49657
13
- lm_deluge/prompt.py,sha256=SaLcUjfzgeIZRzb6fxLp6PTFLxpvcSlaazJq3__2Sqs,35248
14
- lm_deluge/request_context.py,sha256=SfPu9pl5NgDVLaWGQkSXdQZ7Mm-Vw4GSTlOu-PAOE3k,2290
13
+ lm_deluge/prompt.py,sha256=T8o2hwv3RuxG7-fL5pCl0v14WVpmV09PdRzCZzLNszE,35265
14
+ lm_deluge/request_context.py,sha256=l1DrPTtG80WtUhyDWblTiyT695K7Al9lWWDfdl6PMK0,2338
15
15
  lm_deluge/rerank.py,sha256=-NBAJdHz9OB-SWWJnHzkFmeVO4wR6lFV7Vw-SxG7aVo,11457
16
16
  lm_deluge/tool.py,sha256=X6NDabz53BVe1pokaKCeTLCF1-AlMAxOn1_KWiCSb7c,12407
17
17
  lm_deluge/tracker.py,sha256=-EkFDAklh5mclIFR-5SthAwNL4p1yKS8LUN7rhpOVPQ,9266
18
18
  lm_deluge/usage.py,sha256=VMEKghePFIID5JFBObqYxFpgYxnbYm_dnHy7V1-_T6M,4866
19
19
  lm_deluge/api_requests/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
20
- lm_deluge/api_requests/anthropic.py,sha256=nO4Gf59ZddZUURDqkiR3P3Mbr7De7sEcGL6fdYdbozU,7699
21
- lm_deluge/api_requests/base.py,sha256=wKB6a5nNwD-ST_nNRVUlA3l_O9HhccPcGA2fJut7kfw,4430
22
- lm_deluge/api_requests/bedrock.py,sha256=EDYzE7zeYscUeyIai-uHd-fDuPXZszWfSPn55XgUbCI,10846
20
+ lm_deluge/api_requests/anthropic.py,sha256=pgDJLS98R59ZBLUGZxEPuuagEKS3UgjPgvr3LPvsndA,7815
21
+ lm_deluge/api_requests/base.py,sha256=O3-Dsl_hr-xtLTekPLdrNnL5mTTfnfsN6Fcwq0-eKMg,5355
22
+ lm_deluge/api_requests/bedrock.py,sha256=kZtKp6GqF73RpmLJKzEj4XTbllB8Kyq9-QydUuh2iu0,10977
23
23
  lm_deluge/api_requests/common.py,sha256=BZ3vRO5TB669_UsNKugkkuFSzoLHOYJIKt4nV4sf4vc,422
24
- lm_deluge/api_requests/gemini.py,sha256=6brxdouJcsJSEb8OZxklrTaqbZ1M-gWulNkGJqAKWV8,7400
25
- lm_deluge/api_requests/mistral.py,sha256=diflr8NlsJGpSlY1F5Ay0GMZhBDdv9L2JV70UaHnOBs,4431
26
- lm_deluge/api_requests/openai.py,sha256=4MgEoEQ9n_vwsNOyM2tWaPIV3IN5x7UUCrXFlqeZYLk,20782
24
+ lm_deluge/api_requests/gemini.py,sha256=N_94TpjpBLyekdrBjax2w0jPqYf70JycaRgZUNSsAAY,7531
25
+ lm_deluge/api_requests/mistral.py,sha256=5EqYZgu9AfGrWs5-ucr8uJK_0cMEoUKKlEjBV3O6EPc,4561
26
+ lm_deluge/api_requests/openai.py,sha256=vunVsUKW-YwMQ_p7DlVBBUMldjxQUaMinpzl0TyIXm4,21228
27
27
  lm_deluge/api_requests/response.py,sha256=JFSwHAs-yaJYkscOgTAyHkt-v8FDZ5mgER9NmueXTGk,5866
28
28
  lm_deluge/api_requests/deprecated/bedrock.py,sha256=WrcIShCoO8JCUSlFOCHxg6KQCNTZfw3TpYTvSpYk4mA,11320
29
29
  lm_deluge/api_requests/deprecated/cohere.py,sha256=KgDScD6_bWhAzOY5BHZQKSA3kurt4KGENqC4wLsGmcU,5142
@@ -37,16 +37,19 @@ lm_deluge/built_in_tools/anthropic/bash.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5
37
37
  lm_deluge/built_in_tools/anthropic/computer_use.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
38
  lm_deluge/built_in_tools/anthropic/editor.py,sha256=DyC_DrHVTm1khU9QDL39vBuhu4tO5mS5H7xMRIT0Ng4,23327
39
39
  lm_deluge/llm_tools/__init__.py,sha256=TbZTETq9i_9yYskFWQKOG4pGh5ZiyE_D-h3RArfhGp4,231
40
+ lm_deluge/llm_tools/classify.py,sha256=OdMwV5u4XoPlVhjOHX0sng5KPBIKFJmQeOE2fmnPgLU,21
40
41
  lm_deluge/llm_tools/extract.py,sha256=C3drVAMaoFx5jNE38Xi5cXxrqboyoZ9cE7nX5ylWbXw,4482
42
+ lm_deluge/llm_tools/locate.py,sha256=lYNbKTmy9dTvj0lEQkOQ7yrxyqsgYzjD0C_byJKI_4w,6271
41
43
  lm_deluge/llm_tools/ocr.py,sha256=7fDlvs6uUOvbxMasvGGNJx5Fj6biM6z3lijKZaGN26k,23
42
44
  lm_deluge/llm_tools/score.py,sha256=9oGA3-k2U5buHQXkXaEI9M4Wb5yysNhTLsPbGeghAlQ,2580
43
45
  lm_deluge/llm_tools/translate.py,sha256=iXyYvQZ8bC44FWhBk4qpdqjKM1WFF7Shq-H2PxhPgg4,1452
44
46
  lm_deluge/util/json.py,sha256=_4Oar2Cmz2L1DK3EtPLPDxD6rsYHxjROmV8ZpmMjQ-4,5822
45
47
  lm_deluge/util/logprobs.py,sha256=UkBZakOxWluaLqHrjARu7xnJ0uCHVfLGHJdnYlEcutk,11768
48
+ lm_deluge/util/spatial.py,sha256=BsF_UKhE-x0xBirc-bV1xSKZRTUhsOBdGqsMKme20C8,4099
46
49
  lm_deluge/util/validation.py,sha256=hz5dDb3ebvZrZhnaWxOxbNSVMI6nmaOODBkk0htAUhs,1575
47
50
  lm_deluge/util/xml.py,sha256=Ft4zajoYBJR3HHCt2oHwGfymGLdvp_gegVmJ-Wqk4Ck,10547
48
- lm_deluge-0.0.17.dist-info/licenses/LICENSE,sha256=uNNXGXPCw2TC7CUs7SEBkA-Mz6QBQFWUUEWDMgEs1dU,1058
49
- lm_deluge-0.0.17.dist-info/METADATA,sha256=vjbrAWwFloi0nwrDXWcUT31DmhomLoielYzsCf_2y7E,12978
50
- lm_deluge-0.0.17.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
51
- lm_deluge-0.0.17.dist-info/top_level.txt,sha256=hqU-TJX93yBwpgkDtYcXyLr3t7TLSCCZ_reytJjwBaE,10
52
- lm_deluge-0.0.17.dist-info/RECORD,,
51
+ lm_deluge-0.0.18.dist-info/licenses/LICENSE,sha256=uNNXGXPCw2TC7CUs7SEBkA-Mz6QBQFWUUEWDMgEs1dU,1058
52
+ lm_deluge-0.0.18.dist-info/METADATA,sha256=ipVPBfU_QURIDLXW3V-dwTFw_5luECxLTs3_bxku1oc,12978
53
+ lm_deluge-0.0.18.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
54
+ lm_deluge-0.0.18.dist-info/top_level.txt,sha256=hqU-TJX93yBwpgkDtYcXyLr3t7TLSCCZ_reytJjwBaE,10
55
+ lm_deluge-0.0.18.dist-info/RECORD,,