cycls 0.0.2.65__py3-none-any.whl → 0.0.2.66__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/auth.py
ADDED
cycls/sdk.py
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
import json, time, modal, inspect, uvicorn
|
|
2
2
|
from .runtime import Runtime
|
|
3
3
|
from modal.runner import run_app
|
|
4
|
-
from .web import web
|
|
4
|
+
from .web import web, Config
|
|
5
|
+
from .auth import PK_LIVE, PK_TEST, JWKS_PROD, JWKS_TEST
|
|
5
6
|
import importlib.resources
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
from typing import Callable
|
|
6
9
|
|
|
7
10
|
CYCLS_PATH = importlib.resources.files('cycls')
|
|
8
11
|
|
|
12
|
+
class RegisteredAgent(BaseModel):
|
|
13
|
+
func: Callable
|
|
14
|
+
name: str
|
|
15
|
+
domain: str
|
|
16
|
+
config: Config
|
|
17
|
+
|
|
18
|
+
def set_prod(config: Config, prod: bool):
|
|
19
|
+
config.prod = prod
|
|
20
|
+
config.pk = PK_LIVE if prod else PK_TEST
|
|
21
|
+
config.jwks = JWKS_PROD if prod else JWKS_TEST
|
|
22
|
+
|
|
9
23
|
themes = {
|
|
10
24
|
"default": CYCLS_PATH.joinpath('default-theme'),
|
|
11
25
|
"dev": CYCLS_PATH.joinpath('dev-theme'),
|
|
@@ -43,13 +57,21 @@ class Agent:
|
|
|
43
57
|
auth=True
|
|
44
58
|
analytics=True
|
|
45
59
|
def decorator(f):
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
60
|
+
agent_name = name or f.__name__.replace('_', '-')
|
|
61
|
+
self.registered_functions.append(RegisteredAgent(
|
|
62
|
+
func=f,
|
|
63
|
+
name=agent_name,
|
|
64
|
+
domain=domain or f"{agent_name}.cycls.ai",
|
|
65
|
+
config=Config(
|
|
66
|
+
header=header,
|
|
67
|
+
intro=intro,
|
|
68
|
+
title=title,
|
|
69
|
+
auth=auth,
|
|
70
|
+
tier=tier,
|
|
71
|
+
analytics=analytics,
|
|
72
|
+
org=self.org,
|
|
73
|
+
),
|
|
74
|
+
))
|
|
53
75
|
return f
|
|
54
76
|
return decorator
|
|
55
77
|
|
|
@@ -57,13 +79,14 @@ class Agent:
|
|
|
57
79
|
if not self.registered_functions:
|
|
58
80
|
print("Error: No @agent decorated function found.")
|
|
59
81
|
return
|
|
60
|
-
|
|
61
|
-
|
|
82
|
+
|
|
83
|
+
agent = self.registered_functions[0]
|
|
62
84
|
if len(self.registered_functions) > 1:
|
|
63
|
-
print(f"⚠️ Warning: Multiple agents found. Running '{
|
|
85
|
+
print(f"⚠️ Warning: Multiple agents found. Running '{agent.name}'.")
|
|
64
86
|
print(f"🚀 Starting local server at localhost:{port}")
|
|
65
|
-
|
|
66
|
-
|
|
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)
|
|
67
90
|
return
|
|
68
91
|
|
|
69
92
|
def deploy(self, prod=False, port=8080):
|
|
@@ -74,27 +97,29 @@ class Agent:
|
|
|
74
97
|
print("🛑 Error: Please add your Cycls API key")
|
|
75
98
|
return
|
|
76
99
|
|
|
77
|
-
|
|
100
|
+
agent = self.registered_functions[0]
|
|
78
101
|
if len(self.registered_functions) > 1:
|
|
79
|
-
print(f"⚠️ Warning: Multiple agents found. Running '{
|
|
102
|
+
print(f"⚠️ Warning: Multiple agents found. Running '{agent.name}'.")
|
|
80
103
|
|
|
81
|
-
|
|
82
|
-
|
|
104
|
+
set_prod(agent.config, prod)
|
|
105
|
+
func = agent.func
|
|
106
|
+
name = agent.name
|
|
107
|
+
config_dict = agent.config.model_dump()
|
|
83
108
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
109
|
+
files = {str(self.theme): "theme", str(CYCLS_PATH)+"/web.py": "web.py"}
|
|
110
|
+
files.update({f: f for f in self.copy})
|
|
111
|
+
files.update({f: f"public/{f}" for f in self.copy_public})
|
|
87
112
|
|
|
88
113
|
new = Runtime(
|
|
89
|
-
func=lambda port: __import__("web").serve(
|
|
90
|
-
name=
|
|
114
|
+
func=lambda port: __import__("web").serve(func, config_dict, name, port),
|
|
115
|
+
name=name,
|
|
91
116
|
apt_packages=self.apt,
|
|
92
117
|
pip_packages=["fastapi[standard]", "pyjwt", "cryptography", "uvicorn", *self.pip],
|
|
93
|
-
copy=
|
|
118
|
+
copy=files,
|
|
94
119
|
base_url=self.base_url,
|
|
95
120
|
api_key=self.key
|
|
96
121
|
)
|
|
97
|
-
new.deploy(port=port) if prod else new.run(port=port)
|
|
122
|
+
new.deploy(port=port) if prod else new.run(port=port)
|
|
98
123
|
return
|
|
99
124
|
|
|
100
125
|
def modal(self, prod=False):
|
|
@@ -117,22 +142,26 @@ class Agent:
|
|
|
117
142
|
print("Error: No @agent decorated function found.")
|
|
118
143
|
return
|
|
119
144
|
|
|
120
|
-
for
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
145
|
+
for agent in self.registered_functions:
|
|
146
|
+
set_prod(agent.config, prod)
|
|
147
|
+
func = agent.func
|
|
148
|
+
name = agent.name
|
|
149
|
+
domain = agent.domain
|
|
150
|
+
config_dict = agent.config.model_dump()
|
|
151
|
+
self.app.function(serialized=True, name=name)(
|
|
152
|
+
modal.asgi_app(label=name, custom_domains=[domain])
|
|
153
|
+
(lambda: __import__("web").web(func, config_dict))
|
|
125
154
|
)
|
|
126
155
|
if prod:
|
|
127
|
-
for
|
|
128
|
-
print(f"✅ Deployed to ⇒ https://{
|
|
129
|
-
self.app.deploy(client=self.client, name=self.registered_functions[0]
|
|
156
|
+
for agent in self.registered_functions:
|
|
157
|
+
print(f"✅ Deployed to ⇒ https://{agent.domain}")
|
|
158
|
+
self.app.deploy(client=self.client, name=self.registered_functions[0].name)
|
|
130
159
|
return
|
|
131
160
|
else:
|
|
132
161
|
with modal.enable_output():
|
|
133
162
|
run_app(app=self.app, client=self.client)
|
|
134
163
|
print(" Modal development server is running. Press Ctrl+C to stop.")
|
|
135
|
-
with modal.enable_output(), run_app(app=self.app, client=self.client):
|
|
164
|
+
with modal.enable_output(), run_app(app=self.app, client=self.client):
|
|
136
165
|
while True: time.sleep(10)
|
|
137
166
|
|
|
138
167
|
# docker system prune -af
|
cycls/web.py
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import json, inspect
|
|
2
2
|
from pathlib import Path
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
class Config(BaseModel):
|
|
7
|
+
public_path: str = "theme"
|
|
8
|
+
header: str = ""
|
|
9
|
+
intro: str = ""
|
|
10
|
+
title: str = ""
|
|
11
|
+
prod: bool = False
|
|
12
|
+
auth: bool = False
|
|
13
|
+
tier: str = "free"
|
|
14
|
+
analytics: bool = False
|
|
15
|
+
org: Optional[str] = None
|
|
16
|
+
pk: str = ""
|
|
17
|
+
jwks: str = ""
|
|
8
18
|
|
|
9
19
|
async def openai_encoder(stream):
|
|
10
20
|
if inspect.isasyncgen(stream):
|
|
@@ -48,17 +58,20 @@ class Messages(list):
|
|
|
48
58
|
def raw(self):
|
|
49
59
|
return self._raw
|
|
50
60
|
|
|
51
|
-
def web(func,
|
|
61
|
+
def web(func, config):
|
|
52
62
|
from fastapi import FastAPI, Request, HTTPException, status, Depends
|
|
53
|
-
from fastapi.responses import StreamingResponse
|
|
63
|
+
from fastapi.responses import StreamingResponse
|
|
54
64
|
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
|
55
65
|
import jwt
|
|
56
66
|
from jwt import PyJWKClient
|
|
57
|
-
from pydantic import
|
|
67
|
+
from pydantic import EmailStr
|
|
58
68
|
from typing import List, Optional, Any
|
|
59
69
|
from fastapi.staticfiles import StaticFiles
|
|
60
70
|
|
|
61
|
-
|
|
71
|
+
if isinstance(config, dict):
|
|
72
|
+
config = Config(**config)
|
|
73
|
+
|
|
74
|
+
jwks = PyJWKClient(config.jwks)
|
|
62
75
|
|
|
63
76
|
class User(BaseModel):
|
|
64
77
|
id: str
|
|
@@ -67,18 +80,6 @@ def web(func, public_path="", prod=False, org=None, api_token=None, header="", i
|
|
|
67
80
|
org: Optional[str] = None
|
|
68
81
|
plans: List[str] = []
|
|
69
82
|
|
|
70
|
-
class Metadata(BaseModel):
|
|
71
|
-
header: str
|
|
72
|
-
intro: str
|
|
73
|
-
title: str
|
|
74
|
-
prod: bool
|
|
75
|
-
auth: bool
|
|
76
|
-
tier: str
|
|
77
|
-
analytics: bool
|
|
78
|
-
org: Optional[str]
|
|
79
|
-
pk_live: str
|
|
80
|
-
pk_test: str
|
|
81
|
-
|
|
82
83
|
class Context(BaseModel):
|
|
83
84
|
messages: Any
|
|
84
85
|
user: Optional[User] = None
|
|
@@ -99,7 +100,7 @@ def web(func, public_path="", prod=False, org=None, api_token=None, header="", i
|
|
|
99
100
|
@app.post("/")
|
|
100
101
|
@app.post("/chat/cycls")
|
|
101
102
|
@app.post("/chat/completions")
|
|
102
|
-
async def back(request: Request, jwt: Optional[dict] = Depends(validate) if auth else None):
|
|
103
|
+
async def back(request: Request, jwt: Optional[dict] = Depends(validate) if config.auth else None):
|
|
103
104
|
data = await request.json()
|
|
104
105
|
messages = data.get("messages")
|
|
105
106
|
user_data = jwt.get("user") if jwt else None
|
|
@@ -111,29 +112,20 @@ def web(func, public_path="", prod=False, org=None, api_token=None, header="", i
|
|
|
111
112
|
stream = encoder(stream)
|
|
112
113
|
return StreamingResponse(stream, media_type="text/event-stream")
|
|
113
114
|
|
|
114
|
-
@app.get("/
|
|
115
|
-
async def
|
|
116
|
-
return
|
|
117
|
-
header=header,
|
|
118
|
-
intro=intro,
|
|
119
|
-
title=title,
|
|
120
|
-
prod=prod,
|
|
121
|
-
auth=auth,
|
|
122
|
-
tier=tier,
|
|
123
|
-
analytics=analytics,
|
|
124
|
-
org=org,
|
|
125
|
-
pk_live=PK_LIVE,
|
|
126
|
-
pk_test=PK_TEST
|
|
127
|
-
)
|
|
115
|
+
@app.get("/config")
|
|
116
|
+
async def get_config():
|
|
117
|
+
return config
|
|
128
118
|
|
|
129
119
|
if Path("public").is_dir():
|
|
130
120
|
app.mount("/public", StaticFiles(directory="public", html=True))
|
|
131
|
-
app.mount("/", StaticFiles(directory=public_path, html=True))
|
|
121
|
+
app.mount("/", StaticFiles(directory=config.public_path, html=True))
|
|
132
122
|
|
|
133
123
|
return app
|
|
134
124
|
|
|
135
125
|
def serve(func, config, name, port):
|
|
136
126
|
import uvicorn, logging
|
|
127
|
+
if isinstance(config, dict):
|
|
128
|
+
config = Config(**config)
|
|
137
129
|
logging.getLogger("uvicorn.error").addFilter(lambda r: "0.0.0.0" not in r.getMessage())
|
|
138
130
|
print(f"\n🔨 {name} => http://localhost:{port}\n")
|
|
139
|
-
uvicorn.run(web(func,
|
|
131
|
+
uvicorn.run(web(func, config), host="0.0.0.0", port=port)
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
cycls/__init__.py,sha256=bVT0dYTXLdSC3ZURgtm-DEOj-VO6RUM6zGsJB0zuj6Y,61
|
|
2
|
+
cycls/auth.py,sha256=xkndHZyCfnlertMMEKerCJjf23N3fVcTRVTTSXTTuzg,247
|
|
2
3
|
cycls/default-theme/assets/index-B0ZKcm_V.css,sha256=wK9-NhEB8xPcN9Zv69zpOcfGTlFbMwyC9WqTmSKUaKw,6546
|
|
3
4
|
cycls/default-theme/assets/index-D5EDcI4J.js,sha256=sN4qRcAXa7DBd9JzmVcCoCwH4l8cNCM-U9QGUjBvWSo,1346506
|
|
4
5
|
cycls/default-theme/index.html,sha256=bM-yW_g0cGrV40Q5yY3ccY0fM4zI1Wuu5I8EtGFJIxs,828
|
|
5
6
|
cycls/dev-theme/index.html,sha256=QJBHkdNuMMiwQU7o8dN8__8YQeQB45D37D-NCXIWB2Q,11585
|
|
6
7
|
cycls/runtime.py,sha256=hLBtwtGz0FCW1-EPCJy6kMdF2fB3i6Df_H8-bm7qeK0,18223
|
|
7
|
-
cycls/sdk.py,sha256=
|
|
8
|
-
cycls/web.py,sha256=
|
|
9
|
-
cycls-0.0.2.
|
|
10
|
-
cycls-0.0.2.
|
|
11
|
-
cycls-0.0.2.
|
|
8
|
+
cycls/sdk.py,sha256=jT6t2btthVI8Bdp0-BttS4apaH0k7GCobUHxE9BTUj4,6728
|
|
9
|
+
cycls/web.py,sha256=3M3qaWTNY3dpgd7Vq5aXREp-cIFsHrDqBQ1YkGrOaUk,4659
|
|
10
|
+
cycls-0.0.2.66.dist-info/METADATA,sha256=g9ehLjsx8yYm6muJ_16lNlCzPtKchy2LlTjFP5nwzHk,7943
|
|
11
|
+
cycls-0.0.2.66.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
12
|
+
cycls-0.0.2.66.dist-info/RECORD,,
|
|
File without changes
|