cycls 0.0.2.72__py3-none-any.whl → 0.0.2.74__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 +20 -2
- cycls/sdk.py +112 -117
- cycls/web.py +1 -1
- {cycls-0.0.2.72.dist-info → cycls-0.0.2.74.dist-info}/METADATA +52 -50
- {cycls-0.0.2.72.dist-info → cycls-0.0.2.74.dist-info}/RECORD +7 -7
- {cycls-0.0.2.72.dist-info → cycls-0.0.2.74.dist-info}/WHEEL +0 -0
- {cycls-0.0.2.72.dist-info → cycls-0.0.2.74.dist-info}/entry_points.txt +0 -0
cycls/__init__.py
CHANGED
|
@@ -1,2 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
from
|
|
1
|
+
import sys
|
|
2
|
+
from types import ModuleType
|
|
3
|
+
from .sdk import function, agent
|
|
4
|
+
from .runtime import Runtime
|
|
5
|
+
|
|
6
|
+
class _Module(ModuleType):
|
|
7
|
+
def __getattr__(self, name):
|
|
8
|
+
from . import sdk
|
|
9
|
+
if name in ("api_key", "base_url"):
|
|
10
|
+
return getattr(sdk, name)
|
|
11
|
+
raise AttributeError(f"module 'cycls' has no attribute '{name}'")
|
|
12
|
+
|
|
13
|
+
def __setattr__(self, name, value):
|
|
14
|
+
from . import sdk
|
|
15
|
+
if name in ("api_key", "base_url"):
|
|
16
|
+
setattr(sdk, name, value)
|
|
17
|
+
return
|
|
18
|
+
super().__setattr__(name, value)
|
|
19
|
+
|
|
20
|
+
sys.modules[__name__].__class__ = _Module
|
cycls/sdk.py
CHANGED
|
@@ -1,30 +1,21 @@
|
|
|
1
|
-
import os, time,
|
|
1
|
+
import os, time, uvicorn
|
|
2
2
|
from .runtime import Runtime
|
|
3
3
|
from .web import web, Config
|
|
4
4
|
from .auth import PK_LIVE, PK_TEST, JWKS_PROD, JWKS_TEST
|
|
5
5
|
import importlib.resources
|
|
6
|
-
from pydantic import BaseModel
|
|
7
|
-
from typing import Callable
|
|
8
6
|
|
|
9
7
|
CYCLS_PATH = importlib.resources.files('cycls')
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
domain: str
|
|
15
|
-
config: Config
|
|
16
|
-
|
|
17
|
-
def set_prod(config: Config, prod: bool):
|
|
18
|
-
config.prod = prod
|
|
19
|
-
config.pk = PK_LIVE if prod else PK_TEST
|
|
20
|
-
config.jwks = JWKS_PROD if prod else JWKS_TEST
|
|
9
|
+
# Module-level configuration
|
|
10
|
+
api_key = None
|
|
11
|
+
base_url = None
|
|
21
12
|
|
|
22
13
|
themes = {
|
|
23
14
|
"default": CYCLS_PATH.joinpath('default-theme'),
|
|
24
15
|
"dev": CYCLS_PATH.joinpath('dev-theme'),
|
|
25
16
|
}
|
|
26
17
|
|
|
27
|
-
def
|
|
18
|
+
def _resolve_theme(theme):
|
|
28
19
|
"""Resolve theme - accepts string name or path"""
|
|
29
20
|
if isinstance(theme, str):
|
|
30
21
|
if theme in themes:
|
|
@@ -32,77 +23,54 @@ def resolve_theme(theme):
|
|
|
32
23
|
raise ValueError(f"Unknown theme: {theme}. Available: {list(themes.keys())}")
|
|
33
24
|
return theme
|
|
34
25
|
|
|
35
|
-
def
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
26
|
+
def _set_prod(config: Config, prod: bool):
|
|
27
|
+
config.prod = prod
|
|
28
|
+
config.pk = PK_LIVE if prod else PK_TEST
|
|
29
|
+
config.jwks = JWKS_PROD if prod else JWKS_TEST
|
|
30
|
+
|
|
31
|
+
class AgentRuntime:
|
|
32
|
+
"""Wraps an agent function with local/deploy/modal capabilities."""
|
|
33
|
+
|
|
34
|
+
def __init__(self, func, name, theme, pip, apt, copy, copy_public, modal_keys, auth, org, domain, header, intro, title, plan, analytics):
|
|
35
|
+
self.func = func
|
|
36
|
+
self.name = name
|
|
37
|
+
self.theme = _resolve_theme(theme)
|
|
38
|
+
self.pip = pip
|
|
39
|
+
self.apt = apt
|
|
40
|
+
self.copy = copy
|
|
41
|
+
self.copy_public = copy_public
|
|
42
|
+
self.modal_keys = modal_keys
|
|
43
|
+
self.domain = domain or f"{name}.cycls.ai"
|
|
44
|
+
|
|
45
|
+
self.config = Config(
|
|
46
|
+
header=header,
|
|
47
|
+
intro=intro,
|
|
48
|
+
title=title,
|
|
49
|
+
auth=auth,
|
|
50
|
+
plan=plan,
|
|
51
|
+
analytics=analytics,
|
|
52
|
+
org=org,
|
|
53
|
+
)
|
|
44
54
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
self.theme = resolve_theme(theme)
|
|
49
|
-
self.key, self.modal_keys, self.pip, self.apt, self.copy, self.copy_public = key, modal_keys, pip, apt, copy, copy_public
|
|
50
|
-
self.base_url = base_url
|
|
51
|
-
|
|
52
|
-
self.registered_functions = []
|
|
53
|
-
|
|
54
|
-
def __call__(self, name=None, header="", intro="", title="", domain=None, auth=False, tier="free", analytics=False):
|
|
55
|
-
if tier=="cycls_pass":
|
|
56
|
-
auth=True
|
|
57
|
-
analytics=True
|
|
58
|
-
def decorator(f):
|
|
59
|
-
agent_name = name or f.__name__.replace('_', '-')
|
|
60
|
-
self.registered_functions.append(RegisteredAgent(
|
|
61
|
-
func=f,
|
|
62
|
-
name=agent_name,
|
|
63
|
-
domain=domain or f"{agent_name}.cycls.ai",
|
|
64
|
-
config=Config(
|
|
65
|
-
header=header,
|
|
66
|
-
intro=intro,
|
|
67
|
-
title=title,
|
|
68
|
-
auth=auth,
|
|
69
|
-
tier=tier,
|
|
70
|
-
analytics=analytics,
|
|
71
|
-
org=self.org,
|
|
72
|
-
),
|
|
73
|
-
))
|
|
74
|
-
return f
|
|
75
|
-
return decorator
|
|
55
|
+
def __call__(self, *args, **kwargs):
|
|
56
|
+
"""Make the runtime callable - delegates to the wrapped function."""
|
|
57
|
+
return self.func(*args, **kwargs)
|
|
76
58
|
|
|
77
59
|
def _local(self, port=8080, watch=True):
|
|
78
60
|
"""Run directly with uvicorn (no Docker)."""
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
agent = self.registered_functions[0]
|
|
84
|
-
if len(self.registered_functions) > 1:
|
|
85
|
-
print(f"⚠️ Warning: Multiple agents found. Running '{agent.name}'.")
|
|
86
|
-
print(f"🚀 Starting local server at localhost:{port}")
|
|
87
|
-
agent.config.public_path = self.theme
|
|
88
|
-
set_prod(agent.config, False)
|
|
89
|
-
uvicorn.run(web(agent.func, agent.config), host="0.0.0.0", port=port, reload=watch)
|
|
90
|
-
return
|
|
61
|
+
print(f"Starting local server at localhost:{port}")
|
|
62
|
+
self.config.public_path = self.theme
|
|
63
|
+
_set_prod(self.config, False)
|
|
64
|
+
uvicorn.run(web(self.func, self.config), host="0.0.0.0", port=port, reload=watch)
|
|
91
65
|
|
|
92
66
|
def _runtime(self, prod=False):
|
|
93
|
-
"""Create a Runtime instance for
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
return None
|
|
97
|
-
|
|
98
|
-
agent = self.registered_functions[0]
|
|
99
|
-
if len(self.registered_functions) > 1:
|
|
100
|
-
print(f"⚠️ Warning: Multiple agents found. Running '{agent.name}'.")
|
|
67
|
+
"""Create a Runtime instance for deployment."""
|
|
68
|
+
_set_prod(self.config, prod)
|
|
69
|
+
config_dict = self.config.model_dump()
|
|
101
70
|
|
|
102
|
-
|
|
103
|
-
func =
|
|
104
|
-
name =
|
|
105
|
-
config_dict = agent.config.model_dump()
|
|
71
|
+
# Extract to local variables to avoid capturing self in lambda (cloudpickle issue)
|
|
72
|
+
func = self.func
|
|
73
|
+
name = self.name
|
|
106
74
|
|
|
107
75
|
files = {str(self.theme): "theme", str(CYCLS_PATH)+"/web.py": "web.py"}
|
|
108
76
|
files.update({f: f for f in self.copy})
|
|
@@ -114,73 +82,100 @@ class Agent:
|
|
|
114
82
|
apt_packages=self.apt,
|
|
115
83
|
pip_packages=["fastapi[standard]", "pyjwt", "cryptography", "uvicorn", *self.pip],
|
|
116
84
|
copy=files,
|
|
117
|
-
base_url=
|
|
118
|
-
api_key=
|
|
85
|
+
base_url=base_url,
|
|
86
|
+
api_key=api_key
|
|
119
87
|
)
|
|
120
88
|
|
|
121
89
|
def local(self, port=8080, watch=True):
|
|
122
90
|
"""Run locally in Docker with file watching by default."""
|
|
123
|
-
# Child process spawned by watcher - run without watch
|
|
124
91
|
if os.environ.get('_CYCLS_WATCH_CHILD'):
|
|
125
92
|
watch = False
|
|
126
93
|
runtime = self._runtime(prod=False)
|
|
127
|
-
if runtime
|
|
128
|
-
runtime.watch(port=port) if watch else runtime.run(port=port)
|
|
94
|
+
runtime.watch(port=port) if watch else runtime.run(port=port)
|
|
129
95
|
|
|
130
96
|
def deploy(self, port=8080):
|
|
131
97
|
"""Deploy to production."""
|
|
132
|
-
if
|
|
133
|
-
print("
|
|
98
|
+
if api_key is None:
|
|
99
|
+
print("Error: Please set cycls.api_key")
|
|
134
100
|
return
|
|
135
101
|
runtime = self._runtime(prod=True)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
102
|
+
runtime.deploy(port=port)
|
|
103
|
+
|
|
139
104
|
def modal(self, prod=False):
|
|
140
105
|
import modal
|
|
141
106
|
from modal.runner import run_app
|
|
142
|
-
|
|
107
|
+
|
|
108
|
+
# Extract to local variables to avoid capturing self in lambda
|
|
109
|
+
func = self.func
|
|
110
|
+
name = self.name
|
|
111
|
+
domain = self.domain
|
|
112
|
+
|
|
113
|
+
client = modal.Client.from_credentials(*self.modal_keys)
|
|
143
114
|
image = (modal.Image.debian_slim()
|
|
144
115
|
.pip_install("fastapi[standard]", "pyjwt", "cryptography", *self.pip)
|
|
145
116
|
.apt_install(*self.apt)
|
|
146
117
|
.add_local_dir(self.theme, "/root/theme")
|
|
147
118
|
.add_local_file(str(CYCLS_PATH)+"/web.py", "/root/web.py"))
|
|
148
|
-
|
|
119
|
+
|
|
149
120
|
for item in self.copy:
|
|
150
121
|
image = image.add_local_file(item, f"/root/{item}") if "." in item else image.add_local_dir(item, f'/root/{item}')
|
|
151
|
-
|
|
122
|
+
|
|
152
123
|
for item in self.copy_public:
|
|
153
124
|
image = image.add_local_file(item, f"/root/public/{item}") if "." in item else image.add_local_dir(item, f'/root/public/{item}')
|
|
154
125
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
126
|
+
app = modal.App("development", image=image)
|
|
127
|
+
|
|
128
|
+
_set_prod(self.config, prod)
|
|
129
|
+
config_dict = self.config.model_dump()
|
|
130
|
+
|
|
131
|
+
app.function(serialized=True, name=name)(
|
|
132
|
+
modal.asgi_app(label=name, custom_domains=[domain])
|
|
133
|
+
(lambda: __import__("web").web(func, config_dict))
|
|
134
|
+
)
|
|
160
135
|
|
|
161
|
-
for agent in self.registered_functions:
|
|
162
|
-
set_prod(agent.config, prod)
|
|
163
|
-
func = agent.func
|
|
164
|
-
name = agent.name
|
|
165
|
-
domain = agent.domain
|
|
166
|
-
config_dict = agent.config.model_dump()
|
|
167
|
-
self.app.function(serialized=True, name=name)(
|
|
168
|
-
modal.asgi_app(label=name, custom_domains=[domain])
|
|
169
|
-
(lambda: __import__("web").web(func, config_dict))
|
|
170
|
-
)
|
|
171
136
|
if prod:
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
self.app.deploy(client=self.client, name=self.registered_functions[0].name)
|
|
175
|
-
return
|
|
137
|
+
print(f"Deployed to => https://{domain}")
|
|
138
|
+
app.deploy(client=client, name=name)
|
|
176
139
|
else:
|
|
177
140
|
with modal.enable_output():
|
|
178
|
-
run_app(app=
|
|
179
|
-
print("
|
|
180
|
-
with modal.enable_output(), run_app(app=
|
|
141
|
+
run_app(app=app, client=client)
|
|
142
|
+
print("Modal development server is running. Press Ctrl+C to stop.")
|
|
143
|
+
with modal.enable_output(), run_app(app=app, client=client):
|
|
181
144
|
while True: time.sleep(10)
|
|
182
145
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
146
|
+
|
|
147
|
+
def agent(name=None, pip=[], apt=[], copy=[], copy_public=[], theme="default", modal_keys=["", ""], auth=False, org=None, domain=None, header="", intro="", title="", plan="free", analytics=False):
|
|
148
|
+
"""Decorator that transforms a function into a deployable agent."""
|
|
149
|
+
if plan == "cycls_pass":
|
|
150
|
+
auth = True
|
|
151
|
+
analytics = True
|
|
152
|
+
|
|
153
|
+
def decorator(func):
|
|
154
|
+
agent_name = name or func.__name__.replace('_', '-')
|
|
155
|
+
return AgentRuntime(
|
|
156
|
+
func=func,
|
|
157
|
+
name=agent_name,
|
|
158
|
+
theme=theme,
|
|
159
|
+
pip=pip,
|
|
160
|
+
apt=apt,
|
|
161
|
+
copy=copy,
|
|
162
|
+
copy_public=copy_public,
|
|
163
|
+
modal_keys=modal_keys,
|
|
164
|
+
auth=auth,
|
|
165
|
+
org=org,
|
|
166
|
+
domain=domain,
|
|
167
|
+
header=header,
|
|
168
|
+
intro=intro,
|
|
169
|
+
title=title,
|
|
170
|
+
plan=plan,
|
|
171
|
+
analytics=analytics,
|
|
172
|
+
)
|
|
173
|
+
return decorator
|
|
174
|
+
|
|
175
|
+
def function(python_version=None, pip=None, apt=None, run_commands=None, copy=None, name=None):
|
|
176
|
+
"""Decorator that transforms a Python function into a containerized, remotely executable object."""
|
|
177
|
+
def decorator(func):
|
|
178
|
+
func_name = name or func.__name__
|
|
179
|
+
copy_dict = {i: i for i in copy or []}
|
|
180
|
+
return Runtime(func, func_name.replace('_', '-'), python_version, pip, apt, run_commands, copy_dict, base_url, api_key)
|
|
181
|
+
return decorator
|
cycls/web.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cycls
|
|
3
|
-
Version: 0.0.2.
|
|
3
|
+
Version: 0.0.2.74
|
|
4
4
|
Summary: Distribute Intelligence
|
|
5
5
|
Author: Mohammed J. AlRujayi
|
|
6
6
|
Author-email: mj@cycls.com
|
|
@@ -32,6 +32,7 @@ Distribute Intelligence
|
|
|
32
32
|
|
|
33
33
|
<h4 align="center">
|
|
34
34
|
<a href="https://pypi.python.org/pypi/cycls"><img src="https://img.shields.io/pypi/v/cycls.svg?label=cycls+pypi&color=blueviolet" alt="cycls Python package on PyPi" /></a>
|
|
35
|
+
<a href="https://github.com/Cycls/cycls/actions/workflows/tests.yml"><img src="https://github.com/Cycls/cycls/actions/workflows/tests.yml/badge.svg" alt="Tests" /></a>
|
|
35
36
|
<a href="https://blog.cycls.com"><img src="https://img.shields.io/badge/newsletter-blueviolet.svg?logo=substack&label=cycls" alt="Cycls newsletter" /></a>
|
|
36
37
|
<a href="https://x.com/cyclsai">
|
|
37
38
|
<img src="https://img.shields.io/twitter/follow/CyclsAI" alt="Cycls Twitter" />
|
|
@@ -46,31 +47,32 @@ The open-source SDK for distributing AI agents.
|
|
|
46
47
|
|
|
47
48
|
## Distribute Intelligence
|
|
48
49
|
|
|
49
|
-
AI capabilities shouldn't be locked in notebooks or trapped behind months of infrastructure work. Cycls turns your Python functions into production services - complete with APIs, interfaces, auth, and analytics. You focus on the intelligence. Cycls handles the distribution.
|
|
50
|
-
|
|
51
50
|
Write a function. Deploy it as an API, a web interface, or both. Add authentication, analytics, and monetization with flags.
|
|
52
51
|
|
|
53
52
|
```python
|
|
54
53
|
import cycls
|
|
55
54
|
|
|
56
|
-
|
|
55
|
+
cycls.api_key = "YOUR_CYCLS_API_KEY"
|
|
57
56
|
|
|
58
|
-
@agent("
|
|
59
|
-
async def
|
|
57
|
+
@cycls.agent(pip=["openai"])
|
|
58
|
+
async def agent(context):
|
|
60
59
|
from openai import AsyncOpenAI
|
|
61
60
|
client = AsyncOpenAI()
|
|
62
61
|
|
|
63
|
-
|
|
64
|
-
model="
|
|
65
|
-
|
|
66
|
-
stream=True
|
|
62
|
+
stream = await client.responses.create(
|
|
63
|
+
model="o3-mini",
|
|
64
|
+
input=context.messages,
|
|
65
|
+
stream=True,
|
|
66
|
+
reasoning={"effort": "medium", "summary": "auto"},
|
|
67
67
|
)
|
|
68
68
|
|
|
69
|
-
async for
|
|
70
|
-
if
|
|
71
|
-
yield
|
|
69
|
+
async for event in stream:
|
|
70
|
+
if event.type == "response.reasoning_summary_text.delta":
|
|
71
|
+
yield {"type": "thinking", "thinking": event.delta} # Renders as thinking bubble
|
|
72
|
+
elif event.type == "response.output_text.delta":
|
|
73
|
+
yield event.delta
|
|
72
74
|
|
|
73
|
-
agent.deploy() # Live at https://
|
|
75
|
+
agent.deploy() # Live at https://agent.cycls.ai
|
|
74
76
|
```
|
|
75
77
|
|
|
76
78
|
## Installation
|
|
@@ -87,7 +89,7 @@ Requires Docker.
|
|
|
87
89
|
- **Web Interface** - Chat UI served automatically
|
|
88
90
|
- **Authentication** - `auth=True` enables JWT-based access control
|
|
89
91
|
- **Analytics** - `analytics=True` tracks usage
|
|
90
|
-
- **Monetization** - `
|
|
92
|
+
- **Monetization** - `plan="cycls_pass"` integrates with [Cycls Pass](https://cycls.ai) subscriptions
|
|
91
93
|
- **Native UI Components** - Render thinking bubbles, tables, code blocks in responses
|
|
92
94
|
|
|
93
95
|
## Running
|
|
@@ -95,17 +97,33 @@ Requires Docker.
|
|
|
95
97
|
```python
|
|
96
98
|
agent.local() # Development with hot-reload (localhost:8080)
|
|
97
99
|
agent.local(watch=False) # Development without hot-reload
|
|
98
|
-
agent.deploy() # Production: https://agent
|
|
100
|
+
agent.deploy() # Production: https://agent.cycls.ai
|
|
99
101
|
```
|
|
100
102
|
|
|
101
103
|
Get an API key at [cycls.com](https://cycls.com).
|
|
102
104
|
|
|
105
|
+
## Authentication & Analytics
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
@cycls.agent(pip=["openai"], auth=True, analytics=True)
|
|
109
|
+
async def agent(context):
|
|
110
|
+
# context.user available when auth=True
|
|
111
|
+
user = context.user # User(id, email, name, plans)
|
|
112
|
+
yield f"Hello {user.name}!"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
| Flag | Description |
|
|
116
|
+
|------|-------------|
|
|
117
|
+
| `auth=True` | Universal user pool via Cycls Pass (Clerk-based). You can also use your own Clerk auth. |
|
|
118
|
+
| `analytics=True` | Rich usage metrics available on the Cycls dashboard. |
|
|
119
|
+
| `plan="cycls_pass"` | Monetization via Cycls Pass subscriptions. Enables both auth and analytics. |
|
|
120
|
+
|
|
103
121
|
## Native UI Components
|
|
104
122
|
|
|
105
123
|
Yield structured objects for rich streaming responses:
|
|
106
124
|
|
|
107
125
|
```python
|
|
108
|
-
@agent()
|
|
126
|
+
@cycls.agent()
|
|
109
127
|
async def demo(context):
|
|
110
128
|
yield {"type": "thinking", "thinking": "Analyzing the request..."}
|
|
111
129
|
yield "Here's what I found:\n\n"
|
|
@@ -128,32 +146,26 @@ async def demo(context):
|
|
|
128
146
|
| `{"type": "callout", "callout": "...", "style": "..."}` | Yes |
|
|
129
147
|
| `{"type": "image", "src": "..."}` | Yes |
|
|
130
148
|
|
|
131
|
-
###
|
|
149
|
+
### Thinking Bubbles
|
|
132
150
|
|
|
133
|
-
|
|
134
|
-
@agent()
|
|
135
|
-
async def chat(context):
|
|
136
|
-
from openai import AsyncOpenAI
|
|
137
|
-
client = AsyncOpenAI()
|
|
151
|
+
The `{"type": "thinking", "thinking": "..."}` component renders as a collapsible thinking bubble in the UI. Each yield appends to the same bubble until a different component type is yielded:
|
|
138
152
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
)
|
|
153
|
+
```python
|
|
154
|
+
# Multiple yields build one thinking bubble
|
|
155
|
+
yield {"type": "thinking", "thinking": "Let me "}
|
|
156
|
+
yield {"type": "thinking", "thinking": "analyze this..."}
|
|
157
|
+
yield {"type": "thinking", "thinking": " Done thinking."}
|
|
145
158
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
yield {"type": "thinking", "thinking": event.delta}
|
|
149
|
-
elif event.type == "response.output_text.delta":
|
|
150
|
-
yield event.delta
|
|
159
|
+
# Then output the response
|
|
160
|
+
yield "Here's what I found..."
|
|
151
161
|
```
|
|
152
162
|
|
|
163
|
+
This works seamlessly with OpenAI's reasoning models - just map reasoning summaries to the thinking component.
|
|
164
|
+
|
|
153
165
|
## Context Object
|
|
154
166
|
|
|
155
167
|
```python
|
|
156
|
-
@agent()
|
|
168
|
+
@cycls.agent()
|
|
157
169
|
async def chat(context):
|
|
158
170
|
context.messages # [{"role": "user", "content": "..."}]
|
|
159
171
|
context.messages.raw # Full data including UI component parts
|
|
@@ -183,16 +195,17 @@ See [docs/streaming-protocol.md](docs/streaming-protocol.md) for frontend integr
|
|
|
183
195
|
|
|
184
196
|
## Declarative Infrastructure
|
|
185
197
|
|
|
186
|
-
Define your entire runtime in
|
|
198
|
+
Define your entire runtime in the decorator:
|
|
187
199
|
|
|
188
200
|
```python
|
|
189
|
-
|
|
201
|
+
@cycls.agent(
|
|
190
202
|
pip=["openai", "pandas", "numpy"],
|
|
191
203
|
apt=["ffmpeg", "libmagic1"],
|
|
192
|
-
run_commands=["curl -sSL https://example.com/setup.sh | bash"],
|
|
193
204
|
copy=["./utils.py", "./models/", "/absolute/path/to/config.json"],
|
|
194
205
|
copy_public=["./assets/logo.png", "./static/"],
|
|
195
206
|
)
|
|
207
|
+
async def my_agent(context):
|
|
208
|
+
...
|
|
196
209
|
```
|
|
197
210
|
|
|
198
211
|
### `pip` - Python Packages
|
|
@@ -211,17 +224,6 @@ Install system-level dependencies via apt-get. Need ffmpeg for audio processing?
|
|
|
211
224
|
apt=["ffmpeg", "imagemagick", "libpq-dev"]
|
|
212
225
|
```
|
|
213
226
|
|
|
214
|
-
### `run_commands` - Shell Commands
|
|
215
|
-
|
|
216
|
-
Run arbitrary shell commands during the container build. Useful for custom setup scripts, downloading assets, or any build-time configuration.
|
|
217
|
-
|
|
218
|
-
```python
|
|
219
|
-
run_commands=[
|
|
220
|
-
"curl -sSL https://example.com/setup.sh | bash",
|
|
221
|
-
"chmod +x /app/scripts/*.sh"
|
|
222
|
-
]
|
|
223
|
-
```
|
|
224
|
-
|
|
225
227
|
### `copy` - Bundle Files and Directories
|
|
226
228
|
|
|
227
229
|
Include local files and directories in your container. Works with both relative and absolute paths. Copies files and entire directory trees.
|
|
@@ -237,7 +239,7 @@ copy=[
|
|
|
237
239
|
Then import them in your function:
|
|
238
240
|
|
|
239
241
|
```python
|
|
240
|
-
@agent()
|
|
242
|
+
@cycls.agent(copy=["./utils.py"])
|
|
241
243
|
async def chat(context):
|
|
242
244
|
from utils import helper_function # Your bundled module
|
|
243
245
|
...
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
cycls/__init__.py,sha256=
|
|
1
|
+
cycls/__init__.py,sha256=vyI1d_8VP4XW7MliFuUs_P3O9KQxyCwQu-JkxrCyhPQ,597
|
|
2
2
|
cycls/auth.py,sha256=xkndHZyCfnlertMMEKerCJjf23N3fVcTRVTTSXTTuzg,247
|
|
3
3
|
cycls/cli.py,sha256=AKf0z7ZLau3GvBVR_IhB7agmq4nVaHkcuUafNyvv2_A,7978
|
|
4
4
|
cycls/default-theme/assets/index-B0ZKcm_V.css,sha256=wK9-NhEB8xPcN9Zv69zpOcfGTlFbMwyC9WqTmSKUaKw,6546
|
|
@@ -6,9 +6,9 @@ cycls/default-theme/assets/index-D5EDcI4J.js,sha256=sN4qRcAXa7DBd9JzmVcCoCwH4l8c
|
|
|
6
6
|
cycls/default-theme/index.html,sha256=bM-yW_g0cGrV40Q5yY3ccY0fM4zI1Wuu5I8EtGFJIxs,828
|
|
7
7
|
cycls/dev-theme/index.html,sha256=QJBHkdNuMMiwQU7o8dN8__8YQeQB45D37D-NCXIWB2Q,11585
|
|
8
8
|
cycls/runtime.py,sha256=lg7XKHd9fLV_bYksHv2LHf3Lq7HPAC3K5Tr8pNgQ7sM,21641
|
|
9
|
-
cycls/sdk.py,sha256=
|
|
10
|
-
cycls/web.py,sha256=
|
|
11
|
-
cycls-0.0.2.
|
|
12
|
-
cycls-0.0.2.
|
|
13
|
-
cycls-0.0.2.
|
|
14
|
-
cycls-0.0.2.
|
|
9
|
+
cycls/sdk.py,sha256=X8-VAVqtksO0VGJIxlg02HLmeFpwtwMHWu9PNksS5kw,6620
|
|
10
|
+
cycls/web.py,sha256=_QNH8K55vTm90Z7tvcRKal5IybjkB1GY7Pf9p3qu3r8,4659
|
|
11
|
+
cycls-0.0.2.74.dist-info/METADATA,sha256=IXA6mD4bkkGRnGZjK_rQYDda-fEE8nI2ucgVVVIsJgQ,8419
|
|
12
|
+
cycls-0.0.2.74.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
13
|
+
cycls-0.0.2.74.dist-info/entry_points.txt,sha256=vEhqUxFhhuzCKWtq02LbMnT3wpUqdfgcM3Yh-jjXom8,40
|
|
14
|
+
cycls-0.0.2.74.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|