Skip to content

Commit d190da9

Browse files
Merge branch 'dev-v3' into hb-us-23028
2 parents 490dc9a + ddf8c44 commit d190da9

40 files changed

+1071
-616
lines changed

.github/workflows/deploy.yml

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ on:
1313
- cron: "0 11,23 * * *" # Runs at 11:00 AM and 11:00 PM GMT
1414
workflow_dispatch: #Allow manual triggering
1515
env:
16-
GPT_MIN_CAPACITY: 150
16+
GPT_MIN_CAPACITY: 1
1717
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
1818

1919
jobs:
@@ -36,7 +36,7 @@ jobs:
3636
export AZURE_TENANT_ID=${{ secrets.AZURE_TENANT_ID }}
3737
export AZURE_CLIENT_SECRET=${{ secrets.AZURE_CLIENT_SECRET }}
3838
export AZURE_SUBSCRIPTION_ID="${{ secrets.AZURE_SUBSCRIPTION_ID }}"
39-
export GPT_MIN_CAPACITY="150"
39+
export GPT_MIN_CAPACITY="1"
4040
export AZURE_REGIONS="${{ vars.AZURE_REGIONS }}"
4141
4242
chmod +x infra/scripts/checkquota.sh
@@ -140,7 +140,7 @@ jobs:
140140
backendContainerImageTag="${IMAGE_TAG}" \
141141
frontendContainerImageTag="${IMAGE_TAG}" \
142142
azureAiServiceLocation='${{ env.AZURE_LOCATION }}' \
143-
gptModelCapacity=150 \
143+
gptModelCapacity=1 \
144144
createdBy="Pipeline" \
145145
tags="{'SecurityControl':'Ignore','Purpose':'Deploying and Cleaning Up Resources for Validation','CreatedDate':'$current_date'}" \
146146
--output json
@@ -181,20 +181,9 @@ jobs:
181181
echo "SUCCESS=false" >> $GITHUB_OUTPUT
182182
fi
183183
184-
e2e-test:
185-
needs: deploy
186-
if: needs.deploy.outputs.DEPLOYMENT_SUCCESS == 'true'
187-
uses: ./.github/workflows/test-automation.yml
188-
with:
189-
MACAE_WEB_URL: ${{ needs.deploy.outputs.WEBAPP_URL }}
190-
MACAE_URL_API: ${{ needs.deploy.outputs.MACAE_URL_API }}
191-
MACAE_RG: ${{ needs.deploy.outputs.RESOURCE_GROUP_NAME }}
192-
MACAE_CONTAINER_APP: ${{ needs.deploy.outputs.CONTAINER_APP }}
193-
secrets: inherit
194-
195184
cleanup-deployment:
196185
if: always() && needs.deploy.outputs.RESOURCE_GROUP_NAME != ''
197-
needs: [deploy, e2e-test]
186+
needs: [deploy]
198187
runs-on: ubuntu-latest
199188
env:
200189
RESOURCE_GROUP_NAME: ${{ needs.deploy.outputs.RESOURCE_GROUP_NAME }}

.github/workflows/test.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ jobs:
6161
--ignore=src/backend/tests/test_config.py \
6262
--ignore=src/tests/agents/test_human_approval_manager.py \
6363
--ignore=src/backend/tests/test_team_specific_methods.py \
64-
--ignore=src/backend/tests/models/test_messages.py
64+
--ignore=src/backend/tests/models/test_messages.py \
65+
--ignore=src/backend/tests/test_otlp_tracing.py \
66+
--ignore=src/backend/tests/auth/test_auth_utils.py
6567
6668
# - name: Run tests with coverage
6769
# if: env.skip_tests == 'false'

src/backend/app_kernel.py

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,50 @@
11
# app_kernel.py
2-
import asyncio
32
import logging
4-
import os
5-
# Azure monitoring
6-
import re
7-
import uuid
8-
from typing import Dict, List, Optional
3+
4+
from contextlib import asynccontextmanager
5+
96

