Skip to content

Commit e2090fc

Browse files
Fix passing of client configs in settings (#309)
1 parent dac0559 commit e2090fc

9 files changed

Lines changed: 114 additions & 811 deletions

File tree

frontend/src/components/settings.tsx

Lines changed: 0 additions & 642 deletions
This file was deleted.

src/magentic_ui/backend/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ def run_ui(
136136
appdir (str, optional): Path to the app directory where files are stored. Defaults to ~/.magentic_ui.
137137
database_uri (str, optional): Database URI to connect to. Defaults to None.
138138
upgrade_database (bool, optional): Whether to upgrade the database schema. Defaults to False.
139-
config (str, optional): Path to the config file. Defaults to config.yaml if present.
139+
config (str, optional): Path to the LLM config file. Defaults to config.yaml if present.
140140
run_without_docker (bool, optional): Run without docker. This will remove coder and filesurfer agents and disale live browser view. Defaults to False.
141141
"""
142142
# Display a green, bold "Starting Magentic-UI" message

src/magentic_ui/backend/datamodel/db.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ class Settings(SQLModel, table=True):
189189
default_factory=datetime.now,
190190
sa_column=Column(DateTime(timezone=True), onupdate=func.now()),
191191
) # pylint: disable=not-callable
192-
user_id: Optional[str] = None
192+
user_id: Optional[str] = Field(default=None, unique=True)
193193
version: Optional[str] = "0.0.1"
194194
config: Union[SettingsConfig, dict[str, Any]] = Field(
195195
default_factory=lambda: SettingsConfig().model_dump(), sa_column=Column(JSON)

src/magentic_ui/backend/datamodel/types.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from autogen_agentchat.base import TaskResult
66
from autogen_agentchat.messages import BaseChatMessage, BaseTextChatMessage
77
from autogen_core import ComponentModel
8-
from pydantic import BaseModel, Field, field_serializer
8+
from pydantic import BaseModel, field_serializer
99

1010

1111
class MessageConfig(BaseModel):
@@ -87,9 +87,8 @@ class UISettings(BaseModel):
8787

8888

8989
class SettingsConfig(BaseModel):
90-
# Backend-specific settings only
91-
environment: List[EnvironmentVariable] = []
92-
ui: UISettings = Field(default_factory=UISettings)
90+
# empty starting point
91+
pass
9392

9493

9594
# web request/response data models

src/magentic_ui/backend/teammanager/teammanager.py

Lines changed: 79 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,12 @@
1717
Sequence,
1818
Union,
1919
)
20-
import aiofiles
21-
import yaml
2220
from loguru import logger
2321
from autogen_agentchat.base import ChatAgent, TaskResult, Team
2422
from autogen_agentchat.messages import AgentEvent, ChatMessage, TextMessage
2523
from autogen_core import EVENT_LOGGER_NAME, CancellationToken, ComponentModel
2624
from autogen_core.logging import LLMCallEvent
2725
from ...task_team import get_task_team
28-
from ...teams import GroupChat
2926
from ...types import RunPaths
3027
from ...magentic_ui_config import MagenticUIConfig, ModelClientConfigs
3128
from ...input_func import InputFuncType
@@ -73,17 +70,9 @@ def __init__(
7370
@staticmethod
7471
async def load_from_file(path: Union[str, Path]) -> Dict[str, Any]:
7572
"""Load team configuration from JSON/YAML file"""
76-
path = Path(path)
77-
if not path.exists():
78-
raise FileNotFoundError(f"Config file not found: {path}")
79-
80-
async with aiofiles.open(path) as f: # type: ignore
81-
content = await f.read()
82-
if path.suffix == ".json":
83-
return json.loads(content)
84-
elif path.suffix in (".yml", ".yaml"):
85-
return yaml.safe_load(content)
86-
raise ValueError(f"Unsupported file format: {path.suffix}")
73+
raise NotImplementedError(
74+
"This method should be implemented in a subclass or replaced with actual loading logic."
75+
)
8776

8877
def prepare_run_paths(
8978
self,
@@ -178,19 +167,9 @@ def add_uploaded_files(self, file_names: set[str]) -> None:
178167
@staticmethod
179168
async def load_from_directory(directory: Union[str, Path]) -> List[Dict[str, Any]]:
180169
"""Load all team configurations from a directory"""
181-
directory = Path(directory)
182-
configs: List[Dict[str, Any]] = []
183-
valid_extensions = {".json", ".yaml", ".yml"}
184-
185-
for path in directory.iterdir():
186-
if path.is_file() and path.suffix.lower() in valid_extensions:
187-
try:
188-
config = await TeamManager.load_from_file(path)
189-
configs.append(config)
190-
except Exception as e:
191-
logger.error(f"Failed to load {path}: {e}")
192-
193-
return configs
170+
raise NotImplementedError(
171+
"This method should be implemented in a subclass or replaced with actual loading logic."
172+
)
194173

195174
async def _create_team(
196175
self,
@@ -212,123 +191,86 @@ async def _create_team(
212191
playwright_port = -1
213192

214193
try:
215-
if not self.load_from_config:
216-
# The settings_config dictionary provides the Model configs in a key `model_configs`
217-
# But MagenticUIConfig expects `model_client_configs` so we need to update that here
218-
settings_model_configs: Dict[str, Any] = {}
219-
if "model_configs" in settings_config:
220-
try:
221-
settings_model_configs = yaml.safe_load(
222-
settings_config["model_configs"]
223-
)
224-
except Exception as e:
225-
logger.warning(
226-
f"Error loading model configs from UI. Using defaults. Inner exception: {e}"
227-
)
194+
# Logic here: we first see if the config file passed to magentic-ui has valid configs for all clients
195+
# If Yes: this takes precedent over the UI LLM config and is passed to magentic-ui team
196+
# If No: we disregard it and use the UI LLM config
197+
model_client_from_config_file = ModelClientConfigs(
198+
orchestrator=self.config.get("orchestrator_client", None),
199+
web_surfer=self.config.get("web_surfer_client", None),
200+
coder=self.config.get("coder_client", None),
201+
file_surfer=self.config.get("file_surfer_client", None),
202+
action_guard=self.config.get("action_guard_client", None),
203+
)
204+
is_complete_config_from_file = all(
205+
[
206+
model_client_from_config_file.orchestrator,
207+
model_client_from_config_file.web_surfer,
208+
model_client_from_config_file.coder,
209+
model_client_from_config_file.file_surfer,
210+
model_client_from_config_file.action_guard,
211+
]
212+
)
228213

229-
# Use settings_config values if available, otherwise fall back to instance defaults (self.config)
230-
model_client_configs = ModelClientConfigs(
231-
orchestrator=settings_model_configs.get(
232-
"orchestrator_client",
233-
self.config.get("orchestrator_client", None),
234-
),
235-
web_surfer=settings_model_configs.get(
236-
"web_surfer_client",
237-
self.config.get("web_surfer_client", None),
238-
),
239-
coder=settings_model_configs.get(
240-
"coder_client", self.config.get("coder_client", None)
241-
),
242-
file_surfer=settings_model_configs.get(
243-
"file_surfer_client",
244-
self.config.get("file_surfer_client", None),
245-
),
246-
action_guard=settings_model_configs.get(
247-
"action_guard_client",
248-
self.config.get("action_guard_client", None),
249-
),
214+
# Common configuration parameters
215+
config_params = {
216+
**settings_config, # type: ignore,
217+
# These must always be set to the values computed above
218+
"playwright_port": playwright_port,
219+
"novnc_port": novnc_port,
220+
# Defer to self for inside_docker
221+
"inside_docker": self.inside_docker,
222+
}
223+
224+
# Override client configs if complete config from file is available
225+
if is_complete_config_from_file:
226+
config_params["model_client_configs"] = model_client_from_config_file
227+
else:
228+
logger.warning(
229+
"Using LLM client configurations from UI settings (default is OpenAI) since no config file passed or config file incomplete."
250230
)
251-
252-
config_params = {
253-
# Lowest priority defaults
254-
**self.config, # type: ignore
255-
# Provided settings override defaults
256-
**settings_config, # type: ignore,
257-
"model_client_configs": model_client_configs,
258-
# These must always be set to the values computed above
259-
"playwright_port": playwright_port,
260-
"novnc_port": novnc_port,
261-
# Defer to self for inside_docker
262-
"inside_docker": self.inside_docker,
263-
}
264-
if self.run_without_docker:
265-
config_params["run_without_docker"] = True
266-
# Allow browser_headless to be set by settings_config
231+
if self.run_without_docker:
232+
config_params["run_without_docker"] = True
233+
# Allow browser_headless to be set by settings_config
234+
else:
235+
if settings_config.get("run_without_docker", False):
236+
# Allow settings_config to set browser_headless
237+
pass
267238
else:
268-
if settings_config.get("run_without_docker", False):
269-
# Allow settings_config to set browser_headless
239+
config_params["browser_headless"] = False
240+
magentic_ui_config = MagenticUIConfig(**config_params) # type: ignore
241+
self.team = cast(
242+
Team,
243+
await get_task_team(
244+
magentic_ui_config=magentic_ui_config,
245+
input_func=input_func,
246+
paths=paths,
247+
),
248+
)
249+
if hasattr(self.team, "_participants"):
250+
for agent in cast(list[ChatAgent], self.team._participants): # type: ignore
251+
if isinstance(agent, WebSurfer):
252+
novnc_port = agent.novnc_port
253+
playwright_port = agent.playwright_port
254+
255+
if state:
256+
if isinstance(state, str):
257+
# Check if the string is empty or whitespace only
258+
if not state.strip():
259+
# Skip loading if state is empty
270260
pass
271261
else:
272-
config_params["browser_headless"] = False
273-
magentic_ui_config = MagenticUIConfig(**config_params) # type: ignore
274-
275-
self.team = cast(
276-
Team,
277-
await get_task_team(
278-
magentic_ui_config=magentic_ui_config,
279-
input_func=input_func,
280-
paths=paths,
281-
),
282-
)
283-
if hasattr(self.team, "_participants"):
284-
for agent in cast(list[ChatAgent], self.team._participants): # type: ignore
285-
if isinstance(agent, WebSurfer):
286-
novnc_port = agent.novnc_port
287-
playwright_port = agent.playwright_port
288-
289-
if state:
290-
if isinstance(state, str):
291-
# Check if the string is empty or whitespace only
292-
if not state.strip():
293-
# Skip loading if state is empty
294-
pass
295-
else:
296-
try:
297-
state_dict = json.loads(state)
298-
await self.team.load_state(state_dict)
299-
except json.JSONDecodeError as json_error:
300-
# Log error and skip loading invalid JSON state
301-
logger.warning(
302-
f"Warning: Failed to load state - invalid JSON: {json_error}"
303-
)
304-
305-
else:
306-
await self.team.load_state(state)
307-
308-
return self.team, novnc_port, playwright_port
309-
310-
if isinstance(team_config, (str, Path)):
311-
config = await self.load_from_file(team_config)
312-
elif isinstance(team_config, dict):
313-
config = team_config
314-
else:
315-
config = team_config.model_dump()
316-
317-
# Load env vars into environment if provided
318-
if env_vars:
319-
logger.info("Loading environment variables")
320-
for var in env_vars:
321-
os.environ[var.name] = var.value
262+
try:
263+
state_dict = json.loads(state)
264+
await self.team.load_state(state_dict)
265+
except json.JSONDecodeError as json_error:
266+
# Log error and skip loading invalid JSON state
267+
logger.warning(
268+
f"Warning: Failed to load state - invalid JSON: {json_error}"
269+
)
322270

323-
self.team = cast(Team, GroupChat.load_component(config))
271+
else:
272+
await self.team.load_state(state)
324273

325-
if hasattr(self.team, "_participants"):
326-
for agent in cast(list[ChatAgent], self.team._participants): # type: ignore
327-
if hasattr(agent, "input_func"):
328-
agent.input_func = input_func # type: ignore
329-
if isinstance(agent, WebSurfer):
330-
novnc_port = agent.novnc_port or -1
331-
playwright_port = agent.playwright_port or -1
332274
return self.team, novnc_port, playwright_port
333275
except Exception as e:
334276
logger.error(f"Error creating team: {e}")

src/magentic_ui/backend/web/deps.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,6 @@ async def init_managers(
8282
_db_manager = DatabaseManager(engine_uri=database_uri, base_dir=app_root)
8383
_db_manager.initialize_database(auto_upgrade=settings.UPGRADE_DATABASE)
8484

85-
# init default team config
86-
await _db_manager.import_teams_from_directory(
87-
config_dir, settings.DEFAULT_USER_ID, check_exists=True
88-
)
89-
9085
# Initialize connection manager
9186
_websocket_manager = WebSocketManager(
9287
db_manager=_db_manager,

src/magentic_ui/backend/web/managers/connection.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
Run,
3131
RunStatus,
3232
Settings,
33-
SettingsConfig,
3433
TeamResult,
3534
)
3635
from ...teammanager import TeamManager
@@ -157,13 +156,7 @@ async def start_stream(
157156
assert run is not None, f"Run {run_id} not found in database"
158157
assert run.user_id is not None, f"Run {run_id} has no user ID"
159158

160-
# Get user Settings
161-
user_settings = await self._get_settings(run.user_id)
162-
env_vars = (
163-
SettingsConfig(**user_settings.config).environment # type: ignore
164-
if user_settings
165-
else None
166-
)
159+
env_vars = None
167160

168161
settings_config["memory_controller_key"] = run.user_id
169162

src/magentic_ui/backend/web/routes/settingsroute.py

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,43 @@
1111
router = APIRouter()
1212

1313

14+
def _get_or_create_settings(user_id: str, db) -> Settings:
15+
"""Get existing settings or create default ones for user_id"""
16+
response = db.get(Settings, filters={"user_id": user_id})
17+
if response.status and response.data:
18+
return response.data[0]
19+
20+
# Create default settings
21+
default_settings = Settings(user_id=user_id)
22+
upsert_response = db.upsert(default_settings)
23+
if not upsert_response.status:
24+
raise HTTPException(status_code=500, detail=upsert_response.message)
25+
26+
return upsert_response.data
27+
28+
1429
@router.get("/")
1530
async def get_settings(user_id: str, db=Depends(get_db)) -> Dict:
1631
try:
17-
response = db.get(Settings, filters={"user_id": user_id})
18-
if not response.status or not response.data:
19-
# create a default settings - let the model use its default factory
20-
default_settings = Settings(user_id=user_id)
21-
db.upsert(default_settings)
22-
response = db.get(Settings, filters={"user_id": user_id})
23-
# print(response.data[0])
24-
return {"status": True, "data": response.data[0]}
32+
settings = _get_or_create_settings(user_id, db)
33+
return {"status": True, "data": settings}
2534
except Exception as e:
2635
raise HTTPException(status_code=500, detail=str(e)) from e
2736

2837

2938
@router.put("/")
3039
async def update_settings(settings: Settings, db=Depends(get_db)) -> Dict:
31-
response = db.upsert(settings)
32-
if not response.status:
33-
raise HTTPException(status_code=400, detail=response.message)
34-
return {"status": True, "data": response.data}
40+
try:
41+
# Get existing settings to preserve the id
42+
existing = _get_or_create_settings(settings.user_id, db)
43+
settings.id = existing.id if hasattr(existing, "id") else existing["id"]
44+
45+
response = db.upsert(settings)
46+
if not response.status:
47+
raise HTTPException(status_code=400, detail=response.message)
48+
return {"status": True, "data": response.data}
49+
except Exception as e:
50+
raise HTTPException(status_code=500, detail=str(e)) from e
3551

3652

3753
@router.get("/config-info")

src/magentic_ui/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
VERSION = "0.1.1"
1+
VERSION = "0.1.2"
22
__version__ = VERSION
33
APP_NAME = "Magentic-UI"

0 commit comments

Comments
 (0)