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-kernelPython 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/completionsroute - Comfort with
async/awaitand 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.