Skip to content

Commit 7a177fd

Browse files
committed
Merge branch 'feature/azd-semantickernel' of https://github.com/microsoft/Multi-Agent-Custom-Automation-Engine-Solution-Accelerator into feature/azd-semantickernel
2 parents de2fbc6 + 8449cbb commit 7a177fd

File tree

8 files changed

+349
-300
lines changed

8 files changed

+349
-300
lines changed

src/backend/app_kernel.py

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -35,20 +35,20 @@
3535
ActionRequest,
3636
ActionResponse,
3737
)
38-
from utils_kernel import initialize_runtime_and_context, get_agents, retrieve_all_agent_tools, rai_success
38+
from utils_kernel import initialize_runtime_and_context, get_agents, rai_success
3939
from event_utils import track_event_if_configured
4040
from models.agent_types import AgentType
4141
from kernel_agents.agent_factory import AgentFactory
4242

43-
# Check if the Application Insights Instrumentation Key is set in the environment variables
44-
instrumentation_key = os.getenv("APPLICATIONINSIGHTS_INSTRUMENTATION_KEY")
45-
if instrumentation_key:
46-
# Configure Application Insights if the Instrumentation Key is found
47-
configure_azure_monitor(connection_string=instrumentation_key)
48-
logging.info("Application Insights configured with the provided Instrumentation Key")
49-
else:
50-
# Log a warning if the Instrumentation Key is not found
51-
logging.warning("No Application Insights Instrumentation Key found. Skipping configuration")
43+
# # Check if the Application Insights Instrumentation Key is set in the environment variables
44+
# instrumentation_key = os.getenv("APPLICATIONINSIGHTS_INSTRUMENTATION_KEY")
45+
# if instrumentation_key:
46+
# # Configure Application Insights if the Instrumentation Key is found
47+
# configure_azure_monitor(connection_string=instrumentation_key)
48+
# logging.info("Application Insights configured with the provided Instrumentation Key")
49+
# else:
50+
# # Log a warning if the Instrumentation Key is not found
51+
# logging.warning("No Application Insights Instrumentation Key found. Skipping configuration")
5252

5353
# Configure logging
5454
logging.basicConfig(level=logging.INFO)
@@ -59,10 +59,10 @@
5959
)
6060
logging.getLogger("azure.identity.aio._internal").setLevel(logging.WARNING)
6161

62-
# Suppress info logs from OpenTelemetry exporter
63-
logging.getLogger("azure.monitor.opentelemetry.exporter.export._base").setLevel(
64-
logging.WARNING
65-
)
62+
# # Suppress info logs from OpenTelemetry exporter
63+
# logging.getLogger("azure.monitor.opentelemetry.exporter.export._base").setLevel(
64+
# logging.WARNING
65+
# )
6666

6767
# Initialize the FastAPI app
6868
app = FastAPI()
@@ -132,9 +132,10 @@ async def input_task_endpoint(input_task: InputTask, request: Request):
132132
input_task_data["user_id"] = user_id
133133
input_task_json = json.dumps(input_task_data)
134134

135+
logging.info(f"Input task: {input_task}")
135136
# Use the planner to handle the task
136137
result = await planner_agent.handle_input_task(
137-
KernelArguments(input_task_json=input_task_json)
138+
input_task
138139
)
139140

