Skip to content

Commit 7f0caec

Browse files
committed
RC changes
1 parent a217d27 commit 7f0caec

File tree

27 files changed

+431
-506
lines changed

27 files changed

+431
-506
lines changed

src/backend/common/database/database_base.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
from enum import Enum
55
from typing import Dict, List, Optional
66

7-
from common.logger.app_logger import AppLogger
8-
from common.models.api import AgentType, BatchRecord, FileRecord, LogType, ProcessStatus
97
from semantic_kernel.contents import AuthorRole
108

9+
from common.logger.app_logger import AppLogger
10+
from common.models.api import BatchRecord, FileRecord, LogType, ProcessStatus
11+
from sql_agents.helpers.models import AgentType
12+
1113

1214
class DatabaseBase(ABC):
1315
"""Abstract base class for database operations"""

src/backend/common/models/api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from __future__ import annotations
22

33
import json
4+
import logging
45
from datetime import datetime
56
from enum import Enum
6-
import logging
77
from typing import Dict, List
88
from uuid import UUID
99

src/backend/sql_agents/agent_base.py

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@
88
ResponseFormatJsonSchema,
99
ResponseFormatJsonSchemaType,
1010
)
11-
from common.models.api import AgentType
1211
from semantic_kernel.agents.azure_ai.azure_ai_agent import AzureAIAgent
1312
from semantic_kernel.functions import KernelArguments
13+
1414
from sql_agents.agent_config import AgentBaseConfig
15+
from sql_agents.helpers.models import AgentType
1516
from sql_agents.helpers.utils import get_prompt
1617

