nebu 0.1.30__py3-none-any.whl → 0.1.31__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.
nebu/chatx/convert.py CHANGED
@@ -9,7 +9,7 @@ from PIL import Image, UnidentifiedImageError
9
9
 
10
10
 
11
11
  def convert_to_unsloth_inference(
12
- old_schema: Dict[str, Any],
12
+ old_schema: List[Dict[str, Any]],
13
13
  ) -> Tuple[List[Dict[str, Any]], List[Image.Image]]:
14
14
  """
15
15
  Convert from an old OpenAI message format that may look like:
@@ -74,11 +74,16 @@ def convert_to_unsloth_inference(
74
74
  # regardless of how many images were found, and merges all text into one block.
75
75
  new_content = []
76
76
  if image_urls:
77
- new_content.append({"type": "image"})
77
+ # Add image placeholders for each image found
78
+ for _ in image_urls:
79
+ new_content.append({"type": "image"})
78
80
  if merged_text:
79
81
  new_content.append({"type": "text", "text": merged_text})
80
82
 
81
- new_schema.append({"role": role, "content": new_content})
83
+ # Check if there's any content to add
84
+ if new_content:
85
+ new_schema.append({"role": role, "content": new_content})
86
+ # else: Optionally handle cases where a message might become empty
82
87
 
83
88
  return new_schema, all_images
84
89
 
@@ -93,19 +98,22 @@ def oai_to_unsloth(
93
98
  (typical in JSON Lines format) to the Nebulous conversation format.
94
99
  Images specified by URLs or base64 strings are loaded into PIL.Image objects.
95
100
 
96
- Input format example (as dict from JSON line):
97
101
  {
98
102
  "messages": [
99
103
  {
100
104
  "role": "user",
101
105
  "content": [
102
- {"type": "input_text", "text": "Describe the image."},
103
- {"type": "input_image", "image_url": "http://... or base64 string"},
106
+ {
107
+ "type": "text",
108
+ "text": "Who is this an image of?"
109
+ },
110
+ {
111
+ "type": "image_url",
112
+ "image_url": {
113
+ "url": "https://upload.wikimedia.org/wikipedia/commons/5/57/Abraham_Lincoln_1863_Portrait_%283x4_cropped%29.jpg"
114
+ }
115
+ }
104
116
  ]
105
- },
106
- {
107
- "role": "assistant",
108
- "content": [{"type": "text", "text": "This is an image of..."}] # Or potentially just a string
109
117
  }
110
118
  ]
111
119
  }
@@ -146,6 +154,7 @@ def oai_to_unsloth(
146
154
 
147
155
  role = message.get("role")
148
156
  input_content = message.get("content") # Can be list or string
157
+ image_url_top_level = message.get("image_url") # Check for top-level image_url
149
158
 
150
159
  processed_content = []
151
160
 
@@ -163,41 +172,59 @@ def oai_to_unsloth(
163
172
  "image",
164
173
  ): # Accept 'image' as source key too
165
174
  # Use "image_url" first, then fallback to "image" if needed
166
- image_source = item.get("image_url", item.get("image"))
167
- if image_source:
175
+ image_source_value = item.get("image_url", item.get("image"))
176
+ image_url_to_load = None
177
+ pil_image_to_load = None
178
+
179
+ if item_type == "image_url" and isinstance(
180
+ image_source_value, dict
181
+ ):
182
+ # Handle nested {"url": "..."}
183
+ image_url_to_load = image_source_value.get("url")
184
+ elif isinstance(image_source_value, str):
185
+ # Handle direct URL string or base64 string
186
+ image_url_to_load = image_source_value # Could be URL or base64
187
+ elif isinstance(image_source_value, Image.Image):
188
+ # Handle direct PIL image
189
+ pil_image_to_load = image_source_value
190
+
191
+ if pil_image_to_load: # If we already have a PIL image
192
+ processed_content.append(
193
+ {"type": "image", "image": pil_image_to_load}
194
+ )
195
+ elif (
196
+ image_url_to_load
197
+ ): # If we have a URL or base64 string to process
168
198
  pil_image = None
169
199
  try:
170
- if isinstance(
171
- image_source, str
172
- ) and image_source.startswith(("http://", "https://")):
200
+ if image_url_to_load.startswith(("http://", "https://")):
173
201
  # Handle URL
174
- response = requests.get(image_source, stream=True)
202
+ response = requests.get(image_url_to_load, stream=True)
175
203
  response.raise_for_status() # Raise an exception for bad status codes
176
204
  pil_image = Image.open(response.raw)
177
- elif isinstance(image_source, str):
205
+ else: # Assume base64
178
206
  # Handle base64 string
179
207
  # Remove potential data URI prefix (e.g., "data:image/png;base64,")
180
- if "," in image_source:
181
- image_source = image_source.split(",", 1)[1]
182
- image_bytes = base64.b64decode(image_source)
208
+ if "," in image_url_to_load:
209
+ image_url_to_load = image_url_to_load.split(",", 1)[
210
+ 1
211
+ ]
212
+ image_bytes = base64.b64decode(image_url_to_load)
183
213
  pil_image = Image.open(io.BytesIO(image_bytes))
184
214
 
185
- elif isinstance(image_source, Image.Image):
186
- # Handle direct PIL.Image input
187
- pil_image = image_source
188
-
189
215
  if pil_image:
190
216
  processed_content.append(
191
217
  {"type": "image", "image": pil_image}
192
218
  )
193
219
  else:
220
+ # This condition might be less likely now with the separated logic
194
221
  print(
195
- f"Warning: Could not load image from source: {type(image_source)}"
222
+ f"Warning: Could not load image from source: {type(image_url_to_load)}"
196
223
  )
197
224
 
198
225
  except requests.exceptions.RequestException as e:
199
226
  print(
200
- f"Warning: Failed to fetch image from URL {image_source}: {e}"
227
+ f"Warning: Failed to fetch image from URL {image_url_to_load}: {e}"
201
228
  )
202
229
  except (binascii.Error, ValueError) as e:
203
230
  print(f"Warning: Failed to decode base64 image string: {e}")
@@ -210,7 +237,7 @@ def oai_to_unsloth(
210
237
 
211
238
  else:
212
239
  print(
213
- "Warning: Image item provided but 'image_url' or 'image' key is missing or empty."
240
+ "Warning: Image item provided but could not resolve image source (URL, base64, or PIL Image)."
214
241
  )
215
242
 
216
243
  # Add handling for other potential input types if necessary
@@ -219,6 +246,43 @@ def oai_to_unsloth(
219
246
  processed_content.append({"type": "text", "text": input_content})
220
247
  # else: Handle unexpected content format (e.g., log warning, skip message)
221
248
 
249
+ # Handle potential top-level image_url (after processing content)
250
+ if image_url_top_level and isinstance(image_url_top_level, str):
251
+ pil_image = None
252
+ try:
253
+ if image_url_top_level.startswith(("http://", "https://")):
254
+ # Handle URL
255
+ response = requests.get(image_url_top_level, stream=True)
256
+ response.raise_for_status() # Raise an exception for bad status codes
257
+ pil_image = Image.open(response.raw)
258
+ else:
259
+ # Assume base64 string if not URL (could refine this check)
260
+ # Remove potential data URI prefix
261
+ if "," in image_url_top_level:
262
+ image_url_top_level = image_url_top_level.split(",", 1)[1]
263
+ image_bytes = base64.b64decode(image_url_top_level)
264
+ pil_image = Image.open(io.BytesIO(image_bytes))
265
+
266
+ if pil_image:
267
+ processed_content.append({"type": "image", "image": pil_image})
268
+ else:
269
+ print(
270
+ f"Warning: Could not load image from top-level source: {type(image_url_top_level)}"
271
+ )
272
+
273
+ except requests.exceptions.RequestException as e:
274
+ print(
275
+ f"Warning: Failed to fetch top-level image from URL {image_url_top_level}: {e}"
276
+ )
277
+ except (binascii.Error, ValueError) as e:
278
+ print(f"Warning: Failed to decode top-level base64 image string: {e}")
279
+ except (IOError, UnidentifiedImageError) as e:
280
+ print(f"Warning: Failed to open top-level image: {e}")
281
+ except Exception as e:
282
+ print(
283
+ f"Warning: An unexpected error occurred while processing top-level image: {e}"
284
+ )
285
+
222
286
  if role and processed_content:
223
287
  nebu_conversation.append({"role": role, "content": processed_content})
224
288
  # else: Handle missing role or empty content if needed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nebu
3
- Version: 0.1.30
3
+ Version: 0.1.31
4
4
  Summary: A globally distributed container runtime
5
5
  Requires-Python: >=3.10.14
6
6
  Description-Content-Type: text/markdown
@@ -5,7 +5,7 @@ nebu/cache.py,sha256=1aY1plIXWOPmUY6GGq_s_QDXzIi5UMuG34XYBA8PpW8,3803
5
5
  nebu/config.py,sha256=aZzQltkobtOLHFCGcIkpKoE3ITn3Z11Dp0E72w84TA0,5769
6
6
  nebu/data.py,sha256=kIH9-JJ1-iO7P2t28bku6Gn0Y5tgQszGeTW_rpmO03A,38725
7
7
  nebu/meta.py,sha256=CzFHMND9seuewzq9zNNx9WTr6JvrCBExe7BLqDSr7lM,745
8
- nebu/chatx/convert.py,sha256=4rjccr9bI0xmCAumKlxyFpVzW0up9Yj-gJYvDjH6C54,8798
8
+ nebu/chatx/convert.py,sha256=S_0Q7OTZEeGQZTfqCmshp0UaXJbAR_w9dIBTcyqnlBo,12107
9
9
  nebu/chatx/openai.py,sha256=LLSPvVGnauUViAXY7OifwoWlkUGZWfgxEjxR4mjSqZg,44961
10
10
  nebu/containers/container.py,sha256=yb7KaPTVXnEEAlrpdlUi4HNqF6P7z9bmwAILGlq6iqU,13502
11
11
  nebu/containers/decorator.py,sha256=uFtzlAXRHYZECJ-NPusY7oN9GXvdHrHDd_JNrIGr8aQ,3244
@@ -19,8 +19,8 @@ nebu/processors/processor.py,sha256=EQ3-dBf432fSAQE2A_9ATX-cG5LkJ4fjVaOtlxCoXvc,
19
19
  nebu/processors/remote.py,sha256=TeAIPGEMqnDIb7H1iett26IEZrBlcbPB_-DSm6jcH1E,1285
20
20
  nebu/redis/models.py,sha256=coPovAcVXnOU1Xh_fpJL4PO3QctgK9nBe5QYoqEcnxg,1230
21
21
  nebu/services/service.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
- nebu-0.1.30.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
23
- nebu-0.1.30.dist-info/METADATA,sha256=Y_o6hvOdihVgFJMRQPIpALA2V6JfaXybtrlSaQyMNtI,1786
24
- nebu-0.1.30.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
25
- nebu-0.1.30.dist-info/top_level.txt,sha256=uLIbEKJeGSHWOAJN5S0i5XBGwybALlF9bYoB1UhdEgQ,5
26
- nebu-0.1.30.dist-info/RECORD,,
22
+ nebu-0.1.31.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
23
+ nebu-0.1.31.dist-info/METADATA,sha256=gXoUbLOIX6fDnn90IDvX3-fnFhBWRZrVxbJr0ZNbEf0,1786
24
+ nebu-0.1.31.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
25
+ nebu-0.1.31.dist-info/top_level.txt,sha256=uLIbEKJeGSHWOAJN5S0i5XBGwybALlF9bYoB1UhdEgQ,5
26
+ nebu-0.1.31.dist-info/RECORD,,
File without changes