cycls 0.0.2.82__tar.gz → 0.0.2.84__tar.gz
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-0.0.2.82 → cycls-0.0.2.84}/PKG-INFO +17 -19
- {cycls-0.0.2.82 → cycls-0.0.2.84}/README.md +16 -14
- cycls-0.0.2.84/cycls/__init__.py +14 -0
- cycls-0.0.2.84/cycls/app.py +88 -0
- cycls-0.0.2.82/cycls/runtime.py → cycls-0.0.2.84/cycls/function.py +122 -170
- {cycls-0.0.2.82 → cycls-0.0.2.84}/cycls/web.py +11 -5
- {cycls-0.0.2.82 → cycls-0.0.2.84}/pyproject.toml +3 -12
- cycls-0.0.2.82/cycls/__init__.py +0 -20
- cycls-0.0.2.82/cycls/grpc/__init__.py +0 -3
- cycls-0.0.2.82/cycls/grpc/client.py +0 -71
- cycls-0.0.2.82/cycls/grpc/runtime.proto +0 -18
- cycls-0.0.2.82/cycls/grpc/runtime_pb2.py +0 -40
- cycls-0.0.2.82/cycls/grpc/runtime_pb2_grpc.py +0 -100
- cycls-0.0.2.82/cycls/grpc/server.py +0 -60
- cycls-0.0.2.82/cycls/sdk.py +0 -186
- {cycls-0.0.2.82 → cycls-0.0.2.84}/cycls/auth.py +0 -0
- /cycls-0.0.2.82/cycls/chat.py → /cycls-0.0.2.84/cycls/cli.py +0 -0
- {cycls-0.0.2.82/cycls/default-theme → cycls-0.0.2.84/cycls/themes/default}/assets/index-C2r4Daz3.js +0 -0
- {cycls-0.0.2.82/cycls/default-theme → cycls-0.0.2.84/cycls/themes/default}/assets/index-DWGS8zpa.css +0 -0
- {cycls-0.0.2.82/cycls/default-theme → cycls-0.0.2.84/cycls/themes/default}/index.html +0 -0
- {cycls-0.0.2.82/cycls/dev-theme → cycls-0.0.2.84/cycls/themes/dev}/index.html +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cycls
|
|
3
|
-
Version: 0.0.2.
|
|
3
|
+
Version: 0.0.2.84
|
|
4
4
|
Summary: Distribute Intelligence
|
|
5
5
|
Author: Mohammed J. AlRujayi
|
|
6
6
|
Author-email: mj@cycls.com
|
|
@@ -12,14 +12,10 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.13
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
-
Provides-Extra: modal
|
|
16
15
|
Requires-Dist: cloudpickle (>=3.1.1,<4.0.0)
|
|
17
16
|
Requires-Dist: docker (>=7.1.0,<8.0.0)
|
|
18
17
|
Requires-Dist: fastapi (>=0.111.0,<0.112.0)
|
|
19
|
-
Requires-Dist: grpcio (>=1.76.0,<2.0.0)
|
|
20
18
|
Requires-Dist: httpx (>=0.27.0,<0.28.0)
|
|
21
|
-
Requires-Dist: modal (>=1.1.0,<2.0.0) ; extra == "modal"
|
|
22
|
-
Requires-Dist: protobuf (>=6.0,<7.0)
|
|
23
19
|
Requires-Dist: pyjwt (>=2.8.0,<3.0.0)
|
|
24
20
|
Description-Content-Type: text/markdown
|
|
25
21
|
|
|
@@ -49,6 +45,8 @@ The open-source SDK for distributing AI agents.
|
|
|
49
45
|
|
|
50
46
|
The function is the unit of abstraction in cycls. Your agent logic lives in a plain Python function — the decorator layers on everything else: containerization, authentication, deployment, analytics. You write the function, the `@` handles the infrastructure.
|
|
51
47
|
|
|
48
|
+
Cycls is beautifully lean (~862 lines!) and focused on deployment infrastructure for any Python function, not agent logic itself.
|
|
49
|
+
|
|
52
50
|
## Distribute Intelligence
|
|
53
51
|
|
|
54
52
|
Write a function. Deploy it as an API, a web interface, or both. Add authentication, analytics, and monetization with flags.
|
|
@@ -58,8 +56,8 @@ import cycls
|
|
|
58
56
|
|
|
59
57
|
cycls.api_key = "YOUR_CYCLS_API_KEY"
|
|
60
58
|
|
|
61
|
-
@cycls.
|
|
62
|
-
async def
|
|
59
|
+
@cycls.app(pip=["openai"])
|
|
60
|
+
async def app(context):
|
|
63
61
|
from openai import AsyncOpenAI
|
|
64
62
|
client = AsyncOpenAI()
|
|
65
63
|
|
|
@@ -76,7 +74,7 @@ async def agent(context):
|
|
|
76
74
|
elif event.type == "response.output_text.delta":
|
|
77
75
|
yield event.delta
|
|
78
76
|
|
|
79
|
-
|
|
77
|
+
app.deploy() # Live at https://agent.cycls.ai
|
|
80
78
|
```
|
|
81
79
|
|
|
82
80
|
## Installation
|
|
@@ -99,9 +97,9 @@ Requires Docker.
|
|
|
99
97
|
## Running
|
|
100
98
|
|
|
101
99
|
```python
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
100
|
+
app.local() # Development with hot-reload (localhost:8080)
|
|
101
|
+
app.local(watch=False) # Development without hot-reload
|
|
102
|
+
app.deploy() # Production: https://agent.cycls.ai
|
|
105
103
|
```
|
|
106
104
|
|
|
107
105
|
Get an API key at [cycls.com](https://cycls.com).
|
|
@@ -109,8 +107,8 @@ Get an API key at [cycls.com](https://cycls.com).
|
|
|
109
107
|
## Authentication & Analytics
|
|
110
108
|
|
|
111
109
|
```python
|
|
112
|
-
@cycls.
|
|
113
|
-
async def
|
|
110
|
+
@cycls.app(pip=["openai"], auth=True, analytics=True)
|
|
111
|
+
async def app(context):
|
|
114
112
|
# context.user available when auth=True
|
|
115
113
|
user = context.user # User(id, email, name, plans)
|
|
116
114
|
yield f"Hello {user.name}!"
|
|
@@ -127,7 +125,7 @@ async def agent(context):
|
|
|
127
125
|
Yield structured objects for rich streaming responses:
|
|
128
126
|
|
|
129
127
|
```python
|
|
130
|
-
@cycls.
|
|
128
|
+
@cycls.app()
|
|
131
129
|
async def demo(context):
|
|
132
130
|
yield {"type": "thinking", "thinking": "Analyzing the request..."}
|
|
133
131
|
yield "Here's what I found:\n\n"
|
|
@@ -169,7 +167,7 @@ This works seamlessly with OpenAI's reasoning models - just map reasoning summar
|
|
|
169
167
|
## Context Object
|
|
170
168
|
|
|
171
169
|
```python
|
|
172
|
-
@cycls.
|
|
170
|
+
@cycls.app()
|
|
173
171
|
async def chat(context):
|
|
174
172
|
context.messages # [{"role": "user", "content": "..."}]
|
|
175
173
|
context.messages.raw # Full data including UI component parts
|
|
@@ -202,13 +200,13 @@ See [docs/streaming-protocol.md](docs/streaming-protocol.md) for frontend integr
|
|
|
202
200
|
Define your entire runtime in the decorator:
|
|
203
201
|
|
|
204
202
|
```python
|
|
205
|
-
@cycls.
|
|
203
|
+
@cycls.app(
|
|
206
204
|
pip=["openai", "pandas", "numpy"],
|
|
207
205
|
apt=["ffmpeg", "libmagic1"],
|
|
208
206
|
copy=["./utils.py", "./models/", "/absolute/path/to/config.json"],
|
|
209
207
|
copy_public=["./assets/logo.png", "./static/"],
|
|
210
208
|
)
|
|
211
|
-
async def
|
|
209
|
+
async def my_app(context):
|
|
212
210
|
...
|
|
213
211
|
```
|
|
214
212
|
|
|
@@ -243,7 +241,7 @@ copy=[
|
|
|
243
241
|
Then import them in your function:
|
|
244
242
|
|
|
245
243
|
```python
|
|
246
|
-
@cycls.
|
|
244
|
+
@cycls.app(copy=["./utils.py"])
|
|
247
245
|
async def chat(context):
|
|
248
246
|
from utils import helper_function # Your bundled module
|
|
249
247
|
...
|
|
@@ -257,7 +255,7 @@ Files and directories served at the `/public` endpoint. Perfect for images, down
|
|
|
257
255
|
copy_public=["./assets/logo.png", "./downloads/"]
|
|
258
256
|
```
|
|
259
257
|
|
|
260
|
-
Access them at `https://your-
|
|
258
|
+
Access them at `https://your-app.cycls.ai/public/logo.png`.
|
|
261
259
|
|
|
262
260
|
---
|
|
263
261
|
|
|
@@ -24,6 +24,8 @@ The open-source SDK for distributing AI agents.
|
|
|
24
24
|
|
|
25
25
|
The function is the unit of abstraction in cycls. Your agent logic lives in a plain Python function — the decorator layers on everything else: containerization, authentication, deployment, analytics. You write the function, the `@` handles the infrastructure.
|
|
26
26
|
|
|
27
|
+
Cycls is beautifully lean (~862 lines!) and focused on deployment infrastructure for any Python function, not agent logic itself.
|
|
28
|
+
|
|
27
29
|
## Distribute Intelligence
|
|
28
30
|
|
|
29
31
|
Write a function. Deploy it as an API, a web interface, or both. Add authentication, analytics, and monetization with flags.
|
|
@@ -33,8 +35,8 @@ import cycls
|
|
|
33
35
|
|
|
34
36
|
cycls.api_key = "YOUR_CYCLS_API_KEY"
|
|
35
37
|
|
|
36
|
-
@cycls.
|
|
37
|
-
async def
|
|
38
|
+
@cycls.app(pip=["openai"])
|
|
39
|
+
async def app(context):
|
|
38
40
|
from openai import AsyncOpenAI
|
|
39
41
|
client = AsyncOpenAI()
|
|
40
42
|
|
|
@@ -51,7 +53,7 @@ async def agent(context):
|
|
|
51
53
|
elif event.type == "response.output_text.delta":
|
|
52
54
|
yield event.delta
|
|
53
55
|
|
|
54
|
-
|
|
56
|
+
app.deploy() # Live at https://agent.cycls.ai
|
|
55
57
|
```
|
|
56
58
|
|
|
57
59
|
## Installation
|
|
@@ -74,9 +76,9 @@ Requires Docker.
|
|
|
74
76
|
## Running
|
|
75
77
|
|
|
76
78
|
```python
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
app.local() # Development with hot-reload (localhost:8080)
|
|
80
|
+
app.local(watch=False) # Development without hot-reload
|
|
81
|
+
app.deploy() # Production: https://agent.cycls.ai
|
|
80
82
|
```
|
|
81
83
|
|
|
82
84
|
Get an API key at [cycls.com](https://cycls.com).
|
|
@@ -84,8 +86,8 @@ Get an API key at [cycls.com](https://cycls.com).
|
|
|
84
86
|
## Authentication & Analytics
|
|
85
87
|
|
|
86
88
|
```python
|
|
87
|
-
@cycls.
|
|
88
|
-
async def
|
|
89
|
+
@cycls.app(pip=["openai"], auth=True, analytics=True)
|
|
90
|
+
async def app(context):
|
|
89
91
|
# context.user available when auth=True
|
|
90
92
|
user = context.user # User(id, email, name, plans)
|
|
91
93
|
yield f"Hello {user.name}!"
|
|
@@ -102,7 +104,7 @@ async def agent(context):
|
|
|
102
104
|
Yield structured objects for rich streaming responses:
|
|
103
105
|
|
|
104
106
|
```python
|
|
105
|
-
@cycls.
|
|
107
|
+
@cycls.app()
|
|
106
108
|
async def demo(context):
|
|
107
109
|
yield {"type": "thinking", "thinking": "Analyzing the request..."}
|
|
108
110
|
yield "Here's what I found:\n\n"
|
|
@@ -144,7 +146,7 @@ This works seamlessly with OpenAI's reasoning models - just map reasoning summar
|
|
|
144
146
|
## Context Object
|
|
145
147
|
|
|
146
148
|
```python
|
|
147
|
-
@cycls.
|
|
149
|
+
@cycls.app()
|
|
148
150
|
async def chat(context):
|
|
149
151
|
context.messages # [{"role": "user", "content": "..."}]
|
|
150
152
|
context.messages.raw # Full data including UI component parts
|
|
@@ -177,13 +179,13 @@ See [docs/streaming-protocol.md](docs/streaming-protocol.md) for frontend integr
|
|
|
177
179
|
Define your entire runtime in the decorator:
|
|
178
180
|
|
|
179
181
|
```python
|
|
180
|
-
@cycls.
|
|
182
|
+
@cycls.app(
|
|
181
183
|
pip=["openai", "pandas", "numpy"],
|
|
182
184
|
apt=["ffmpeg", "libmagic1"],
|
|
183
185
|
copy=["./utils.py", "./models/", "/absolute/path/to/config.json"],
|
|
184
186
|
copy_public=["./assets/logo.png", "./static/"],
|
|
185
187
|
)
|
|
186
|
-
async def
|
|
188
|
+
async def my_app(context):
|
|
187
189
|
...
|
|
188
190
|
```
|
|
189
191
|
|
|
@@ -218,7 +220,7 @@ copy=[
|
|
|
218
220
|
Then import them in your function:
|
|
219
221
|
|
|
220
222
|
```python
|
|
221
|
-
@cycls.
|
|
223
|
+
@cycls.app(copy=["./utils.py"])
|
|
222
224
|
async def chat(context):
|
|
223
225
|
from utils import helper_function # Your bundled module
|
|
224
226
|
...
|
|
@@ -232,7 +234,7 @@ Files and directories served at the `/public` endpoint. Perfect for images, down
|
|
|
232
234
|
copy_public=["./assets/logo.png", "./downloads/"]
|
|
233
235
|
```
|
|
234
236
|
|
|
235
|
-
Access them at `https://your-
|
|
237
|
+
Access them at `https://your-app.cycls.ai/public/logo.png`.
|
|
236
238
|
|
|
237
239
|
---
|
|
238
240
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from . import function as _function_module
|
|
2
|
+
from .function import function, Function
|
|
3
|
+
from .app import app, App
|
|
4
|
+
|
|
5
|
+
def __getattr__(name):
|
|
6
|
+
if name in ("api_key", "base_url"):
|
|
7
|
+
return getattr(_function_module, name)
|
|
8
|
+
raise AttributeError(f"module 'cycls' has no attribute '{name}'")
|
|
9
|
+
|
|
10
|
+
def __setattr__(name, value):
|
|
11
|
+
if name in ("api_key", "base_url"):
|
|
12
|
+
setattr(_function_module, name, value)
|
|
13
|
+
else:
|
|
14
|
+
raise AttributeError(f"module 'cycls' has no attribute '{name}'")
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import uvicorn
|
|
3
|
+
import importlib.resources
|
|
4
|
+
|
|
5
|
+
from .function import Function, _get_api_key, _get_base_url
|
|
6
|
+
from .web import web, Config
|
|
7
|
+
|
|
8
|
+
CYCLS_PATH = importlib.resources.files('cycls')
|
|
9
|
+
|
|
10
|
+
THEMES = ["default", "dev"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class App(Function):
|
|
14
|
+
"""App extends Function with web UI serving capabilities."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, func, name, theme="default", pip=None, apt=None, copy=None, copy_public=None,
|
|
17
|
+
auth=False, org=None, header=None, intro=None, title=None, plan="free", analytics=False):
|
|
18
|
+
if theme not in THEMES:
|
|
19
|
+
raise ValueError(f"Unknown theme: {theme}. Available: {THEMES}")
|
|
20
|
+
self.user_func = func
|
|
21
|
+
self.theme = theme
|
|
22
|
+
self.copy_public = copy_public or []
|
|
23
|
+
|
|
24
|
+
self.config = Config(
|
|
25
|
+
header=header,
|
|
26
|
+
intro=intro,
|
|
27
|
+
title=title,
|
|
28
|
+
auth=auth,
|
|
29
|
+
plan=plan,
|
|
30
|
+
analytics=analytics,
|
|
31
|
+
org=org,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Build files dict for Function (theme is inside cycls/)
|
|
35
|
+
files = {str(CYCLS_PATH): "cycls"}
|
|
36
|
+
files.update({f: f for f in copy or []})
|
|
37
|
+
files.update({f: f"public/{f}" for f in self.copy_public})
|
|
38
|
+
|
|
39
|
+
super().__init__(
|
|
40
|
+
func=func,
|
|
41
|
+
name=name,
|
|
42
|
+
pip=["fastapi[standard]", "pyjwt", "cryptography", "uvicorn", "docker", *(pip or [])],
|
|
43
|
+
apt=apt,
|
|
44
|
+
copy=files,
|
|
45
|
+
base_url=_get_base_url(),
|
|
46
|
+
api_key=_get_api_key()
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
def __call__(self, *args, **kwargs):
|
|
50
|
+
return self.user_func(*args, **kwargs)
|
|
51
|
+
|
|
52
|
+
def _prepare_func(self, prod):
|
|
53
|
+
self.config.set_prod(prod)
|
|
54
|
+
self.config.public_path = f"cycls/themes/{self.theme}"
|
|
55
|
+
user_func, config, name = self.user_func, self.config, self.name
|
|
56
|
+
self.func = lambda port: __import__("cycls").web.serve(user_func, config, name, port)
|
|
57
|
+
|
|
58
|
+
def _local(self, port=8080):
|
|
59
|
+
"""Run directly with uvicorn (no Docker)."""
|
|
60
|
+
print(f"Starting local server at localhost:{port}")
|
|
61
|
+
self.config.public_path = str(CYCLS_PATH.joinpath(f"themes/{self.theme}"))
|
|
62
|
+
self.config.set_prod(False)
|
|
63
|
+
uvicorn.run(web(self.user_func, self.config), host="0.0.0.0", port=port)
|
|
64
|
+
|
|
65
|
+
def local(self, port=8080, watch=True):
|
|
66
|
+
"""Run locally in Docker with file watching by default."""
|
|
67
|
+
if os.environ.get('_CYCLS_WATCH'):
|
|
68
|
+
watch = False
|
|
69
|
+
self._prepare_func(prod=False)
|
|
70
|
+
self.watch(port=port) if watch else self.run(port=port)
|
|
71
|
+
|
|
72
|
+
def deploy(self, port=8080):
|
|
73
|
+
"""Deploy to production."""
|
|
74
|
+
if self.api_key is None:
|
|
75
|
+
raise RuntimeError("Missing API key. Set cycls.api_key or CYCLS_API_KEY environment variable.")
|
|
76
|
+
self._prepare_func(prod=True)
|
|
77
|
+
return super().deploy(port=port)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def app(name=None, **kwargs):
|
|
81
|
+
"""Decorator that transforms a function into a deployable App."""
|
|
82
|
+
if kwargs.get("plan") == "cycls_pass":
|
|
83
|
+
kwargs["auth"] = True
|
|
84
|
+
kwargs["analytics"] = True
|
|
85
|
+
|
|
86
|
+
def decorator(func):
|
|
87
|
+
return App(func=func, name=name or func.__name__, **kwargs)
|
|
88
|
+
return decorator
|