# SendKmsByAppAsync Execution Flow: Tracing Knowledge Base Q&A in AntSK

> Explore SendKmsByAppAsync execution flow for AntSK knowledge base Q&A. Understand document retrieval, semantic search, reranking, and LLM calls for real-time answers.

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

---

**`SendKmsByAppAsync` is the central async streaming method that orchestrates document retrieval, semantic search, optional reranking, and LLM invocation to power real-time knowledge base Q&A interactions in the AntSK framework.**

The `SendKmsByAppAsync` method serves as the primary pipeline for knowledge-base question-answering within the [AntSK](https://github.com/aidotnet/antsk) project. Located in [`ChatService.cs`](https://github.com/aidotnet/antsk/blob/main/ChatService.cs), this `IAsyncEnumerable<StreamingKernelContent>` implementation bridges user queries from both web UI and API clients with semantic memory retrieval and large language model generation. Tracing its execution reveals a sophisticated flow that handles dynamic document ingestion, configurable reranking, and streaming response generation.

## Service Entry Points

`SendKmsByAppAsync` accepts an application configuration, user question, chat history, and optional file path to initiate the Q&A pipeline. The method is invoked from two primary locations depending on the client interface.

**Web UI Invocation** via the Blazor component:

```csharp
// ChatView.razor.cs – line 296
var chatResult = _chatService.SendKmsByAppAsync(app, questions, history, filePath, _relevantSources);

```

**HTTP API Invocation** for Server-Sent Events streaming:

```csharp
// OpenApiService.cs – line 73
var chatResult = _chatService.SendKmsByAppAsync(app, questions, history, "");

```

Both callers receive an async enumerable stream of `StreamingKernelContent` chunks that yield partial responses until the LLM completes generation.

## Kernel Selection and Initialization

Upon entry, the method immediately retrieves or constructs the appropriate **Kernel** instance for the specified application:

```csharp
// ChatService.cs – line 95-96
var _kernel = _kernelService.GetKernelByApp(app);

```

The `KernelService` configures this instance based on the app's specific model settings, temperature parameters, and prompt templates. This kernel will later execute the semantic functions and manage the LLM communication.

## Document Upload Path

When the caller provides a `filePath` parameter, `SendKmsByAppAsync` executes the file-based Q&A branch ([`ChatService.cs`](https://github.com/aidotnet/antsk/blob/main/ChatService.cs) lines 98-133). This path handles dynamic document ingestion without requiring prior manual indexing.

The system performs these operations:

- **Extracts a GUID** from the filename using regex pattern `\b[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}\b`
- **Checks indexing status** via `memory.IsDocumentReadyAsync(fileId, index: KmsConstantcs.KmsIndex)`
- **Imports new documents** using `memory.ImportDocumentAsync()` with the file-specific `FileIndex` and metadata tags (`AppIdTag`, `FileIdTag`)
- **Searches the file index** with filtered memory queries restricted to the specific `fileId`

Relevant chunks are converted to `RelevantSource` objects containing `SourceName`, `Text` (converted from Markdown to HTML), and `Relevance` scores.

## Knowledge Base Retrieval Path

When no file path is provided, the method falls back to the standard knowledge base search ([`ChatService.cs`](https://github.com/aidotnet/antsk/blob/main/ChatService.cs) lines 136-138):

```csharp
else
{
    // 从知识库问答
    relevantSourceList = await _kMService.GetRelevantSourceList(app, questions);
}

```

The `KMService` queries the **KMS** memory index using the application's configured tags, returning candidate chunks that match the semantic meaning of the user's question.

## Optional Reranking and Relevance Filtering

If the application configuration specifies a **rerank model ID**, the method executes a secondary scoring pass to improve result quality ([`ChatService.cs`](https://github.com/aidotnet/antsk/blob/main/ChatService.cs) lines 144-158):

```csharp
if (!string.IsNullOrEmpty(app.RerankModelID))
{
    var rerankModel = _aIModels_Repositories.GetById(app.RerankModelID);
    BegRerankConfig.LoadModel(rerankModel.EndPoint, rerankModel.ModelName);
    foreach (var item in relevantSourceList)
    {
        List<string> rerank = new List<string> { questions, item.Text };
        item.RerankScore = BegRerankConfig.Rerank(rerank);
    }
    relevantSourceList = relevantSourceList
        .OrderByDescending(p => p.RerankScore)
        .Take(app.MaxMatchesCount)
        .ToList();
}

```

Following reranking (or immediately after retrieval if no model is configured), the system filters sources against the application's **relevance threshold** ([`ChatService.cs`](https://github.com/aidotnet/antsk/blob/main/ChatService.cs) lines 160-176). The threshold comparison uses `app.Relevance / 100` to normalize the percentage value:

- With reranking: checks `item.RerankScore >= threshold`
- Without reranking: checks `item.Relevance >= threshold`

Passing sources are concatenated into a `dataMsg` markdown string that serves as the grounded context for the LLM prompt.

## Prompt Construction and Streaming Invocation

When at least one source passes the relevance filter, `SendKmsByAppAsync` constructs the final prompt and invokes the kernel streaming interface ([`ChatService.cs`](https://github.com/aidotnet/antsk/blob/main/ChatService.cs) lines 186-215):

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

var chatResult = _kernel.InvokeStreamingAsync(
    function: func,
    arguments: new KernelArguments()
    {
        ["doc"] = dataMsg.ToString(),
        ["history"] = string.Join("\n", history.Select(x => x.Role + ": " + x.Content)),
        ["input"] = questions
    });

await foreach (var content in chatResult)
{
    yield return content;
}

```

The temperature normalization converts the UI's 0-100 scale to the LLM's expected 0.0-1.0 range. The `KernelArguments` inject the concatenated document context (`doc`), formatted chat history (`history`), and original user input (`input`) into the prompt template.

If no sources meet the relevance threshold, the method yields a fallback message:

```csharp
// ChatService.cs – line 221-224
else
{
    yield return new StreamingTextContent(KmsConstantcs.KmsSearchNull);
}

```

## Consuming the Stream

### Server-Sent Events (SSE)

The `OpenApiService` consumes the async enumerable and transmits chunks as SSE payloads:

```csharp
// OpenApiService.cs – line 73-81
var chatResult = _chatService.SendKmsByAppAsync(app, questions, history, "");
int i = 0;
await foreach (var content in chatResult)
{
    result.choices[0].delta.content = content.ConvertToString();
    string message = $"data: {JsonConvert.SerializeObject(result)}\n\n";
    await HttpContext.Response.WriteAsync(message, Encoding.UTF8);
    await HttpContext.Response.Body.FlushAsync();
    await Task.Delay(TimeSpan.FromMilliseconds(50));
}
await HttpContext.Response.WriteAsync("data: [DONE]");

```

### UI Rendering

The Blazor `ChatView` component follows a similar pattern, iterating the `IAsyncEnumerable` and appending each chunk to the rendered answer buffer while triggering `StateHasChanged()` for real-time UI updates.

## Summary

- **`SendKmsByAppAsync`** in [`ChatService.cs`](https://github.com/aidotnet/antsk/blob/main/ChatService.cs) is the central async streaming pipeline for knowledge base Q&A in AntSK
- The method supports **dual entry points**: web UI components and HTTP API endpoints
- Execution branches between **file-based ingestion** (dynamic import) and **static knowledge base retrieval** based on the `filePath` parameter
- **Optional reranking** via `BegRerankConfig` improves result relevance when configured
- **Strict filtering** by relevance scores ensures only quality context reaches the LLM
- **Streaming invocation** through `InvokeStreamingAsync` enables real-time token-by-token response delivery
- **Graceful degradation** returns a "no answer" constant when no relevant sources are found

## Frequently Asked Questions

### What triggers the file upload branch in SendKmsByAppAsync?

The file upload branch activates when the `filePath` parameter contains a non-empty string. The method extracts a GUID from the filename to check if the document is already indexed via `IsDocumentReadyAsync`. If not indexed, it imports the document into the `FileIndex` with appropriate metadata tags before searching that specific index.

### How does the reranking mechanism improve search results?

When `app.RerankModelID` is configured, the method loads a dedicated rerank model via `BegRerankConfig.LoadModel()` and scores each candidate source against the original question. Sources are then reordered by their `RerankScore` and truncated to `app.MaxMatchesCount`. This secondary scoring pass typically improves precision over initial vector similarity by using cross-encoder models trained specifically for relevance ranking.

### Why does the method use IAsyncEnumerable instead of returning a complete string?

The `IAsyncEnumerable<StreamingKernelContent>` return type enables **Server-Sent Events (SSE)** and real-time UI updates. As the LLM generates tokens, each chunk yields immediately to the caller, allowing users to see partial responses before generation completes. This approach minimizes perceived latency and improves user experience for long-form answers.

### Where is the fallback "no answer" message defined?

When no sources pass the relevance threshold check (`isSearch == false`), the method yields `KmsConstantcs.KmsSearchNull` as a `StreamingTextContent`. This constant provides a standardized response indicating that the knowledge base contains no relevant information for the given query, preventing hallucinated answers from the LLM.