55from typing import Dict , List , Optional , Any , Tuple
66
77import semantic_kernel as sk
8- from semantic_kernel .agents import AgentGroupChat
8+ from semantic_kernel .functions .kernel_arguments import KernelArguments
9+ from semantic_kernel .agents import AgentGroupChat # pylint: disable=E0611
10+
911from semantic_kernel .agents .strategies import (
1012 SequentialSelectionStrategy ,
1113 TerminationStrategy ,
1214)
15+ # Updated imports for compatibility
16+ try :
17+ # Try importing from newer structure first
18+ from semantic_kernel .contents import ChatMessageContent , ChatHistory
19+ except ImportError :
20+ # Fall back to older structure for compatibility
21+ class ChatMessageContent :
22+ """Compatibility class for older SK versions."""
23+ def __init__ (self , role = "" , content = "" , name = None ):
24+ self .role = role
25+ self .content = content
26+ self .name = name
27+
28+ class ChatHistory :
29+ """Compatibility class for older SK versions."""
30+ def __init__ (self ):
31+ self .messages = []
1332
1433from kernel_agents .agent_base import BaseAgent
1534from context .cosmos_memory_kernel import CosmosMemoryContext
@@ -103,42 +122,46 @@ async def select_agent(self, agents, history):
103122
104123 Args:
105124 agents: List of available agents
106- history: Chat history
125+ history: Chat history (ChatHistory object)
107126
108127 Returns:
109128 The next agent to take the turn
110129 """
111130 # If no history, start with the PlannerAgent
112- if not history :
131+ if not history or not history . messages :
113132 return next ((agent for agent in agents if agent .name == "PlannerAgent" ), None )
114133
115134 # Get the last message
116- last_message = history [- 1 ]
135+ last_message = history . messages [- 1 ]
117136
118- match last_message .name :
119- case "PlannerAgent" :
120- # After the planner creates a plan, HumanAgent should review it
121- return next ((agent for agent in agents if agent .name == "HumanAgent" ), None )
122-
123- case "HumanAgent" :
124- # After human feedback, the specific agent for the step should proceed
125- # Need to extract which agent should be next from the plan
126- # For demo purposes, going with a simple approach
127- # In a real implementation, we would look up the next step in the plan
128- return next ((agent for agent in agents if agent .name == "GenericAgent" ), None )
129-
130- case "GroupChatManager" :
131- # If the manager just assigned a step, the specific agent should execute it
132- # For demo purposes, we'll just use the next agent in a simple rotation
133- current_agent_index = next ((i for i , agent in enumerate (agents )
134- if agent .name == last_message .name ), 0 )
135- next_index = (current_agent_index + 1 ) % len (agents )
137+ # Extract name from the message - in SK ChatMessageContent
138+ last_sender = last_message .role
139+ if hasattr (last_message , 'name' ) and last_message .name :
140+ last_sender = last_message .name
141+
142+ # Route based on the last sender
143+ if last_sender == "PlannerAgent" :
144+ # After the planner creates a plan, HumanAgent should review it
145+ return next ((agent for agent in agents if agent .name == "HumanAgent" ), None )
146+ elif last_sender == "HumanAgent" :
147+ # After human feedback, the specific agent for the step should proceed
148+ # For simplicity, use GenericAgent as fallback
149+ return next ((agent for agent in agents if agent .name == "GenericAgent" ), None )
150+ elif last_sender == "GroupChatManager" :
151+ # If the manager just assigned a step, find the agent that should execute it
152+ # For simplicity, just rotate to the next agent
153+ agent_names = [agent .name for agent in agents ]
154+ try :
155+ current_index = agent_names .index (last_sender )
156+ next_index = (current_index + 1 ) % len (agents )
136157 return agents [next_index ]
137-
138- case _:
139- # Default to the Group Chat Manager to coordinate next steps
140- return next ((agent for agent in agents if agent .name == "GroupChatManager" ), None )
141-
158+ except ValueError :
159+ return agents [0 ] if agents else None
160+ else :
161+ # Default to the Group Chat Manager
162+ return next ((agent for agent in agents if agent .name == "GroupChatManager" ),
163+ agents [0 ] if agents else None )
164+
142165 class PlanTerminationStrategy (TerminationStrategy ):
143166 """Strategy for determining when the agent group chat should terminate.
144167
@@ -154,23 +177,29 @@ def __init__(self, agents, maximum_iterations=10, automatic_reset=True):
154177 maximum_iterations: Maximum number of iterations before termination
155178 automatic_reset: Whether to reset the agent after termination
156179 """
157- super ().__init__ (agents , maximum_iterations , automatic_reset )
180+ super ().__init__ (maximum_iterations , automatic_reset )
181+ self ._agents = agents
158182
159- async def should_agent_terminate (self , agent , history ) :
160- """Check if the agent should terminate.
183+ async def should_terminate (self , history , agents = None ) -> bool :
184+ """Check if the chat should terminate.
161185
162186 Args:
163- agent: The current agent
164- history: Chat history
187+ history: Chat history as a ChatHistory object
188+ agents: List of agents (optional, uses self._agents if not provided)
165189
166190 Returns:
167- True if the agent should terminate, False otherwise
191+ True if the chat should terminate, False otherwise
168192 """
169- # Default termination conditions
170- if not history :
193+ # Default termination conditions from parent class
194+ if await super ().should_terminate (history , agents or self ._agents ):
195+ return True
196+
197+ # If no history, continue the chat
198+ if not history or not history .messages :
171199 return False
172200
173- last_message = history [- 1 ]
201+ # Get the last message
202+ last_message = history .messages [- 1 ]
174203
175204 # End the chat if the plan is completed or if human intervention is required
176205 if "plan completed" in last_message .content .lower ():
@@ -469,35 +498,72 @@ async def run_group_chat(self, user_input: str, plan_id: str = "", step_id: str
469498 await self .initialize_group_chat ()
470499
471500 try :
472- # Run the group chat
473- chat_result = await self ._agent_group_chat .invoke_async (user_input )
501+ # Run the group chat with Semantic Kernel
502+ result = await self ._agent_group_chat .invoke_async (user_input )
503+
504+ # Process the result which could be a ChatHistory object or something else
505+ if hasattr (result , "messages" ) and result .messages :
506+ messages = result .messages
507+ elif hasattr (result , "value" ) and isinstance (result .value , list ):
508+ messages = result .value
509+ else :
510+ # Fallback for other formats
511+ messages = []
512+ if isinstance (result , str ):
513+ # If it's just a string response
514+ logging .debug (f"Group chat returned a string: { result [:100 ]} ..." )
515+ await self ._memory_store .add_item (
516+ AgentMessage (
517+ session_id = self ._session_id ,
518+ user_id = self ._user_id ,
519+ plan_id = plan_id ,
520+ content = result ,
521+ source = "GroupChatManager" ,
522+ step_id = step_id ,
523+ )
524+ )
525+ return result
526+
527+ # Process the messages from the chat result
528+ final_response = None
474529
475- # Process and store results
476- messages = chat_result .value
477530 for msg in messages :
478531 # Skip the initial user message
479- if msg .role == "user" and msg .content == user_input :
532+ if hasattr ( msg , "role" ) and msg .role == "user" and msg .content == user_input :
480533 continue
481-
482- # Store agent messages in the memory
534+
535+ # Determine the source/agent name
536+ source = "assistant"
537+ if hasattr (msg , "name" ) and msg .name :
538+ source = msg .name
539+ elif hasattr (msg , "role" ) and msg .role :
540+ source = msg .role
541+
542+ # Get the message content
543+ content = msg .content if hasattr (msg , "content" ) else str (msg )
544+
545+ # Store the message in memory
483546 await self ._memory_store .add_item (
484547 AgentMessage (
485548 session_id = self ._session_id ,
486549 user_id = self ._user_id ,
487550 plan_id = plan_id ,
488- content = msg . content ,
489- source = msg . name if hasattr ( msg , "name" ) else msg . role ,
551+ content = content ,
552+ source = source ,
490553 step_id = step_id ,
491554 )
492555 )
556+
557+ # Keep track of the final response
558+ final_response = content
493559
494560 # Return the final message from the chat
495- if messages :
496- return messages [ - 1 ]. content
561+ if final_response :
562+ return final_response
497563 return "Group chat completed with no messages."
498564
499565 except Exception as e :
500- logging .error (f"Error running group chat: { str ( e ) } " )
566+ logging .exception (f"Error running group chat: { e } " )
501567 return f"Error running group chat: { str (e )} "
502568
503569 async def execute_next_step (self , session_id : str , plan_id : str ) -> str :
0 commit comments