nebu 0.1.23__py3-none-any.whl → 0.1.27__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 +2 -1
- nebu/config.py +38 -2
- nebu/convert.py +130 -0
- nebu/data.py +952 -0
- nebu/processors/consumer.py +6 -9
- nebu/processors/decorate.py +16 -22
- nebu/processors/models.py +12 -0
- {nebu-0.1.23.dist-info → nebu-0.1.27.dist-info}/METADATA +3 -1
- {nebu-0.1.23.dist-info → nebu-0.1.27.dist-info}/RECORD +12 -10
- {nebu-0.1.23.dist-info → nebu-0.1.27.dist-info}/WHEEL +0 -0
- {nebu-0.1.23.dist-info → nebu-0.1.27.dist-info}/licenses/LICENSE +0 -0
- {nebu-0.1.23.dist-info → nebu-0.1.27.dist-info}/top_level.txt +0 -0
nebu/__init__.py
CHANGED
@@ -6,8 +6,9 @@
|
|
6
6
|
from .config import *
|
7
7
|
from .containers.container import Container
|
8
8
|
from .containers.models import *
|
9
|
+
from .convert import convert_to_unsloth
|
10
|
+
from .data import *
|
9
11
|
from .meta import *
|
10
12
|
from .processors.decorate import *
|
11
13
|
from .processors.models import *
|
12
|
-
from .processors.models import V1StreamMessage as Message
|
13
14
|
from .processors.processor import *
|
nebu/config.py
CHANGED
@@ -32,7 +32,7 @@ class GlobalConfig:
|
|
32
32
|
@classmethod
|
33
33
|
def read(cls) -> GlobalConfig:
|
34
34
|
"""
|
35
|
-
Read the config from ~/.agentsea/nebu.yaml, or create a default if it doesn
|
35
|
+
Read the config from ~/.agentsea/nebu.yaml, or create a default if it doesn't exist.
|
36
36
|
Then ensure that we either find or create a matching server from environment variables,
|
37
37
|
and set that as the `current_server` if relevant (mimicking the Rust logic).
|
38
38
|
"""
|
@@ -78,7 +78,7 @@ class GlobalConfig:
|
|
78
78
|
# Ensure it has a name, so we can set current_server to it
|
79
79
|
if found_server.name is None:
|
80
80
|
found_server.name = server_name
|
81
|
-
# Use that server
|
81
|
+
# Use that server's name as current
|
82
82
|
config.current_server = found_server.name
|
83
83
|
else:
|
84
84
|
# Create a new server entry
|
@@ -133,3 +133,39 @@ def _get_config_file_path() -> str:
|
|
133
133
|
home = os.path.expanduser("~")
|
134
134
|
config_dir = os.path.join(home, ".agentsea")
|
135
135
|
return os.path.join(config_dir, "nebu.yaml")
|
136
|
+
|
137
|
+
|
138
|
+
@dataclass
|
139
|
+
class ContainerConfig:
|
140
|
+
"""
|
141
|
+
Configuration loaded from environment variables inside the container.
|
142
|
+
"""
|
143
|
+
|
144
|
+
api_key: Optional[str] = None
|
145
|
+
server: Optional[str] = None
|
146
|
+
namespace: Optional[str] = None
|
147
|
+
name: Optional[str] = None
|
148
|
+
container_id: Optional[str] = None
|
149
|
+
date: Optional[str] = None
|
150
|
+
hf_home: Optional[str] = None
|
151
|
+
namespace_volume_uri: Optional[str] = None
|
152
|
+
name_volume_uri: Optional[str] = None
|
153
|
+
ts_authkey: Optional[str] = None
|
154
|
+
|
155
|
+
@classmethod
|
156
|
+
def from_env(cls) -> ContainerConfig:
|
157
|
+
"""
|
158
|
+
Load configuration from environment variables.
|
159
|
+
"""
|
160
|
+
return cls(
|
161
|
+
api_key=os.environ.get("NEBU_API_KEY"),
|
162
|
+
server=os.environ.get("NEBU_SERVER"),
|
163
|
+
namespace=os.environ.get("NEBU_NAMESPACE"),
|
164
|
+
name=os.environ.get("NEBU_NAME"),
|
165
|
+
container_id=os.environ.get("NEBU_CONTAINER_ID"),
|
166
|
+
date=os.environ.get("NEBU_DATE"),
|
167
|
+
hf_home=os.environ.get("HF_HOME"),
|
168
|
+
namespace_volume_uri=os.environ.get("NAMESPACE_VOLUME_URI"),
|
169
|
+
name_volume_uri=os.environ.get("NAME_VOLUME_URI"),
|
170
|
+
ts_authkey=os.environ.get("TS_AUTHKEY"),
|
171
|
+
)
|
nebu/convert.py
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
import base64
|
2
|
+
import binascii
|
3
|
+
import io
|
4
|
+
from typing import Any, Dict, List
|
5
|
+
|
6
|
+
import requests
|
7
|
+
from PIL import Image, UnidentifiedImageError
|
8
|
+
|
9
|
+
|
10
|
+
def convert_to_unsloth(
|
11
|
+
messages_input: List[Dict[str, Any]],
|
12
|
+
) -> Dict[str, List[Dict[str, Any]]]:
|
13
|
+
"""
|
14
|
+
Converts a list of messages from an OpenAI-like chat format to the Nebulous conversation format.
|
15
|
+
Images specified by URLs or base64 strings are loaded into PIL.Image objects.
|
16
|
+
|
17
|
+
Input format example:
|
18
|
+
[
|
19
|
+
{
|
20
|
+
"role": "user",
|
21
|
+
"content": [
|
22
|
+
{"type": "input_text", "text": "Describe the image."},
|
23
|
+
{"type": "input_image", "image_url": "http://... or base64 string"},
|
24
|
+
]
|
25
|
+
},
|
26
|
+
{
|
27
|
+
"role": "assistant",
|
28
|
+
"content": [{"type": "text", "text": "This is an image of..."}] # Or potentially just a string
|
29
|
+
}
|
30
|
+
]
|
31
|
+
|
32
|
+
Output format example:
|
33
|
+
{
|
34
|
+
"messages": [
|
35
|
+
{
|
36
|
+
"role": "user",
|
37
|
+
"content": [
|
38
|
+
{"type": "text", "text": "Describe the image."},
|
39
|
+
{"type": "image", "image": <PIL.Image.Image object>},
|
40
|
+
]
|
41
|
+
},
|
42
|
+
{
|
43
|
+
"role": "assistant",
|
44
|
+
"content": [{"type": "text", "text": "This is an image of..."}]
|
45
|
+
}
|
46
|
+
]
|
47
|
+
}
|
48
|
+
"""
|
49
|
+
nebu_conversation = []
|
50
|
+
for message in messages_input:
|
51
|
+
role = message.get("role")
|
52
|
+
input_content = message.get("content") # Can be list or string
|
53
|
+
|
54
|
+
processed_content = []
|
55
|
+
|
56
|
+
if isinstance(input_content, list):
|
57
|
+
# Process list content (multi-modal)
|
58
|
+
for item in input_content:
|
59
|
+
item_type = item.get("type")
|
60
|
+
if item_type in ("input_text", "text"):
|
61
|
+
processed_content.append(
|
62
|
+
{"type": "text", "text": item.get("text", "")}
|
63
|
+
)
|
64
|
+
elif item_type in (
|
65
|
+
"input_image",
|
66
|
+
"image_url",
|
67
|
+
"image",
|
68
|
+
): # Accept 'image' as source key too
|
69
|
+
# Use "image_url" first, then fallback to "image" if needed
|
70
|
+
image_source = item.get("image_url", item.get("image"))
|
71
|
+
if image_source:
|
72
|
+
pil_image = None
|
73
|
+
try:
|
74
|
+
if isinstance(
|
75
|
+
image_source, str
|
76
|
+
) and image_source.startswith(("http://", "https://")):
|
77
|
+
# Handle URL
|
78
|
+
response = requests.get(image_source, stream=True)
|
79
|
+
response.raise_for_status() # Raise an exception for bad status codes
|
80
|
+
pil_image = Image.open(response.raw)
|
81
|
+
elif isinstance(image_source, str):
|
82
|
+
# Handle base64 string
|
83
|
+
# Remove potential data URI prefix (e.g., "data:image/png;base64,")
|
84
|
+
if "," in image_source:
|
85
|
+
image_source = image_source.split(",", 1)[1]
|
86
|
+
image_bytes = base64.b64decode(image_source)
|
87
|
+
pil_image = Image.open(io.BytesIO(image_bytes))
|
88
|
+
|
89
|
+
elif isinstance(image_source, Image.Image):
|
90
|
+
# Handle direct PIL.Image input
|
91
|
+
pil_image = image_source
|
92
|
+
|
93
|
+
if pil_image:
|
94
|
+
processed_content.append(
|
95
|
+
{"type": "image", "image": pil_image}
|
96
|
+
)
|
97
|
+
else:
|
98
|
+
print(
|
99
|
+
f"Warning: Could not load image from source: {type(image_source)}"
|
100
|
+
)
|
101
|
+
|
102
|
+
except requests.exceptions.RequestException as e:
|
103
|
+
print(
|
104
|
+
f"Warning: Failed to fetch image from URL {image_source}: {e}"
|
105
|
+
)
|
106
|
+
except (binascii.Error, ValueError) as e:
|
107
|
+
print(f"Warning: Failed to decode base64 image string: {e}")
|
108
|
+
except (IOError, UnidentifiedImageError) as e:
|
109
|
+
print(f"Warning: Failed to open image: {e}")
|
110
|
+
except Exception as e:
|
111
|
+
print(
|
112
|
+
f"Warning: An unexpected error occurred while processing image: {e}"
|
113
|
+
)
|
114
|
+
|
115
|
+
else:
|
116
|
+
print(
|
117
|
+
"Warning: Image item provided but 'image_url' or 'image' key is missing or empty."
|
118
|
+
)
|
119
|
+
|
120
|
+
# Add handling for other potential input types if necessary
|
121
|
+
elif isinstance(input_content, str):
|
122
|
+
# Handle simple string content (common for assistant messages)
|
123
|
+
processed_content.append({"type": "text", "text": input_content})
|
124
|
+
# else: Handle unexpected content format (e.g., log warning, skip message)
|
125
|
+
|
126
|
+
if role and processed_content:
|
127
|
+
nebu_conversation.append({"role": role, "content": processed_content})
|
128
|
+
# else: Handle missing role or empty content if needed
|
129
|
+
|
130
|
+
return {"messages": nebu_conversation}
|