Skip to content

Commit 384ea75

Browse files
committed
Clean up tool config and old code cleanup
1 parent a9070f5 commit 384ea75

File tree

13 files changed

+83
-364
lines changed

13 files changed

+83
-364
lines changed

src/backend/kernel_agents/agent_base.py

Lines changed: 2 additions & 200 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from semantic_kernel.functions import KernelFunction
99
from semantic_kernel.functions.kernel_arguments import KernelArguments
1010
from semantic_kernel.functions.kernel_function_decorator import kernel_function
11-
from semantic_kernel.agents import AzureAIAgentThread
1211

1312
# Updated imports for compatibility
1413
try:
@@ -78,18 +77,7 @@ def __init__(
7877
definition: The definition required by AzureAIAgent
7978
"""
8079
# Add plugins if not already set
81-
# if not self.plugins:
82-
# If agent_type is provided, load tools from config automatically
83-
if agent_type and not tools:
84-
tools = self.get_tools_from_config(kernel, agent_type)
85-
# If system_message isn't provided, try to get it from config
86-
if not system_message:
87-
config = self.load_tools_config(agent_type)
88-
system_message = config.get(
89-
"system_message", self._default_system_message(agent_name)
90-
)
91-
else:
92-
tools = tools or []
80+
tools = tools or []
9381
system_message = system_message or self._default_system_message(agent_name)
9482

9583
# Call AzureAIAgent constructor with required client and definition
@@ -120,15 +108,6 @@ def __init__(
120108
# Required properties for AgentGroupChat compatibility
121109
self.name = agent_name # This is crucial for AgentGroupChat to identify agents
122110

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
131-
132111
def _default_system_message(self, agent_name=None) -> str:
133112
name = agent_name or getattr(self, "_agent_name", "Agent")
134113
return f"You are an AI assistant named {name}. Help the user by providing accurate and helpful information."
@@ -186,14 +165,10 @@ async def handle_action_request(self, action_request: ActionRequest) -> str:
186165
)
187166

188167
try:
189-
# Use the agent to process the action
190-
# chat_history = self._chat_history.copy()
191168

192169
# Call the agent to handle the action
193170
thread = None
194-
# thread = self.client.agents.get_thread(
195-
# thread=step.session_id
196-
# ) # AzureAIAgentThread(thread_id=step.session_id)
171+
197172
async_generator = self._agent.invoke(
198173
messages=f"{action_request.action}\n\nPlease perform this action",
199174
thread=thread,
@@ -293,179 +268,6 @@ async def handle_action_request(self, action_request: ActionRequest) -> str:
293268

294269
return response.json()
295270

296-
@staticmethod
297-
def create_dynamic_function(
298-
name: str,
299-
response_template: str,
300-
description: Optional[str] = None,
301-
formatting_instr: str = DEFAULT_FORMATTING_INSTRUCTIONS,
302-
) -> Callable[..., Awaitable[str]]:
303-
"""Create a dynamic function for agent tools based on the name and template.
304-
305-
Args:
306-
name: The name of the function to create
307-
response_template: The template string to use for the response
308-
formatting_instr: Optional formatting instructions to append to the response
309-
310-
Returns:
311-
A dynamic async function that can be registered with the semantic kernel
312-
"""
313-
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-
321-
async def dynamic_function(**kwargs) -> str:
322-
try:
323-
# Format the template with the provided kwargs
324-
formatted_response = response_template.format(**kwargs)
325-
# Append formatting instructions if not already included in the template
326-
if formatting_instr and formatting_instr not in formatted_response:
327-
formatted_response = f"{formatted_response}\n{formatting_instr}"
328-
return formatted_response
329-
except KeyError as e:
330-
return f"Error: Missing parameter {e} for {name}"
331-
except Exception as e:
332-
return f"Error processing {name}: {str(e)}"
333-
334-
# Name the function properly for better debugging
335-
dynamic_function.__name__ = name
336-
337-
# Create a wrapped kernel function that matches the expected signature
338-
logging.info(f"Creating dynamic function: {name} {len(name)}")
339-
340-
@kernel_function(description=f"Dynamic function {name}", name=name)
341-
async def kernel_wrapper(
342-
kernel_arguments: KernelArguments = None, **kwargs
343-
) -> str:
344-
# Combine all arguments into one dictionary
345-
all_args = {}
346-
if kernel_arguments:
347-
for key, value in kernel_arguments.items():
348-
all_args[key] = value
349-
all_args.update(kwargs)
350-
return await dynamic_function(**all_args)
351-
352-
return kernel_wrapper
353-
354-
@staticmethod
355-
def load_tools_config(
356-
filename: str, config_path: Optional[str] = None
357-
) -> Dict[str, Any]:
358-
"""Load tools configuration from a JSON file.
359-
360-
Args:
361-
filename: The filename without extension (e.g., "hr", "marketing")
362-
config_path: Optional explicit path to the configuration file
363-
364-
Returns:
365-
A dictionary containing the configuration
366-
"""
367-
if config_path is None:
368-
# Default path relative to the tools directory
369-
current_dir = os.path.dirname(os.path.abspath(__file__))
370-
backend_dir = os.path.dirname(
371-
current_dir
372-
) # Just one level up to get to backend dir
373-
374-
# Normalize filename to avoid issues with spaces and capitalization
375-
# Convert "Hr Agent" to "hr" and "TechSupport Agent" to "tech_support"
376-
logging.info(f"Normalizing filename: {filename}")
377-
normalized_filename = filename.replace(" ", "_").replace("-", "_").lower()
378-
# If it ends with "_agent", remove it
379-
if normalized_filename.endswith("_agent"):
380-
normalized_filename = normalized_filename[:-6]
381-
382-
config_path = os.path.join(
383-
backend_dir, "tools", f"{normalized_filename}_tools.json"
384-
)
385-
logging.info(f"Looking for tools config at: {config_path}")
386-
387-
try:
388-
with open(config_path, "r") as f:
389-
return json.load(f)
390-
except Exception as e:
391-
logging.error(f"Error loading {filename} tools configuration: {e}")
392-
# Return empty default configuration
393-
return {
394-
"agent_name": f"{filename.capitalize()}Agent",
395-
"system_message": "You are an AI assistant",
396-
"tools": [],
397-
}
398-
399-
@classmethod
400-
def get_tools_from_config(
401-
cls, kernel: sk.Kernel, agent_type: str, config_path: Optional[str] = None
402-
) -> List[KernelFunction]:
403-
"""Get the list of tools for an agent from configuration.
404-
405-
Args:
406-
kernel: The semantic kernel instance
407-
agent_type: The type of agent (e.g., "marketing", "hr")
408-
config_path: Optional explicit path to the configuration file
409-
410-
Returns:
411-
A list of KernelFunction objects representing the tools
412-
"""
413-
# Load configuration
414-
config = cls.load_tools_config(agent_type, config_path)
415-
416-
# Convert the configured tools to kernel functions
417-
kernel_functions = []
418-
plugin_name = f"{agent_type}_plugin"
419-
420-
# Early return if no tools defined - prevent empty iteration
421-
if not config.get("tools"): # or agent_type == "Product_Agent":
422-
logging.info(
423-
f"No tools defined for agent type '{agent_type}'. Returning empty list."
424-
)
425-
return kernel_functions
426-
427-
for tool in config.get("tools", []):
428-
try:
429-
function_name = tool["name"]
430-
description = tool.get("description", "")
431-
# Create a dynamic function using the JSON response_template
432-
response_template = (
433-
tool.get("response_template") or tool.get("prompt_template") or ""
434-
)
435-
436-
# Generate a dynamic function using our improved approach
437-
dynamic_fn = cls.create_dynamic_function(
438-
function_name, response_template
439-
)
440-
441-
# Create kernel function from the decorated function
442-
kernel_func = KernelFunction.from_method(dynamic_fn)
443-
444-
# Add parameter metadata from JSON to the kernel function
445-
for param in tool.get("parameters", []):
446-
param_name = param.get("name", "")
447-
param_desc = param.get("description", "")
448-
param_type = param.get("type", "string")
449-
450-
# Set this parameter in the function's metadata
451-
if param_name:
452-
logging.info(
453-
f"Adding parameter '{param_name}' to function '{function_name}'"
454-
)
455-
456-
# Register the function with the kernel
457-
458-
kernel_functions.append(kernel_func)
459-
logging.info(
460-
f"Successfully created dynamic tool '{function_name}' for {agent_type}"
461-
)
462-
except Exception as e:
463-
logging.error(
464-
f"Failed to create tool '{tool.get('name', 'unknown')}': {str(e)}"
465-
)
466-
467-
return kernel_functions
468-
469271
def save_state(self) -> Mapping[str, Any]:
470272
"""Save the state of this agent."""
471273
return {"memory": self._memory_store.save_state()}

src/backend/kernel_agents/agent_factory.py

Lines changed: 18 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,34 @@
11
"""Factory for creating agents in the Multi-Agent Custom Automation Engine."""
22

3+
import inspect
34
import logging
4-
from typing import Dict, List, Callable, Any, Optional, Type
55
from types import SimpleNamespace
6-
from semantic_kernel import Kernel
7-
from semantic_kernel.functions import KernelFunction
8-
from semantic_kernel.agents.azure_ai.azure_ai_agent import AzureAIAgent
9-
import inspect
10-
11-
from kernel_agents.agent_base import BaseAgent
6+
from typing import Any, Callable, Dict, List, Optional, Type
127

138
# Import the new AppConfig instance
149
from app_config import config
10+
from azure.ai.projects.models import (
11+
ResponseFormatJsonSchema,
12+
ResponseFormatJsonSchemaType,
13+
)
14+
from context.cosmos_memory_kernel import CosmosMemoryContext
15+
from kernel_agents.agent_base import BaseAgent
16+
from kernel_agents.generic_agent import GenericAgent
17+
from kernel_agents.group_chat_manager import GroupChatManager
1518

1619
# Import all specialized agent implementations
1720
from kernel_agents.hr_agent import HrAgent
1821
from kernel_agents.human_agent import HumanAgent
1922
from kernel_agents.marketing_agent import MarketingAgent
20-
from kernel_agents.generic_agent import GenericAgent
21-
from kernel_agents.tech_support_agent import TechSupportAgent
23+
from kernel_agents.planner_agent import PlannerAgent # Add PlannerAgent import
2224
from kernel_agents.procurement_agent import ProcurementAgent
2325
from kernel_agents.product_agent import ProductAgent
24-
from kernel_agents.planner_agent import PlannerAgent # Add PlannerAgent import
25-
from kernel_agents.group_chat_manager import GroupChatManager
26+
from kernel_agents.tech_support_agent import TechSupportAgent
27+
from models.messages_kernel import AgentType, PlannerResponsePlan
28+
from semantic_kernel import Kernel
29+
from semantic_kernel.agents.azure_ai.azure_ai_agent import AzureAIAgent
30+
from semantic_kernel.functions import KernelFunction
2631
from semantic_kernel.prompt_template.prompt_template_config import PromptTemplateConfig
27-
from context.cosmos_memory_kernel import CosmosMemoryContext
28-
from models.messages_kernel import PlannerResponsePlan, AgentType
29-
30-
from azure.ai.projects.models import (
31-
ResponseFormatJsonSchema,
32-
ResponseFormatJsonSchemaType,
33-
)
3432

3533
logger = logging.getLogger(__name__)
3634

@@ -176,7 +174,7 @@ async def create_agent(
176174
agent_type_str = cls._agent_type_strings.get(
177175
agent_type, agent_type.value.lower()
178176
)
179-
tools = await cls._load_tools_for_agent(kernel, agent_type_str)
177+
# tools = await cls._load_tools_for_agent(kernel, agent_type_str)
180178

181179
# Build the agent definition (functions schema)
182180
definition = None
@@ -223,7 +221,7 @@ async def create_agent(
223221
"session_id": session_id,
224222
"user_id": user_id,
225223
"memory_store": memory_store,
226-
"tools": tools,
224+
"tools": [],
227225
"system_message": system_message,
228226
"client": client,
229227
"definition": definition,
@@ -253,37 +251,6 @@ async def create_agent(
253251

254252
return agent
255253

256-
@classmethod
257-
async def _load_tools_for_agent(
258-
cls, kernel: Kernel, agent_type: str
259-
) -> List[KernelFunction]:
260-
"""Load tools for an agent from the tools directory.
261-
262-
This tries to load tool configurations from JSON files. If that fails,
263-
it returns an empty list for agents that don't need tools.
264-
265-
Args:
266-
kernel: The semantic kernel instance
267-
agent_type: The agent type string identifier
268-
269-
Returns:
270-
A list of kernel functions for the agent
271-
"""
272-
try:
273-
# Try to use the BaseAgent's tool loading mechanism
274-
tools = BaseAgent.get_tools_from_config(kernel, agent_type)
275-
logger.info(f"Successfully loaded {len(tools)} tools for {agent_type}")
276-
return tools
277-
except FileNotFoundError:
278-
# No tool configuration file found - this is expected for some agents
279-
logger.info(
280-
f"No tools defined for agent type '{agent_type}'. Returning empty list."
281-
)
282-
return []
283-
except Exception as e:
284-
logger.warning(f"Error loading tools for {agent_type}: {e}")
285-
return []
286-
287254
@classmethod
288255
async def create_all_agents(
289256
cls, session_id: str, user_id: str, temperature: float = 0.0

src/backend/kernel_agents/generic_agent.py

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@
22
from typing import List, Optional
33

44
import semantic_kernel as sk
5-
from semantic_kernel.functions import KernelFunction
6-
7-
from kernel_agents.agent_base import BaseAgent
85
from context.cosmos_memory_kernel import CosmosMemoryContext
6+
from kernel_agents.agent_base import BaseAgent
7+
from kernel_tools.generic_tools import GenericTools
98
from models.messages_kernel import AgentType
10-
from src.backend.kernel_tools.generic_tools import GenericTools
9+
from semantic_kernel.functions import KernelFunction
1110

1211

1312
class GenericAgent(BaseAgent):
@@ -42,27 +41,15 @@ def __init__(
4241
"""
4342
# Load configuration if tools not provided
4443
if tools is None:
45-
# Get tools directly from GenericTools class
46-
tools_dict = GenericTools.get_all_kernel_functions()
47-
logging.info(
48-
f"GenericAgent: Got tools_dict with {len(tools_dict)} functions: {list(tools_dict.keys())}"
49-
)
50-
51-
tools = [KernelFunction.from_method(func) for func in tools_dict.values()]
52-
logging.info(
53-
f"GenericAgent: Created {len(tools)} KernelFunctions from tools_dict"
54-
)
55-
56-
# Load the generic tools configuration for system message
57-
config = self.load_tools_config("generic", config_path)
44+
tools = [GenericTools]
5845

5946
# Use system message from config if not explicitly provided
6047
if not system_message:
61-
system_message = config.get(
62-
"system_message",
63-
"You are a generic agent. You are used to handle generic tasks that a general Large Language Model can assist with. "
64-
"You are being called as a fallback, when no other agents are able to use their specialised functions in order to solve "
65-
"the user's task. Summarize back to the user what was done.",
48+
system_message = (
49+
"system_message"
50+
+ "You are a generic agent. You are used to handle generic tasks that a general Large Language Model can assist with. "
51+
+ "You are being called as a fallback, when no other agents are able to use their specialized functions in order to solve "
52+
+ "the user's task. Summarize back to the user what was done.",
6653
)
6754

6855
# Use agent name from config if available

0 commit comments

Comments
 (0)