Skip to content

Commit 648f734

Browse files
victordibiarysweet
andauthored
Fix component.label error in AGS frontend (#5845)
Fix issue here in this discussion - #4208 (comment) <!-- Thank you for your contribution! Please review https://microsoft.github.io/autogen/docs/Contribute before opening a pull request. --> <!-- Please add a reviewer to the assignee section when you create a PR. If you don't have the access to it, we will shortly find a reviewer and assign them to your PR. --> ## Why are these changes needed? Fix bug in AGS UI where frontend crashes because the default team config is null - update /teams endpoint to always return a default team if none is found for the user - update UI to check for team before rendering - also update run_id type to be autoincrement int (similar to team id) instead of uuid. This helps side step the migration failed errors related to UUID type when using an sqlite backend <!-- Please give a short summary of the change and the problem this solves. --> ## Related issue number <!-- For example: "Closes #1234" --> ## Checks - [ ] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally. - [ ] I've added tests (if relevant) corresponding to the changes introduced in this PR. - [ ] I've made sure all auto checks have passed. --------- Co-authored-by: Ryan Sweet <[email protected]>
1 parent 7e5c115 commit 648f734

File tree

14 files changed

+90
-59
lines changed

14 files changed

+90
-59
lines changed

python/packages/autogen-core/docs/src/user-guide/autogenstudio-user-guide/faq.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,28 @@ A: If you are running the server on a remote machine (or a local machine that fa
114114
autogenstudio ui --port 8081 --host 0.0.0.0
115115
```
116116

117+
## Q: How do I use AutoGen Studio with a different database?
118+
119+
A: By default, AutoGen Studio uses SQLite as the database. However, it uses the SQLModel library, which supports multiple database backends. You can use any database supported by SQLModel, such as PostgreSQL or MySQL. To use a different database, you need to specify the connection string for the database using the `--database-uri` argument when running the application. Example connection strings include:
120+
121+
- SQLite: `sqlite:///database.sqlite`
122+
- PostgreSQL: `postgresql+psycopg://user:password@localhost/dbname`
123+
- MySQL: `mysql+pymysql://user:password@localhost/dbname`
124+
- AzureSQL: `mssql+pyodbc:///?odbc_connect=DRIVER%3D%7BODBC+Driver+17+for+SQL+Server%7D%3BSERVER%3Dtcp%3Aservername.database.windows.net%2C1433%3BDATABASE%3Ddatabasename%3BUID%3Dusername%3BPWD%3Dpassword123%3BEncrypt%3Dyes%3BTrustServerCertificate%3Dno%3BConnection+Timeout%3D30%3B`
125+
126+
You can then run the application with the specified database URI. For example, to use PostgreSQL, you can run the following command:
127+
128+
```bash
129+
autogenstudio ui --database-uri postgresql+psycopg://user:password@localhost/dbname
130+
```
131+
132+
> **Note:** Make sure to install the appropriate database drivers for your chosen database:
133+
>
134+
> - PostgreSQL: `pip install psycopg2` or `pip install psycopg2-binary`
135+
> - MySQL: `pip install pymysql`
136+
> - SQL Server/Azure SQL: `pip install pyodbc`
137+
> - Oracle: `pip install cx_oracle`
138+
117139
## Q: Can I export my agent workflows for use in a python app?
118140

119141
Yes. In the Team Builder view, you select a team and download its specification. This file can be imported in a python application using the `TeamManager` class. For example:

python/packages/autogen-studio/autogenstudio/database/schema_manager.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,10 +193,12 @@ def run_migrations_online() -> None:
193193
poolclass=pool.NullPool,
194194
)
195195
with connectable.connect() as connection:
196+
is_sqlite = connection.dialect.name == "sqlite"
196197
context.configure(
197198
connection=connection,
198199
target_metadata=target_metadata,
199200
compare_type=True
201+
render_as_batch=is_sqlite,
200202
)
201203
with context.begin_transaction():
202204
context.run_migrations()
@@ -213,10 +215,11 @@ def _generate_alembic_ini_content(self) -> str:
213215
"""
214216
Generates content for alembic.ini file.
215217
"""
218+
engine_url = str(self.engine.url).replace("%", "%%")
216219
return f"""
217220
[alembic]
218221
script_location = {self.alembic_dir}
219-
sqlalchemy.url = {self.engine.url}
222+
sqlalchemy.url = {engine_url}
220223
221224
[loggers]
222225
keys = root,sqlalchemy,alembic

python/packages/autogen-studio/autogenstudio/datamodel/db.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@
33
from datetime import datetime
44
from enum import Enum
55
from typing import List, Optional, Union
6-
from uuid import UUID, uuid4
76

87
from autogen_core import ComponentModel
98
from pydantic import ConfigDict
10-
from sqlalchemy import UUID as SQLAlchemyUUID
119
from sqlalchemy import ForeignKey, Integer, String
1210
from sqlmodel import JSON, Column, DateTime, Field, SQLModel, func
1311

@@ -45,11 +43,9 @@ class Message(SQLModel, table=True):
4543
version: Optional[str] = "0.0.1"
4644
config: Union[MessageConfig, dict] = Field(default_factory=MessageConfig, sa_column=Column(JSON))
4745
session_id: Optional[int] = Field(
48-
default=None, sa_column=Column(Integer, ForeignKey("session.id", ondelete="CASCADE"))
49-
)
50-
run_id: Optional[UUID] = Field(
51-
default=None, sa_column=Column(SQLAlchemyUUID, ForeignKey("run.id", ondelete="CASCADE"))
46+
default=None, sa_column=Column(Integer, ForeignKey("session.id", ondelete="NO ACTION"))
5247
)
48+
run_id: Optional[int] = Field(default=None, sa_column=Column(Integer, ForeignKey("run.id", ondelete="CASCADE")))
5349

5450
message_meta: Optional[Union[MessageMeta, dict]] = Field(default={}, sa_column=Column(JSON))
5551

@@ -84,7 +80,7 @@ class Run(SQLModel, table=True):
8480

8581
__table_args__ = {"sqlite_autoincrement": True}
8682

87-
id: UUID = Field(default_factory=uuid4, sa_column=Column(SQLAlchemyUUID, primary_key=True, index=True, unique=True))
83+
id: Optional[int] = Field(default=None, primary_key=True)
8884
created_at: datetime = Field(
8985
default_factory=datetime.now, sa_column=Column(DateTime(timezone=True), server_default=func.now())
9086
)
@@ -106,7 +102,7 @@ class Run(SQLModel, table=True):
106102
version: Optional[str] = "0.0.1"
107103
messages: Union[List[Message], List[dict]] = Field(default_factory=list, sa_column=Column(JSON))
108104

109-
model_config = ConfigDict(json_encoders={UUID: str, datetime: lambda v: v.isoformat()})
105+
model_config = ConfigDict(json_encoders={datetime: lambda v: v.isoformat()})
110106
user_id: Optional[str] = None
111107

112108

@@ -125,7 +121,7 @@ class Gallery(SQLModel, table=True):
125121
version: Optional[str] = "0.0.1"
126122
config: Union[GalleryConfig, dict] = Field(default_factory=GalleryConfig, sa_column=Column(JSON))
127123

128-
model_config = ConfigDict(json_encoders={datetime: lambda v: v.isoformat(), UUID: str})
124+
model_config = ConfigDict(json_encoders={datetime: lambda v: v.isoformat()})
129125

130126

131127
class Settings(SQLModel, table=True):

python/packages/autogen-studio/autogenstudio/teammanager/teammanager.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,8 @@ async def run_stream(
129129
yield event
130130
finally:
131131
# Cleanup - remove our handler
132-
logger.handlers.remove(llm_event_logger)
132+
if llm_event_logger in logger.handlers:
133+
logger.handlers.remove(llm_event_logger)
133134

134135
# Ensure cleanup happens
135136
if team and hasattr(team, "_participants"):

python/packages/autogen-studio/autogenstudio/web/managers/connection.py

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import traceback
44
from datetime import datetime, timezone
55
from typing import Any, Callable, Dict, Optional, Union
6-
from uuid import UUID
76

87
from autogen_agentchat.base._task import TaskResult
98
from autogen_agentchat.messages import (
@@ -42,11 +41,11 @@ class WebSocketManager:
4241

4342
def __init__(self, db_manager: DatabaseManager):
4443
self.db_manager = db_manager
45-
self._connections: Dict[UUID, WebSocket] = {}
46-
self._cancellation_tokens: Dict[UUID, CancellationToken] = {}
44+
self._connections: Dict[int, WebSocket] = {}
45+
self._cancellation_tokens: Dict[int, CancellationToken] = {}
4746
# Track explicitly closed connections
48-
self._closed_connections: set[UUID] = set()
49-
self._input_responses: Dict[UUID, asyncio.Queue] = {}
47+
self._closed_connections: set[int] = set()
48+
self._input_responses: Dict[int, asyncio.Queue] = {}
5049

5150
self._cancel_message = TeamResult(
5251
task_result=TaskResult(
@@ -63,7 +62,7 @@ def _get_stop_message(self, reason: str) -> dict:
6362
duration=0,
6463
).model_dump()
6564

66-
async def connect(self, websocket: WebSocket, run_id: UUID) -> bool:
65+
async def connect(self, websocket: WebSocket, run_id: int) -> bool:
6766
try:
6867
await websocket.accept()
6968
self._connections[run_id] = websocket
@@ -80,7 +79,7 @@ async def connect(self, websocket: WebSocket, run_id: UUID) -> bool:
8079
logger.error(f"Connection error for run {run_id}: {e}")
8180
return False
8281

83-
async def start_stream(self, run_id: UUID, task: str, team_config: dict) -> None:
82+
async def start_stream(self, run_id: int, task: str, team_config: dict) -> None:
8483
"""Start streaming task execution with proper run management"""
8584
if run_id not in self._connections or run_id in self._closed_connections:
8685
raise ValueError(f"No active connection for run {run_id}")
@@ -161,7 +160,7 @@ async def start_stream(self, run_id: UUID, task: str, team_config: dict) -> None
161160
finally:
162161
self._cancellation_tokens.pop(run_id, None)
163162

164-
async def _save_message(self, run_id: UUID, message: Union[AgentEvent | ChatMessage, ChatMessage]) -> None:
163+
async def _save_message(self, run_id: int, message: Union[AgentEvent | ChatMessage, ChatMessage]) -> None:
165164
"""Save a message to the database"""
166165

167166
run = await self._get_run(run_id)
@@ -175,7 +174,7 @@ async def _save_message(self, run_id: UUID, message: Union[AgentEvent | ChatMess
175174
self.db_manager.upsert(db_message)
176175

177176
async def _update_run(
178-
self, run_id: UUID, status: RunStatus, team_result: Optional[dict] = None, error: Optional[str] = None
177+
self, run_id: int, status: RunStatus, team_result: Optional[dict] = None, error: Optional[str] = None
179178
) -> None:
180179
"""Update run status and result"""
181180
run = await self._get_run(run_id)
@@ -187,7 +186,7 @@ async def _update_run(
187186
run.error_message = error
188187
self.db_manager.upsert(run)
189188

190-
def create_input_func(self, run_id: UUID) -> Callable:
189+
def create_input_func(self, run_id: int) -> Callable:
191190
"""Creates an input function for a specific run"""
192191

193192
async def input_handler(prompt: str = "", cancellation_token: Optional[CancellationToken] = None) -> str:
@@ -216,14 +215,14 @@ async def input_handler(prompt: str = "", cancellation_token: Optional[Cancellat
216215

217216
return input_handler
218217

219-
async def handle_input_response(self, run_id: UUID, response: str) -> None:
218+
async def handle_input_response(self, run_id: int, response: str) -> None:
220219
"""Handle input response from client"""
221220
if run_id in self._input_responses:
222221
await self._input_responses[run_id].put(response)
223222
else:
224223
logger.warning(f"Received input response for inactive run {run_id}")
225224

226-
async def stop_run(self, run_id: UUID, reason: str) -> None:
225+
async def stop_run(self, run_id: int, reason: str) -> None:
227226
if run_id in self._cancellation_tokens:
228227
logger.info(f"Stopping run {run_id}")
229228

@@ -253,7 +252,7 @@ async def stop_run(self, run_id: UUID, reason: str) -> None:
253252
# We might want to force disconnect here if db update failed
254253
# await self.disconnect(run_id) # Optional
255254

256-
async def disconnect(self, run_id: UUID) -> None:
255+
async def disconnect(self, run_id: int) -> None:
257256
"""Clean up connection and associated resources"""
258257
logger.info(f"Disconnecting run {run_id}")
259258

@@ -268,11 +267,11 @@ async def disconnect(self, run_id: UUID) -> None:
268267
self._cancellation_tokens.pop(run_id, None)
269268
self._input_responses.pop(run_id, None)
270269

271-
async def _send_message(self, run_id: UUID, message: dict) -> None:
270+
async def _send_message(self, run_id: int, message: dict) -> None:
272271
"""Send a message through the WebSocket with connection state checking
273272
274273
Args:
275-
run_id: UUID of the run
274+
run_id: id of the run
276275
message: Message dictionary to send
277276
"""
278277
if run_id in self._closed_connections:
@@ -292,7 +291,7 @@ async def _send_message(self, run_id: UUID, message: dict) -> None:
292291
await self._update_run_status(run_id, RunStatus.ERROR, str(e))
293292
await self.disconnect(run_id)
294293

295-
async def _handle_stream_error(self, run_id: UUID, error: Exception) -> None:
294+
async def _handle_stream_error(self, run_id: int, error: Exception) -> None:
296295
"""Handle stream errors with proper run updates"""
297296
if run_id not in self._closed_connections:
298297
error_result = TeamResult(
@@ -366,11 +365,11 @@ def _format_message(self, message: Any) -> Optional[dict]:
366365
logger.error(f"Message formatting error: {e}")
367366
return None
368367

369-
async def _get_run(self, run_id: UUID) -> Optional[Run]:
368+
async def _get_run(self, run_id: int) -> Optional[Run]:
370369
"""Get run from database
371370
372371
Args:
373-
run_id: UUID of the run to retrieve
372+
run_id: id of the run to retrieve
374373
375374
Returns:
376375
Optional[Run]: Run object if found, None otherwise
@@ -388,11 +387,11 @@ async def _get_settings(self, user_id: str) -> Optional[Settings]:
388387
response = self.db_manager.get(filters={"user_id": user_id}, model_class=Settings, return_json=False)
389388
return response.data[0] if response.status and response.data else None
390389

391-
async def _update_run_status(self, run_id: UUID, status: RunStatus, error: Optional[str] = None) -> None:
390+
async def _update_run_status(self, run_id: int, status: RunStatus, error: Optional[str] = None) -> None:
392391
"""Update run status in database
393392
394393
Args:
395-
run_id: UUID of the run to update
394+
run_id: id of the run to update
396395
status: New status to set
397396
error: Optional error message
398397
"""
@@ -451,11 +450,11 @@ async def cleanup(self) -> None:
451450
self._input_responses.clear()
452451

453452
@property
454-
def active_connections(self) -> set[UUID]:
453+
def active_connections(self) -> set[int]:
455454
"""Get set of active run IDs"""
456455
return set(self._connections.keys()) - self._closed_connections
457456

458457
@property
459-
def active_runs(self) -> set[UUID]:
458+
def active_runs(self) -> set[int]:
460459
"""Get set of runs with active cancellation tokens"""
461460
return set(self._cancellation_tokens.keys())

python/packages/autogen-studio/autogenstudio/web/routes/runs.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# /api/runs routes
22
from typing import Dict
3-
from uuid import UUID
43

54
from fastapi import APIRouter, Body, Depends, HTTPException
65
from pydantic import BaseModel
@@ -40,7 +39,7 @@ async def create_run(
4039
),
4140
return_json=False,
4241
)
43-
return {"status": run.status, "data": {"run_id": str(run.data.id)}}
42+
return {"status": run.status, "data": {"run_id": run.data.id}}
4443
except Exception as e:
4544
raise HTTPException(status_code=500, detail=str(e)) from e
4645

