nebu 0.1.45__py3-none-any.whl → 0.1.48__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/__init__.py +0 -1
- nebu/builders/builder.py +0 -0
- nebu/data.py +24 -3
- nebu/processors/consumer.py +581 -389
- nebu/processors/decorate.py +441 -411
- {nebu-0.1.45.dist-info → nebu-0.1.48.dist-info}/METADATA +1 -1
- {nebu-0.1.45.dist-info → nebu-0.1.48.dist-info}/RECORD +10 -12
- {nebu-0.1.45.dist-info → nebu-0.1.48.dist-info}/WHEEL +1 -1
- nebu/adapter.py +0 -20
- nebu/chatx/convert.py +0 -362
- nebu/chatx/openai.py +0 -976
- {nebu-0.1.45.dist-info → nebu-0.1.48.dist-info}/licenses/LICENSE +0 -0
- {nebu-0.1.45.dist-info → nebu-0.1.48.dist-info}/top_level.txt +0 -0
@@ -1,27 +1,25 @@
|
|
1
|
-
nebu/__init__.py,sha256=
|
2
|
-
nebu/adapter.py,sha256=lqpvfY_up-qVvWxxfYKLFXkYvlEilv-BrRNt554cixU,591
|
1
|
+
nebu/__init__.py,sha256=_CvNCsTMaz8TEwmRbYNicAIZf7jTnC01wkQwbfQyM6E,442
|
3
2
|
nebu/auth.py,sha256=N_v6SPFD9HU_UoRDTaouH03g2Hmo9C-xxqInE1FweXE,1471
|
4
3
|
nebu/cache.py,sha256=jmluqvWnE9N8uNq6nppXSxEJK7DKWaB79GicaGg9KmY,4718
|
5
4
|
nebu/config.py,sha256=aZzQltkobtOLHFCGcIkpKoE3ITn3Z11Dp0E72w84TA0,5769
|
6
|
-
nebu/data.py,sha256=
|
5
|
+
nebu/data.py,sha256=yAQ5EXmdwsQfaFC30DB8IrzKNS53NhUkPrwKyM5nbxA,39782
|
7
6
|
nebu/meta.py,sha256=CzFHMND9seuewzq9zNNx9WTr6JvrCBExe7BLqDSr7lM,745
|
7
|
+
nebu/builders/builder.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
8
|
nebu/builders/models.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
-
nebu/chatx/convert.py,sha256=1x6Dz_-posZoxo-xC4QDqeKjrd5RgOkobBZT9K3Ze74,14478
|
10
|
-
nebu/chatx/openai.py,sha256=VsJvV2MbYeJj2Ita9Q9X3qj5r5F3P-aPDhpSFr-Q-dw,44950
|
11
9
|
nebu/containers/container.py,sha256=yb7KaPTVXnEEAlrpdlUi4HNqF6P7z9bmwAILGlq6iqU,13502
|
12
10
|
nebu/containers/decorator.py,sha256=uFtzlAXRHYZECJ-NPusY7oN9GXvdHrHDd_JNrIGr8aQ,3244
|
13
11
|
nebu/containers/models.py,sha256=0j6NGy4yto-enRDh_4JH_ZTbHrLdSpuMOqNQPnIrwC4,6815
|
14
12
|
nebu/containers/server.py,sha256=yFa2Y9PzBn59E1HftKiv0iapPonli2rbGAiU6r-wwe0,2513
|
15
|
-
nebu/processors/consumer.py,sha256=
|
16
|
-
nebu/processors/decorate.py,sha256=
|
13
|
+
nebu/processors/consumer.py,sha256=QQM1vi393ogn0KghDwGD8bXFWVLPT8adr2534I0KOlM,33553
|
14
|
+
nebu/processors/decorate.py,sha256=dV906ZR0hxTFaX0UjURntmq_dwl-QcdQQnce_OCyQVM,44215
|
17
15
|
nebu/processors/default.py,sha256=W4slJenG59rvyTlJ7gRp58eFfXcNOTT2Hfi6zzJAobI,365
|
18
16
|
nebu/processors/models.py,sha256=y40HoW-MEzDWB2dm_tsYlUy3Nf3s6eiLC0iGO9BoNog,3956
|
19
17
|
nebu/processors/processor.py,sha256=cptZEN9ZGcaoFNreaw3BkwV0qKHvjP9b4nNxlQjFT3s,15405
|
20
18
|
nebu/processors/remote.py,sha256=TeAIPGEMqnDIb7H1iett26IEZrBlcbPB_-DSm6jcH1E,1285
|
21
19
|
nebu/redis/models.py,sha256=coPovAcVXnOU1Xh_fpJL4PO3QctgK9nBe5QYoqEcnxg,1230
|
22
20
|
nebu/services/service.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
23
|
-
nebu-0.1.
|
24
|
-
nebu-0.1.
|
25
|
-
nebu-0.1.
|
26
|
-
nebu-0.1.
|
27
|
-
nebu-0.1.
|
21
|
+
nebu-0.1.48.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
22
|
+
nebu-0.1.48.dist-info/METADATA,sha256=vCL9vSxuXDsbtaDT4iQN0TBSvnlIWII0UHQ0L_9aSOc,1731
|
23
|
+
nebu-0.1.48.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
|
24
|
+
nebu-0.1.48.dist-info/top_level.txt,sha256=uLIbEKJeGSHWOAJN5S0i5XBGwybALlF9bYoB1UhdEgQ,5
|
25
|
+
nebu-0.1.48.dist-info/RECORD,,
|
nebu/adapter.py
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
import time
|
2
|
-
from typing import List
|
3
|
-
|
4
|
-
from pydantic import BaseModel, Field
|
5
|
-
|
6
|
-
|
7
|
-
class Adapter(BaseModel):
|
8
|
-
created_at: int = Field(default_factory=lambda: int(time.time()))
|
9
|
-
name: str
|
10
|
-
uri: str
|
11
|
-
base_model: str
|
12
|
-
owner: str
|
13
|
-
epochs_trained: int = Field(default=0)
|
14
|
-
last_trained: int = Field(default=0)
|
15
|
-
lora_rank: int = Field(default=8)
|
16
|
-
lora_alpha: int = Field(default=16)
|
17
|
-
lora_dropout: float = Field(default=0.1)
|
18
|
-
lora_target_modules: List[str] = Field(default=[])
|
19
|
-
learning_rate: float = Field(default=0.0001)
|
20
|
-
examples_trained: int = Field(default=0)
|
nebu/chatx/convert.py
DELETED
@@ -1,362 +0,0 @@
|
|
1
|
-
import base64
|
2
|
-
import binascii
|
3
|
-
import io
|
4
|
-
from io import BytesIO
|
5
|
-
from typing import Any, Dict, List, Tuple
|
6
|
-
|
7
|
-
import requests
|
8
|
-
from PIL import Image, UnidentifiedImageError
|
9
|
-
|
10
|
-
# Define a standard User-Agent header
|
11
|
-
REQUESTS_HEADERS = {
|
12
|
-
"User-Agent": "Nebulous-py/0.1 (https://github.com/agentsea/nebulous-py; contact@agentsea.ai)"
|
13
|
-
}
|
14
|
-
|
15
|
-
|
16
|
-
def convert_to_unsloth_inference(
|
17
|
-
old_schema: List[Dict[str, Any]],
|
18
|
-
) -> Tuple[List[Dict[str, Any]], List[Image.Image]]:
|
19
|
-
"""
|
20
|
-
Convert from an old OpenAI message format that may look like:
|
21
|
-
[
|
22
|
-
{
|
23
|
-
"role": "user",
|
24
|
-
"content": [
|
25
|
-
{"type": "text", "text": "some text"},
|
26
|
-
{"type": "image_url", "image_url": {"url": "https://..."}},
|
27
|
-
...
|
28
|
-
],
|
29
|
-
}
|
30
|
-
]
|
31
|
-
|
32
|
-
to a new format:
|
33
|
-
[
|
34
|
-
{
|
35
|
-
"role": "user",
|
36
|
-
"content": [
|
37
|
-
{"type": "image"},
|
38
|
-
{"type": "text", "text": "merged user text"}
|
39
|
-
],
|
40
|
-
}
|
41
|
-
]
|
42
|
-
|
43
|
-
Along with the new format, return a list of downloaded PIL Image objects.
|
44
|
-
"""
|
45
|
-
|
46
|
-
new_schema = []
|
47
|
-
all_images = [] # Will store PIL images as we convert them
|
48
|
-
|
49
|
-
for message in old_schema:
|
50
|
-
role = message.get("role", "user")
|
51
|
-
|
52
|
-
# Collect all text pieces and all image URLs
|
53
|
-
text_chunks = []
|
54
|
-
image_urls = []
|
55
|
-
|
56
|
-
for content_item in message.get("content", []):
|
57
|
-
content_type = content_item.get("type")
|
58
|
-
if content_type == "text":
|
59
|
-
text_chunks.append(content_item.get("text", ""))
|
60
|
-
elif content_type == "image_url":
|
61
|
-
image_url = content_item.get("image_url", {}).get("url")
|
62
|
-
if image_url:
|
63
|
-
image_urls.append(image_url)
|
64
|
-
|
65
|
-
# Merge text chunks into one
|
66
|
-
merged_text = " ".join(text_chunks).strip()
|
67
|
-
|
68
|
-
# Convert each URL into a PIL image
|
69
|
-
for url in image_urls:
|
70
|
-
# Download the image
|
71
|
-
response = requests.get(url, headers=REQUESTS_HEADERS)
|
72
|
-
response.raise_for_status()
|
73
|
-
image_data = BytesIO(response.content)
|
74
|
-
pil_img = Image.open(image_data).convert("RGB")
|
75
|
-
all_images.append(pil_img)
|
76
|
-
|
77
|
-
# Construct new message format
|
78
|
-
# For simplicity, this example only places one {"type": "image"} placeholder
|
79
|
-
# regardless of how many images were found, and merges all text into one block.
|
80
|
-
new_content = []
|
81
|
-
if image_urls:
|
82
|
-
# Add image placeholders for each image found
|
83
|
-
for _ in image_urls:
|
84
|
-
new_content.append({"type": "image"})
|
85
|
-
if merged_text:
|
86
|
-
new_content.append({"type": "text", "text": merged_text})
|
87
|
-
|
88
|
-
# Check if there's any content to add
|
89
|
-
if new_content:
|
90
|
-
new_schema.append({"role": role, "content": new_content})
|
91
|
-
# else: Optionally handle cases where a message might become empty
|
92
|
-
|
93
|
-
return new_schema, all_images
|
94
|
-
|
95
|
-
|
96
|
-
def oai_to_unsloth(
|
97
|
-
messages_input: Dict[
|
98
|
-
str, Any
|
99
|
-
], # Assume input is always dict like {'messages': [...]}
|
100
|
-
) -> Dict[str, List[Dict[str, Any]]]:
|
101
|
-
"""
|
102
|
-
Converts messages from a JSON object containing a 'messages' key
|
103
|
-
(typical in JSON Lines format) to the Nebulous conversation format.
|
104
|
-
Images specified by URLs or base64 strings are loaded into PIL.Image objects.
|
105
|
-
|
106
|
-
{
|
107
|
-
"messages": [
|
108
|
-
{
|
109
|
-
"role": "user",
|
110
|
-
"content": [
|
111
|
-
{
|
112
|
-
"type": "text",
|
113
|
-
"text": "Who is this an image of?"
|
114
|
-
},
|
115
|
-
{
|
116
|
-
"type": "image_url",
|
117
|
-
"image_url": {
|
118
|
-
"url": "https://upload.wikimedia.org/wikipedia/commons/5/57/Abraham_Lincoln_1863_Portrait_%283x4_cropped%29.jpg"
|
119
|
-
}
|
120
|
-
}
|
121
|
-
]
|
122
|
-
}
|
123
|
-
]
|
124
|
-
}
|
125
|
-
|
126
|
-
Output format example:
|
127
|
-
{
|
128
|
-
"messages": [
|
129
|
-
{
|
130
|
-
"role": "user",
|
131
|
-
"content": [
|
132
|
-
{"type": "text", "text": "Describe the image."},
|
133
|
-
{"type": "image", "image": <PIL.Image.Image object>},
|
134
|
-
]
|
135
|
-
},
|
136
|
-
{
|
137
|
-
"role": "assistant",
|
138
|
-
"content": [{"type": "text", "text": "This is an image of..."}]
|
139
|
-
}
|
140
|
-
]
|
141
|
-
}
|
142
|
-
"""
|
143
|
-
# Directly extract the list of messages, assuming input structure
|
144
|
-
messages_to_process = messages_input.get("messages", [])
|
145
|
-
|
146
|
-
# Validate that 'messages' key contained a list
|
147
|
-
if not isinstance(messages_to_process, list):
|
148
|
-
print(
|
149
|
-
f"Warning: Input dict provided, but 'messages' key does not contain a list: {type(messages_to_process)}. Returning empty."
|
150
|
-
)
|
151
|
-
return {"messages": []}
|
152
|
-
|
153
|
-
nebu_conversation = []
|
154
|
-
for message in messages_to_process: # Use the extracted list
|
155
|
-
# Add check here for robustness against malformed items *within* the list
|
156
|
-
if not isinstance(message, dict):
|
157
|
-
print(f"Warning: Skipping non-dictionary item in message list: {message!r}")
|
158
|
-
continue
|
159
|
-
|
160
|
-
role = message.get("role")
|
161
|
-
input_content = message.get("content") # Can be list or string
|
162
|
-
image_url_top_level = message.get("image_url") # Check for top-level image_url
|
163
|
-
|
164
|
-
processed_content = []
|
165
|
-
|
166
|
-
if isinstance(input_content, list):
|
167
|
-
# Process list content (multi-modal)
|
168
|
-
for item in input_content:
|
169
|
-
item_type = item.get("type")
|
170
|
-
if item_type in ("input_text", "text"):
|
171
|
-
processed_content.append(
|
172
|
-
{"type": "text", "text": item.get("text", "")}
|
173
|
-
)
|
174
|
-
elif item_type in (
|
175
|
-
"input_image",
|
176
|
-
"image_url",
|
177
|
-
"image",
|
178
|
-
): # Accept 'image' as source key too
|
179
|
-
# Use "image_url" first, then fallback to "image" if needed
|
180
|
-
image_source_value = item.get("image_url", item.get("image"))
|
181
|
-
image_url_to_load = None
|
182
|
-
pil_image_to_load = None
|
183
|
-
|
184
|
-
if item_type == "image_url" and isinstance(
|
185
|
-
image_source_value, dict
|
186
|
-
):
|
187
|
-
# Handle nested {"url": "..."}
|
188
|
-
image_url_to_load = image_source_value.get("url")
|
189
|
-
elif isinstance(image_source_value, str):
|
190
|
-
# Handle direct URL string or base64 string
|
191
|
-
image_url_to_load = image_source_value # Could be URL or base64
|
192
|
-
elif isinstance(image_source_value, Image.Image):
|
193
|
-
# Handle direct PIL image
|
194
|
-
pil_image_to_load = image_source_value
|
195
|
-
|
196
|
-
if pil_image_to_load: # If we already have a PIL image
|
197
|
-
processed_content.append(
|
198
|
-
{"type": "image", "image": pil_image_to_load}
|
199
|
-
)
|
200
|
-
elif (
|
201
|
-
image_url_to_load
|
202
|
-
): # If we have a URL or base64 string to process
|
203
|
-
pil_image = None
|
204
|
-
try:
|
205
|
-
if image_url_to_load.startswith(("http://", "https://")):
|
206
|
-
# Handle URL
|
207
|
-
response = requests.get(
|
208
|
-
image_url_to_load,
|
209
|
-
stream=True,
|
210
|
-
headers=REQUESTS_HEADERS,
|
211
|
-
)
|
212
|
-
response.raise_for_status() # Raise an exception for bad status codes
|
213
|
-
pil_image = Image.open(response.raw)
|
214
|
-
else: # Assume base64
|
215
|
-
# Handle base64 string
|
216
|
-
# Remove potential data URI prefix (e.g., "data:image/png;base64,")
|
217
|
-
if "," in image_url_to_load:
|
218
|
-
image_url_to_load = image_url_to_load.split(",", 1)[
|
219
|
-
1
|
220
|
-
]
|
221
|
-
image_bytes = base64.b64decode(image_url_to_load)
|
222
|
-
pil_image = Image.open(io.BytesIO(image_bytes))
|
223
|
-
|
224
|
-
if pil_image:
|
225
|
-
processed_content.append(
|
226
|
-
{"type": "image", "image": pil_image}
|
227
|
-
)
|
228
|
-
else:
|
229
|
-
# This condition might be less likely now with the separated logic
|
230
|
-
print(
|
231
|
-
f"Warning: Could not load image from source: {type(image_url_to_load)}"
|
232
|
-
)
|
233
|
-
|
234
|
-
except requests.exceptions.RequestException as e:
|
235
|
-
print(
|
236
|
-
f"Warning: Failed to fetch image from URL {image_url_to_load}: {e}"
|
237
|
-
)
|
238
|
-
except (binascii.Error, ValueError) as e:
|
239
|
-
print(f"Warning: Failed to decode base64 image string: {e}")
|
240
|
-
except (IOError, UnidentifiedImageError) as e:
|
241
|
-
print(f"Warning: Failed to open image: {e}")
|
242
|
-
except Exception as e:
|
243
|
-
print(
|
244
|
-
f"Warning: An unexpected error occurred while processing image: {e}"
|
245
|
-
)
|
246
|
-
|
247
|
-
else:
|
248
|
-
print(
|
249
|
-
"Warning: Image item provided but could not resolve image source (URL, base64, or PIL Image)."
|
250
|
-
)
|
251
|
-
|
252
|
-
# Add handling for other potential input types if necessary
|
253
|
-
elif isinstance(input_content, str):
|
254
|
-
# Handle simple string content (common for assistant messages)
|
255
|
-
processed_content.append({"type": "text", "text": input_content})
|
256
|
-
# else: Handle unexpected content format (e.g., log warning, skip message)
|
257
|
-
|
258
|
-
# Handle potential top-level image_url (after processing content)
|
259
|
-
if image_url_top_level and isinstance(image_url_top_level, str):
|
260
|
-
pil_image = None
|
261
|
-
try:
|
262
|
-
if image_url_top_level.startswith(("http://", "https://")):
|
263
|
-
# Handle URL
|
264
|
-
response = requests.get(
|
265
|
-
image_url_top_level, stream=True, headers=REQUESTS_HEADERS
|
266
|
-
)
|
267
|
-
response.raise_for_status() # Raise an exception for bad status codes
|
268
|
-
pil_image = Image.open(response.raw)
|
269
|
-
else:
|
270
|
-
# Assume base64 string if not URL (could refine this check)
|
271
|
-
# Remove potential data URI prefix
|
272
|
-
if "," in image_url_top_level:
|
273
|
-
image_url_top_level = image_url_top_level.split(",", 1)[1]
|
274
|
-
image_bytes = base64.b64decode(image_url_top_level)
|
275
|
-
pil_image = Image.open(io.BytesIO(image_bytes))
|
276
|
-
|
277
|
-
if pil_image:
|
278
|
-
processed_content.append({"type": "image", "image": pil_image})
|
279
|
-
else:
|
280
|
-
print(
|
281
|
-
f"Warning: Could not load image from top-level source: {type(image_url_top_level)}"
|
282
|
-
)
|
283
|
-
|
284
|
-
except requests.exceptions.RequestException as e:
|
285
|
-
print(
|
286
|
-
f"Warning: Failed to fetch top-level image from URL {image_url_top_level}: {e}"
|
287
|
-
)
|
288
|
-
except (binascii.Error, ValueError) as e:
|
289
|
-
print(f"Warning: Failed to decode top-level base64 image string: {e}")
|
290
|
-
except (IOError, UnidentifiedImageError) as e:
|
291
|
-
print(f"Warning: Failed to open top-level image: {e}")
|
292
|
-
except Exception as e:
|
293
|
-
print(
|
294
|
-
f"Warning: An unexpected error occurred while processing top-level image: {e}"
|
295
|
-
)
|
296
|
-
|
297
|
-
if role and processed_content:
|
298
|
-
nebu_conversation.append({"role": role, "content": processed_content})
|
299
|
-
# else: Handle missing role or empty content if needed
|
300
|
-
|
301
|
-
return {"messages": nebu_conversation}
|
302
|
-
|
303
|
-
|
304
|
-
def oai_to_qwen(
|
305
|
-
messages_input: List[Dict[str, Any]],
|
306
|
-
) -> List[Dict[str, Any]]:
|
307
|
-
"""
|
308
|
-
Convert from an OpenAI message format to a format where image URLs
|
309
|
-
are kept as strings.
|
310
|
-
|
311
|
-
Input format example:
|
312
|
-
[
|
313
|
-
{
|
314
|
-
"role": "user",
|
315
|
-
"content": [
|
316
|
-
{"type": "text", "text": "some text"},
|
317
|
-
{"type": "image_url", "image_url": {"url": "https://..."}},
|
318
|
-
],
|
319
|
-
}
|
320
|
-
]
|
321
|
-
|
322
|
-
Output format example:
|
323
|
-
[
|
324
|
-
{
|
325
|
-
"role": "user",
|
326
|
-
"content": [
|
327
|
-
{"type": "text", "text": "some text"},
|
328
|
-
{"type": "image", "image": "https://..."},
|
329
|
-
],
|
330
|
-
}
|
331
|
-
]
|
332
|
-
"""
|
333
|
-
new_schema = []
|
334
|
-
for message in messages_input:
|
335
|
-
role = message.get("role")
|
336
|
-
input_content = message.get("content")
|
337
|
-
|
338
|
-
if not isinstance(role, str) or not isinstance(input_content, list):
|
339
|
-
# Skip malformed messages
|
340
|
-
print(f"Warning: Skipping malformed message: {message!r}")
|
341
|
-
continue
|
342
|
-
|
343
|
-
processed_content = []
|
344
|
-
for item in input_content:
|
345
|
-
item_type = item.get("type")
|
346
|
-
if item_type == "text":
|
347
|
-
text = item.get("text", "")
|
348
|
-
processed_content.append({"type": "text", "text": text})
|
349
|
-
elif item_type == "image_url":
|
350
|
-
image_url_dict = item.get("image_url", {})
|
351
|
-
url = image_url_dict.get("url")
|
352
|
-
if url:
|
353
|
-
processed_content.append({"type": "image", "image": url})
|
354
|
-
else:
|
355
|
-
print(f"Warning: image_url item missing 'url': {item!r}")
|
356
|
-
# else: Handle or ignore other types if necessary
|
357
|
-
|
358
|
-
if role and processed_content:
|
359
|
-
new_schema.append({"role": role, "content": processed_content})
|
360
|
-
# else: Handle cases with missing role or empty resulting content if needed
|
361
|
-
|
362
|
-
return new_schema
|