BrandGhost
Semantic Kernel Function Calling in C#: Native vs Prompt Functions Explained

Semantic Kernel Function Calling in C#: Native vs Prompt Functions Explained

When building AI applications with large language models, function calling is one of the most powerful capabilities -- letting the LLM invoke your code to retrieve data, perform calculations, or take real-world actions. Semantic Kernel function calling in C# makes this straightforward by bridging natural language and your .NET code, exposing C# methods or prompt templates as tools the AI can intelligently execute. Understanding the distinction between native functions and prompt functions is key to building robust, maintainable AI applications.

In this article, I'll walk you through how function calling works in Semantic Kernel, explain the critical differences between native and prompt functions, show you how to configure auto-invoke behavior with FunctionChoiceBehavior options, and help you decide which approach fits your scenarios. Whether you're just getting started with Semantic Kernel plugins or leveling up your implementation, this guide provides the practical knowledge you need.

What Is Function Calling in Semantic Kernel?

Function calling in Semantic Kernel refers to the pattern where the LLM analyzes user input and determines it needs to invoke functions to fulfill the request. Instead of hallucinating an answer, the LLM returns a structured response indicating which function to call with what parameters. Semantic Kernel handles orchestration -- invoking the function, capturing results, and feeding them back to the LLM for natural language response generation.

The LLM receives tool descriptions in JSON format during chat completion requests, understanding available capabilities. When a function is needed, it responds with a function call object rather than text. Semantic Kernel intercepts this, executes your code, and continues the conversation with the output. This round-trip enables your AI to interact with databases, APIs, file systems, and any .NET code you expose.

Native Functions: C# Methods as AI Tools

Native functions in Semantic Kernel are C# methods decorated with the [KernelFunction] attribute, making them discoverable and invokable by the LLM. They're compile-time safe, strongly typed, and leverage the full .NET ecosystem. Semantic Kernel automatically generates the JSON tool description sent to the LLM, including parameter types and descriptions. Here's a complete working example:

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using System.ComponentModel;

public class MathPlugin
{
    [KernelFunction("calculate")]
    [Description("Performs basic arithmetic: add, subtract, multiply, or divide")]
    public double Calculate(
        [Description("The operation: 'add', 'subtract', 'multiply', or 'divide'")] string operation,
        [Description("The first number")] double a,
        [Description("The second number")] double b)
    {
        return operation.ToLower() switch
        {
            "add" => a + b,
            "subtract" => a - b,
            "multiply" => a * b,
            "divide" when b != 0 => a / b,
            "divide" => throw new ArgumentException("Cannot divide by zero"),
            _ => throw new ArgumentException($"Unknown operation: {operation}")
        };
    }
}

var builder = Kernel.CreateBuilder();
builder.AddOpenAIChatCompletion("gpt-4o", Environment.GetEnvironmentVariable("OPENAI_API_KEY")!);
builder.Plugins.AddFromType<MathPlugin>();
var kernel = builder.Build();

var settings = new OpenAIPromptExecutionSettings
{
    FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};

var chatService = kernel.GetRequiredService<IChatCompletionService>();
var history = new ChatHistory("You are a helpful math assistant.");
history.AddUserMessage("What is 847 multiplied by 23?");

var response = await chatService.GetChatMessageContentAsync(history, settings, kernel);
Console.WriteLine(response.Content); // LLM invoked Calculate, got result, answered naturally

Notice how the [Description] attributes on both the method and parameters help the LLM understand when and how to call this function. These descriptions become part of the tool definition JSON, so write them clearly and be specific about what values are expected. You can learn more about creating robust plugins in my guide on how to create custom plugins in Semantic Kernel.

Prompt Functions: AI-Defined Logic

Prompt functions define behavior using prompt templates instead of C# logic. You create a prompt that the LLM executes when called, useful for text transformation, translation, summarization, or tasks naturally expressed as prompts. Created with KernelFunction.CreateFromPrompt(), they support template variables replaced at invocation. Here's a translation example:

using Microsoft.SemanticKernel;

var builder = Kernel.CreateBuilder();
builder.AddOpenAIChatCompletion("gpt-4o", Environment.GetEnvironmentVariable("OPENAI_API_KEY")!);
var kernel = builder.Build();

var translateFunction = KernelFunction.CreateFromPrompt(
    """
    Translate the following text from {{$sourceLanguage}} to {{$targetLanguage}}:
    
    {{$text}}
    
    Provide only the translated text, no explanations.
    """,
    functionName: "translate",
    description: "Translates text from one language to another");

kernel.Plugins.AddFromFunctions("TranslationPlugin", [translateFunction]);

var result = await kernel.InvokeAsync(translateFunction, new KernelArguments
{
    { "sourceLanguage", "English" },
    { "targetLanguage", "Spanish" },
    { "text", "Hello, how are you?" }
});
Console.WriteLine(result.GetValue<string>());

