# Best Practices for Implementing Custom Prompt Templates for Knowledge Base Queries in AntSK

> Learn best practices for AntSK custom prompt templates. Master required placeholders like {{$doc}} and {{$input}} for effective knowledge base queries with Semantic Kernel. Boost your chatbot's accuracy.

- Repository: [AIDotNet/antsk](https://github.com/aidotnet/antsk)
- Tags: best-practices
- Published: 2026-02-24

---

**Custom knowledge base prompts in AntSK must include three required placeholders—`{{$doc}}` for source documents, `{{$input}}` for the user question, and `{{ConversationSummaryPlugin.SummarizeConversation $history}}` for conversation context—to function correctly with the Semantic Kernel runtime.**

AntSK implements knowledge base (KMS) queries as dynamic Semantic Kernel functions generated from user-defined prompt templates. When building custom prompts for the `aidotnet/antsk` repository, understanding the template lifecycle and mandatory placeholders ensures reliable retrieval-augmented generation (RAG) performance across different LLM providers.

## Understanding the Prompt Template Lifecycle

AntSK manages prompt templates through a five-stage pipeline that spans definition, validation, storage, and execution.

### Template Definition and Default Structure

The default KMS prompt resides in `KmsConstantcs.KmsPrompt` within [`src/AntSK.Domain/Domain/Model/Constant/KmsConstantcs.cs`](https://github.com/aidotnet/antsk/blob/main/src/AntSK.Domain/Domain/Model/Constant/KmsConstantcs.cs). This template establishes the baseline structure containing the three essential placeholders that the kernel expects at runtime. The default implementation wraps retrieved documents in markdown formatting and positions conversation history after static instructions to optimize token consumption.

### Validation and Storage

When creating or editing a KMS-type application, the UI enforces placeholder requirements. In [`src/AntSK/Pages/AppPage/AddApp.razor.cs`](https://github.com/aidotnet/antsk/blob/main/src/AntSK/Pages/AppPage/AddApp.razor.cs) (lines 94-100), the validation logic explicitly checks that the prompt contains both `{{$doc}}` and `{{$input}}` before allowing persistence:

```csharp
if (_appModel.Type == AppType.kms.ToString())
{
    if (string.IsNullOrEmpty(_appModel.Prompt) ||
        !_appModel.Prompt.Contains("{{$doc}}") ||
        !_appModel.Prompt.Contains("{{$input}}"))
    {
        _ = Message.Error("知识库提示词必须包含 {{$doc}} 和 {{$input}}", 2);
        return;
    }
}

```

Valid templates are stored in the `Prompt` property of the `Apps` entity defined in [`src/AntSK.Domain/Domain/Repositories/AI/App/Apps.cs`](https://github.com/aidotnet/antsk/blob/main/src/AntSK.Domain/Domain/Repositories/AI/App/Apps.cs).

### Runtime Execution

Each request dynamically constructs a `KernelFunction` using `Kernel.CreateFunctionFromPrompt`. Both [`src/AntSK/Services/OpenApi/OpenApiService.cs`](https://github.com/aidotnet/antsk/blob/main/src/AntSK/Services/OpenApi/OpenApiService.cs) (line 214) and [`src/AntSK.Domain/Domain/Service/ChatService.cs`](https://github.com/aidotnet/antsk/blob/main/src/AntSK.Domain/Domain/Service/ChatService.cs) (line 210) implement this pattern, invoking the function with `KernelArguments` containing the document context, conversation history, and current query. The execution settings apply temperature scaling and model parameters separately from the template text.

## Required Placeholders and Their Functions

AntSK's Semantic Kernel integration requires three specific template variables. Missing any placeholder causes runtime errors because the kernel cannot resolve undefined variables.

- **`{{$doc}}`**: Contains concatenated, markdown-formatted source snippets retrieved from Kernel Memory. The implementation in `KMService` uses `item.ToString()` to preserve raw markdown rather than HTML conversion, ensuring optimal LLM comprehension of structured content.
- **`{{$input}}`**: Represents the end-user's current question passed directly from the interface. This variable binds to the `questions` parameter at invocation time.
- **`{{ConversationSummaryPlugin.SummarizeConversation $history}}`**: Injects a summarized version of prior dialogue to maintain context without exceeding token limits. This plugin-based approach prevents passing full conversation logs that could consume the entire context window.

## Design Guidelines for Custom Templates

Following architectural patterns from the AntSK source code ensures your custom prompts remain efficient and portable across different LLM providers.

### Maintain Model Agnosticism

Avoid hard-coding model-specific instructions like "You are GPT-4" or temperature directives within the template text. Instead, rely on the `OpenAIPromptExecutionSettings` object configured in [`OpenApiService.cs`](https://github.com/aidotnet/antsk/blob/main/OpenApiService.cs) (line 31) and [`ChatService.cs`](https://github.com/aidotnet/antsk/blob/main/ChatService.cs) (line 44), where the integer temperature value (0-100) is scaled by dividing by 100.0:

```csharp
var temperature = app.Temperature / 100.0;
var settings = new OpenAIPromptExecutionSettings { Temperature = temperature };
var func = kernel.CreateFunctionFromPrompt(app.Prompt, settings);

```

### Optimize Token Usage

Place static instructions before the history placeholder to ensure the model processes recent context last—the optimal ordering for most transformer architectures. Limit retrieved sources using `app.MaxMatchesCount` and consider re-ranking to stay within context windows. Keep system instructions concise; verbose prompts waste tokens that could otherwise accommodate additional source material from the knowledge base.

### Preserve Markdown Formatting

Documents retrieved via `KMService` arrive as markdown text. Maintain this format in your templates rather than converting to HTML, as LLMs reason more effectively over raw markdown structures including bullet lists, code fences, and tables. The default prompt preserves this formatting by directly injecting `StringBuilder` contents containing markdown source.

## Implementation Example

The following end-to-end example demonstrates defining a custom prompt, validating storage, and executing against the Semantic Kernel:

```csharp
// 1. Define custom prompt with required placeholders
const string customPrompt = @"
使用<data></data>标记的内容作为你的知识：
<data>
{{$doc}}
</data>
--------------------------
回答要求：
- 简洁回答，使用Markdown
- 如不确定请说明
--------------------------
历史对话摘要:
{{ConversationSummaryPlugin.SummarizeConversation $history}}
--------------------------
用户问题:
{{$input}}";

// 2. Persist to Apps entity (validation occurs in AddApp.razor.cs)
var app = new Apps {
    Id = Guid.NewGuid().ToString(),
    Type = "kms",
    Prompt = customPrompt,
    Temperature = 70  // Stored as integer, scaled to 0.7 at runtime
};
_apps_Repositories.Insert(app);

// 3. Runtime execution following OpenApiService.cs pattern
var temperature = app.Temperature / 100.0;
var settings = new OpenAIPromptExecutionSettings { Temperature = temperature };
var kernel = _kernelService.GetKernelByApp(app);
var func = kernel.CreateFunctionFromPrompt(app.Prompt, settings);

// 4. Prepare arguments with retrieved documents
var relevant = await _kMService.GetRelevantSourceList(app, question);
var docBuilder = new StringBuilder();
foreach (var src in relevant) 
    docBuilder.AppendLine(src.ToString());

var args = new KernelArguments {
    ["doc"] = docBuilder.ToString(),
    ["history"] = string.Join("\n", history.Select(m => $"{m.Role}: {m.Content}")),
    ["input"] = question
};

// 5. Invoke (non-streaming for API, streaming available in ChatService.cs)
var result = await kernel.InvokeAsync(func, args);
string answer = result.GetValue<string>();

```

## Summary

- **Always include** the three mandatory placeholders: `{{$doc}}`, `{{$input}}`, and the history summary helper to prevent runtime kernel errors.
- **Validate templates** at the UI layer before persistence, as implemented in [`AddApp.razor.cs`](https://github.com/aidotnet/antsk/blob/main/AddApp.razor.cs), to ensure all required variables are present.
- **Scale temperature** values correctly by dividing the stored integer (0-100) by 100.0 before passing to `OpenAIPromptExecutionSettings`, following the pattern in [`ChatService.cs`](https://github.com/aidotnet/antsk/blob/main/ChatService.cs).
- **Preserve markdown** formatting for source documents rather than converting to HTML, using `item.ToString()` from the Kernel Memory results.
- **Limit context** length by controlling `MaxMatchesCount` and keeping system instructions concise to maximize available tokens for source material.

## Frequently Asked Questions

### What happens if I forget to include {{$doc}} in my custom template?

The Semantic Kernel runtime throws a resolution error when attempting to invoke the function because the template references an undefined variable. The validation logic in [`AddApp.razor.cs`](https://github.com/aidotnet/antsk/blob/main/AddApp.razor.cs) (lines 94-100) specifically checks for the presence of `{{$doc}}` and `{{$input}}` to prevent deployment of invalid templates that would fail at runtime.

### Can I modify the conversation history placeholder format?

While you can reposition `{{ConversationSummaryPlugin.SummarizeConversation $history}}` within the template, you must invoke it through the ConversationSummaryPlugin to ensure proper summarization. Directly using `{{$history}}` would pass raw, potentially lengthy conversation logs that could exceed token limits and degrade performance.

### How does AntSK handle temperature values in custom prompts?

Temperature values are stored as integers (0-100) in the `Apps` entity and scaled to decimal values (0.0-1.0) at runtime by dividing by 100.0, as implemented in [`OpenApiService.cs`](https://github.com/aidotnet/antsk/blob/main/OpenApiService.cs) and [`ChatService.cs`](https://github.com/aidotnet/antsk/blob/main/ChatService.cs). Never hardcode temperature instructions in the template text itself; instead, rely on the `OpenAIPromptExecutionSettings` configuration object.

### Should I convert retrieved documents to HTML before injecting them into the prompt?

No. Kernel Memory returns documents in markdown format, and the default implementation in `KMService` uses `item.ToString()` to preserve this markdown. LLMs process markdown structures more effectively than HTML tags, making raw markdown the optimal format for source context in retrieval-augmented generation workflows.