-
Notifications
You must be signed in to change notification settings - Fork 8.7k
Expand file tree
/
Copy pathstudio.py
More file actions
245 lines (211 loc) · 8.54 KB
/
studio.py
File metadata and controls
245 lines (211 loc) · 8.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
import json
import os
import subprocess
import tempfile
import threading
import time
import webbrowser
from pathlib import Path
from typing import Any, Dict, Union
import uvicorn
from autogen_core import ComponentModel
class LiteStudio:
"""
Core class for managing AutoGen Studio lite mode instances.
Supports both file-based and programmatic team configurations.
"""
def __init__(
self,
team: Union[str, Path, Dict[str, Any], ComponentModel, None] = None,
host: str = "127.0.0.1",
port: int = 8080,
session_name: str = "Lite Session",
auto_open: bool = True,
):
"""
Initialize LiteStudio instance.
Args:
team: Team configuration - can be:
- str: Path to team JSON file
- Path: Path object to team JSON file
- Dict[str, Any]: Team configuration dictionary
- ComponentModel: AutoGen ComponentModel instance
- None: Creates default team
host: Host to run server on
port: Port to run server on
session_name: Name for the auto-created session
auto_open: Whether to auto-open browser
"""
self.host = host
self.port = port
self.session_name = session_name
self.auto_open = auto_open
self.server_process = None
self.server_thread = None # Handle team loading
self.team_file_path = self._load_team(team)
def _load_team(self, team: Union[str, Path, Dict[str, Any], ComponentModel, None]) -> str:
"""
Load team from file path, object, or create default.
Returns the file path to the team JSON.
Args:
team: Can be file path (str/Path), dict, ComponentModel, or None
"""
if team is None:
# Create default team
from autogenstudio.gallery.builder import create_default_lite_team
return create_default_lite_team()
elif isinstance(team, (str, Path)):
# File path provided
team_path = Path(team)
if not team_path.exists():
raise FileNotFoundError(f"Team file not found: {team_path}")
return str(team_path.absolute())
elif isinstance(team, dict):
# Team dict provided - save to temp file
temp_file = tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False)
try:
json.dump(team, temp_file, indent=2)
temp_file.flush()
return temp_file.name
finally:
temp_file.close()
elif isinstance(team, ComponentModel):
# ComponentModel - use model_dump directly
team_dict = team.model_dump()
temp_file = tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False)
try:
json.dump(team_dict, temp_file, indent=2)
temp_file.flush()
return temp_file.name
finally:
temp_file.close()
else:
# Try to serialize other team objects
team_dict = None
# Try dump_component() method (AutoGen teams)
if hasattr(team, "dump_component"):
component = team.dump_component()
if hasattr(component, "model_dump"):
team_dict = component.model_dump()
elif hasattr(component, "dict"):
team_dict = component.dict()
else:
team_dict = dict(component)
# Try model_dump() method (Pydantic v2)
elif hasattr(team, "model_dump"):
team_dict = team.model_dump()
# Try dict() method (Pydantic v1)
elif hasattr(team, "dict"):
team_dict = team.dict()
if team_dict is None:
raise ValueError(
f"Cannot serialize team object of type {type(team)}. "
f"Expected: file path, dict, ComponentModel, or object with dump_component()/model_dump()/dict() method."
)
# Save serialized team to temp file
temp_file = tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False)
try:
json.dump(team_dict, temp_file, indent=2)
temp_file.flush()
return temp_file.name
finally:
temp_file.close()
def _get_env_file_path(self) -> str:
"""Get path for environment variables file."""
app_dir = os.path.join(os.path.expanduser("~"), ".autogenstudio")
if not os.path.exists(app_dir):
os.makedirs(app_dir, exist_ok=True)
return os.path.join(app_dir, "temp_env_vars.env")
def _setup_environment(self) -> str:
"""
Setup environment variables for lite mode.
Returns path to the environment file.
"""
env_vars = {
"AUTOGENSTUDIO_HOST": self.host,
"AUTOGENSTUDIO_PORT": str(self.port),
"AUTOGENSTUDIO_LITE_MODE": "true",
"AUTOGENSTUDIO_API_DOCS": "false",
"AUTOGENSTUDIO_AUTH_DISABLED": "true",
"AUTOGENSTUDIO_LITE_SESSION_NAME": self.session_name,
"AUTOGENSTUDIO_LITE_TEAM_FILE": self.team_file_path,
"AUTOGENSTUDIO_DATABASE_URI": "sqlite:///:memory:",
}
env_file_path = self._get_env_file_path()
with open(env_file_path, "w", encoding="utf-8") as temp_env:
for key, value in env_vars.items():
temp_env.write(f"{key}={value}\n")
return env_file_path
def _setup_browser_opening(self):
"""Setup browser opening in a separate thread."""
if self.auto_open:
def open_browser():
time.sleep(3) # Wait for server startup
url = f"http://{self.host}:{self.port}/lite"
webbrowser.open(url)
threading.Thread(target=open_browser, daemon=True).start()
def start(self, background: bool = False):
"""
Start the lite studio server.
Args:
background: If True, run server in background thread
"""
# Check if already running
if self.server_thread and self.server_thread.is_alive():
raise RuntimeError("LiteStudio is already running")
# Setup environment
env_file_path = self._setup_environment()
# Setup browser opening
self._setup_browser_opening()
if background:
# Run server in background thread
def run_server():
uvicorn.run(
"autogenstudio.web.app:app",
host=self.host,
port=self.port,
workers=1,
env_file=env_file_path,
)
self.server_thread = threading.Thread(target=run_server, daemon=True)
self.server_thread.start()
else:
# Run server in foreground (blocking)
uvicorn.run(
"autogenstudio.web.app:app",
host=self.host,
port=self.port,
workers=1,
env_file=env_file_path,
)
def stop(self):
"""Stop the lite studio server."""
if self.server_thread and self.server_thread.is_alive():
# For background threads, we can't easily stop uvicorn
# This is a limitation - in production you'd want proper shutdown
self.server_thread.join(timeout=5)
self.server_thread = None
def __enter__(self):
"""Context manager entry - start in background."""
self.start(background=True)
return self
def __exit__(self, exc_type, exc_val, exc_tb): # type: ignore
"""Context manager exit - stop server."""
self.stop()
@classmethod
def shutdown_port(cls, port: int):
"""
Utility to shutdown any process running on the specified port.
Args:
port: Port number to shutdown
"""
try:
# Try to find and kill process on port (Unix/Linux/Mac)
result = subprocess.run(["lsof", "-ti", f":{port}"], capture_output=True, text=True)
if result.returncode == 0 and result.stdout.strip():
pids = result.stdout.strip().split("\n")
for pid in pids:
subprocess.run(["kill", "-9", pid], check=False)
except (subprocess.SubprocessError, FileNotFoundError):
# lsof might not be available on all systems
pass