Prompt functions offer flexibility to adjust behavior without recompiling. Load them from configuration files, allow user customization, or generate them dynamically. The tradeoff is losing compile-time safety, but for AI-centric tasks, this approach is often more maintainable than procedural logic.

The Function Calling Loop

When you send a message to the chat service with functions available and auto-invoke enabled, Semantic Kernel manages orchestration behind the scenes. The loop can execute multiple times if the LLM needs to chain function calls. The flow: you send a user message → LLM analyzes available functions → determines which to call with parameters → Semantic Kernel executes your code → result added to chat history → LLM generates natural language response. This entire sequence happens in a single async/await call when using Auto().

FunctionChoiceBehavior Options

The FunctionChoiceBehavior property acts as a strategy pattern for controlling how Semantic Kernel handles function calls. It determines whether functions are automatically invoked, manually managed, or unavailable. The most common is Auto(), which automatically executes any function the LLM requests:

var autoSettings = new OpenAIPromptExecutionSettings
{
    FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};

var response = await chatService.GetChatMessageContentAsync(history, autoSettings, kernel);
// Functions are called automatically, result includes final LLM response

For more control (security, auditing, user confirmation), use Required(). The LLM knows about functions, but you receive metadata and must invoke manually:

var manualSettings = new OpenAIPromptExecutionSettings
{
    FunctionChoiceBehavior = FunctionChoiceBehavior.Required()
};

var response = await chatService.GetChatMessageContentAsync(history, manualSettings, kernel);

// Check if the response contains function calls
foreach (var item in response.Items.OfType<FunctionCallContent>())
{
    // You decide whether to invoke
    var result = await kernel.InvokeAsync(item.PluginName, item.FunctionName, item.Arguments);
    // Then manually add result to history and continue conversation
}

Use None() when you don't want function calling. For structured output or guaranteed execution, use Required to force a specific function call:

var calculateFn = kernel.Plugins.GetFunction("MathPlugin", "calculate");
var requiredSettings = new OpenAIPromptExecutionSettings
{
    FunctionChoiceBehavior = FunctionChoiceBehavior.Required([calculateFn])
};

// LLM must call calculate function, cannot respond without using it

Choose the right FunctionChoiceBehavior based on your security requirements and control needs. For most applications, Auto() provides the best balance.

Parallel Function Calling

Modern LLMs like GPT-4 support parallel function calling, requesting multiple invocations in a single response. Instead of sequential round trips, the LLM recognizes independent functions and requests them at once. Semantic Kernel executes them concurrently, collects results, and sends them back together, dramatically reducing latency. Here's an example:

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using System.ComponentModel;

public class DataPlugin
{
    [KernelFunction("get_weather")]
    [Description("Gets the current weather for a city")]
    public string GetWeather([Description("The city name")] string city)
    {
        return $"Weather in {city}: 72°F, sunny";
    }

    [KernelFunction("get_population")]
    [Description("Gets the population of a city")]
    public string GetPopulation([Description("The city name")] string city)
    {
        return $"Population of {city}: 8.3 million";
    }

    [KernelFunction("get_timezone")]
    [Description("Gets the timezone of a city")]
    public string GetTimezone([Description("The city name")] string city)
    {
        return $"Timezone for {city}: EST (UTC-5)";
    }
}

var builder = Kernel.CreateBuilder();
builder.AddOpenAIChatCompletion("gpt-4o", Environment.GetEnvironmentVariable("OPENAI_API_KEY")!);
builder.Plugins.AddFromType<DataPlugin>();
var kernel = builder.Build();

var settings = new OpenAIPromptExecutionSettings
{
    FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};

var chatService = kernel.GetRequiredService<IChatCompletionService>();
var history = new ChatHistory();
history.AddUserMessage("Tell me about New York City -- the weather, population, and timezone.");

var response = await chatService.GetChatMessageContentAsync(history, settings, kernel);
Console.WriteLine(response.Content);
// LLM called all three functions in parallel, then synthesized the answer

The LLM recognizes the three functions are independent and can execute simultaneously. Semantic Kernel invokes them concurrently, and the LLM receives all results at once. This happens automatically with Auto().

Streaming and Function Calls

Streaming shows text as it's generated, but function calling complicates this because the LLM must finish generating function call details before execution. With GetStreamingChatMessageContentsAsync, you receive content chunks -- mostly StreamingTextContent with text fragments, but StreamingFunctionCallUpdateContent chunks for function calls. After the stream completes, aggregate chunks, invoke the function, and continue the conversation:

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;

var builder = Kernel.CreateBuilder();
builder.AddOpenAIChatCompletion("gpt-4o", Environment.GetEnvironmentVariable("OPENAI_API_KEY")!);
builder.Plugins.AddFromType<MathPlugin>();
var kernel = builder.Build();

var settings = new OpenAIPromptExecutionSettings
{
    FunctionChoiceBehavior = FunctionChoiceBehavior.Required() // Manual mode for streaming
};

var chatService = kernel.GetRequiredService<IChatCompletionService>();
var history = new ChatHistory();
history.AddUserMessage("What is 150 divided by 5?");

