mirror of
https://github.com/marcominerva/SqlDatabaseVectorSearch.git
synced 2026-06-20 12:23:10 +00:00
Add streaming support and improve JSON serialization
- Updated `Response` record class to allow nullable `Question` and `Answer` properties; moved `StreamState` enum to a new file. - Added `JsonStringEnumConverter` in `Program.cs` for better enum serialization. - Corrected terminology in document upload endpoint description. - Introduced `/api/ask-streaming` endpoint for streaming question responses. - Added `AskStreamingAsync` method in `VectorSearchService` for handling streaming logic. - Created `StreamState.cs` to define `StreamState` enum with `Start`, `Append`, and `End` values.
This commit is contained in:
@@ -1,10 +1,4 @@
|
||||
namespace SqlDatabaseVectorSearch.Models;
|
||||
|
||||
public record class Response(string Question, string Answer, StreamState? StreamState = null);
|
||||
|
||||
public enum StreamState
|
||||
{
|
||||
Start,
|
||||
Append,
|
||||
End
|
||||
}
|
||||
// Question and Asnwer can be null when using response streaming.
|
||||
public record class Response(string? Question, string? Answer, StreamState? StreamState = null);
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace SqlDatabaseVectorSearch.Models;
|
||||
|
||||
public enum StreamState
|
||||
{
|
||||
Start,
|
||||
Append,
|
||||
End
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.SemanticKernel;
|
||||
@@ -49,6 +50,11 @@ builder.Services.AddSingleton<TokenizerService>();
|
||||
builder.Services.AddSingleton<ChatService>();
|
||||
builder.Services.AddScoped<VectorSearchService>();
|
||||
|
||||
builder.Services.ConfigureHttpJsonOptions(options =>
|
||||
{
|
||||
options.SerializerOptions.Converters.Add(new JsonStringEnumConverter());
|
||||
});
|
||||
|
||||
builder.Services.AddOpenApi(options =>
|
||||
{
|
||||
options.RemoveServerList();
|
||||
@@ -114,7 +120,7 @@ documentsApiGroup.MapPost(string.Empty, async (IFormFile file, VectorSearchServi
|
||||
.DisableAntiforgery()
|
||||
.ProducesProblem(StatusCodes.Status400BadRequest)
|
||||
.WithSummary("Uploads a document")
|
||||
.WithDescription("Uploads a document to SQL Database and saves its embedding using the new native Vector type. The document will be indexed and used to answer questions. Currently, only PDF files are supported.");
|
||||
.WithDescription("Uploads a document to SQL Database and saves its embedding using the native VECTOR type. The document will be indexed and used to answer questions. Currently, only PDF files are supported.");
|
||||
|
||||
documentsApiGroup.MapDelete("{documentId:guid}", async (Guid documentId, VectorSearchService vectorSearchService) =>
|
||||
{
|
||||
@@ -134,4 +140,25 @@ app.MapPost("/api/ask", async (Question question, VectorSearchService vectorSear
|
||||
.WithDescription("The question will be reformulated taking into account the context of the chat identified by the given ConversationId.")
|
||||
.WithTags("Ask");
|
||||
|
||||
app.MapPost("/api/ask-streaming", (Question question, VectorSearchService vectorSearchService,
|
||||
[Description("If true, the question will be reformulated taking into account the context of the chat identified by the given ConversationId.")] bool reformulate = true) =>
|
||||
{
|
||||
async IAsyncEnumerable<Response> Stream()
|
||||
{
|
||||
// Requests a streaming response.
|
||||
var responseStream = vectorSearchService.AskStreamingAsync(question, reformulate);
|
||||
|
||||
await foreach (var delta in responseStream)
|
||||
{
|
||||
yield return delta;
|
||||
await Task.Delay(50);
|
||||
}
|
||||
}
|
||||
|
||||
return Stream();
|
||||
})
|
||||
.WithSummary("Asks a question and gets the response as streaming")
|
||||
.WithDescription("The question will be reformulated taking into account the context of the chat identified by the given ConversationId.")
|
||||
.WithTags("Ask");
|
||||
|
||||
app.Run();
|
||||
@@ -88,6 +88,25 @@ public class VectorSearchService(ApplicationDbContext dbContext, ITextEmbeddingG
|
||||
return new Response(reformulatedQuestion, answer);
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<Response> AskStreamingAsync(Question question, bool reformulate = true)
|
||||
{
|
||||
var (reformulatedQuestion, chunks) = await CreateContextAsync(question, reformulate);
|
||||
|
||||
var answerStream = chatService.AskStreamingAsync(question.ConversationId, chunks, reformulatedQuestion);
|
||||
|
||||
// The first message contains the original question.
|
||||
yield return new Response(reformulatedQuestion, null, StreamState.Start);
|
||||
|
||||
// Return each token as a partial response.
|
||||
await foreach (var token in answerStream)
|
||||
{
|
||||
yield return new Response(null, token, StreamState.Append);
|
||||
}
|
||||
|
||||
// The last message tells the client that the stream has ended.
|
||||
yield return new Response(null, null, StreamState.End);
|
||||
}
|
||||
|
||||
private async Task<(string Question, IEnumerable<string> Chunks)> CreateContextAsync(Question question, bool reformulate = true)
|
||||
{
|
||||
// Reformulate the following question taking into account the context of the chat to perform keyword search and embeddings:
|
||||
|
||||
Reference in New Issue
Block a user