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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nebu
3
- Version: 0.1.45
3
+ Version: 0.1.48
4
4
  Summary: A globally distributed container runtime
5
5
  Requires-Python: >=3.10.14
6
6
  Description-Content-Type: text/markdown
@@ -1,27 +1,25 @@
1
- nebu/__init__.py,sha256=5sepbzdAdoA_8TIxws60S4ugFY1apQd_savzn20a4cY,465
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=kIH9-JJ1-iO7P2t28bku6Gn0Y5tgQszGeTW_rpmO03A,38725
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=qQleTn5BmaZvlH4YkrZqqEZNvMJDv5RpKRVRHX28Qs0,24496
16
- nebu/processors/decorate.py,sha256=T-nnsu85eH5ui-66E0IJfMj5KG1fMDHeSmj4oT2scZA,40990
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.45.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
24
- nebu-0.1.45.dist-info/METADATA,sha256=vIX229cGrssD34IEFshj-ykqq9nMKrXNGBB6k2ky7B8,1731
25
- nebu-0.1.45.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
26
- nebu-0.1.45.dist-info/top_level.txt,sha256=uLIbEKJeGSHWOAJN5S0i5XBGwybALlF9bYoB1UhdEgQ,5
27
- nebu-0.1.45.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.0)
2
+ Generator: setuptools (79.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
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