140141
print(f"Result: {result}")
@@ -819,7 +820,7 @@ async def get_agent_tools():
819820
type: string
820821
description: Arguments required by the tool function
821822
"""
822-
return retrieve_all_agent_tools()
823+
return []
823824

824825

825826
# Initialize the application when it starts

src/backend/config_kernel.py

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@
33
import logging
44
import semantic_kernel as sk
55
from semantic_kernel.kernel import Kernel
6-
from semantic_kernel.contents import ChatHistory
6+
# Updated imports for compatibility
7+
try:
8+
# Try newer structure
9+
from semantic_kernel.contents import ChatHistory
10+
except ImportError:
11+
# Fall back to older structure for compatibility
12+
from semantic_kernel.connectors.ai.chat_completion_client import ChatHistory
713
from semantic_kernel.agents.azure_ai.azure_ai_agent import AzureAIAgent
814

915
# Import AppConfig from app_config
@@ -54,26 +60,3 @@ def CreateKernel():
5460
def GetAIProjectClient():
5561
"""Get an AIProjectClient using the AppConfig implementation."""
5662
return config.get_ai_project_client()
57-
58-
@staticmethod
59-
async def CreateAzureAIAgent(
60-
kernel: Kernel,
61-
agent_name: str,
62-
instructions: str,
63-
agent_type: str = "assistant",
64-
tools=None,
65-
tool_resources=None,
66-
response_format=None,
67-
temperature: float = 0.0
68-
):
69-
"""Creates a new Azure AI Agent using the AppConfig implementation."""
70-
return await config.create_azure_ai_agent(
71-
kernel=kernel,
72-
agent_name=agent_name,
73-
instructions=instructions,
74-
agent_type=agent_type,
75-
tools=tools,
76-
tool_resources=tool_resources,
77-
response_format=response_format,
78-
temperature=temperature
79-
)

src/backend/kernel_agents/agent_base.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,24 @@
99
from semantic_kernel.functions.kernel_arguments import KernelArguments
1010
from semantic_kernel.agents.azure_ai.azure_ai_agent import AzureAIAgent
1111

12+
# Updated imports for compatibility
13+
try:
14+
# Try importing from newer structure first
15+
from semantic_kernel.contents import ChatMessageContent, ChatHistory
16+
except ImportError:
17+
# Fall back to older structure for compatibility
18+
class ChatMessageContent:
19+
"""Compatibility class for older SK versions."""
20+
def __init__(self, role="", content="", name=None):
21+
self.role = role
22+
self.content = content
23+
self.name = name
24+
25+
class ChatHistory:
26+
"""Compatibility class for older SK versions."""
27+
def __init__(self):
28+
self.messages = []
29+
1230
from context.cosmos_memory_kernel import CosmosMemoryContext
1331
from models.messages_kernel import (
1432
ActionRequest,
@@ -64,6 +82,7 @@ def __init__(
6482
else:
6583
tools = tools or []
6684
system_message = system_message or self._default_system_message(agent_name)
85+
6786
# Call AzureAIAgent constructor with required client and definition
6887
super().__init__(
6988
kernel=kernel,
@@ -76,6 +95,8 @@ def __init__(
7695
client=client,
7796
definition=definition
7897
)
98+
99+
# Store instance variables
79100
self._agent_name = agent_name
80101
self._kernel = kernel
81102
self._session_id = session_id
@@ -84,8 +105,14 @@ def __init__(
84105
self._tools = tools
85106
self._system_message = system_message
86107
self._chat_history = [{"role": "system", "content": self._system_message}]
108+
self._agent = None # Will be initialized in async_init
109+
110+
# Required properties for AgentGroupChat compatibility
111+
self.name = agent_name # This is crucial for AgentGroupChat to identify agents
112+
87113
# Log initialization
88114
logging.info(f"Initialized {agent_name} with {len(self._tools)} tools")
115+
89116
# Register the handler functions
90117
self._register_functions()
91118

@@ -107,6 +134,53 @@ async def async_init(self):
107134
# Tools are registered with the kernel via get_tools_from_config
108135
return self
109136

137+
async def invoke_async(self, *args, **kwargs):
138+
"""Invoke this agent asynchronously.
139+
140+
This method is required for compatibility with AgentGroupChat.
141+
142+
Args:
143+
*args: Positional arguments
144+
**kwargs: Keyword arguments
145+
146+
Returns:
147+
The agent's response
148+
"""
149+
# Ensure agent is initialized
150+
if self._agent is None:
151+
await self.async_init()
152+
153+
# Get the text input from args or kwargs
154+
text = None
155+
if args and isinstance(args[0], str):
156+
text = args[0]
157+
elif "text" in kwargs:
158+
text = kwargs["text"]
159+
elif "arguments" in kwargs and hasattr(kwargs["arguments"], "get"):
160+
text = kwargs["arguments"].get("text") or kwargs["arguments"].get("input")
161+
162+
if not text:
163+
settings = kwargs.get("settings", {})
164+
if isinstance(settings, dict) and "input" in settings:
165+
text = settings["input"]
166+
167+
# If text is still not found, create a default message
168+
if not text:
169+
text = "Hello, please assist with a task."
170+
171+
# Use the text to invoke the agent
172+
try:
173+
logging.info(f"Invoking {self._agent_name} with text: {text[:100]}...")
174+
response = await self._agent.invoke(
175+
self._kernel,
176+
text,
177+
settings=kwargs.get("settings", {})
178+
)
179+
return response
180+
except Exception as e:
181+
logging.error(f"Error invoking {self._agent_name}: {e}")
182+
return f"Error: {str(e)}"
183+
110184
def _register_functions(self):
111185
"""Register this agent's functions with the kernel."""
112186
# Use the kernel function decorator approach instead of from_native_method
@@ -126,6 +200,37 @@ async def handle_action_request_wrapper(*args, **kwargs):
126200
kernel_func = KernelFunction.from_method(handle_action_request_wrapper)
127201
# Use agent name as plugin for handler
128202
self._kernel.add_function(self._agent_name, kernel_func)
203+
204+
# Required method for AgentGroupChat compatibility
205+
async def send_message_async(self, message_content: ChatMessageContent, chat_history: ChatHistory):
206+
"""Send a message to the agent asynchronously, adding it to chat history.
207+
208+
Args:
209+
message_content: The content of the message
210+
chat_history: The chat history
211+
212+
Returns:
213+
None
214+
"""
215+
# Convert message to format expected by the agent
216+
if hasattr(message_content, "role") and hasattr(message_content, "content"):
217+
self._chat_history.append({
218+
"role": message_content.role,
219+
"content": message_content.content
220+
})
221+
222+
# If chat history is provided, update our internal history
223+
if chat_history and hasattr(chat_history, "messages"):
224+
# Update with the latest messages from chat history
225+
for msg in chat_history.messages[-5:]: # Only use last 5 messages to avoid history getting too long
226+
if msg not in self._chat_history:
227+
self._chat_history.append({
228+
"role": msg.role,
229+
"content": msg.content
230+
})
231+
232+
# No need to return anything as we're just updating state
233+
return None
129234

