Skip to content

Commit f4a0ef3

Browse files
committed
Add deployment_name to team configs and refactor agent ID logic
Introduces a deployment_name field to agent team JSON files and TeamConfiguration model for dynamic model selection. Refactors agent ID generation and retrieval into utility functions, updates orchestration and lifecycle logic to use these utilities, and improves error logging and RAI validation handling in API routes.
1 parent db9dd41 commit f4a0ef3

9 files changed

Lines changed: 115 additions & 58 deletions

File tree

data/agent_teams/hr.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"status": "visible",
66
"created": "",
77
"created_by": "",
8+
"deployment_name": "gpt-4.1-mini",
89
"agents": [
910
{
1011
"input_key": "",

data/agent_teams/marketing.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"status": "visible",
66
"created": "",
77
"created_by": "",
8+
"deployment_name": "gpt-4.1-mini",
89
"agents": [
910
{
1011
"input_key": "",

data/agent_teams/retail.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"status": "visible",
66
"created": "",
77
"created_by": "",
8+
"deployment_name": "gpt-4.1-mini",
89
"agents": [
910
{
1011
"input_key": "",

data/agent_teams/rfp_analysis_team.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"status": "visible",
66
"created": "",
77
"created_by": "",
8+
"deployment_name": "gpt-4.1-mini",
89
"description": "A specialized multi-agent team that analyzes RFP and contract documents to summarize content, identify potential risks, check compliance gaps, and provide action plans for contract improvement.",
910
"logo": "",
1011
"plan": "",

src/backend/common/models/messages_af.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ class TeamConfiguration(BaseDataModel):
196196
status: str
197197
created: str
198198
created_by: str
199+
deployment_name: str
199200
agents: List[TeamAgent] = Field(default_factory=list)
200201
description: str = ""
201202
logo: str = ""

src/backend/common/utils/utils_af.py

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,28 @@
1-
"""Utility functions for agent_framework-based integration and agent management (converted from agent framework )."""
1+
"""Utility functions for agent_framework-based integration and agent management."""
22

3+
from http import client
34
import logging
5+
import secrets
6+
import string
7+
from typing import Optional # <-- Add this import
8+
9+
from tomlkit import (
10+
string as toml_string,
11+
) # <-- If you need tomlkit.string elsewhere, alias it
412

513
from common.config.app_config import config
6-
# Converted import path (agent_framework version of FoundryAgentTemplate)
14+
715
from common.database.database_base import DatabaseBase
816
from common.models.messages_af import TeamConfiguration
917
from v4.common.services.team_service import TeamService
1018
from v4.config.agent_registry import agent_registry
11-
from v4.magentic_agents.foundry_agent import \
12-
FoundryAgentTemplate # formerly v4.magentic_agents.foundry_agent
19+
from v4.magentic_agents.foundry_agent import (
20+
FoundryAgentTemplate,
21+
) # formerly v4.magentic_agents.foundry_agent
1322

1423
logging.basicConfig(level=logging.INFO)
1524

25+
1626
async def find_first_available_team(team_service: TeamService, user_id: str) -> str:
1727
"""
1828
Check teams in priority order (4 to 1) and return the first available team ID.
@@ -38,7 +48,10 @@ async def find_first_available_team(team_service: TeamService, user_id: str) ->
3848
print("No teams found in priority order")
3949
return None
4050

41-
async def create_RAI_agent(team: TeamConfiguration, memory_store: DatabaseBase) -> FoundryAgentTemplate:
51+
52+
async def create_RAI_agent(
53+
team: TeamConfiguration, memory_store: DatabaseBase
54+
) -> FoundryAgentTemplate:
4255
"""Create and initialize a FoundryAgentTemplate for Responsible AI (RAI) checks."""
4356
agent_name = "RAIAgent"
4457
agent_description = "A comprehensive research assistant for integration testing"
@@ -55,7 +68,7 @@ async def create_RAI_agent(team: TeamConfiguration, memory_store: DatabaseBase)
5568
"- Is completely meaningless, incoherent, or appears to be spam\n"
5669
"Respond with 'TRUE' if the input violates any rules and should be blocked, otherwise respond with 'FALSE'."
5770
)
58-
71+
5972
model_deployment_name = config.AZURE_OPENAI_DEPLOYMENT_NAME
6073
team.team_id = "rai_team" # Use a fixed team ID for RAI agent
6174
team.name = "RAI Team"
@@ -114,7 +127,9 @@ async def _get_agent_response(agent: FoundryAgentTemplate, query: str) -> str:
114127
return "TRUE" # Default to blocking on error
115128

116129

117-
async def rai_success(description: str, team_config: TeamConfiguration, memory_store: DatabaseBase) -> bool:
130+
async def rai_success(
131+
description: str, team_config: TeamConfiguration, memory_store: DatabaseBase
132+
) -> bool:
118133
"""
119134
Run a RAI compliance check on the provided description using the RAIAgent.
120135
Returns True if content is safe (should proceed), False if it should be blocked.
@@ -148,7 +163,9 @@ async def rai_success(description: str, team_config: TeamConfiguration, memory_
148163
pass
149164

150165

151-
async def rai_validate_team_config(team_config_json: dict, team_config: TeamConfiguration, memory_store: DatabaseBase) -> tuple[bool, str]:
166+
async def rai_validate_team_config(
167+
team_config_json: dict, memory_store: DatabaseBase
168+
) -> tuple[bool, str]:
152169
"""
153170
Validate a team configuration for RAI compliance.
154171
@@ -189,7 +206,7 @@ async def rai_validate_team_config(team_config_json: dict, team_config: TeamConf
189206
combined = " ".join(text_content).strip()
190207
if not combined:
191208
return False, "Team configuration contains no readable text content."
192-
209+
team_config = TeamConfiguration(**team_config_json)
193210
if not await rai_success(combined, team_config, memory_store):
194211
return (
195212
False,
@@ -200,3 +217,37 @@ async def rai_validate_team_config(team_config_json: dict, team_config: TeamConf
200217
except Exception as e:
201218
logging.error("Error validating team configuration content: %s", e)
202219
return False, "Unable to validate team configuration content. Please try again."
220+
221+
222+
def generate_assistant_id(prefix: str = "asst_", length: int = 24) -> str:
223+
"""
224+
Generate a unique ID like 'asst_jRgR5t2U7o8nUPkNGv5HWOgV'.
225+
226+
- prefix: leading string (defaults to 'asst_')
227+
- length: number of random characters after the prefix
228+
"""
229+
# URL-safe characters similar to what OpenAI-style IDs use
230+
alphabet = string.ascii_letters + string.digits # a-zA-Z0-9
231+
232+
# cryptographically strong randomness
233+
random_part = "".join(secrets.choice(alphabet) for _ in range(length))
234+
return f"{prefix}{random_part}"
235+
236+
237+
async def get_database_team_agent_id(
238+
memory_store: DatabaseBase, team_config: TeamConfiguration, agent_name: str
239+
) -> Optional[str]:
240+
"""Retrieve existing team agent from database, if any."""
241+
agent_id = None
242+
try:
243+
currentAgent = await memory_store.get_team_agent(
244+
team_id=team_config.team_id, agent_name=agent_name
245+
)
246+
if currentAgent and currentAgent.agent_foundry_id:
247+
agent_id = currentAgent.agent_foundry_id
248+
249+
except (
250+
Exception
251+
) as ex: # Consider narrowing this to specific exceptions if possible
252+
logging.error("Failed to initialize Get database team agent: %s", ex)
253+
return agent_id

src/backend/v4/api/router.py

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -438,13 +438,14 @@ async def plan_approval(
438438
return {"status": "approval recorded"}
439439
else:
440440
logging.warning(
441-
f"No orchestration or plan found for plan_id: {human_feedback.m_plan_id}"
441+
"No orchestration or plan found for plan_id: %s",
442+
human_feedback.m_plan_id
442443
)
443444
raise HTTPException(
444445
status_code=404, detail="No active plan found for approval"
445446
)
446447
except Exception as e:
447-
logging.error(f"Error processing plan approval: {e}")
448+
logging.error("Error processing plan approval: %s", e)
448449
raise HTTPException(status_code=500, detail="Internal server error")
449450

450451

@@ -710,16 +711,7 @@ async def upload_team_config(
710711
raise HTTPException(status_code=400, detail="no user found")
711712
try:
712713
memory_store = await DatabaseFactory.get_database(user_id=user_id)
713-
user_current_team = await memory_store.get_current_team(user_id=user_id)
714-
team_id = None
715-
if user_current_team:
716-
team_id = user_current_team.team_id
717-
team = await memory_store.get_team_by_id(team_id=team_id)
718-
if not team:
719-
raise HTTPException(
720-
status_code=404,
721-
detail=f"Team configuration '{team_id}' not found or access denied",
722-
)
714+
723715
except Exception as e:
724716
raise HTTPException(
725717
status_code=400,
@@ -740,11 +732,11 @@ async def upload_team_config(
740732
except json.JSONDecodeError as e:
741733
raise HTTPException(
742734
status_code=400, detail=f"Invalid JSON format: {str(e)}"
743-
)
735+
) from e
744736

745737
# Validate content with RAI before processing
746738
if not team_id:
747-
rai_valid, rai_error = await rai_validate_team_config(json_data, team, memory_store)
739+
rai_valid, rai_error = await rai_validate_team_config(json_data, memory_store)
748740
if not rai_valid:
749741
track_event_if_configured(
750742
"Team configuration RAI validation failed",
@@ -819,7 +811,7 @@ async def upload_team_config(
819811
json_data, user_id
820812
)
821813
except ValueError as e:
822-
raise HTTPException(status_code=400, detail=str(e))
814+
raise HTTPException(status_code=400, detail=str(e)) from e
823815

824816
# Save the configuration
825817
try:
@@ -831,7 +823,7 @@ async def upload_team_config(
831823
except ValueError as e:
832824
raise HTTPException(
833825
status_code=500, detail=f"Failed to save configuration: {str(e)}"
834-
)
826+
) from e
835827

836828
track_event_if_configured(
837829
"Team configuration uploaded",
@@ -855,7 +847,7 @@ async def upload_team_config(
855847
except HTTPException:
856848
raise
857849
except Exception as e:
858-
logging.error(f"Unexpected error uploading team configuration: {str(e)}")
850+
logging.error("Unexpected error uploading team configuration: %s", str(e))
859851
raise HTTPException(status_code=500, detail="Internal server error occurred")
860852

861853

src/backend/v4/magentic_agents/common/lifecycle.py

Lines changed: 16 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,27 @@
11
from __future__ import annotations
22

33
import logging
4-
import os
54
import secrets
65
import string
76
from contextlib import AsyncExitStack
87
from typing import Any, Optional
98

10-
from agent_framework import (AggregateContextProvider, ChatAgent,
11-
ChatClientProtocol, ChatMessage,
12-
ChatMessageStoreProtocol, ChatOptions,
13-
ContextProvider, HostedMCPTool,
14-
MCPStreamableHTTPTool, Middleware, Role, ToolMode,
15-
ToolProtocol)
9+
from agent_framework import (
10+
ChatAgent,
11+
HostedMCPTool,
12+
MCPStreamableHTTPTool,
13+
)
14+
1615
# from agent_framework.azure import AzureAIAgentClient
1716
from agent_framework_azure_ai import AzureAIAgentClient
1817
from azure.ai.agents.aio import AgentsClient
1918
from azure.identity.aio import DefaultAzureCredential
2019
from common.database.database_base import DatabaseBase
2120
from common.models.messages_af import CurrentTeamAgent, TeamConfiguration
21+
from src.backend.common.utils.utils_af import (
22+
generate_assistant_id,
23+
get_database_team_agent_id,
24+
)
2225
from v4.common.services.team_service import TeamService
2326
from v4.config.agent_registry import agent_registry
2427
from v4.magentic_agents.models.agent_models import MCPConfig
@@ -147,19 +150,6 @@ def get_chat_client(self, chat_client) -> AzureAIAgentClient:
147150
extra={"agent_id": chat_client.agent_id},
148151
)
149152
return chat_client
150-
def generate_assistant_id(self, prefix: str = "asst_", length: int = 24) -> str:
151-
"""
152-
Generate a unique ID like 'asst_jRgR5t2U7o8nUPkNGv5HWOgV'.
153-
154-
- prefix: leading string (defaults to 'asst_')
155-
- length: number of random characters after the prefix
156-
"""
157-
# URL-safe characters similar to what OpenAI-style IDs use
158-
alphabet = string.ascii_letters + string.digits # a-zA-Z0-9
159-
160-
# cryptographically strong randomness
161-
random_part = "".join(secrets.choice(alphabet) for _ in range(length))
162-
return f"{prefix}{random_part}"
163153

164154
def get_agent_id(self, chat_client) -> str:
165155
"""Return the underlying agent ID."""
@@ -171,21 +161,20 @@ def get_agent_id(self, chat_client) -> str:
171161
and self._agent.chat_client.agent_id is not None
172162
):
173163
return self._agent.chat_client.agent_id # type: ignore
174-
id = self.generate_assistant_id()
164+
id = generate_assistant_id()
175165
self.logger.info("Generated new agent ID: %s", id)
176166
return id
177167

178168
async def get_database_team_agent(self) -> Optional[AzureAIAgentClient]:
179169
"""Retrieve existing team agent from database, if any."""
180170
chat_client = None
181171
try:
182-
currentAgent = await self.memory_store.get_team_agent(
183-
team_id=self.team_config.team_id, agent_name=self.agent_name
172+
agent_id = await get_database_team_agent_id(
173+
self.memory_store, self.team_config, self.agent_name
184174
)
185-
if currentAgent and currentAgent.agent_foundry_id:
186-
agent = await self.client.get_agent(
187-
agent_id=currentAgent.agent_foundry_id
188-
)
175+
176+
if agent_id:
177+
agent = await self.client.get_agent(agent_id=agent_id)
189178
if agent and agent.id is not None:
190179
chat_client = AzureAIAgentClient(
191180
project_endpoint=self.project_endpoint,

src/backend/v4/orchestration/orchestration_manager.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@
2121
from common.config.app_config import config
2222
from common.models.messages_af import TeamConfiguration
2323

24+
from src.backend.common.database.database_base import DatabaseBase
25+
from src.backend.common.utils.utils_af import (
26+
generate_assistant_id,
27+
get_database_team_agent_id,
28+
)
2429
from v4.common.services.team_service import TeamService
2530
from v4.callbacks.response_handlers import (
2631
agent_response_callback,
@@ -45,7 +50,13 @@ def __init__(self):
4550
# Orchestration construction
4651
# ---------------------------
4752
@classmethod
48-
async def init_orchestration(cls, agents: List, user_id: str | None = None):
53+
async def init_orchestration(
54+
cls,
55+
agents: List,
56+
team_config: TeamConfiguration,
57+
memory_store: DatabaseBase,
58+
user_id: str | None = None,
59+
):
4960
"""
5061
Initialize a Magentic workflow with:
5162
- Provided agents (participants)
@@ -64,16 +75,23 @@ async def init_orchestration(cls, agents: List, user_id: str | None = None):
6475

6576
# Create Azure AI Agent client for orchestration using config
6677
# This replaces AzureChatCompletion from SK
78+
agent_name = team_config.name if team_config.name else "OrchestratorAgent"
79+
db_agent_id = await get_database_team_agent_id(
80+
memory_store, team_config, agent_name
81+
)
82+
agent_id = db_agent_id or generate_assistant_id()
6783
try:
6884
chat_client = AzureAIAgentClient(
6985
project_endpoint=config.AZURE_AI_PROJECT_ENDPOINT,
70-
model_deployment_name=config.AZURE_OPENAI_DEPLOYMENT_NAME,
86+
model_deployment_name=team_config.deployment_name,
87+
agent_id=agent_id,
88+
agent_name=agent_name,
7189
async_credential=credential,
7290
)
7391

7492
cls.logger.info(
7593
"Created AzureAIAgentClient for orchestration with model '%s' at endpoint '%s'",
76-
config.AZURE_OPENAI_DEPLOYMENT_NAME,
94+
team_config.deployment_name,
7795
config.AZURE_AI_PROJECT_ENDPOINT,
7896
)
7997
except Exception as e:
@@ -150,7 +168,7 @@ async def get_current_or_new_orchestration(
150168
user_id: str,
151169
team_config: TeamConfiguration,
152170
team_switched: bool,
153-
team_service:TeamService = None,
171+
team_service: TeamService = None,
154172
):
155173
"""
156174
Return an existing workflow for the user or create a new one if:
@@ -194,7 +212,9 @@ async def get_current_or_new_orchestration(
194212
try:
195213
cls.logger.info("Initializing new orchestration for user '%s'", user_id)
196214
orchestration_config.orchestrations[user_id] = (
197-
await cls.init_orchestration(agents, user_id)
215+
await cls.init_orchestration(
216+
agents, team_config, team_service.memory_context, user_id
217+
)
198218
)
199219
except Exception as e:
200220
cls.logger.error(

0 commit comments

Comments
 (0)