cycls 0.0.2.33__py3-none-any.whl → 0.0.2.34__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.
- cycls/__init__.py +2 -2
- cycls/runtime.py +17 -12
- cycls/sdk.py +100 -20
- cycls/{cycls.py → web.py} +2 -76
- {cycls-0.0.2.33.dist-info → cycls-0.0.2.34.dist-info}/METADATA +1 -1
- cycls-0.0.2.34.dist-info/RECORD +9 -0
- cycls-0.0.2.33.dist-info/RECORD +0 -9
- {cycls-0.0.2.33.dist-info → cycls-0.0.2.34.dist-info}/WHEEL +0 -0
cycls/__init__.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
from .
|
|
2
|
-
from .
|
|
1
|
+
from .sdk import Agent
|
|
2
|
+
from .runtime import Runtime
|
cycls/runtime.py
CHANGED
|
@@ -132,7 +132,11 @@ class Runtime:
|
|
|
132
132
|
if self.apt_packages else ""
|
|
133
133
|
)
|
|
134
134
|
run_shell_commands = "\n".join([f"RUN {cmd}" for cmd in self.run_commands]) if self.run_commands else ""
|
|
135
|
-
|
|
135
|
+
|
|
136
|
+
# fixes absolute path copy, but causes collision
|
|
137
|
+
copy_lines = "\n".join([f"COPY {Path(src).name} {dst}" for src, dst in self.copy.items()])
|
|
138
|
+
# copy_lines = "\n".join([f"COPY {src} {dst}" for src, dst in self.copy.items()])
|
|
139
|
+
|
|
136
140
|
expose_line = f"EXPOSE {port}" if port else ""
|
|
137
141
|
|
|
138
142
|
return f"""
|
|
@@ -166,8 +170,14 @@ COPY {self.payload_file} {self.io_dir}/
|
|
|
166
170
|
(workdir / self.payload_file).write_bytes(payload_bytes)
|
|
167
171
|
|
|
168
172
|
if self.copy:
|
|
173
|
+
# for src in self.copy.keys():
|
|
174
|
+
# _copy_path(Path(src), workdir / src)
|
|
175
|
+
|
|
176
|
+
# fixes absolute path copy
|
|
169
177
|
for src in self.copy.keys():
|
|
170
|
-
|
|
178
|
+
src_path = Path(src)
|
|
179
|
+
dest_path = workdir / src_path.name if src_path.is_absolute() else workdir / src
|
|
180
|
+
_copy_path(src_path, dest_path)
|
|
171
181
|
|
|
172
182
|
def _build_image_if_needed(self):
|
|
173
183
|
"""Checks if the base Docker image exists locally and builds it if not."""
|
|
@@ -178,8 +188,7 @@ COPY {self.payload_file} {self.io_dir}/
|
|
|
178
188
|
except docker.errors.ImageNotFound:
|
|
179
189
|
print(f"🛠️ Building new base image: {self.tag}")
|
|
180
190
|
|
|
181
|
-
|
|
182
|
-
with tempfile.TemporaryDirectory(dir="/tmp") as tmpdir_str:
|
|
191
|
+
with tempfile.TemporaryDirectory() as tmpdir_str:
|
|
183
192
|
tmpdir = Path(tmpdir_str)
|
|
184
193
|
# Prepare context without payload for the base image
|
|
185
194
|
self._prepare_build_context(tmpdir)
|
|
@@ -210,8 +219,7 @@ COPY {self.payload_file} {self.io_dir}/
|
|
|
210
219
|
container = None
|
|
211
220
|
ports_mapping = {f'{port}/tcp': port} if port else None
|
|
212
221
|
|
|
213
|
-
|
|
214
|
-
with tempfile.TemporaryDirectory(dir="/tmp") as tmpdir_str:
|
|
222
|
+
with tempfile.TemporaryDirectory() as tmpdir_str:
|
|
215
223
|
tmpdir = Path(tmpdir_str)
|
|
216
224
|
payload_path = tmpdir / self.payload_file
|
|
217
225
|
result_path = tmpdir / self.result_file
|
|
@@ -277,8 +285,7 @@ COPY {self.payload_file} {self.io_dir}/
|
|
|
277
285
|
except docker.errors.ImageNotFound:
|
|
278
286
|
print(f"🛠️ Building new deployable image: {final_tag}")
|
|
279
287
|
|
|
280
|
-
|
|
281
|
-
with tempfile.TemporaryDirectory(dir="/tmp") as tmpdir_str:
|
|
288
|
+
with tempfile.TemporaryDirectory() as tmpdir_str:
|
|
282
289
|
tmpdir = Path(tmpdir_str)
|
|
283
290
|
self._prepare_build_context(tmpdir, include_payload=True, args=args, kwargs=kwargs)
|
|
284
291
|
|
|
@@ -309,8 +316,7 @@ COPY {self.payload_file} {self.io_dir}/
|
|
|
309
316
|
payload_hash = hashlib.sha256(cloudpickle.dumps((self.func, args, kwargs))).hexdigest()[:16]
|
|
310
317
|
archive_name = f"source-{self.tag.split(':')[1]}-{payload_hash}.tar.gz"
|
|
311
318
|
|
|
312
|
-
|
|
313
|
-
with tempfile.TemporaryDirectory(dir="/tmp") as tmpdir_str:
|
|
319
|
+
with tempfile.TemporaryDirectory() as tmpdir_str:
|
|
314
320
|
tmpdir = Path(tmpdir_str)
|
|
315
321
|
self._prepare_build_context(tmpdir, include_payload=True, args=args, kwargs=kwargs)
|
|
316
322
|
|
|
@@ -375,8 +381,7 @@ COPY {self.payload_file} {self.io_dir}/
|
|
|
375
381
|
|
|
376
382
|
port = kwargs.get('port', 8080)
|
|
377
383
|
|
|
378
|
-
|
|
379
|
-
with tempfile.TemporaryDirectory(dir="/tmp") as tmpdir_str:
|
|
384
|
+
with tempfile.TemporaryDirectory() as tmpdir_str:
|
|
380
385
|
tmpdir = Path(tmpdir_str)
|
|
381
386
|
self._prepare_build_context(tmpdir, include_payload=True, args=args, kwargs=kwargs)
|
|
382
387
|
|
cycls/sdk.py
CHANGED
|
@@ -1,22 +1,102 @@
|
|
|
1
|
+
import json, time, modal, inspect, uvicorn
|
|
1
2
|
from .runtime import Runtime
|
|
3
|
+
from modal.runner import run_app
|
|
4
|
+
from .web import web
|
|
5
|
+
import importlib.resources
|
|
2
6
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
7
|
+
theme_path = importlib.resources.files('cycls').joinpath('theme')
|
|
8
|
+
cycls_path = importlib.resources.files('cycls')
|
|
9
|
+
|
|
10
|
+
class Agent:
|
|
11
|
+
def __init__(self, theme=theme_path, org=None, api_token=None, pip=[], apt=[], copy=[], keys=["",""], api_key=None):
|
|
12
|
+
self.org, self.api_token = org, api_token
|
|
13
|
+
self.theme = theme
|
|
14
|
+
self.keys, self.pip, self.apt, self.copy = keys, pip, apt, copy
|
|
15
|
+
self.api_key = api_key
|
|
16
|
+
|
|
17
|
+
self.registered_functions = []
|
|
18
|
+
|
|
19
|
+
def __call__(self, name="", header="", intro="", domain=None, auth=False):
|
|
20
|
+
def decorator(f):
|
|
21
|
+
self.registered_functions.append({
|
|
22
|
+
"func": f,
|
|
23
|
+
"config": ["public", False, self.org, self.api_token, header, intro, auth],
|
|
24
|
+
"name": name,
|
|
25
|
+
"domain": domain or f"{name}.cycls.ai",
|
|
26
|
+
})
|
|
27
|
+
return f
|
|
28
|
+
return decorator
|
|
29
|
+
|
|
30
|
+
def run(self, port=8080):
|
|
31
|
+
if not self.registered_functions:
|
|
32
|
+
print("Error: No @agent decorated function found.")
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
i = self.registered_functions[0]
|
|
36
|
+
if len(self.registered_functions) > 1:
|
|
37
|
+
print(f"⚠️ Warning: Multiple agents found. Running '{i['name']}'.")
|
|
38
|
+
print(f"🚀 Starting local server at localhost:{port}")
|
|
39
|
+
i["config"][0], i["config"][6] = self.theme, False
|
|
40
|
+
uvicorn.run(web(i["func"], *i["config"]), host="0.0.0.0", port=port)
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
def deploy(self, prod=False, port=8080):
|
|
44
|
+
if not self.registered_functions:
|
|
45
|
+
print("Error: No @agent decorated function found.")
|
|
46
|
+
return
|
|
47
|
+
if (self.api_key is None) and prod:
|
|
48
|
+
print("🛑 Error: Please add your Cycls API key")
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
i = self.registered_functions[0]
|
|
52
|
+
if len(self.registered_functions) > 1:
|
|
53
|
+
print(f"⚠️ Warning: Multiple agents found. Running '{i['name']}'.")
|
|
54
|
+
|
|
55
|
+
i["config"][6] = False
|
|
56
|
+
|
|
57
|
+
new = Runtime(
|
|
58
|
+
func=lambda port: __import__("uvicorn").run(__import__("web").web(i["func"], *i["config"]), host="0.0.0.0", port=port),
|
|
59
|
+
name="web-agent",
|
|
60
|
+
pip_packages=["fastapi[standard]", "pyjwt", "cryptography", "uvicorn", *self.pip],
|
|
61
|
+
copy={str(cycls_path.joinpath('theme')):"public", str(cycls_path)+"/web.py":"app/web.py"},
|
|
62
|
+
api_key=self.api_key
|
|
63
|
+
)
|
|
64
|
+
new.deploy(port=port) if prod else new.run(port=port)
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
def push(self, prod=False):
|
|
68
|
+
self.client = modal.Client.from_credentials(*self.keys)
|
|
69
|
+
image = (modal.Image.debian_slim()
|
|
70
|
+
.pip_install("fastapi[standard]", "pyjwt", "cryptography", *self.pip)
|
|
71
|
+
.apt_install(*self.apt)
|
|
72
|
+
.add_local_dir(self.theme, "/root/public")
|
|
73
|
+
.add_local_file(str(cycls_path)+"/web.py", "/root/web.py"))
|
|
74
|
+
for item in self.copy:
|
|
75
|
+
image = image.add_local_file(item, f"/root/{item}") if "." in item else image.add_local_dir(item, f'/root/{item}')
|
|
76
|
+
self.app = modal.App("development", image=image)
|
|
77
|
+
|
|
78
|
+
if not self.registered_functions:
|
|
79
|
+
print("Error: No @agent decorated function found.")
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
for i in self.registered_functions:
|
|
83
|
+
i["config"][1] = True if prod else False
|
|
84
|
+
self.app.function(serialized=True, name=i["name"])(
|
|
85
|
+
modal.asgi_app(label=i["name"], custom_domains=[i["domain"]])
|
|
86
|
+
(lambda: __import__("web").web(i["func"], *i["config"]))
|
|
87
|
+
)
|
|
88
|
+
if prod:
|
|
89
|
+
for i in self.registered_functions:
|
|
90
|
+
print(f"✅ Deployed to ⇒ https://{i['domain']}")
|
|
91
|
+
self.app.deploy(client=self.client, name=self.registered_functions[0]["name"])
|
|
92
|
+
return
|
|
93
|
+
else:
|
|
94
|
+
with modal.enable_output():
|
|
95
|
+
run_app(app=self.app, client=self.client)
|
|
96
|
+
print(" Modal development server is running. Press Ctrl+C to stop.")
|
|
97
|
+
with modal.enable_output(), run_app(app=self.app, client=self.client):
|
|
98
|
+
while True: time.sleep(10)
|
|
99
|
+
|
|
100
|
+
# poetry config pypi-token.pypi <your-token>
|
|
101
|
+
# poetry run python agent.py
|
|
102
|
+
# poetry publish --build
|
cycls/{cycls.py → web.py}
RENAMED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import json,
|
|
2
|
-
from modal.runner import run_app
|
|
3
|
-
|
|
4
|
-
import importlib.resources
|
|
5
|
-
theme_path = importlib.resources.files('cycls').joinpath('theme')
|
|
1
|
+
import json, inspect
|
|
6
2
|
|
|
7
3
|
async def openai_encoder(stream): # clean up the meta data / new API?
|
|
8
4
|
async for message in stream:
|
|
@@ -41,7 +37,6 @@ XwIDAQAB
|
|
|
41
37
|
"""
|
|
42
38
|
|
|
43
39
|
def web(func, front_end_path="", prod=False, org=None, api_token=None, header="", intro="", auth=True): # API auth
|
|
44
|
-
print(front_end_path)
|
|
45
40
|
from fastapi import FastAPI, Request, HTTPException, status, Depends
|
|
46
41
|
from fastapi.responses import StreamingResponse , HTMLResponse
|
|
47
42
|
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
|
@@ -97,73 +92,4 @@ def web(func, front_end_path="", prod=False, org=None, api_token=None, header=""
|
|
|
97
92
|
"pk_live": "pk_live_Y2xlcmsuY3ljbHMuY29tJA", "pk_test": "pk_test_c2VsZWN0LXNsb3RoLTU4LmNsZXJrLmFjY291bnRzLmRldiQ"
|
|
98
93
|
})
|
|
99
94
|
app.mount("/", StaticFiles(directory=front_end_path, html=False))
|
|
100
|
-
return app
|
|
101
|
-
|
|
102
|
-
class Agent:
|
|
103
|
-
def __init__(self, theme=theme_path, org=None, api_token=None, pip=[], apt=[], copy=[], keys=["",""]):
|
|
104
|
-
self.org, self.api_token = org, api_token
|
|
105
|
-
self.theme = theme
|
|
106
|
-
self.keys, self.pip, self.apt, self.copy = keys, pip, apt, copy
|
|
107
|
-
|
|
108
|
-
self.registered_functions = []
|
|
109
|
-
|
|
110
|
-
def __call__(self, name="", header="", intro="", domain=None, auth=False):
|
|
111
|
-
def decorator(f):
|
|
112
|
-
self.registered_functions.append({
|
|
113
|
-
"func": f,
|
|
114
|
-
"config": ["public", False, self.org, self.api_token, header, intro, auth],
|
|
115
|
-
"name": name,
|
|
116
|
-
"domain": domain or f"{name}.cycls.ai",
|
|
117
|
-
})
|
|
118
|
-
return f
|
|
119
|
-
return decorator
|
|
120
|
-
|
|
121
|
-
def run(self, port=8000):
|
|
122
|
-
if not self.registered_functions:
|
|
123
|
-
print("Error: No @agent decorated function found.")
|
|
124
|
-
return
|
|
125
|
-
|
|
126
|
-
i = self.registered_functions[0]
|
|
127
|
-
if len(self.registered_functions) > 1:
|
|
128
|
-
print(f"⚠️ Warning: Multiple agents found. Running '{i['name']}'.")
|
|
129
|
-
print(f"🚀 Starting local server at localhost:{port}")
|
|
130
|
-
i["config"][0], i["config"][6] = self.theme, False
|
|
131
|
-
uvicorn.run(web(i["func"], *i["config"]), host="0.0.0.0", port=port)
|
|
132
|
-
return
|
|
133
|
-
|
|
134
|
-
def push(self, prod=False):
|
|
135
|
-
self.client = modal.Client.from_credentials(*self.keys)
|
|
136
|
-
image = (modal.Image.debian_slim()
|
|
137
|
-
.pip_install("fastapi[standard]", "pyjwt", "cryptography", *self.pip)
|
|
138
|
-
.apt_install(*self.apt)
|
|
139
|
-
.add_local_dir(self.theme, "/root/public")
|
|
140
|
-
.add_local_python_source("cycls"))
|
|
141
|
-
for item in self.copy:
|
|
142
|
-
image = image.add_local_file(item, f"/root/{item}") if "." in item else image.add_local_dir(item, f'/root/{item}')
|
|
143
|
-
self.app = modal.App("development", image=image)
|
|
144
|
-
|
|
145
|
-
if not self.registered_functions:
|
|
146
|
-
print("Error: No @agent decorated function found.")
|
|
147
|
-
return
|
|
148
|
-
|
|
149
|
-
for i in self.registered_functions:
|
|
150
|
-
i["config"][1] = True if prod else False
|
|
151
|
-
self.app.function(serialized=True, name=i["name"])(
|
|
152
|
-
modal.asgi_app(label=i["name"], custom_domains=[i["domain"]])
|
|
153
|
-
(lambda: web(i["func"], *i["config"]))
|
|
154
|
-
)
|
|
155
|
-
if prod:
|
|
156
|
-
for i in self.registered_functions:
|
|
157
|
-
print(f"✅ Deployed to ⇒ https://{i['domain']}")
|
|
158
|
-
self.app.deploy(client=self.client, name=self.registered_functions[0]["name"])
|
|
159
|
-
return
|
|
160
|
-
else:
|
|
161
|
-
with modal.enable_output():
|
|
162
|
-
run_app(app=self.app, client=self.client)
|
|
163
|
-
print(" Modal development server is running. Press Ctrl+C to stop.")
|
|
164
|
-
with modal.enable_output(), run_app(app=self.app, client=self.client):
|
|
165
|
-
while True: time.sleep(10)
|
|
166
|
-
|
|
167
|
-
# poetry config pypi-token.pypi <your-token>
|
|
168
|
-
# poetry run python agent.py
|
|
169
|
-
# poetry publish --build
|
|
95
|
+
return app
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
cycls/__init__.py,sha256=ysfA4R_r_INokmpZGK2S8wf2ypLCqwyj0WdfX-hx0bs,51
|
|
2
|
+
cycls/runtime.py,sha256=rUQnaweV9y5fPcKxTmOqrQO2nT_A5wqgSElonY6GNmw,16366
|
|
3
|
+
cycls/sdk.py,sha256=tzTWNzWVdQPh-I-AmhkkgQ0M3xJyMy-x-vFAlq1XihU,4408
|
|
4
|
+
cycls/theme/assets/index-D0-uI8sw.js,sha256=aUsqm9HZtEJz38o-0MW12ZVeOlSeKigwc_fYJBntiyI,1068551
|
|
5
|
+
cycls/theme/index.html,sha256=epB4cgSjC7xJOXpVuCwt9r7ivoGvLiXSrxsoOgINw58,895
|
|
6
|
+
cycls/web.py,sha256=nSEJMUQoPaz8qjgVmsC1JiDRv9Y1UKVzTH4_pRHp_VE,4260
|
|
7
|
+
cycls-0.0.2.34.dist-info/METADATA,sha256=fRwJekFv5Rzy2izqhMtf6IhMmHd3GBdZRnwEO66FxbY,5666
|
|
8
|
+
cycls-0.0.2.34.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
9
|
+
cycls-0.0.2.34.dist-info/RECORD,,
|
cycls-0.0.2.33.dist-info/RECORD
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
cycls/__init__.py,sha256=9oKTafASGMuJbq_Sk0sfCSQKKwUD8mcdYFovDDzVuW4,50
|
|
2
|
-
cycls/cycls.py,sha256=rLhssEPs4EwDbNsO5cGdKkP888dOaYToMPKVLZWan5U,7479
|
|
3
|
-
cycls/runtime.py,sha256=y_ilfi418m-9ZsRvY7H7zrO0FYdhz2X-hnZ9DACaSW4,16289
|
|
4
|
-
cycls/sdk.py,sha256=EcUYi0pGKtCajKzwLg3uH8CRPfc78KBHXgB6ShABxdE,1110
|
|
5
|
-
cycls/theme/assets/index-D0-uI8sw.js,sha256=aUsqm9HZtEJz38o-0MW12ZVeOlSeKigwc_fYJBntiyI,1068551
|
|
6
|
-
cycls/theme/index.html,sha256=epB4cgSjC7xJOXpVuCwt9r7ivoGvLiXSrxsoOgINw58,895
|
|
7
|
-
cycls-0.0.2.33.dist-info/METADATA,sha256=HKNRnxvSkLwolKtk2-QUgQUGzbpKHSqv_hOx9Gpjnis,5666
|
|
8
|
-
cycls-0.0.2.33.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
9
|
-
cycls-0.0.2.33.dist-info/RECORD,,
|
|
File without changes
|