Skip to content

Commit 911638d

Browse files
Merge pull request #177 from microsoft/psl-agent-management
feat: Centralized agents Management
2 parents 2a9c143 + eddccea commit 911638d

5 files changed

Lines changed: 441 additions & 307 deletions

File tree

src/backend/app.py

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
11
"""Create and configure the FastAPI application."""
2+
from contextlib import asynccontextmanager
3+
24
from api.api_routes import router as backend_router
35

6+
from azure.identity.aio import DefaultAzureCredential
7+
8+
from common.config.config import app_config
49
from common.logger.app_logger import AppLogger
510

611
from dotenv import load_dotenv
712

813
from fastapi import FastAPI
914
from fastapi.middleware.cors import CORSMiddleware
1015

16+
from semantic_kernel.agents.azure_ai.azure_ai_agent import AzureAIAgent # pylint: disable=E0611
17+
18+
from sql_agents.agent_manager import clear_sql_agents, set_sql_agents
19+
from sql_agents.agents.agent_config import AgentBaseConfig
20+
from sql_agents.helpers.agents_manager import SqlAgents
21+
1122
import uvicorn
1223
# from agent_services.agents_routes import router as agents_router
1324

@@ -17,10 +28,67 @@
1728
# Configure logging
1829
logger = AppLogger("app")
1930

31+
# Global variables for agents
32+
sql_agents: SqlAgents = None
33+
azure_client = None
34+
35+
36+
@asynccontextmanager
37+
async def lifespan(app: FastAPI):
38+
"""Manage application lifespan - startup and shutdown."""
39+
global sql_agents, azure_client
40+
41+
# Startup
42+
try:
43+
logger.logger.info("Initializing SQL agents...")
44+
45+
# Create Azure credentials and client
46+
creds = DefaultAzureCredential()
47+
azure_client = AzureAIAgent.create_client(
48+
credential=creds,
49+
endpoint=app_config.ai_project_endpoint
50+
)
51+
52+
# Setup agent configuration with default conversion settings
53+
agent_config = AgentBaseConfig(
54+
project_client=azure_client,
55+
sql_from="informix", # Default source dialect
56+
sql_to="tsql" # Default target dialect
57+
)
58+
59+
# Create SQL agents
60+
sql_agents = await SqlAgents.create(agent_config)
61+
62+
# Set the global agents instance
63+
set_sql_agents(sql_agents)
64+
logger.logger.info("SQL agents initialized successfully.")
65+
66+
except Exception as exc:
67+
logger.logger.error("Failed to initialize SQL agents: %s", exc)
68+
# Don't raise the exception to allow the app to start even if agents fail
69+
70+
yield # Application runs here
71+
72+
# Shutdown
73+
try:
74+
if sql_agents:
75+
logger.logger.info("Application shutting down - cleaning up SQL agents...")
76+
await sql_agents.delete_agents()
77+
logger.logger.info("SQL agents cleaned up successfully.")
78+
79+
# Clear the global agents instance
80+
await clear_sql_agents()
81+
82+
if azure_client:
83+
await azure_client.close()
84+
85+
except Exception as exc:
86+
logger.logger.error("Error during agent cleanup: %s", exc)
87+
2088

2189
def create_app() -> FastAPI:
2290
"""Create and return the FastAPI application instance."""
23-
app = FastAPI(title="Code Gen Accelerator", version="1.0.0")
91+
app = FastAPI(title="Code Gen Accelerator", version="1.0.0", lifespan=lifespan)
2492