107
from azure.monitor.opentelemetry import configure_azure_monitor
118
from common.config.app_config import config
129
from common.models.messages_kernel import UserLanguage
10+
1311
# FastAPI imports
14-
from fastapi import FastAPI, Query, Request
12+
from fastapi import FastAPI, Request
1513
from fastapi.middleware.cors import CORSMiddleware
14+
1615
# Local imports
1716
from middleware.health_check import HealthCheckMiddleware
1817
from v3.api.router import app_v3
18+
19+
# Azure monitoring
20+
1921
# Semantic Kernel imports
20-
from v3.orchestration.orchestration_manager import OrchestrationManager
22+
from v3.config.agent_registry import agent_registry
23+
24+
25+
@asynccontextmanager
26+
async def lifespan(app: FastAPI):
27+
"""Manage FastAPI application lifecycle - startup and shutdown."""
28+
logger = logging.getLogger(__name__)
29+
30+
# Startup
31+
logger.info("🚀 Starting MACAE application...")
32+
yield
33+
34+
# Shutdown
35+
logger.info("🛑 Shutting down MACAE application...")
36+
try:
37+
# Clean up all agents from Azure AI Foundry when container stops
38+
await agent_registry.cleanup_all_agents()
39+
logger.info("✅ Agent cleanup completed successfully")
40+
41+
except ImportError as ie:
42+
logger.error(f"❌ Could not import agent_registry: {ie}")
43+
except Exception as e:
44+
logger.error(f"❌ Error during shutdown cleanup: {e}")
45+
46+
logger.info("👋 MACAE application shutdown complete")
47+
2148

2249
# Check if the Application Insights Instrumentation Key is set in the environment variables
2350
connection_string = config.APPLICATIONINSIGHTS_CONNECTION_STRING
@@ -48,7 +75,7 @@
4875
)
4976

5077
# Initialize the FastAPI app
51-
app = FastAPI()
78+
app = FastAPI(lifespan=lifespan)
5279

5380
frontend_url = config.FRONTEND_SITE_NAME
5481

@@ -104,4 +131,11 @@ async def user_browser_language_endpoint(user_language: UserLanguage, request: R
104131
if __name__ == "__main__":
105132
import uvicorn
106133

107-
uvicorn.run("app_kernel:app", host="127.0.0.1", port=8000, reload=True, log_level="info", access_log=False)
134+
uvicorn.run(
135+
"app_kernel:app",
136+
host="127.0.0.1",
137+
port=8000,
138+
reload=True,
139+
log_level="info",
140+
access_log=False,
141+
)

src/backend/common/config/app_config.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,23 @@ def __init__(self):
7676
# Optional MCP server endpoint (for local MCP server or remote)
7777
# Example: http://127.0.0.1:8000/mcp
7878
self.MCP_SERVER_ENDPOINT = self._get_optional("MCP_SERVER_ENDPOINT")
79-
self.MCP_SERVER_NAME = self._get_optional("MCP_SERVER_NAME", "MCPGreetingServer")
80-
self.MCP_SERVER_DESCRIPTION = self._get_optional("MCP_SERVER_DESCRIPTION", "MCP server with greeting and planning tools")
79+
self.MCP_SERVER_NAME = self._get_optional(
80+
"MCP_SERVER_NAME", "MCPGreetingServer"
81+
)
82+
self.MCP_SERVER_DESCRIPTION = self._get_optional(
83+
"MCP_SERVER_DESCRIPTION", "MCP server with greeting and planning tools"
84+
)
8185
self.TENANT_ID = self._get_optional("AZURE_TENANT_ID")
8286
self.CLIENT_ID = self._get_optional("AZURE_CLIENT_ID")
83-
self.AZURE_AI_SEARCH_CONNECTION_NAME = self._get_optional("AZURE_AI_SEARCH_CONNECTION_NAME")
84-
self.AZURE_AI_SEARCH_INDEX_NAME = self._get_optional("AZURE_AI_SEARCH_INDEX_NAME")
87+
self.AZURE_AI_SEARCH_CONNECTION_NAME = self._get_optional(
88+
"AZURE_AI_SEARCH_CONNECTION_NAME"
89+
)
90+
self.AZURE_AI_SEARCH_INDEX_NAME = self._get_optional(
91+
"AZURE_AI_SEARCH_INDEX_NAME"
92+
)
8593
self.AZURE_AI_SEARCH_ENDPOINT = self._get_optional("AZURE_AI_SEARCH_ENDPOINT")
8694
self.AZURE_AI_SEARCH_API_KEY = self._get_optional("AZURE_AI_SEARCH_API_KEY")
87-
# self.BING_CONNECTION_NAME = self._get_optional("BING_CONNECTION_NAME")
95+
# self.BING_CONNECTION_NAME = self._get_optional("BING_CONNECTION_NAME")
8896

8997
test_team_json = self._get_optional("TEST_TEAM_JSON")
9098

@@ -117,7 +125,7 @@ def get_azure_credential(self, client_id=None):
117125
) # CodeQL [SM05139] Okay use of DefaultAzureCredential as it is only used in development
118126
else:
119127
return ManagedIdentityCredential(client_id=client_id)
120-
128+
121129
def get_azure_credentials(self):
122130
"""Retrieve Azure credentials, either from environment variables or managed identity."""
123131
if self._azure_credentials is None:
@@ -192,7 +200,8 @@ def get_cosmos_database_client(self):
192200
try:
193201
if self._cosmos_client is None:
194202
self._cosmos_client = CosmosClient(
195-
self.COSMOSDB_ENDPOINT, credential=self.get_azure_credential(self.AZURE_CLIENT_ID)
203+
self.COSMOSDB_ENDPOINT,
204+
credential=self.get_azure_credential(self.AZURE_CLIENT_ID),
196205
)
197206

