Skip to content

Commit 4bb348a

Browse files
author
Heena Ugale
committed
RR collab final version
1 parent 1c82e8d commit 4bb348a

File tree

1 file changed

+232
-0
lines changed

1 file changed

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

0 commit comments

Comments
 (0)