Skip to main content

Fix Semantic Kernel Template HTML-Encoding & Plugin Serialization Errors

Verified Apr 2026 Intermediate Original .NET 10 Microsoft.SemanticKernel 1.54.0
By Rajesh Mishra · Mar 12, 2026 · 8 min read
In 30 Seconds

Troubleshooting guide for Semantic Kernel template and plugin errors. Covers: HTML-encoding breaking change in templates, complex type serialization failures in plugin functions, and KernelException handling patterns.

⚠️
Error Fix Guide

Root cause analysis and verified fix. Code examples use Microsoft.SemanticKernel 1.54.0.

✓ SOLVED

Fixes at a Glance

  1. HTML-encoded template arguments — switch from the default Handlebars renderer to the PromptTemplateConfig with AllowDangerouslySetContent = true, or pre-encode your inputs correctly for the template engine
  2. Complex type serialisation failure — register a custom JsonConverter for your type or serialise to a string before passing to the kernel function
  3. KernelException — Function Not Found — verify the plugin name and function name match exactly (case-sensitive) and that the plugin was registered before the template was rendered

Error 1: Template Arguments Are HTML-Encoded

What You See

Your prompt template contains code or JSON, but the LLM receives garbled text:

// Expected prompt sent to LLM:
Analyze this code: var list = new List<string>();

// Actual prompt sent to LLM:
Analyze this code: var list = new List&lt;string&gt;();

The LLM either produces incorrect output or complains about malformed code in the prompt.

Why It Happens

Semantic Kernel version 1.25 introduced HTML-encoding of template arguments as a security measure against prompt injection. Any argument value containing <, >, ", ', or & gets encoded:

CharacterEncoded As
<&lt;
>&gt;
"&quot;
'&#x27;
&&amp;

This is the right default for user-facing input, but it breaks prompts that intentionally contain code, JSON, or markup.

The Fix

Option 1: Disable encoding for trusted templates

If your template arguments come from your own code (not user input), you can disable encoding:

var templateConfig = new PromptTemplateConfig
{
    Template = """
        Analyze this C# code and suggest improvements:

        ```csharp
        {{$code}}
        ```

        Focus on performance and readability.
        """,
    InputVariables =
    [
        new() { Name = "code", AllowDangerouslySetContent = true }
    ]
};

var function = kernel.CreateFunctionFromPrompt(templateConfig);

var result = await kernel.InvokeAsync(function, new KernelArguments
{
    ["code"] = "var items = new List<string> { \"one\", \"two\" };"
});

The key is AllowDangerouslySetContent = true on the input variable. The name is intentionally scary — it reminds you that this bypasses encoding protection.

Option 2: Use the safe content wrapper

For mixed scenarios where some arguments are user input and some are trusted:

var arguments = new KernelArguments
{
    ["userQuestion"] = userInput,  // This gets encoded (safe)
    ["codeExample"] = new TrustedContent(codeSnippet)  // This bypasses encoding
};

Option 3: Pass code as a separate message, not a template variable

Instead of embedding code in the prompt template, add it as a separate chat message:

var history = new ChatHistory();
history.AddSystemMessage("You are a code reviewer. Analyze the code the user provides.");
history.AddUserMessage($"Review this code:\n\n```csharp\n{codeSnippet}\n```");

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

This avoids the template engine entirely and gives you full control over content.

Error 2: Complex Type Serialization Failure

What You See

System.Text.Json.JsonException: Deserialization of types without a 
parameterless constructor is not supported.
Type: 'MyApp.Models.AnalysisRequest'

Or:

KernelException: Unable to deserialize parameter 'request' of function 'analyze_data'.

Why It Happens

When the LLM calls a plugin function, Semantic Kernel deserializes the function arguments from JSON. If your parameter type can’t round-trip through System.Text.Json, deserialization fails.

Common causes:

  1. No parameterless constructorSystem.Text.Json needs one by default
  2. Read-only properties without [JsonConstructor]
  3. Interface or abstract type parameters
  4. Circular references in the object graph

The Fix

Make your types JSON-friendly:

// ❌ This fails — no parameterless constructor
public class AnalysisRequest
{
    public string Data { get; }
    public int Threshold { get; }

    public AnalysisRequest(string data, int threshold)
    {
        Data = data;
        Threshold = threshold;
    }
}

// ✅ Option 1: Add a parameterless constructor
public class AnalysisRequest
{
    public string Data { get; set; } = string.Empty;
    public int Threshold { get; set; }
}

// ✅ Option 2: Use JsonConstructor
public class AnalysisRequest
{
    public string Data { get; }
    public int Threshold { get; }

    [JsonConstructor]
    public AnalysisRequest(string data, int threshold)
    {
        Data = data;
        Threshold = threshold;
    }
}

Keep plugin function parameters simple when possible:

// ❌ Complex type — serialization risk
[KernelFunction("analyze")]
public Task<string> AnalyzeAsync(AnalysisRequest request) { ... }

// ✅ Simple parameters — always works
[KernelFunction("analyze")]
[Description("Analyze data with a threshold filter")]
public Task<string> AnalyzeAsync(
    [Description("The data to analyze")] string data,
    [Description("Minimum threshold (1-100)")] int threshold) { ... }

The LLM is better at filling individual parameters than constructing complex JSON objects. Simple parameters also produce better tool descriptions, which improves tool selection accuracy.

If you must use complex types, test the round-trip:

