Add streaming and refactor chat/question handling

Updated `Response` record in `Response.cs` to include an optional `StreamState` property, which can be `Start`, `Append`, or `End`. Added a new `StreamState` enum to `Response.cs`.

In `ChatService.cs`, added new methods `AskQuestionAsync` and `AskStreamingAsync` to handle asking questions and streaming responses, respectively. Refactored `CreateChatAsync` to return a `ChatHistory` object.

In `VectorSearchService.cs`, added a new `AskQuestionAsync` method to handle questions using `ChatService`. Updated `CreateContextAsync` to return a tuple with the reformulated question and chunks. Removed the previous implementation of `AskQuestionAsync` and replaced it with the new method utilizing `ChatService`.
This commit is contained in:
Marco Minerva
2025-01-28 10:14:47 +01:00
parent 14bc1c131f
commit 44c6193674
3 changed files with 54 additions and 13 deletions
+8 -1
View File
@@ -1,3 +1,10 @@
namespace SqlDatabaseVectorSearch.Models;
public record class Response(string Question, string Answer);
public record class Response(string Question, string Answer, StreamState? StreamState = null);
public enum StreamState
{
Start,
Append,
End
}
+37 -10
View File
@@ -35,6 +35,42 @@ public class ChatService(IChatCompletionService chatCompletionService, Tokenizer
}
public async Task<string> AskQuestionAsync(Guid conversationId, IEnumerable<string> chunks, string question)
{
var chat = CreateChatAsync(chunks, question);
var answer = await chatCompletionService.GetChatMessageContentAsync(chat, new AzureOpenAIPromptExecutionSettings
{
MaxTokens = appSettings.MaxOutputTokens
});
// Add question and answer to the chat history.
await SetChatHistoryAsync(conversationId, question, answer.Content!);
return answer.Content!;
}
public async IAsyncEnumerable<string> AskStreamingAsync(Guid conversationId, IEnumerable<string> chunks, string question)
{
var chat = CreateChatAsync(chunks, question);
var answer = new StringBuilder();
await foreach (var token in chatCompletionService.GetStreamingChatMessageContentsAsync(chat, new AzureOpenAIPromptExecutionSettings
{
MaxTokens = appSettings.MaxOutputTokens
}))
{
if (!string.IsNullOrEmpty(token.Content))
{
yield return token.Content;
answer.Append(token.Content);
}
}
// Add question and answer to the chat history.
await SetChatHistoryAsync(conversationId, question, answer.ToString());
}
private ChatHistory CreateChatAsync(IEnumerable<string> chunks, string question)
{
var chat = new ChatHistory("""
You can use only the information provided in this chat to answer questions. If you don't know the answer, reply suggesting to refine the question.
@@ -79,16 +115,7 @@ public class ChatService(IChatCompletionService chatCompletionService, Tokenizer
}
chat.AddUserMessage(prompt.ToString());
var answer = await chatCompletionService.GetChatMessageContentAsync(chat, new AzureOpenAIPromptExecutionSettings
{
MaxTokens = appSettings.MaxOutputTokens
});
// Add question and answer to the chat history.
await SetChatHistoryAsync(conversationId, question, answer.Content!);
return answer.Content!;
return chat;
}
private async Task UpdateCacheAsync(Guid conversationId, ChatHistory chat)
@@ -81,6 +81,14 @@ public class VectorSearchService(ApplicationDbContext dbContext, ITextEmbeddingG
=> dbContext.Documents.Where(d => d.Id == documentId).ExecuteDeleteAsync();
public async Task<Response> AskQuestionAsync(Question question, bool reformulate = true)
{
var (reformulatedQuestion, chunks) = await CreateContextAsync(question, reformulate);
var answer = await chatService.AskQuestionAsync(question.ConversationId, chunks, reformulatedQuestion);
return new Response(reformulatedQuestion, answer);
}
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:
var reformulatedQuestion = reformulate ? await chatService.CreateQuestionAsync(question.ConversationId, question.Text) : question.Text;
@@ -94,8 +102,7 @@ public class VectorSearchService(ApplicationDbContext dbContext, ITextEmbeddingG
.Take(appSettings.MaxRelevantChunks)
.ToListAsync();
var answer = await chatService.AskQuestionAsync(question.ConversationId, chunks, reformulatedQuestion);
return new Response(reformulatedQuestion, answer);
return (reformulatedQuestion, chunks);
}
private static Task<string> GetContentAsync(Stream stream)