1718
# Type variable for response models
@@ -45,8 +46,8 @@ def __init__(
4546

4647
@property
4748
@abstractmethod
48-
def response_schema(self) -> type:
49-
"""Get the response schema for this agent."""
49+
def response_object(self) -> type:
50+
"""Get the response object for this agent."""
5051
pass
5152

5253
@property
@@ -105,20 +106,24 @@ async def setup(self) -> AzureAIAgent:
105106

106107
kernel_args = self.get_kernel_arguments()
107108

108-
# Define an agent on the Azure AI agent service
109-
agent_definition = await self.config.ai_project_client.agents.create_agent(
110-
model=_deployment_name,
111-
name=_name,
112-
instructions=template_content,
113-
temperature=self.temperature,
114-
response_format=ResponseFormatJsonSchemaType(
115-
json_schema=ResponseFormatJsonSchema(
116-
name=self.response_schema.__name__,
117-
description=f"respond with {self.response_schema.__name__.lower()}",
118-
schema=self.response_schema.model_json_schema(),
119-
)
120-
),
121-
)
109+
try:
110+
# Define an agent on the Azure AI agent service
111+
agent_definition = await self.config.ai_project_client.agents.create_agent(
112+
model=_deployment_name,
113+
name=_name,
114+
instructions=template_content,
115+
temperature=self.temperature,
116+
response_format=ResponseFormatJsonSchemaType(
117+
json_schema=ResponseFormatJsonSchema(
118+
name=self.response_object.__name__,
119+
description=f"respond with {self.response_object.__name__.lower()}",
120+
schema=self.response_object.model_json_schema(),
121+
)
122+
),
123+
)
124+
except Exception as exc:
125+
logger.error("Error creating agent definition: %s", exc)
126+
# Set the agent definition with the response format
122127

123128
# Create a Semantic Kernel agent based on the agent definition
124129
agent_kwargs = {

src/backend/sql_agents/agent_config.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
from enum import Enum
1515

1616
from azure.ai.projects.aio import AIProjectClient
17-
from common.models.api import AgentType
17+
18+
from sql_agents.helpers.models import AgentType
1819

1920

2021
class AgentBaseConfig:

src/backend/sql_agents/agent_factory.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
import logging
44
from typing import Any, Dict, Optional, Type, TypeVar
55

6-
from common.models.api import AgentType
76
from semantic_kernel.agents.azure_ai.azure_ai_agent import AzureAIAgent
7+
88
from sql_agents.agent_base import BaseSQLAgent
99
from sql_agents.agent_config import AgentBaseConfig
1010
from sql_agents.fixer.agent import FixerAgent
11+
from sql_agents.helpers.models import AgentType
1112
from sql_agents.migrator.agent import MigratorAgent
1213
from sql_agents.picker.agent import PickerAgent
1314
from sql_agents.semantic_verifier.agent import SemanticVerifierAgent
@@ -62,8 +63,16 @@ async def create_agent(
6263
"temperature": temperature,
6364
**kwargs,
6465
}
65-
66-
agent = agent_class(**params)
66+
try:
67+
agent = agent_class(**params)
68+
except TypeError as e:
69+
logger.error(
70+
"Error creating agent of type %s with parameters %s: %s",
71+
agent_type,
72+
params,
73+
e,
74+
)
75+
raise
6776
return await agent.setup()
6877

6978
@classmethod

src/backend/sql_agents/fixer/agent.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
import logging
44

5-
from common.models.api import AgentType
65
from sql_agents.agent_base import BaseSQLAgent
76
from sql_agents.fixer.response import FixerResponse
7+
from sql_agents.helpers.models import AgentType
88

99
logger = logging.getLogger(__name__)
1010
logger.setLevel(logging.DEBUG)
@@ -14,7 +14,7 @@ class FixerAgent(BaseSQLAgent[FixerResponse]):
1414
"""Fixer agent for correcting SQL syntax errors."""
1515

1616
@property
17-
def response_schema(self) -> type:
17+
def response_object(self) -> type:
1818
"""Get the response schema for the fixer agent."""
1919
return FixerResponse
2020

src/backend/sql_agents/fixer/setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22

33
import logging
44

5-
from common.models.api import AgentType
65
from semantic_kernel.agents.azure_ai.azure_ai_agent import AzureAIAgent
6+
77
from sql_agents.agent_config import AgentBaseConfig
88
from sql_agents.agent_factory import SQLAgentFactory
9+
from sql_agents.helpers.models import AgentType
910

1011
logger = logging.getLogger(__name__)
1112
logger.setLevel(logging.DEBUG)
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import json
2+
import logging
3+
4+
from semantic_kernel.agents import AzureAIAgent # pylint: disable=E0611
5+
6+
from sql_agents.agent_config import AgentBaseConfig
7+
from sql_agents.fixer.setup import setup_fixer_agent
8+
from sql_agents.helpers.models import AgentType
9+
from sql_agents.migrator.setup import setup_migrator_agent
10+
from sql_agents.picker.setup import setup_picker_agent
11+
from sql_agents.semantic_verifier.setup import setup_semantic_verifier_agent
12+
from sql_agents.syntax_checker.setup import setup_syntax_checker_agent
13+
14+
logger = logging.getLogger(__name__)
15+
logger.setLevel(logging.DEBUG)
16+
17+
18+
class SqlAgents:
19+
"""Class to setup the SQL agents for migration."""
20+
21+
# List of agents in the solution
22+
agent_fixer: AzureAIAgent = None
23+
agent_migrator: AzureAIAgent = None
24+
agent_picker: AzureAIAgent = None
25+
agent_syntax_checker: AzureAIAgent = None
26+
agent_semantic_verifier: AzureAIAgent = None
27+
# selection_function = None
28+
# termination_function = None
29+
agent_config: AgentBaseConfig = None
30+
31+
def __init__(self):
32+
pass
33+
34+
@classmethod
35+
async def create(cls, config: AgentBaseConfig):
36+
"""Create the SQL agents for migration.
37+
Required as init cannot be async"""
38+
self = cls() # Create an instance
39+
try:
40+
self.agent_config = config
41+
self.agent_fixer = await setup_fixer_agent(config)
42+
self.agent_migrator = await setup_migrator_agent(config)
43+
self.agent_picker = await setup_picker_agent(config)
44+
self.agent_syntax_checker = await setup_syntax_checker_agent(config)
45+
self.agent_semantic_verifier = await setup_semantic_verifier_agent(config)
46+
except ValueError as exc:
47+
logger.error("Error setting up agents.")
48+
raise exc
49+
50+
return self
51+
52+
@property
53+
def agents(self):
54+
"""Return a list of the agents."""
55+
return [
56+
self.agent_migrator,
57+
self.agent_picker,
58+
self.agent_syntax_checker,
59+
self.agent_fixer,
60+
self.agent_semantic_verifier,
61+
]
62+
63+
@property
64+
def idx_agents(self):
65+
"""Return a list of the main agents."""
66+
return {
67+
AgentType.MIGRATOR: self.agent_migrator,
68+
AgentType.PICKER: self.agent_picker,
69+
AgentType.SYNTAX_CHECKER: self.agent_syntax_checker,
70+
AgentType.FIXER: self.agent_fixer,
71+
AgentType.SEMANTIC_VERIFIER: self.agent_semantic_verifier,
72+
}
73+
74+
async def delete_agents(self):
75+
"""cleans up the agents from Azure Foundry"""
76+
try:
77+
for agent in self.agents:
78+
await self.agent_config.ai_project_client.agents.delete_agent(agent.id)
79+
except Exception as exc:
80+
logger.error("Error deleting agents: %s", exc)
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
"""Manages all agent communication and selection strategies for the SQL agents."""
2+
3+
from semantic_kernel.agents import AgentGroupChat # pylint: disable=E0611
4+
from semantic_kernel.agents.azure_ai.azure_ai_agent import AzureAIAgent
5+
from semantic_kernel.agents.strategies import (
6+
SequentialSelectionStrategy,
7+
TerminationStrategy,
8+
)
9+
10+
from sql_agents.fixer.response import FixerResponse
11+
from sql_agents.helpers.models import AgentType
12+
from sql_agents.migrator.response import MigratorResponse
13+
from sql_agents.semantic_verifier.response import SemanticVerifierResponse
14+
from sql_agents.syntax_checker.response import SyntaxCheckerResponse
15+
16+
17+
class CommsManager:
18+
"""Manages all agent communication and selection strategies for the SQL agents."""
19+
20+
group_chat: AgentGroupChat = None
21+
22+
class SelectionStrategy(SequentialSelectionStrategy):
23+
"""A strategy for determining which agent should take the next turn in the chat."""
24+
25+
# Select the next agent that should take the next turn in the chat
26+
async def select_agent(self, agents, history):
27+
""" "Check which agent should take the next turn in the chat."""
28+
29+
match history[-1].name:
30+
case AgentType.MIGRATOR.value:
31+
# The Migrator should go first
32+
agent_name = AgentType.PICKER.value
33+
return next(
34+
(agent for agent in agents if agent.name == agent_name), None
35+
)
36+
# The Incident Manager should go after the User or the Devops Assistant
37+
case AgentType.PICKER.value:
38+
agent_name = AgentType.SYNTAX_CHECKER.value
39+
return next(
40+
(agent for agent in agents if agent.name == agent_name), None
41+
)
42+
case AgentType.SYNTAX_CHECKER.value:
43+
agent_name = AgentType.FIXER.value
44+
return next(
45+
(agent for agent in agents if agent.name == agent_name),
46+
None,
47+
)
48+
case AgentType.FIXER.value:
49+
# The Fixer should always go after the Syntax Checker
50+
agent_name = AgentType.SYNTAX_CHECKER.value
51+
return next(
52+
(agent for agent in agents if agent.name == agent_name), None
53+
)
54+
case "candidate":
55+
# The candidate message is created in the orchestration loop to pass the
56+
# candidate and source sql queries to the Semantic Verifier
57+
# It is created when the Syntax Checker returns an empty list of errors
58+
agent_name = AgentType.SEMANTIC_VERIFIER.value
59+
return next(
60+
(agent for agent in agents if agent.name == agent_name),
61+
None,
62+
)
63+
case _:
64+
# Start run with this one - no history
65+
return next(
66+
(
67+
agent
68+
for agent in agents
69+
if agent.name == AgentType.MIGRATOR.value
70+
),
71+
None,
72+
)
73+
74+
# class for termination strategy
75+
class ApprovalTerminationStrategy(TerminationStrategy):
76+
"""
77+
A strategy for determining when an agent should terminate.
78+
This, combined with the maximum_iterations setting on the group chat, determines
79+
when the agents are finished processing a file when there are no errors.
80+
"""
81+
82+
async def should_agent_terminate(self, agent, history):
83+
"""Check if the agent should terminate."""
84+
# May need to convert to models to get usable content using history[-1].name
85+
terminate: bool = False
86+
lower_case_hist: str = history[-1].content.lower()
87+
match history[-1].name:
88+
case AgentType.MIGRATOR.value:
89+
response = MigratorResponse.model_validate_json(
90+
lower_case_hist or ""
91+
)
92+
if (
93+
response.input_error is not None
94+
or response.rai_error is not None
95+
):
96+
terminate = True
97+
case AgentType.SEMANTIC_VERIFIER.value:
98+
# Always terminate after the Semantic Verifier runs
99+
terminate = True
100+
case _:
101+
# If the agent is not the Migrator or Semantic Verifier, don't terminate
102+
# Note that the Syntax Checker and Fixer loop are only terminated by correct SQL
103+
# or by iterations exceeding the max_iterations setting
104+
pass
105+
106+
return terminate
107+
108+
def __init__(self, agent_dict):
109+
"""Initialize the CommsManager and agent_chat with the given agents."""
110+
self.group_chat = AgentGroupChat(
111+
agents=agent_dict.values(),
112+
termination_strategy=self.ApprovalTerminationStrategy(
113+
agents=[
114+
agent_dict[AgentType.MIGRATOR],
115+
agent_dict[AgentType.SEMANTIC_VERIFIER],
116+
],
117+
maximum_iterations=10,
118+
automatic_reset=True,
119+
),
120+
selection_strategy=self.SelectionStrategy(agents=agent_dict.values()),
121+
)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"""Models for SQL agents."""
2+
3+
from enum import Enum
4+
5+
6+
class AgentType(Enum):
7+
"""Agent types."""
8+
9+
MIGRATOR = "migrator"
10+
FIXER = "fixer"
11+
PICKER = "picker"
12+
SEMANTIC_VERIFIER = "semantic_verifier"
13+
SYNTAX_CHECKER = "syntax_checker"
14+
SELECTION = "selection"
15+
TERMINATION = "termination"
16+
HUMAN = "human"
17+
ALL = "agents" # For all agents
18+
19+
def __new__(cls, value):
20+
# If value is a string, normalize it to lowercase
21+
if isinstance(value, str):
22+
value = value.lower()
23+
obj = object.__new__(cls)
24+
obj._value_ = value
25+
return obj
26+
27+
@classmethod
28+
def _missing_(cls, value):
29+
return cls.ALL

0 commit comments

Comments
 (0)