Fixes at a Glance
- HTML-encoded template arguments — switch from the default Handlebars renderer to the
PromptTemplateConfigwithAllowDangerouslySetContent = true, or pre-encode your inputs correctly for the template engine - Complex type serialisation failure — register a custom
JsonConverterfor your type or serialise to astringbefore passing to the kernel function - 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<string>();
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:
| Character | Encoded As |
|---|---|
< | < |
> | > |
" | " |
' | ' |
& | & |
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:
- No parameterless constructor —
System.Text.Jsonneeds one by default - Read-only properties without
[JsonConstructor] - Interface or abstract type parameters
- 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:
- Function not found — Plugin not registered, or function name doesn’t match what the LLM produces
- Function invocation error — Your plugin method threw an exception
- 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.
Related Articles
- What is Semantic Kernel? — SK fundamentals
- Semantic Kernel Plugins Guide — Building reliable plugins
- Fix NuGet Dependency Conflicts — Version management for SK packages