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 +91 -27
- {nebu-0.1.30.dist-info → nebu-0.1.31.dist-info}/METADATA +1 -1
- {nebu-0.1.30.dist-info → nebu-0.1.31.dist-info}/RECORD +6 -6
- {nebu-0.1.30.dist-info → nebu-0.1.31.dist-info}/WHEEL +0 -0
- {nebu-0.1.30.dist-info → nebu-0.1.31.dist-info}/licenses/LICENSE +0 -0
- {nebu-0.1.30.dist-info → nebu-0.1.31.dist-info}/top_level.txt +0 -0
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
|
-
|
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
|
-
|
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
|
-
{
|
103
|
-
|
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
|
-
|
167
|
-
|
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
|
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(
|
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
|
-
|
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
|
181
|
-
|
182
|
-
|
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(
|
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 {
|
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
|
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
|
@@ -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=
|
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.
|
23
|
-
nebu-0.1.
|
24
|
-
nebu-0.1.
|
25
|
-
nebu-0.1.
|
26
|
-
nebu-0.1.
|
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
|
File without changes
|
File without changes
|