n4nAI

Semantic Kernel agent tutorial with n4n.ai function calling

Hands-on tutorial: build a Semantic Kernel ChatCompletionAgent that calls Python functions via the n4n.ai OpenAI-compatible gateway. Step-by-step code.

n4n Team2 min read547 words

Audio narration

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

This tutorial builds a working semantic kernel agent n4n.ai function calling pipeline: a Kernel-based agent that orchestrates native Python functions through an OpenAI-compatible gateway. You’ll stand up a ChatCompletionAgent that calls a weather plugin and a calculator plugin, then watch the model drive the calls. By the end you’ll have runnable code that demonstrates tool use without hand-rolling JSON schemas.

Prerequisites

  • Python 3.10 or newer
  • semantic-kernel Python package (version 1.12.0+; agent classes are in preview but stable for local use)
  • An API key from a gateway that exposes an OpenAI-compatible /v1/chat/completions route
  • Comfort with async/await and environment variables

Set up a clean environment:

python -m venv .venv
source .venv/bin/activate
pip install "semantic-kernel>=1.12.0" python-dotenv

Project layout

Keep everything in one file for the demo. You only need two artifacts:

agent_demo.py
.env

In .env, store the key:

N4N_API_KEY=sk-your-key-here

Define function plugins

Semantic Kernel turns a class method decorated with @kernel_function into a callable tool. The agent uses the method name, docstring, and type hints to generate the OpenAI function schema automatically—no manual JSON.

from semantic_kernel.functions import kernel_function

class WeatherPlugin:
    @kernel_function(
        name="get_weather",
        description="Return current temperature and condition for a given city",
    )
    def get_weather(self, city: str) -> str:
        # Stand-in for a real HTTP call
        data = {"city": city, "temp_c": 19, "condition": "partly cloudy"}
        return str(data)

class CalculatorPlugin:
    @kernel_function(
        name="compute",
        description="Evaluate a basic arithmetic expression, e.g. '3 * (4 + 5)'",
    )
    def compute(self, expression: str) -> str:
        try:
            result = eval(expression, {"__builtins__": {}}, {})
            return str(result)
        except Exception as e:
            return f"error: {e}"

Returning a string keeps the contract simple. The model receives exactly that text as the tool result.

Configure the kernel and endpoint

Create a Kernel and register the OpenAI chat connector pointed at the n4n.ai OpenAI-compatible endpoint. That single endpoint fronts 240+ models and handles provider fallback, so you don’t need per-provider keys in your code.

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()
kernel.add_service(
    OpenAIChatCompletion(
        ai_model_id="gpt-4o-mini",
        api_key=os.getenv("N4N_API_KEY"),
        endpoint="https://api.n4n.ai/v1",
    )
)

Swap ai_model_id to any supported model without touching plugin or agent code.

Build the agent

ChatCompletionAgent wraps the kernel, system instructions, and a list of plugins. It runs the standard ReAct-style loop: model emits a tool call → kernel executes → result fed back → model answers.

from semantic_kernel.agents import ChatCompletionAgent

agent = ChatCompletionAgent(
    kernel=kernel,
    name="ToolAgent",
    instructions="You are a concise assistant. Use provided functions when they help answer.",
    functions=[WeatherPlugin(), CalculatorPlugin()],
)

Run and observe function calls

Invoke the agent from an async entrypoint:

import asyncio

async def main():
    response = await agent.invoke("What is the weather in Lisbon and what is 12 * 8?")
    print("FINAL:", response.content)

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

Expected output (wording varies by model):

FINAL: Lisbon is partly cloudy at 19°C. 12 * 8 equals 96.

The gateway logged two tool executions server-side. To confirm locally, add a print inside each plugin method and rerun.

Inspecting intermediate steps

For debugging, log inside the plugin:

@kernel_function(name="get_weather", description="Return weather for a city")
def get_weather(self, city: str) -> str:
    print(f"[plugin] get_weather -> {city}")
    return str({"city": city, "temp_c": 19, "condition": "partly cloudy"})

Checkpoint output after rerun:

[plugin] get_weather -> Lisbon
[plugin] compute -> 12 * 8
FINAL: Lisbon is partly cloudy at 19°C. 12 * 8 equals 96.

You now have explicit proof the semantic kernel agent n4n.ai function calling path executed your Python code, not the model’s internal knowledge.

Streaming responses

For chat UIs, stream tokens as they arrive:

async def main_stream():
    async for chunk in agent.invoke_streaming("Weather in Paris?"):
        if chunk.content:
            print(chunk.content, end="", flush=True)

The agent still calls the function before streaming the final natural-language answer.

Testing plugins without the model

Unit-test the native functions directly through the kernel to avoid burning tokens:

async def test_plugins():
    result = await kernel.invoke(WeatherPlugin().get_weather, city="Tokyo")
    print("plugin output:", result.value)

This validates schema and return formatting independent of model behavior.

Adding routing directives

The semantic kernel agent n4n.ai function calling setup can pass provider routing hints through the gateway. If you need a specific backend, send metadata in the invoke call. n4n.ai honors client routing directives and forwards cache-control hints, so repeated weather queries can hit provider-side caches.

response = await agent.invoke(
    "Weather in Lisbon again?",
    metadata={"routing": {"provider": "anthropic"}, "cache": {"ttl": 300}},
)

This is optional; default round-robin fallback already covers provider degradation.

Error handling

Never let a plugin raise into the agent unless you want the exception serialized. Wrap external calls:

def get_weather(self, city: str) -> str:
    try:
        # real http request here
        return fetch(city)
    except HTTPError as e:
        return f"unavailable: {e.status}"

The model sees the error string and can apologize or ask for a different city.

Why this pattern holds up

The semantic kernel agent n4n.ai function calling approach decouples model selection from tool logic. You can switch ai_model_id to a cheaper model or a different provider without editing plugin code. Because the gateway meters per-token usage, you get accounting for free across every agent turn.

Full file

import os, asyncio
from dotenv import load_dotenv
from semantic_kernel import Kernel
from semantic_kernel.functions import kernel_function
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
from semantic_kernel.agents import ChatCompletionAgent

load_dotenv()

class WeatherPlugin:
    @kernel_function(name="get_weather", description="Return weather for a city")
    def get_weather(self, city: str) -> str:
        return str({"city": city, "temp_c": 19, "condition": "partly cloudy"})

class CalculatorPlugin:
    @kernel_function(name="compute", description="Evaluate arithmetic")
    def compute(self, expression: str) -> str:
        try:
            return str(eval(expression, {"__builtins__": {}}, {}))
        except Exception as e:
            return f"error: {e}"

kernel = Kernel()
kernel.add_service(OpenAIChatCompletion(
    ai_model_id="gpt-4o-mini",
    api_key=os.getenv("N4N_API_KEY"),
    endpoint="https://api.n4n.ai/v1",
))

agent = ChatCompletionAgent(
    kernel=kernel,
    name="ToolAgent",
    instructions="Concise assistant using functions when helpful.",
    functions=[WeatherPlugin(), CalculatorPlugin()],
)

async def main():
    r = await agent.invoke("Weather in Lisbon and 12 * 8?")
    print("FINAL:", r.content)

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

Run python agent_demo.py. You now have a runnable semantic kernel agent n4n.ai function calling demo that extends to any native or semantic function you register.

Tagssemantic-kernelagentfunction-callingn4n-ai

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 →