130235
async def handle_action_request(self, action_request_json: str) -> str:
131236
"""Handle an action request from another agent or the system.

src/backend/kernel_agents/agent_factory.py

Lines changed: 17 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,14 @@
2323
from kernel_agents.product_agent import ProductAgent
2424
from kernel_agents.planner_agent import PlannerAgent # Add PlannerAgent import
2525
from kernel_agents.group_chat_manager import GroupChatManager
26-
26+
from semantic_kernel.prompt_template.prompt_template_config import PromptTemplateConfig
2727
from context.cosmos_memory_kernel import CosmosMemoryContext
28+
from models.messages_kernel import PlannerResponsePlan
2829

29-
30+
from azure.ai.projects.models import (
31+
ResponseFormatJsonSchema,
32+
ResponseFormatJsonSchemaType,
33+
)
3034
logger = logging.getLogger(__name__)
3135

3236

@@ -108,6 +112,7 @@ async def create_agent(
108112
user_id: str,
109113
temperature: float = 0.0,
110114
system_message: Optional[str] = None,
115+
response_format: Optional[Any] = None,
111116
**kwargs
112117
) -> BaseAgent:
113118
"""Create an agent of the specified type.
@@ -174,7 +179,7 @@ async def create_agent(
174179
name=agent_type_str,
175180
instructions=system_message,
176181
temperature=temperature,
177-
response_format=None # Add response_format if required
182+
response_format=response_format # Add response_format if required
178183
)
179184
logger.info(f"Successfully created agent definition for {agent_type_str}")
180185
except Exception as agent_exc:
@@ -224,57 +229,6 @@ async def create_agent(
224229

225230
return agent
226231

227-
@classmethod
228-
async def create_azure_ai_agent(
229-
cls,
230-
agent_name: str,
231-
session_id: str,
232-
system_prompt: str,
233-
tools: List[KernelFunction] = None
234-
) -> AzureAIAgent:
235-
"""Create an Azure AI Agent.
236-
237-
Args:
238-
agent_name: The name of the agent
239-
session_id: The session ID
240-
system_prompt: The system prompt for the agent
241-
tools: Optional list of tools for the agent
242-
243-
Returns:
244-
An Azure AI Agent instance
245-
"""
246-
# Check if we already have an agent in the cache
247-
cache_key = f"{session_id}_{agent_name}"
248-
if session_id in cls._azure_ai_agent_cache and cache_key in cls._azure_ai_agent_cache[session_id]:
249-
# If tools are provided, make sure they are registered with the cached agent
250-
agent = cls._azure_ai_agent_cache[session_id][cache_key]
251-
if tools:
252-
for tool in tools:
253-
agent.add_function(tool)
254-
return agent
255-
256-
# Create a kernel using the AppConfig instance
257-
kernel = config.create_kernel()
258-
259-
# Await creation since create_azure_ai_agent is async
260-
agent = await config.create_azure_ai_agent(
261-
kernel=kernel,
262-
agent_name=agent_name,
263-
instructions=system_prompt
264-
)
265-
266-
# Register tools if provided
267-
if tools:
268-
for tool in tools:
269-
agent.add_function(tool)
270-
271-
# Cache the agent instance
272-
if session_id not in cls._azure_ai_agent_cache:
273-
cls._azure_ai_agent_cache[session_id] = {}
274-
cls._azure_ai_agent_cache[session_id][cache_key] = agent
275-
276-
return agent
277-
278232
@classmethod
279233
async def _load_tools_for_agent(cls, kernel: Kernel, agent_type: str) -> List[KernelFunction]:
280234
"""Load tools for an agent from the tools directory.
@@ -310,7 +264,7 @@ async def _load_tools_for_agent(cls, kernel: Kernel, agent_type: str) -> List[Ke
310264
# For other agent types, try to create a simple fallback tool
311265
try:
312266
# Use PromptTemplateConfig to create a simple tool
313-
from semantic_kernel.prompt_template.prompt_template_config import PromptTemplateConfig
267+
314268

315269
# Simple minimal prompt
316270
prompt = f"""You are a helpful assistant specialized in {agent_type} tasks.
@@ -398,7 +352,14 @@ async def create_all_agents(
398352
session_id=session_id,
399353
user_id=user_id,
400354
temperature=temperature,
401-
agent_instances=agent_instances # Pass agent instances to the planner
355+
agent_instances=agent_instances, # Pass agent instances to the planner
356+
response_format=ResponseFormatJsonSchemaType(
357+
json_schema=ResponseFormatJsonSchema(
358+
name=PlannerResponsePlan.__name__,
359+
description=f"respond with {PlannerResponsePlan.__name__.lower()}",
360+
schema=PlannerResponsePlan.model_json_schema(),
361+
)
362+
)
402363
)
403364
agents[planner_agent_type] = planner_agent
404365

0 commit comments

Comments
 (0)