2593
# Configure CORS
2694
app.add_middleware(

src/backend/common/services/batch_service.py

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,14 @@ async def get_file_report(self, file_id: str) -> Optional[Dict]:
6161
storage = await BlobStorageFactory.get_storage()
6262
if file_record.translated_path not in ["", None]:
6363
translated_content = await storage.get_file(file_record.translated_path)
64+
else:
65+
# If translated_path is empty, try to get translated content from logs
66+
# Look for the final success log with the translated result
67+
if logs:
68+
for log in logs:
69+
if (log.get("log_type") == "success" and log.get("agent_type") == "agents" and log.get("last_candidate")):
70+
translated_content = log["last_candidate"]
71+
break
6472
except IOError as e:
6573
self.logger.error(f"Error downloading file content: {str(e)}")
6674

@@ -79,6 +87,14 @@ async def get_file_translated(self, file: dict):
7987
storage = await BlobStorageFactory.get_storage()
8088
if file["translated_path"] not in ["", None]:
8189
translated_content = await storage.get_file(file["translated_path"])
90+
else:
91+
# If translated_path is empty, try to get translated content from logs
92+
# Look for the final success log with the translated result
93+
if "logs" in file and file["logs"]:
94+
for log in file["logs"]:
95+
if (log.get("log_type") == "success" and log.get("agent_type") == "agents" and log.get("last_candidate")):
96+
translated_content = log["last_candidate"]
97+
break
8298
except IOError as e:
8399
self.logger.error(f"Error downloading file content: {str(e)}")
84100

@@ -126,19 +142,22 @@ async def get_batch_summary(self, batch_id: str, user_id: str) -> Optional[Dict]
126142
try:
127143
logs = await self.database.get_file_logs(file["file_id"])
128144
file["logs"] = logs
129-
if file["translated_path"]:
130-
try:
131-
translated_content = await self.get_file_translated(file)
132-
file["translated_content"] = translated_content
133-
except Exception as e:
134-
self.logger.error(
135-
f"Error retrieving translated content for file {file['file_id']}: {str(e)}"
136-
)
145+
# Try to get translated content for all files, but prioritize completed files
146+
try:
147+
translated_content = await self.get_file_translated(file)
148+
file["translated_content"] = translated_content
149+
except Exception as e:
150+
self.logger.error(
151+
f"Error retrieving translated content for file {file['file_id']}: {str(e)}"
152+
)
153+
# Ensure translated_content field exists even if empty
154+
file["translated_content"] = ""
137155
except Exception as e:
138156
self.logger.error(
139157
f"Error retrieving logs for file {file['file_id']}: {str(e)}"
140158
)
141159
file["logs"] = [] # Set empty logs on error
160+
file["translated_content"] = "" # Ensure field exists
142161

143162
return {
144163
"files": files,
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""
2+
Global agent manager for SQL agents.
3+
This module manages the global SQL agents instance to avoid circular imports.
4+
"""
5+
6+
import logging
7+
from typing import Optional
8+
9+
from sql_agents.helpers.agents_manager import SqlAgents
10+
11+
logger = logging.getLogger(__name__)
12+
13+
# Global variable to store the SQL agents instance
14+
_sql_agents: Optional[SqlAgents] = None
15+
16+
17+
def set_sql_agents(agents: SqlAgents) -> None:
18+
"""Set the global SQL agents instance."""
19+
global _sql_agents
20+
_sql_agents = agents
21+
logger.info("Global SQL agents instance has been set")
22+
23+
24+
def get_sql_agents() -> Optional[SqlAgents]:
25+
"""Get the global SQL agents instance."""
26+
return _sql_agents
27+
28+
29+
async def update_agent_config(convert_from: str, convert_to: str) -> None:
30+
"""Update the global agent configuration for different SQL conversion types."""
31+
if _sql_agents and _sql_agents.agent_config:
32+
_sql_agents.agent_config.sql_from = convert_from
33+
_sql_agents.agent_config.sql_to = convert_to
34+
logger.info(f"Updated agent configuration: {convert_from} -> {convert_to}")
35+
else:
36+
logger.warning("SQL agents not initialized, cannot update configuration")
37+
38+
39+
async def clear_sql_agents() -> None:
40+
"""Clear the global SQL agents instance."""
41+
global _sql_agents
42+
await _sql_agents.delete_agents()
43+
_sql_agents = None
44+
logger.info("Global SQL agents instance has been cleared")

0 commit comments

Comments
 (0)