198207
if self._cosmos_database is None:

src/backend/common/database/cosmosdb.py

Lines changed: 22 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,24 @@
11
"""CosmosDB implementation of the database interface."""
22

3-
import json
4-
import logging
5-
import uuid
6-
73
import datetime
4+
import logging
85
from typing import Any, Dict, List, Optional, Type
96

10-
from azure.cosmos import PartitionKey, exceptions
7+
import v3.models.messages as messages
118
from azure.cosmos.aio import CosmosClient
129
from azure.cosmos.aio._database import DatabaseProxy
13-
from azure.cosmos.exceptions import CosmosResourceExistsError
14-
import v3.models.messages as messages
1510

16-
from common.models.messages_kernel import (
17-
AgentMessage,
18-
Plan,
19-
Step,
20-
TeamConfiguration,
21-
)
22-
from common.utils.utils_date import DateTimeEncoder
23-
24-
from .database_base import DatabaseBase
2511
from ..models.messages_kernel import (
12+
AgentMessage,
2613
AgentMessageData,
2714
BaseDataModel,
15+
DataType,
2816
Plan,
2917
Step,
30-
AgentMessage,
3118
TeamConfiguration,
32-
DataType,
3319
UserCurrentTeam,
3420
)
21+
from .database_base import DatabaseBase
3522

3623

3724
class CosmosDBClient(DatabaseBase):
@@ -189,7 +176,6 @@ async def delete_item(self, item_id: str, partition_key: str) -> None:
189176
self.logger.error("Failed to delete item from CosmosDB: %s", str(e))
190177
raise
191178

192-
193179
# Plan Operations
194180
async def add_plan(self, plan: Plan) -> None:
195181
"""Add a plan to CosmosDB."""
@@ -199,7 +185,6 @@ async def update_plan(self, plan: Plan) -> None:
199185
"""Update a plan in CosmosDB."""
200186
await self.update_item(plan)
201187

202-
203188
async def get_plan_by_plan_id(self, plan_id: str) -> Optional[Plan]:
204189
"""Retrieve a plan by plan_id."""
205190
query = "SELECT * FROM c WHERE c.id=@plan_id AND c.data_type=@data_type"
@@ -234,8 +219,9 @@ async def get_all_plans_by_team_id(self, team_id: str) -> List[Plan]:
234219
]
235220
return await self.query_items(query, parameters, Plan)
236221