@@ -49,7 +48,7 @@ async def create_run(
4948

5049

5150
@router.get("/{run_id}")
52-
async def get_run(run_id: UUID, db=Depends(get_db)) -> Dict:
51+
async def get_run(run_id: int, db=Depends(get_db)) -> Dict:
5352
"""Get run details including task and result"""
5453
run = db.get(Run, filters={"id": run_id}, return_json=False)
5554
if not run.status or not run.data:
@@ -59,7 +58,7 @@ async def get_run(run_id: UUID, db=Depends(get_db)) -> Dict:
5958

6059

6160
@router.get("/{run_id}/messages")
62-
async def get_run_messages(run_id: UUID, db=Depends(get_db)) -> Dict:
61+
async def get_run_messages(run_id: int, db=Depends(get_db)) -> Dict:
6362
"""Get all messages for a run"""
6463
messages = db.get(Message, filters={"run_id": run_id}, order="created_at asc", return_json=False)
6564

python/packages/autogen-studio/autogenstudio/web/routes/teams.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from fastapi import APIRouter, Depends, HTTPException
55

66
from ...datamodel import Team
7+
from ...gallery.builder import create_default_gallery
78
from ..deps import get_db
89

910
router = APIRouter()
@@ -13,6 +14,13 @@
1314
async def list_teams(user_id: str, db=Depends(get_db)) -> Dict:
1415
"""List all teams for a user"""
1516
response = db.get(Team, filters={"user_id": user_id})
17+
if not response.data or len(response.data) == 0:
18+
default_gallery = create_default_gallery()
19+
default_team = Team(user_id=user_id, component=default_gallery.components.teams[0].model_dump())
20+
21+
db.upsert(default_team)
22+
response = db.get(Team, filters={"user_id": user_id})
23+
1624
return {"status": True, "data": response.data}
1725

1826

python/packages/autogen-studio/autogenstudio/web/routes/ws.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import asyncio
33
import json
44
from datetime import datetime
5-
from uuid import UUID
65

76
from fastapi import APIRouter, Depends, WebSocket, WebSocketDisconnect
87
from loguru import logger
@@ -17,7 +16,7 @@
1716
@router.websocket("/runs/{run_id}")
1817
async def run_websocket(
1918
websocket: WebSocket,
20-
run_id: UUID,
19+
run_id: int,
2120
ws_manager: WebSocketManager = Depends(get_websocket_manager),
2221
db=Depends(get_db),
2322
):

python/packages/autogen-studio/frontend/src/components/types/datamodel.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ export interface DBModel {
277277
export interface Message extends DBModel {
278278
config: AgentMessageConfig;
279279
session_id: number;
280-
run_id: string;
280+
run_id: number;
281281
}
282282

283283
export interface Team extends DBModel {
@@ -321,7 +321,7 @@ export interface TeamResult {
321321
}
322322

323323
export interface Run {
324-
id: string;
324+
id: number;
325325
created_at: string;
326326
updated_at?: string;
327327
status: RunStatus;

0 commit comments

Comments
 (0)