// Verification test — run this before deploying
var original = new AnalysisRequest { Data = "test", Threshold = 50 };
var json = JsonSerializer.Serialize(original);
var deserialized = JsonSerializer.Deserialize<AnalysisRequest>(json);

Debug.Assert(deserialized!.Data == original.Data);
Debug.Assert(deserialized.Threshold == original.Threshold);
Console.WriteLine("JSON round-trip: OK");

Error 3: KernelException — Function Not Found and Runtime Failures

What You See

Microsoft.SemanticKernel.KernelException: 
Function 'search_documents' not found in plugin 'memory'.

Or:

Microsoft.SemanticKernel.KernelException: 
Error occurred while invoking function 'my_plugin-process_data'.

Why It Happens

KernelException is the catch-all for Semantic Kernel runtime failures. The most common triggers:

  1. Function not found — Plugin not registered, or function name doesn’t match what the LLM produces
  2. Function invocation error — Your plugin method threw an exception
  3. Connector failure — The LLM API call failed (wrapped in KernelException)

The Fix

Structured error handling in agent loops:

try
{
    var response = await chatService.GetChatMessageContentAsync(
        history, settings, kernel);
    history.Add(response);
}
catch (KernelException ex) when (ex.Message.Contains("not found"))
{
    // Function not found — LLM hallucinated a tool name
    Console.WriteLine($"Function not found: {ex.Message}");
    history.AddAssistantMessage(
        "I tried to use a tool that doesn't exist. Let me try a different approach.");
}
catch (KernelException ex) when (ex.InnerException is HttpRequestException httpEx)
{
    // LLM API failure
    Console.WriteLine($"API error: {httpEx.StatusCode}{httpEx.Message}");

    if (httpEx.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
    {
        await Task.Delay(TimeSpan.FromSeconds(5));
        // Retry the request
    }
}
catch (KernelException ex)
{
    // Function invocation error — your plugin code threw
    Console.WriteLine($"Plugin error: {ex.InnerException?.Message ?? ex.Message}");
    history.AddAssistantMessage(
        "I encountered an error while processing. Could you rephrase your request?");
}

Debug function registration:

// List all registered functions for debugging
foreach (var plugin in kernel.Plugins)
{
    Console.WriteLine($"Plugin: {plugin.Name}");
    foreach (var function in plugin)
    {
        Console.WriteLine($"  - {function.Name}: {function.Description}");
    }
}

If the LLM consistently calls functions with wrong names, improve the function descriptions. The LLM uses descriptions to choose and name function calls — vague descriptions lead to hallucinated function names.

Prevention: Log the Rendered Prompt

The single most useful debugging technique for template issues:

// Enable SK logging to see rendered prompts
var builder = Kernel.CreateBuilder();

builder.Services.AddLogging(logging =>
{
    logging.AddConsole();
    logging.SetMinimumLevel(LogLevel.Trace); // Shows rendered prompts
});

With Trace level logging, SK logs the final rendered prompt before sending it to the LLM. When output quality degrades after an upgrade, compare the rendered prompts to find encoding or template changes.

⚠ Production Considerations

  • The HTML-encoding change was silent — no compiler warning, no runtime exception. Your prompts just start containing &lt; instead of < and the LLM produces garbage. Pin your SK version in CI and test prompt output when upgrading.
  • Plugin functions that accept string work fine. The moment you change to a complex type, you inherit JSON deserialization requirements. Test your plugin functions with realistic input through Kernel.InvokeAsync, not just direct method calls.

🧠 Architect’s Note

Template errors are the hardest to debug because the symptom (bad LLM output) is far from the cause (encoded template). Add logging that captures the final rendered prompt before it hits the LLM. When output quality degrades after an upgrade, compare rendered prompts between versions.

AI-Friendly Summary

Summary

Troubleshooting guide for Semantic Kernel template and plugin errors. Covers: HTML-encoding breaking change in templates, complex type serialization failures in plugin functions, and KernelException handling patterns.

Key Takeaways

  • SK 1.25+ HTML-encodes template arguments by default
  • Use PromptTemplateConfig to control encoding behavior
  • Plugin function parameters must be JSON-serializable
  • Add JsonConstructor attribute for types with no parameterless constructor
  • Catch KernelException and inspect InnerException for root cause

Implementation Checklist

  • Check SK version — template encoding behavior changed in 1.25
  • Verify plugin function parameter types are JSON-serializable
  • Add error handling around kernel.InvokeAsync calls
  • Test templates with special characters (angle brackets, quotes, JSON)
  • Use KernelArguments for passing typed data to templates

Frequently Asked Questions

Why are my Semantic Kernel template arguments HTML-encoded?

Starting with Semantic Kernel 1.25+, template arguments are HTML-encoded by default for safety. This means angle brackets, quotes, and ampersands in your arguments get escaped. If your prompts contain code snippets or JSON, you need to either disable encoding or use the safe rendering option.

Why does my plugin function throw a serialization error?

Semantic Kernel serializes function parameters from JSON. If your function accepts a complex type (a class with nested objects), the type must be JSON-serializable. Common issues: no parameterless constructor, read-only properties without JsonConstructor, or circular references.

What is KernelException and how do I handle it?

KernelException is the base exception for Semantic Kernel runtime errors. It wraps specific failures like function-not-found, template rendering errors, and connector failures. Catch KernelException in your agent loop and inspect InnerException for the root cause.

You Might Also Enjoy

#Semantic Kernel #Error Handling #.NET AI #Troubleshooting #Templates

Was this article useful?

Feedback is anonymous and helps us improve content quality.