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 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 doesnt exist.
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 servers name as current
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}