237-
238-
async def get_all_plans_by_team_id_status(self, user_id: str,team_id: str, status: str) -> List[Plan]:
222+
async def get_all_plans_by_team_id_status(
223+
self, user_id: str, team_id: str, status: str
224+
) -> List[Plan]:
239225
"""Retrieve all plans for a specific team."""
240226
query = "SELECT * FROM c WHERE c.team_id=@team_id AND c.data_type=@data_type and c.user_id=@user_id and c.overall_status=@status ORDER BY c._ts DESC"
241227
parameters = [
@@ -245,6 +231,7 @@ async def get_all_plans_by_team_id_status(self, user_id: str,team_id: str, statu
245231
{"name": "@status", "value": status},
246232
]
247233
return await self.query_items(query, parameters, Plan)
234+
248235
# Step Operations
249236
async def add_step(self, step: Step) -> None:
250237
"""Add a step to CosmosDB."""
@@ -414,8 +401,6 @@ async def get_current_team(self, user_id: str) -> Optional[UserCurrentTeam]:
414401
teams = await self.query_items(query, parameters, UserCurrentTeam)
415402
return teams[0] if teams else None
416403

417-
418-
419404
async def delete_current_team(self, user_id: str) -> bool:
420405
"""Delete the current team for a user."""
421406
query = "SELECT c.id, c.session_id FROM c WHERE c.user_id=@user_id AND c.data_type=@data_type"
@@ -429,9 +414,13 @@ async def delete_current_team(self, user_id: str) -> bool:
429414
if items:
430415
async for doc in items:
431416
try:
432-
await self.container.delete_item(doc["id"], partition_key=doc["session_id"])
417+
await self.container.delete_item(
418+
doc["id"], partition_key=doc["session_id"]
419+
)
433420
except Exception as e:
434-
self.logger.warning("Failed deleting current team doc %s: %s", doc.get("id"), e)
421+
self.logger.warning(
422+
"Failed deleting current team doc %s: %s", doc.get("id"), e
423+
)
435424

436425
return True
437426

@@ -457,9 +446,13 @@ async def delete_plan_by_plan_id(self, plan_id: str) -> bool:
457446
if items:
458447
async for doc in items:
459448
try:
460-
await self.container.delete_item(doc["id"], partition_key=doc["session_id"])
449+
await self.container.delete_item(
450+
doc["id"], partition_key=doc["session_id"]
451+
)
461452
except Exception as e:
462-
self.logger.warning("Failed deleting current team doc %s: %s", doc.get("id"), e)
453+
self.logger.warning(
454+
"Failed deleting current team doc %s: %s", doc.get("id"), e
455+
)
463456

464457
return True
465458

@@ -471,7 +464,6 @@ async def update_mplan(self, mplan: messages.MPlan) -> None:
471464
"""Update a team configuration in the database."""
472465
await self.update_item(mplan)
473466

474-
475467
async def get_mplan(self, plan_id: str) -> Optional[messages.MPlan]:
476468
"""Retrieve a mplan configuration by mplan_id."""
477469
query = "SELECT * FROM c WHERE c.plan_id=@plan_id AND c.data_type=@data_type"
@@ -481,7 +473,6 @@ async def get_mplan(self, plan_id: str) -> Optional[messages.MPlan]:
481473
]
482474
results = await self.query_items(query, parameters, messages.MPlan)
483475
return results[0] if results else None
484-
485476

486477
async def add_agent_message(self, message: AgentMessageData) -> None:
487478
"""Add an agent message to the database."""
@@ -499,4 +490,4 @@ async def get_agent_messages(self, plan_id: str) -> List[AgentMessageData]:
499490
{"name": "@data_type", "value": DataType.m_plan_message},
500491
]
501492

502-
return await self.query_items(query, parameters, AgentMessageData)
493+
return await self.query_items(query, parameters, AgentMessageData)

0 commit comments

Comments
 (0)