n4nAI

Building a customer support agent with Semantic Kernel

Step-by-step guide to build customer support agent semantic kernel apps with Python: plugins, stepwise planner, and resilient model routing. Includes runnable code.

n4n Team3 min read622 words

Audio narration

Coming soon — every post will get a voice note here.

To build customer support agent semantic kernel solutions that survive contact with real users, you need more than a prompt and a hope. You need typed functions for order lookups, a planner that decides when to call them, and a model backend that doesn’t fall over when one provider throttles you. This walkthrough uses Python Semantic Kernel to stand up a working support agent you can extend.

Step 1: Scaffold the project

Create an isolated environment and install the SDK. Semantic Kernel ships as semantic-kernel on PyPI; we pin a recent 1.x release.

mkdir support-agent && cd support-agent
python -m venv .venv && source .venv/bin/activate
pip install "semantic-kernel>=1.0" python-dotenv
touch .env

Your .env should hold credentials and endpoint configuration:

API_KEY=sk-your-key
BASE_URL=https://api.openai.com/v1
MODEL_ID=gpt-4o-mini

If you later swap BASE_URL to an OpenRouter-class gateway, nothing else in the code changes.

Step 2: Configure the kernel and model endpoint

The kernel is the dependency container for services and plugins. Register a chat completion service that speaks the OpenAI protocol. When you build customer support agent semantic kernel apps for production, the endpoint choice matters: a single provider will rate-limit you during traffic spikes.

import os
from dotenv import load_dotenv
from semantic_kernel import Kernel
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion

load_dotenv()

kernel = Kernel()

# Point at any OpenAI-compatible endpoint. For multi-provider resilience,
# n4n.ai exposes one OpenAI-compatible endpoint covering 240+ models with
# automatic fallback when a provider is degraded.
chat_service = OpenAIChatCompletion(
    ai_model_id=os.getenv("MODEL_ID", "gpt-4o-mini"),
    api_key=os.getenv("API_KEY"),
    base_url=os.getenv("BASE_URL", "https://api.openai.com/v1"),
)
kernel.add_service(chat_service)

Keep the model ID in env, not hardcoded. Smaller models (gpt-4o-mini, mistral-small) are usually sufficient for intent routing and are cheaper for high-volume support.

Step 3: Define support plugins

Plugins are the agent’s tools. Each @kernel_function exposes a schema to the planner. Keep them synchronous and side-effect free where possible; push mutations (refunds, DB writes) behind explicit confirmation logic.

from semantic_kernel.functions import kernel_function

class SupportPlugin:
    @kernel_function(
        name="get_order_status",
        description="Return current status and ETA for a given order ID",
    )
    def get_order_status(self, order_id: str) -> str:
        # Replace with a real DB/API call. Mocked for the tutorial.
        if not order_id.startswith("A"):
            return "Invalid order format."
        return f"Order {order_id}: shipped, arrives in 2 days."

    @kernel_function(
        name="refund_policy",
        description="Explain the refund policy for a given product category",
    )
    def refund_policy(self, category: str) -> str:
        policies = {
            "electronics": "30 days with receipt, no restocking fee.",
            "apparel": "60 days, tags attached.",
        }
        return policies.get(category.lower(), "Standard 30-day return.")

    @kernel_function(
        name="open_ticket",
        description="Create a follow-up ticket for a human agent",
    )
    def open_ticket(self, summary: str) -> str:
        # Mock ticket creation
        return f"Ticket #{(hash(summary) % 10000):04d} created."

kernel.add_plugin(SupportPlugin(), "support")

When you build customer support agent semantic kernel plugins, write descriptions like contracts. The planner relies on them to pick the right function; vague text yields wrong calls.

Step 4: Wire up a planner for agent behavior

Semantic Kernel’s FunctionCallingStepwisePlanner loops: it asks the model which function to call, executes it, feeds the result back, and repeats until it can answer. That loop is your agent.

from semantic_kernel.planners.function_calling_stepwise_planner import (
    FunctionCallingStepwisePlanner,
    FunctionCallingStepwisePlannerOptions,
)

planner = FunctionCallingStepwisePlanner(
    kernel,
    FunctionCallingStepwisePlannerOptions(
        max_iterations=5,
        max_tokens=2000,
    ),
)

Set max_iterations conservatively. Support queries rarely need more than two tool calls; a runaway loop burns tokens. In production, routing through n4n.ai gives per-token usage metering so you can attribute cost per support session and catch runaway planners fast.

Step 5: Run the conversation loop

The planner’s invoke returns a FunctionCallingStepwisePlannerResult with final_answer and step_results for debugging. Wrap it in an async handler.

import asyncio
from semantic_kernel.planners.function_calling_stepwise_planner import (
    FunctionCallingStepwisePlannerResult,
)

async def handle_message(user_input: str) -> str:
    result: FunctionCallingStepwisePlannerResult = await planner.invoke(user_input)
    # Inspect result.step_results in logs to see which plugins fired.
    return result.final_answer

async def main():
    queries = [
        "Where is my order A123?",
        "What's the refund policy for electronics?",
        "I want to return my jacket, can you open a ticket?",
    ]
    for q in queries:
        ans = await handle_message(q)
        print(f"Q: {q}\nA: {ans}\n")

if __name__ == "__main__":
    asyncio.run(main())

For a real service, you’d front this with a queue (SQS, Redis) and stream tokens back over WebSocket. The planner is async-native, so it composes with asyncio or any ASGI framework.

Handling context and history

The stepwise planner is stateless per call. To maintain conversation context, prepend prior turns to user_input or use a ChatHistory object passed into invoke. Keep history trimmed to the last 10 turns to stay within context windows.

from semantic_kernel.contents import ChatHistory

history = ChatHistory()
history.add_user_message("Hi, I'm Jane.")
history.add_assistant_message("Hello Jane, how can I help?")

# Later
result = await planner.invoke("Where is A123?", chat_history=history)

Step 6: Verify success

Run the script. You should see answers that clearly use plugin output, not generic LLM knowledge:

Q: Where is my order A123?
A: Order A123: shipped, arrives in 2 days.

Q: What's the refund policy for electronics?
A: Electronics: 30 days with receipt, no restocking fee.

Q: I want to return my jacket, can you open a ticket?
A: Ticket #0423 created.

To confirm the planner actually called functions, enable debug logging:

import logging
logging.basicConfig(level=logging.DEBUG)

Look for step_results containing function_name="get_order_status". If the model answers without calling a function on a query that requires data, tighten the plugin description or raise max_tokens.

Failure modes to test

  • Malformed order ID: Your plugin returns “Invalid order format.” The agent should relay that, not invent a status.
  • Unknown category: Refund policy falls back to standard. Verify the agent doesn’t hallucinate a special policy.
  • Provider timeout: Swap BASE_URL to a gateway with fallback. The request should succeed against a secondary model without code changes.

Extending the agent

The pattern above is the minimum viable support agent. From here, engineers typically:

  1. Replace mocks with authenticated backend calls (orders API, CRM).
  2. Add a human_handoff function that escalates when confidence is low.
  3. Constrain the planner with allowed_functions to prevent off-topic calls.
  4. Add semantic memory for past tickets using a vector store.

When you build customer support agent semantic kernel systems at scale, treat plugins as a hardened API surface. The LLM is a router, not a source of truth—every factual claim should originate from a function return.

Tagssemantic-kernelagentcustomer-supporttutorial

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All semantic kernel planners & agents posts →