Build an AI Agent with Microsoft Semantic Kernel
Microsoft Semantic Kernel is an enterprise-grade open-source SDK for integrating AI models into production applications. It is the framework powering Copilot across Microsoft 365, Bing, and Azure — meaning it is battle-tested at scale in ways most frameworks are not.
Semantic Kernel's core philosophy is that AI should be a plugin in your existing software stack, not the reverse. It provides a kernel orchestration layer, a plugin system for wrapping existing code as AI-callable functions, and a Planner that lets the model dynamically chain plugins to accomplish goals.
In this tutorial you will build an enterprise HR assistant that combines a semantic (prompt-based) plugin for policy lookup, a native (Python function) plugin for database queries, and the Function Calling Stepwise Planner to orchestrate them automatically.
What You'll Learn#
- How to install Semantic Kernel and configure it with OpenAI or Azure OpenAI
- How to create native plugins from Python classes and functions
- How to create semantic plugins from prompt templates
- How to use the Stepwise Planner for automatic goal decomposition
- How to configure kernel memory and chat history for multi-turn conversations
- How to deploy against Azure OpenAI for enterprise compliance
Prerequisites#
- Python 3.10 or higher installed
- An OpenAI API key, OR an Azure OpenAI deployment (endpoint + API key + deployment name)
- Basic understanding of AI agents and agent frameworks
- Comfort with Python classes and decorators
Step 1: Project Setup#
mkdir semantic-kernel-demo && cd semantic-kernel-demo
python -m venv .venv && source .venv/bin/activate
# Core Semantic Kernel package
pip install semantic-kernel python-dotenv
# Optional: Azure OpenAI support is included in the base package
Create .env:
# For standard OpenAI
OPENAI_API_KEY=sk-...
# For Azure OpenAI (comment out the above and uncomment below)
# AZURE_OPENAI_API_KEY=...
# AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com/
# AZURE_OPENAI_DEPLOYMENT=gpt-4o
Step 2: Configure the Kernel#
The Kernel is the central orchestrator in Semantic Kernel. Everything — plugins, memory, AI services — is registered on it.
# kernel_setup.py
import os
from dotenv import load_dotenv
import semantic_kernel as sk
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion, AzureChatCompletion
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior
from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.open_ai_prompt_execution_settings import (
OpenAIChatPromptExecutionSettings,
)
load_dotenv()
def create_kernel() -> sk.Kernel:
"""Create and configure the Semantic Kernel instance."""
kernel = sk.Kernel()
# Choose your AI backend
if os.getenv("AZURE_OPENAI_API_KEY"):
service = AzureChatCompletion(
service_id="chat",
deployment_name=os.getenv("AZURE_OPENAI_DEPLOYMENT", "gpt-4o"),
endpoint=os.getenv("AZURE_OPENAI_ENDPOINT"),
api_key=os.getenv("AZURE_OPENAI_API_KEY"),
)
else:
service = OpenAIChatCompletion(
service_id="chat",
ai_model_id="gpt-4o",
api_key=os.getenv("OPENAI_API_KEY"),
)
kernel.add_service(service)
return kernel
Step 3: Create a Native Plugin#
Native plugins wrap regular Python code as kernel functions that the AI can call. Use the @kernel_function decorator to mark methods as callable tools.
# plugins/hr_plugin.py
import json
from datetime import datetime, date
from semantic_kernel.functions import kernel_function
class HRPlugin:
"""Plugin for querying HR data and employee information."""
def __init__(self):
# In production, this would be a database connection
self._employees = {
"E001": {
"name": "Alice Chen",
"department": "Engineering",
"title": "Senior AI Engineer",
"hire_date": "2022-03-15",
"pto_balance_days": 18,
"manager_id": "E042",
},
"E002": {
"name": "Bob Martinez",
"department": "Product",
"title": "Product Manager",
"hire_date": "2021-07-01",
"pto_balance_days": 12,
"manager_id": "E039",
},
}
@kernel_function(
name="get_employee_info",
description="Retrieve an employee's profile, title, department, and PTO balance by employee ID.",
)
def get_employee_info(self, employee_id: str) -> str:
"""Look up employee information.
Args:
employee_id: The unique employee identifier (e.g., 'E001').
"""
employee = self._employees.get(employee_id.upper())
if not employee:
return json.dumps({"error": f"Employee {employee_id} not found."})
return json.dumps(employee)
@kernel_function(
name="check_pto_availability",
description="Check if an employee has enough PTO days available for a requested time-off period.",
)
def check_pto_availability(self, employee_id: str, days_requested: int) -> str:
"""Check PTO availability for an employee.
Args:
employee_id: The employee's ID.
days_requested: Number of PTO days being requested.
"""
employee = self._employees.get(employee_id.upper())
if not employee:
return json.dumps({"error": f"Employee {employee_id} not found."})
available = employee["pto_balance_days"]
approved = days_requested <= available
return json.dumps({
"employee_id": employee_id,
"days_requested": days_requested,
"days_available": available,
"approved": approved,
"message": f"Request {'approved' if approved else 'denied'}. "
f"{'Remaining' if approved else 'Shortfall'}: "
f"{abs(available - days_requested)} days.",
})
@kernel_function(
name="get_policy",
description="Retrieve company HR policy text for a given policy category.",
)
def get_policy(self, policy_name: str) -> str:
"""Retrieve HR policy text.
Args:
policy_name: Policy category (e.g., 'remote_work', 'pto', 'onboarding').
"""
policies = {
"pto": "Employees accrue 1.5 PTO days per month. Unused PTO up to 10 days rolls over annually.",
"remote_work": "Full-time remote is allowed for all roles. Employees must be available 9 AM–3 PM in their local timezone.",
"onboarding": "New hires complete a 30-day onboarding program including IT setup, product training, and manager 1:1s.",
}
return policies.get(policy_name.lower(), f"Policy '{policy_name}' not found. Try: pto, remote_work, onboarding.")
Step 4: Create a Semantic Plugin#
Semantic plugins use prompt templates rather than Python code. They are powerful for tasks like summarization, classification, or tone transformation.
# plugins/writing_plugin.py
from semantic_kernel.functions import KernelFunction
from semantic_kernel.prompt_template import PromptTemplateConfig
POLICY_SUMMARY_TEMPLATE = """
You are an HR communication specialist. Summarize the following policy excerpt in plain,
friendly language suitable for an employee who is unfamiliar with corporate jargon.
Policy Text:
{{$policy_text}}
Target Employee Level: {{$employee_level}}
Write a 2-3 sentence summary that is accurate, encouraging, and uses simple language.
"""
def create_writing_plugin(kernel):
"""Register semantic (prompt-based) functions on the kernel."""
kernel.add_function(
plugin_name="WritingPlugin",
function_name="summarize_policy",
prompt=POLICY_SUMMARY_TEMPLATE,
description="Summarize an HR policy in plain language for an employee.",
template_format="semantic-kernel",
)
Step 5: Build the Agent with Chat History#
Semantic Kernel provides ChatHistory for managing multi-turn conversations. Combined with function calling, this creates a fully stateful agent loop.
# agent.py
import asyncio
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior
from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.open_ai_prompt_execution_settings import (
OpenAIChatPromptExecutionSettings,
)
from semantic_kernel.contents import ChatHistory
from kernel_setup import create_kernel
from plugins.hr_plugin import HRPlugin
async def run_hr_assistant():
kernel = create_kernel()
# Register the native HR plugin
kernel.add_plugin(HRPlugin(), plugin_name="HRPlugin")
# Configure execution settings — enable automatic function calling
settings = OpenAIChatPromptExecutionSettings(
service_id="chat",
max_tokens=2048,
temperature=0.1, # Low temperature for factual HR queries
function_choice_behavior=FunctionChoiceBehavior.Auto(
filters={"included_plugins": ["HRPlugin"]}
),
)
# Initialize chat history with system instructions
chat_history = ChatHistory()
chat_history.add_system_message(
"""You are a helpful HR assistant. Help employees find information about their
PTO balances, company policies, and HR processes. Always be accurate and cite
the data source when providing employee-specific information."""
)
chat_service = kernel.get_service(service_id="chat")
# Multi-turn conversation loop
print("HR Assistant ready. Type 'quit' to exit.\n")
while True:
user_input = input("You: ").strip()
if user_input.lower() in ("quit", "exit", "q"):
break
if not user_input:
continue
chat_history.add_user_message(user_input)
# Get response with automatic function calling
response = await chat_service.get_chat_message_content(
chat_history=chat_history,
settings=settings,
kernel=kernel,
)
assistant_message = str(response)
chat_history.add_assistant_message(assistant_message)
print(f"\nAssistant: {assistant_message}\n")
if __name__ == "__main__":
asyncio.run(run_hr_assistant())
Step 6: Using the Stepwise Planner#
The Stepwise Planner enables the model to autonomously decompose a high-level goal into a sequence of function calls without you specifying the order. It is powerful for complex, multi-step queries.
# planner_demo.py
import asyncio
from semantic_kernel.planners.function_calling_stepwise_planner import (
FunctionCallingStepwisePlanner,
FunctionCallingStepwisePlannerOptions,
)
from kernel_setup import create_kernel
from plugins.hr_plugin import HRPlugin
async def run_planner():
kernel = create_kernel()
kernel.add_plugin(HRPlugin(), plugin_name="HRPlugin")
# Configure planner options
options = FunctionCallingStepwisePlannerOptions(
max_iterations=10,
max_tokens=4096,
)
planner = FunctionCallingStepwisePlanner(service_id="chat", options=options)
# Complex goal that requires multiple tool calls
goal = (
"Employee E001 wants to take 5 days of PTO. "
"Check if they have enough PTO balance, and if approved, "
"also retrieve the company PTO policy so I can share it with them."
)
print(f"Goal: {goal}\n")
result = await planner.invoke(kernel=kernel, question=goal)
print("=== Planner Result ===")
print(result.final_answer)
print("\n=== Steps Executed ===")
for i, step in enumerate(result.chat_history.messages, 1):
if step.role.value == "tool":
print(f" Step {i}: Tool call result — {str(step)[:100]}...")
if __name__ == "__main__":
asyncio.run(run_planner())
What's Next#
You have built an enterprise-ready agent with plugins, semantic templates, and autonomous planning. Next steps:
- Compare with LangChain: The Semantic Kernel vs LangChain comparison covers when each framework is the better choice
- Directory entry: See the Semantic Kernel directory entry for advanced features like process frameworks and memory stores
- OpenAI alternative: Learn building with the OpenAI Agents SDK for a lighter-weight option
- LangChain tutorial: Explore building with LangChain for the most mature ecosystem
- Agent glossary: Deepen your understanding of agent frameworks and how they compare
Frequently Asked Questions#
What is the difference between a native plugin and a semantic plugin?
A native plugin wraps Python code — it runs deterministically and can call APIs, databases, or any Python library. A semantic plugin is a prompt template — it uses the AI model itself to process text. Both are registered the same way and are callable by the planner or via function calling.
How does Semantic Kernel differ from LangChain?
Semantic Kernel is designed for enterprise integration with Microsoft's ecosystem (Azure, Office 365, Copilot). It emphasizes the plugin model — wrapping your existing code — and has first-class Azure OpenAI support including managed identity authentication. LangChain has a broader community ecosystem and more integrations, but Semantic Kernel often has simpler, less abstracted APIs for common patterns.
Can I use Semantic Kernel with local models?
Yes. Semantic Kernel supports Ollama and any OpenAI-compatible local endpoint via OpenAIChatCompletion with a custom base_url. For Azure-local scenarios, it also supports Azure OpenAI deployments in sovereign clouds.
What is Process Framework in Semantic Kernel?
Process Framework (introduced in SK v1.x) is a workflow orchestration layer for long-running, stateful business processes. It lets you define multi-step workflows with explicit state machines, error handling, and checkpointing — useful for enterprise workflows that might span hours or days.
Is Semantic Kernel production-ready?
Yes. It is the technology stack powering Microsoft Copilot products used by millions daily. The Python SDK reached v1.0 stability in 2024. Microsoft provides official support, SLAs via Azure, and the framework has been through extensive security review for regulated industries.