Skip to content

Commit c4d01b4

Browse files
author
Heena Ugale
committed
roundrobin using SK updated SDK
1 parent 4982b0a commit c4d01b4

File tree

1 file changed

+252
-0
lines changed

1 file changed

+252
-0
lines changed
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
import asyncio
2+
import logging
3+
import os
4+
from dotenv import load_dotenv
5+
from typing import Optional
6+
7+
from base_agent import BaseAgent
8+
from semantic_kernel import Kernel
9+
10+
from semantic_kernel.agents import ChatCompletionAgent, GroupChatOrchestration, RoundRobinGroupChatManager
11+
from semantic_kernel.agents.runtime import InProcessRuntime
12+
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior
13+
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
14+
from semantic_kernel.connectors.mcp import MCPSsePlugin
15+
from semantic_kernel.contents import ChatMessageContent, AuthorRole
16+
from semantic_kernel.functions import KernelArguments
17+
18+
# Load .env
19+
load_dotenv()
20+
21+
# Logging setup
22+
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
23+
logger = logging.getLogger(__name__)
24+
25+
26+
class Agent(BaseAgent):
27+
def __new__(cls, state_store: dict, session_id: str):
28+
if session_id in state_store:
29+
return state_store[session_id]
30+
instance = super().__new__(cls)
31+
state_store[session_id] = instance
32+
return instance
33+
34+
def __init__(self, state_store: dict, session_id: str) -> None:
35+
if hasattr(self, "_constructed"):
36+
return
37+
self._constructed = True
38+
super().__init__(state_store, session_id)
39+
self._orchestration: Optional[GroupChatOrchestration] = None
40+
41+
async def _setup_team(self) -> None:
42+
if getattr(self, "_initialized", False):
43+
return
44+
45+
# Shared service config
46+
service = AzureChatCompletion(
47+
api_key=self.azure_openai_key,
48+
endpoint=self.azure_openai_endpoint,
49+
api_version=self.api_version,
50+
deployment_name=self.azure_deployment,
51+
)
52+
53+
# Connect plugin
54+
self.contoso_plugin = MCPSsePlugin(
55+
name="ContosoMCP",
56+
description="Contoso MCP Plugin",
57+
url=self.mcp_server_uri,
58+
headers={"Content-Type": "application/json"},
59+
timeout=30,
60+
)
61+
await self.contoso_plugin.connect()
62+
63+
# Specialist kernel
64+
specialist_kernel = Kernel()
65+
specialist_kernel.add_service(service)
66+
specialist_kernel.add_plugin(self.contoso_plugin, plugin_name="ContosoMCP")
67+
68+
def make_agent(name, description, instructions, included_tools=[]):
69+
settings = specialist_kernel.get_prompt_execution_settings_from_service_id("default")
70+
function_choice_behavior = FunctionChoiceBehavior.Auto(
71+
filters={"included_functions": included_tools} if included_tools else None
72+
)
73+
74+
return ChatCompletionAgent(
75+
name=name,
76+
description=description,
77+
instructions=instructions,
78+
service=service,
79+
function_choice_behavior=function_choice_behavior,
80+
kernel=specialist_kernel,
81+
)
82+
83+
84+
participants = [
85+
make_agent(
86+
name="crm_billing",
87+
description="CRM & Billing Agent",
88+
instructions=(
89+
"You are the CRM & Billing Agent.\n"
90+
"- Query structured CRM / billing systems for account, subscription, "
91+
"invoice, and payment information as needed.\n"
92+
"- For each response you **MUST** cross‑reference relevant *Knowledge Base* articles on billing policies, payment "
93+
"processing, refund rules, etc., to ensure responses are accurate "
94+
"and policy‑compliant.\n"
95+
"- Reply with concise, structured information and flag any policy "
96+
"concerns you detect.\n"
97+
"Only respond with data you retrieve using your tools.\n"
98+
"DO NOT respond to anything out of your domain."
99+
),
100+
included_tools=[
101+
"ContosoMCP-get_all_customers",
102+
"ContosoMCP-get_customer_detail",
103+
"ContosoMCP-get_subscription_detail",
104+
"ContosoMCP-get_invoice_payments",
105+
"ContosoMCP-pay_invoice",
106+
"ContosoMCP-get_data_usage",
107+
"ContosoMCP-search_knowledge_base",
108+
"ContosoMCP-get_customer_orders",
109+
"ContosoMCP-update_subscription",
110+
"ContosoMCP-get_billing_summary",
111+
],
112+
),
113+
make_agent(
114+
name="product_promotions",
115+
description="Product & Promo Agent",
116+
instructions=(
117+
"You are the Product & Promotions Agent.\n"
118+
"- Retrieve promotional offers, product availability, eligibility "
119+
"criteria, and discount information from structured sources.\n"
120+
"- For each response you **MUST** cross‑reference relevant *Knowledge Base* FAQs, terms & conditions, "
121+
"and best practices.\n"
122+
"- Provide factual, up‑to‑date product/promo details."
123+
"Only respond with data you retrieve using your tools.\n"
124+
"DO NOT respond to anything out of your domain."
125+
),
126+
included_tools=[
127+
"ContosoMCP-get_all_customers",
128+
"ContosoMCP-get_customer_detail",
129+
"ContosoMCP-get_promotions",
130+
"ContosoMCP-get_eligible_promotions",
131+
"ContosoMCP-search_knowledge_base",
132+
"ContosoMCP-get_products",
133+
"ContosoMCP-get_product_detail",
134+
],
135+
),
136+
make_agent(
137+
name="security_authentication",
138+
description="Security & Authentication Agent",
139+
instructions=(
140+
"You are the Security & Authentication Agent.\n"
141+
"- Investigate authentication logs, account lockouts, and security "
142+
"incidents in structured security databases.\n"
143+
"- For each response you **MUST** cross‑reference relevant *Knowledge Base* security policies and "
144+
"lockout troubleshooting guides.\n"
145+
"- Return clear risk assessments and recommended remediation steps."
146+
"Only respond with data you retrieve using your tools.\n"
147+
"DO NOT respond to anything out of your domain."
148+
),
149+
included_tools=[
150+
"ContosoMCP-get_all_customers",
151+
"ContosoMCP-get_customer_detail",
152+
"ContosoMCP-get_security_logs",
153+
"ContosoMCP-search_knowledge_base",
154+
"ContosoMCP-unlock_account",
155+
],
156+
),
157+
make_agent(
158+
name="analysis_planning",
159+
description="Analysis & Planning Agent",
160+
instructions=(
161+
"You are the Analysis & Planning Agent (the planner/orchestrator).\n"
162+
"\n"
163+
"1. Decide if the user’s request can be satisfied directly:\n"
164+
" - If YES (e.g. greetings, very simple Q&A), answer immediately using the prefix:\n"
165+
" FINAL ANSWER: <your reply>\n"
166+
"\n"
167+
"2. Otherwise you MUST delegate atomic sub‑tasks one‑by‑one to specialists.\n"
168+
" - Output format WHEN DELEGATING (strict):\n"
169+
" <specialist_name>: <task>\n"
170+
" – No other text, no quotation marks, no ‘FINAL ANSWER’.\n"
171+
" - Delegate only one sub‑task per turn, then wait for the specialist’s reply.\n"
172+
"\n"
173+
"3. After all required information is gathered, compose ONE comprehensive response and\n"
174+
" send it to the user prefixed with:\n"
175+
" FINAL ANSWER: <your synthesized reply>\n"
176+
"\n"
177+
"4. If you need clarification from the user, ask it immediately and prefix with\n"
178+
" FINAL ANSWER: <your question>\n"
179+
"\n"
180+
"Specialist directory – choose the SINGLE best match for each sub‑task:\n"
181+
"- crm_billing – Accesses CRM & billing systems for account, subscription, invoice,\n"
182+
" payment status, refunds and policy compliance questions.\n"
183+
"- product_promotions – Provides product catalogue details, current promotions,\n"
184+
" discount eligibility rules and T&Cs from structured sources & FAQs.\n"
185+
"- security_authentication – Investigates authentication logs, account lock‑outs,\n"
186+
" security incidents; references security KBs and recommends remediation steps.\n"
187+
"\n"
188+
"STRICT RULES:\n"
189+
"- Do not emit planning commentary or bullet lists to the user.\n"
190+
"- Only ‘FINAL ANSWER’ messages or specialist delegations are allowed.\n"
191+
"- After all agents discuss, make sure you respond only relevant information asked as per user request.\n"
192+
"- Never include ‘FINAL ANSWER’ when talking to a specialist.\n"
193+
),
194+
),
195+
]
196+
197+
self._orchestration = GroupChatOrchestration(
198+
members=participants,
199+
manager=RoundRobinGroupChatManager(
200+
max_rounds=1,
201+
allow_repeat_speaker=False,
202+
initial_speaker="analysis_planning",
203+
completion_criteria=lambda message: str(message.content).lower().startswith("final answer:")
204+
)
205+
)
206+
207+
# <-- CHANGED: Create and start runtime once here***
208+
self._group_chat_runtime = InProcessRuntime()
209+
self._group_chat_runtime.start()
210+
211+
self._initialized = True
212+
213+
async def chat_async(self, prompt: str) -> str:
214+
await self._setup_team()
215+
216+
if not self._orchestration or not self._group_chat_runtime:
217+
return "Multi-agent system not initialized."
218+
219+
# <-- CHANGED: Removed unsupported 'agent_response_callback' argument from invoke call***
220+
orchestration_result = await self._orchestration.invoke(
221+
task=prompt,
222+
runtime=self._group_chat_runtime,
223+
# agent_response_callback=agent_response_callback, # Removed because not supported
224+
)
225+
226+
value = await orchestration_result.get()
227+
print(f"***** Final Result *****\n{value}")
228+
229+
# <-- CHANGED: Removed stopping runtime here - will stop later in main***
230+
# await self._group_chat_runtime.stop_when_idle()
231+
232+
self.append_to_chat_history([
233+
{"role": "user", "content": prompt},
234+
{"role": "assistant", "content": value}
235+
])
236+
237+
return value
238+
239+
# Demo runner
240+
if __name__ == "__main__":
241+
async def _demo():
242+
dummy_state: dict = {}
243+
agent = Agent(dummy_state, session_id="demo")
244+
user_question = "My customer id is 101, what is my current balance?"
245+
answer = await agent.chat_async(user_question)
246+
print("\n>>> Assistant reply:\n", answer)
247+
try:
248+
await agent.contoso_plugin.close()
249+
except Exception as exc:
250+
logger.warning(f"SSE plugin close failed: {exc}")
251+
252+
asyncio.run(_demo())

0 commit comments

Comments
 (0)