var fullMessage = "";
FunctionCallContent? functionCall = null;

await foreach (var chunk in chatService.GetStreamingChatMessageContentsAsync(history, settings, kernel))
{
    if (chunk.Content != null)
    {
        fullMessage += chunk.Content;
        Console.Write(chunk.Content);
    }

    // Check for function call updates
    var functionCallUpdate = chunk.Items.OfType<StreamingFunctionCallUpdateContent>().FirstOrDefault();
    if (functionCallUpdate != null)
    {
        // Aggregate function call details from streaming chunks
        // In practice, you'd accumulate these until the stream ends
        Console.WriteLine($"
Function call detected: {functionCallUpdate.Name}");
    }
}

// After stream completes, check if we need to invoke functions
// Then add results to history and continue the conversation

Streaming with function calling requires more manual orchestration. Many developers use non-streaming mode when function calling is involved, or disable auto-invoke and handle function calls after stream completion.

Comparing Native vs Prompt Functions

Understanding when to use native versus prompt functions is crucial for building maintainable AI applications. Native functions excel for system interaction, calculations, and procedural logic. Prompt functions shine for text transformation, generation, and analysis where LLMs excel. Here's a comparison:

Aspect Native Functions Prompt Functions
Definition C# methods with [KernelFunction] attribute Prompt templates with CreateFromPrompt()
Type Safety Full compile-time type checking Runtime string-based template variables
Logic Type Procedural C# code Natural language instructions
Modification Requires recompilation Can be loaded from config, changed at runtime
Best For API calls, database queries, calculations, system integration Text transformation, summarization, classification, creative tasks
Performance Fast execution of compiled code Requires LLM inference (additional latency)
Testability Standard unit testing Requires LLM to test, harder to validate
Intellisense Full IDE support String templates, limited tooling
Parameters Strongly typed with validation String-based, validated at runtime
Debugging Full debugger support Prompt engineering and experimentation

Use native functions for database queries, API calls, file operations, calculations, or .NET library integration. Use prompt functions for text classification, entity extraction, content reformatting, or tasks where LLM language understanding is the core value. As you build Semantic Kernel applications, you'll develop intuition for which approach fits each scenario.

Frequently Asked Questions

Can I mix native and prompt functions in the same plugin?

Yes! A plugin can contain both types. This is common -- use native functions for data retrieval and system integration, prompt functions for text processing. The LLM sees all functions equally and chooses based on descriptions.

How does Semantic Kernel decide which function to call?

The LLM decides, not Semantic Kernel. Semantic Kernel sends JSON descriptions of available functions to the LLM. The model analyzes the message, considers tools, and returns text or a function call request. Description quality directly impacts selection accuracy.

What happens if a function throws an exception?

With auto-invoke, Semantic Kernel catches exceptions and sends error messages back to the LLM as function results. The LLM then explains the error to the user. You can handle exceptions explicitly in functions and return meaningful messages.

Can the LLM call the same function multiple times in one conversation?

Yes, with different parameters, either in parallel during one round trip or sequentially across interactions. This is common when a question requires the same operation on different inputs, like checking weather in multiple cities.

Do I need to use OpenAI, or can I use other LLM providers?

Semantic Kernel supports multiple providers including Azure OpenAI, Anthropic Claude, and Google Gemini. Function calling has full support in OpenAI and Azure OpenAI connectors; support varies for other providers. Check connector documentation for capabilities.

Conclusion

Mastering Semantic Kernel function calling in C# opens possibilities for building AI applications beyond simple chatbots. Understanding native versus prompt functions lets you architect solutions leveraging compiled C# for system integration and LLM-powered prompts for text processing. Auto-invoke with FunctionChoiceBehavior makes it simple to start, while manual invocation provides fine-grained control.

As you build applications, you'll reach for native functions for databases, APIs, and file systems, while prompt functions handle text analysis, transformation, and generation. Parallel calling ensures responsiveness with multiple operations, and streaming provides excellent user experiences.

Start experimenting with both types in your projects. Begin with simple native functions exposing existing C# methods, then explore prompt functions for tasks previously solved with regex or string manipulation. The combination of these approaches, orchestrated by Semantic Kernel, makes .NET exceptional for production-ready AI applications.

Semantic Kernel Plugins in C#: The Complete Guide

Master Semantic Kernel plugins in C# with this guide. Learn to create native functions, prompt functions, and OpenAPI plugins with real code examples.

Semantic Kernel in C#: Complete AI Orchestration Guide

Master Semantic Kernel in C# with this complete guide. Learn plugins, agents, RAG, and vector stores to build production AI applications with .NET.

How to Create Custom Plugins for Semantic Kernel in C#

Learn how to create custom plugins for Semantic Kernel in C# step by step. Build native function plugins, prompt plugins, and multi-function plugin classes with KernelFunction, Description attributes, and dependency injection.

An error has occurred. This application may no longer respond until reloaded. Reload