Skip to content

Commit 5396e45

Browse files
authored
Merge pull request #139 from Fr4nc3/main
tools
2 parents 27bc997 + f93bec9 commit 5396e45

27 files changed

+2623
-377
lines changed

src/backend/app_config.py

Lines changed: 10 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from semantic_kernel.kernel import Kernel
1010
from semantic_kernel.contents import ChatHistory
1111
from semantic_kernel.agents.azure_ai.azure_ai_agent import AzureAIAgent
12+
from semantic_kernel.functions import KernelFunction
1213

1314
# Load environment variables from .env file
1415
load_dotenv()
@@ -193,9 +194,7 @@ async def create_azure_ai_agent(
193194
kernel: Kernel,
194195
agent_name: str,
195196
instructions: str,
196-
agent_type: str = "assistant",
197-
tools=None,
198-
tool_resources=None,
197+
tools: Optional[List[KernelFunction]] = None,
199198
response_format=None,
200199
temperature: float = 0.0,
201200
):
@@ -228,7 +227,10 @@ async def create_azure_ai_agent(
228227

229228
# Create the agent instance directly with project_client and existing definition
230229
agent = AzureAIAgent(
231-
client=project_client, definition=existing_definition, kernel=kernel
230+
client=project_client,
231+
definition=existing_definition,
232+
kernel=kernel,
233+
plugins=tools,
232234
)
233235

234236
logging.info(
@@ -248,53 +250,23 @@ async def create_azure_ai_agent(
248250
f"Unexpected error while retrieving agent {agent_name}: {str(e)}. Attempting to create new agent."
249251
)
250252

251-
# Tool handling: We need to distinguish between our SK functions and
252-
# the tool definitions needed by project_client.agents.create_agent
253-
tool_definitions = None
254-
kernel_functions = []
255-
256-
# If tools are provided and they are SK KernelFunctions, we need to handle them differently
257-
# than if they are already tool definitions expected by AIProjectClient
258-
if tools:
259-
# Check if tools are SK KernelFunctions
260-
if all(
261-
hasattr(tool, "name") and hasattr(tool, "invoke") for tool in tools
262-
):
263-
# Store the kernel functions to register with the agent later
264-
kernel_functions = tools
265-
# For now, we don't extract tool definitions from kernel functions
266-
# This would require additional code to convert SK functions to AI Project tool definitions
267-
logging.warning(
268-
"Kernel functions provided as tools will be registered with the agent after creation"
269-
)
270-
else:
271-
# Assume these are already proper tool definitions for create_agent
272-
tool_definitions = tools
273-
274-
logging.info(f"Creating new agent with ID: {agent_name}")
275-
276253
# Create the agent using the project client with the agent_name as both name and assistantId
277254
agent_definition = await project_client.agents.create_agent(
278255
model=self.AZURE_OPENAI_DEPLOYMENT_NAME,
279256
name=agent_name,
280257
instructions=instructions,
281-
tools=tool_definitions,
282-
tool_resources=tool_resources,
283258
temperature=temperature,
284259
response_format=response_format,
285260
)
286261

287262
# Create the agent instance directly with project_client and definition
288263
agent = AzureAIAgent(
289-
client=project_client, definition=agent_definition, kernel=kernel
264+
client=project_client,
265+
definition=agent_definition,
266+
kernel=kernel,
267+
plugins=tools,
290268
)
291269

292-
# Register the kernel functions with the agent if any were provided
293-
if kernel_functions:
294-
for function in kernel_functions:
295-
if hasattr(agent, "add_function"):
296-
agent.add_function(function)
297-
298270
return agent
299271
except Exception as exc:
300272
logging.error("Failed to create Azure AI Agent: %s", exc)

src/backend/kernel_agents/agent_base.py

Lines changed: 41 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
1-
import logging
21
import json
2+
import logging
33
import os
4-
from typing import Any, Dict, List, Mapping, Optional, Callable, Awaitable
4+
from typing import Any, Awaitable, Callable, Dict, List, Mapping, Optional, Union
55

66
import semantic_kernel as sk
7+
from semantic_kernel.agents.azure_ai.azure_ai_agent import AzureAIAgent
78
from semantic_kernel.functions import KernelFunction
8-
from semantic_kernel.functions.kernel_function_decorator import kernel_function
99
from semantic_kernel.functions.kernel_arguments import KernelArguments
10-
from semantic_kernel.agents.azure_ai.azure_ai_agent import AzureAIAgent
10+
from semantic_kernel.functions.kernel_function_decorator import kernel_function
11+
from semantic_kernel.agents import AzureAIAgentThread
1112

1213
# Updated imports for compatibility
1314
try:
1415
# Try importing from newer structure first
15-
from semantic_kernel.contents import ChatMessageContent, ChatHistory
16+
from semantic_kernel.contents import ChatHistory, ChatMessageContent
1617
except ImportError:
1718
# Fall back to older structure for compatibility
1819
class ChatMessageContent:
@@ -30,7 +31,10 @@ def __init__(self):
3031
self.messages = []
3132

3233

34+
# Import the new AppConfig instance
35+
from app_config import config
3336
from context.cosmos_memory_kernel import CosmosMemoryContext
37+
from event_utils import track_event_if_configured
3438
from models.messages_kernel import (
3539
ActionRequest,
3640
ActionResponse,
@@ -39,10 +43,6 @@ def __init__(self):
3943
StepStatus,
4044
)
4145

42-
# Import the new AppConfig instance
43-
from app_config import config
44-
from event_utils import track_event_if_configured
45-
4646
# Default formatting instructions used across agents
4747
DEFAULT_FORMATTING_INSTRUCTIONS = "Instructions: returning the output of this function call verbatim to the user in markdown. Then write AGENT SUMMARY: and then include a summary of what you did."
4848

@@ -77,6 +77,8 @@ def __init__(
7777
client: The client required by AzureAIAgent
7878
definition: The definition required by AzureAIAgent
7979
"""
80+
# Add plugins if not already set
81+
# if not self.plugins:
8082
# If agent_type is provided, load tools from config automatically
8183
if agent_type and not tools:
8284
tools = self.get_tools_from_config(kernel, agent_type)
@@ -94,6 +96,7 @@ def __init__(
9496
super().__init__(
9597
kernel=kernel,
9698
deployment_name=None, # Set as needed
99+
plugins=tools, # Use the loaded plugins,
97100
endpoint=None, # Set as needed
98101
api_version=None, # Set as needed
99102
token=None, # Set as needed
@@ -117,8 +120,14 @@ def __init__(
117120
# Required properties for AgentGroupChat compatibility
118121
self.name = agent_name # This is crucial for AgentGroupChat to identify agents
119122

120-
# Register the handler functions
121-
self._register_functions()
123+
# @property
124+
# def plugins(self) -> Optional[dict[str, Callable]]:
125+
# """Get the plugins for this agent.
126+
127+
# Returns:
128+
# A list of plugins, or None if not applicable.
129+
# """
130+
# return None
122131

123132
def _default_system_message(self, agent_name=None) -> str:
124133
name = agent_name or getattr(self, "_agent_name", "Agent")
@@ -129,37 +138,17 @@ async def async_init(self):
129138
130139
This method must be called after creating the agent to complete initialization.
131140
"""
141+
logging.info(f"Initializing agent: {self._agent_name}")
132142
# Create Azure AI Agent or fallback
133143
self._agent = await config.create_azure_ai_agent(
134144
kernel=self._kernel,
135145
agent_name=self._agent_name,
136146
instructions=self._system_message,
147+
tools=self._tools,
137148
)
138149
# Tools are registered with the kernel via get_tools_from_config
139150
return self
140151

141-
def _register_functions(self):
142-
"""Register this agent's functions with the kernel."""
143-
# Use the kernel function decorator approach instead of from_native_method
144-
# which isn't available in SK 1.28.0
145-
function_name = "handle_action_request"
146-
147-
# Define the function using the kernel function decorator
148-
@kernel_function(
149-
description="Handle an action request from another agent or the system",
150-
name=function_name,
151-
)
152-
async def handle_action_request_wrapper(*args, **kwargs):
153-
# Forward to the instance method
154-
return await self.handle_action_request(*args, **kwargs)
155-
156-
# Wrap the decorated function into a KernelFunction and register under this agent's plugin
157-
kernel_func = KernelFunction.from_method(handle_action_request_wrapper)
158-
# Use agent name as plugin for handler
159-
self._kernel.add_function(self._agent_name, kernel_func)
160-
161-
# Required method for AgentGroupChat compatibility
162-
163152
async def handle_action_request(self, action_request: ActionRequest) -> str:
164153
"""Handle an action request from another agent or the system.
165154
@@ -201,8 +190,13 @@ async def handle_action_request(self, action_request: ActionRequest) -> str:
201190
# chat_history = self._chat_history.copy()
202191

203192
# Call the agent to handle the action
193+
thread = None
194+
# thread = self.client.agents.get_thread(
195+
# thread=step.session_id
196+
# ) # AzureAIAgentThread(thread_id=step.session_id)
204197
async_generator = self._agent.invoke(
205-
f"{action_request.action}\n\nPlease perform this action"
198+
messages=f"{action_request.action}\n\nPlease perform this action",
199+
thread=thread,
206200
)
207201

208202
response_content = ""
@@ -299,73 +293,11 @@ async def handle_action_request(self, action_request: ActionRequest) -> str:
299293

300294
return response.json()
301295

302-
async def invoke_tool(self, tool_name: str, arguments: Dict[str, Any]) -> str:
303-
"""Invoke a specific tool by name with the provided arguments.
304-
305-
Args:
306-
tool_name: The name of the tool to invoke
307-
arguments: A dictionary of arguments to pass to the tool
308-
309-
Returns:
310-
The result of the tool invocation as a string
311-
312-
Raises:
313-
ValueError: If the tool is not found
314-
"""
315-
# Find the tool by name in the agent's tools list
316-
tool = next((t for t in self._tools if t.name == tool_name), None)
317-
318-
if not tool:
319-
# Try looking up the tool in the kernel's plugins
320-
plugin_name = f"{self._agent_name.lower().replace('agent', '')}_plugin"
321-
try:
322-
tool = self._kernel.get_function(plugin_name, tool_name)
323-
except Exception:
324-
raise ValueError(
325-
f"Tool '{tool_name}' not found in agent tools or kernel plugins"
326-
)
327-
328-
if not tool:
329-
raise ValueError(f"Tool '{tool_name}' not found")
330-
331-
try:
332-
# Create kernel arguments from the dictionary
333-
kernel_args = KernelArguments()
334-
for key, value in arguments.items():
335-
kernel_args[key] = value
336-
337-
# Invoke the tool
338-
logging.info(f"Invoking tool '{tool_name}' with arguments: {arguments}")
339-
340-
# Use invoke_with_args_dict directly instead of relying on KernelArguments
341-
if hasattr(tool, "invoke_with_args_dict") and callable(
342-
tool.invoke_with_args_dict
343-
):
344-
result = await tool.invoke_with_args_dict(arguments)
345-
else:
346-
# Fall back to standard invoke method
347-
result = await tool.invoke(kernel_args)
348-
349-
# Log telemetry if configured
350-
track_event_if_configured(
351-
"AgentToolInvocation",
352-
{
353-
"agent_name": self._agent_name,
354-
"tool_name": tool_name,
355-
"session_id": self._session_id,
356-
"user_id": self._user_id,
357-
},
358-
)
359-
360-
return str(result)
361-
except Exception as e:
362-
logging.error(f"Error invoking tool '{tool_name}': {str(e)}")
363-
raise
364-
365296
@staticmethod
366297
def create_dynamic_function(
367298
name: str,
368299
response_template: str,
300+
description: Optional[str] = None,
369301
formatting_instr: str = DEFAULT_FORMATTING_INSTRUCTIONS,
370302
) -> Callable[..., Awaitable[str]]:
371303
"""Create a dynamic function for agent tools based on the name and template.
@@ -379,6 +311,13 @@ def create_dynamic_function(
379311
A dynamic async function that can be registered with the semantic kernel
380312
"""
381313

314+
# Truncate function name to 64 characters if it exceeds the limit
315+
if len(name) > 64:
316+
logging.warning(
317+
f"Function name '{name}' exceeds 64 characters (length: {len(name)}). Truncating to 64 characters."
318+
)
319+
name = name[:64]
320+
382321
async def dynamic_function(**kwargs) -> str:
383322
try:
384323
# Format the template with the provided kwargs
@@ -396,7 +335,9 @@ async def dynamic_function(**kwargs) -> str:
396335
dynamic_function.__name__ = name
397336

398337
# Create a wrapped kernel function that matches the expected signature
399-
@kernel_function(description=f"Dynamic function: {name}", name=name)
338+
logging.info(f"Creating dynamic function: {name} {len(name)}")
339+
340+
@kernel_function(description=f"Dynamic function {name}", name=name)
400341
async def kernel_wrapper(
401342
kernel_arguments: KernelArguments = None, **kwargs
402343
) -> str:
@@ -477,7 +418,7 @@ def get_tools_from_config(
477418
plugin_name = f"{agent_type}_plugin"
478419

479420
# Early return if no tools defined - prevent empty iteration
480-
if not config.get("tools"):
421+
if not config.get("tools"): # or agent_type == "Product_Agent":
481422
logging.info(
482423
f"No tools defined for agent type '{agent_type}'. Returning empty list."
483424
)
@@ -513,7 +454,7 @@ def get_tools_from_config(
513454
)
514455

515456
# Register the function with the kernel
516-
kernel.add_function(plugin_name, kernel_func)
457+
517458
kernel_functions.append(kernel_func)
518459
logging.info(
519460
f"Successfully created dynamic tool '{function_name}' for {agent_type}"

0 commit comments

Comments
 (0)