Skip to content

implement chat protocal #299

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<AzureFormRecognizerVersion>4.1.0</AzureFormRecognizerVersion>
<AzureIdentityVersion>1.10.1</AzureIdentityVersion>
<AzureIdentityVersion>1.11.0</AzureIdentityVersion>
<AzureSearchDocumentsVersion>11.5.0-beta.4</AzureSearchDocumentsVersion>
<AzureStorageBlobsVersion>12.17.0</AzureStorageBlobsVersion>
<AzureOpenAIVersion>1.0.0-beta.7</AzureOpenAIVersion>
Expand Down
2 changes: 1 addition & 1 deletion app/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<PackageVersion Include="Azure.AI.FormRecognizer" Version="4.1.0" />
<PackageVersion Include="Azure.AI.OpenAI" Version="1.0.0-beta.12" />
<PackageVersion Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.3.0" />
<PackageVersion Include="Azure.Identity" Version="1.10.4" />
<PackageVersion Include="Azure.Identity" Version="1.11.0" />
<PackageVersion Include="Azure.Search.Documents" Version="11.5.1" />
<PackageVersion Include="Azure.Storage.Blobs" Version="12.19.1" />
<PackageVersion Include="Azure.Storage.Files.Shares" Version="12.17.1" />
Expand Down
8 changes: 4 additions & 4 deletions app/SharedWebComponents/Components/Answer.razor
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,20 @@
</MudTabPanel>
<MudTabPanel Icon="@Icons.Material.Filled.Lightbulb" Text="Thought process"
ToolTip="Show thought process."
Disabled="@(Retort is { Thoughts: null })">
Disabled="@(Retort is { Context.Thoughts: null } or { Context.Thoughts.Length: 0})">
<ChildContent>
<MudPaper Class="pa-6" Elevation="3">
<pre style="white-space: normal; font-size: 1.2em;">
@(RemoveLeadingAndTrailingLineBreaks(Retort.Thoughts!))
@(RemoveLeadingAndTrailingLineBreaks(Retort.Context.ThoughtsString))
</pre>
</MudPaper>
</ChildContent>
</MudTabPanel>
<MudTabPanel Icon="@Icons.Material.Filled.TextSnippet" Text="Supporting Content"
ToolTip="Show the supporting content." Disabled="@(Retort is { DataPoints: null } or { DataPoints.Length: 0 })">
ToolTip="Show the supporting content." Disabled="@(Retort is { Context.DataPoints.Text: null } or { Context.DataPoints.Text.Length: 0 })">
<ChildContent>
<MudPaper Class="pa-2" Elevation="3">
<SupportingContent DataPoints="Retort.DataPoints" Images="Retort.Images ?? []" />
<SupportingContent DataPoints="Retort.Context.DataPointsContent" Images="Retort.Context.DataPointsImages ?? []" />
</MudPaper>
</ChildContent>
</MudTabPanel>
Expand Down
4 changes: 2 additions & 2 deletions app/SharedWebComponents/Components/Answer.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace SharedWebComponents.Components;

public sealed partial class Answer
{
[Parameter, EditorRequired] public required ApproachResponse Retort { get; set; }
[Parameter, EditorRequired] public required ResponseChoice Retort { get; set; }
[Parameter, EditorRequired] public required EventCallback<string> FollowupQuestionClicked { get; set; }

[Inject] public required IPdfViewer PdfViewer { get; set; }
Expand All @@ -14,7 +14,7 @@ public sealed partial class Answer
protected override void OnParametersSet()
{
_parsedAnswer = ParseAnswerToHtml(
Retort.Answer, Retort.CitationBaseUrl);
Retort.Message.Content, Retort.CitationBaseUrl);

base.OnParametersSet();
}
Expand Down
4 changes: 2 additions & 2 deletions app/SharedWebComponents/Components/AnswerError.razor
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
<MudStack Spacing="4" Class="full-width">
<MudIcon Icon="@Icons.Material.Filled.Error" Color="Color.Error" Size="Size.Large" />
<MudText Typo="Typo.subtitle2">
@Error.Answer
@Error.Error
</MudText>
<MudText Typo="Typo.body1">
@Error.Error
Unable to retrieve valid response from the server.
</MudText>
<div>
<MudButton Variant="Variant.Filled" Size="Size.Small" Color="Color.Info"
Expand Down
2 changes: 1 addition & 1 deletion app/SharedWebComponents/Components/AnswerError.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace SharedWebComponents.Components;
public sealed partial class AnswerError
{
[Parameter, EditorRequired] public required string Question { get; set; }
[Parameter, EditorRequired] public required ApproachResponse Error { get; set; }
[Parameter, EditorRequired] public required ChatAppResponseOrError Error { get; set; }
[Parameter, EditorRequired] public required EventCallback<string> OnRetryClicked { get; set; }

private async Task OnRetryClickedAsync()
Expand Down
2 changes: 1 addition & 1 deletion app/SharedWebComponents/Models/AnswerResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ namespace SharedWebComponents.Models;

public readonly record struct AnswerResult<TRequest>(
bool IsSuccessful,
ApproachResponse? Response,
ChatAppResponseOrError? Response,
Approach Approach,
TRequest Request) where TRequest : ApproachRequest;
2 changes: 1 addition & 1 deletion app/SharedWebComponents/Pages/Chat.razor
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
<MudBadge Origin="Origin.TopLeft" Overlap="true" Color="Color.Secondary"
Icon="@Icons.Material.Filled.AutoAwesome"
Style="display:inherit">
<Answer Retort="@answer" FollowupQuestionClicked="@OnAskQuestionAsync" />
<Answer Retort="@answer.Choices[0]" FollowupQuestionClicked="@OnAskQuestionAsync" />
</MudBadge>
}
</div>
Expand Down
10 changes: 5 additions & 5 deletions app/SharedWebComponents/Pages/Chat.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public sealed partial class Chat
private string _lastReferenceQuestion = "";
private bool _isReceivingResponse = false;

private readonly Dictionary<UserQuestion, ApproachResponse?> _questionAndAnswerMap = [];
private readonly Dictionary<UserQuestion, ChatAppResponseOrError?> _questionAndAnswerMap = [];

[Inject] public required ISessionStorageService SessionStorage { get; set; }

Expand Down Expand Up @@ -42,13 +42,13 @@ private async Task OnAskClickedAsync()
try
{
var history = _questionAndAnswerMap
.Where(x => x.Value is not null)
.Select(x => new ChatTurn(x.Key.Question, x.Value!.Answer))
.Where(x => x.Value?.Choices is { Length: > 0})
.SelectMany(x => new ChatMessage[] { new ChatMessage("user", x.Key.Question), new ChatMessage("assistant", x.Value!.Choices[0].Message.Content) })
.ToList();

history.Add(new ChatTurn(_userQuestion));
history.Add(new ChatMessage("user", _userQuestion));

var request = new ChatRequest([.. history], Settings.Approach, Settings.Overrides);
var request = new ChatRequest([.. history], Settings.Overrides);
var result = await ApiClient.ChatConversationAsync(request);

_questionAndAnswerMap[_currentQuestion] = result.Response;
Expand Down
14 changes: 6 additions & 8 deletions app/SharedWebComponents/Services/ApiClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,21 +112,19 @@ private async Task<AnswerResult<TRequest>> PostRequestAsync<TRequest>(

if (response.IsSuccessStatusCode)
{
var answer = await response.Content.ReadFromJsonAsync<ApproachResponse>();
var answer = await response.Content.ReadFromJsonAsync<ChatAppResponseOrError>();
return result with
{
IsSuccessful = answer is not null,
Response = answer
Response = answer,
};
}
else
{
var answer = new ApproachResponse(
$"HTTP {(int)response.StatusCode} : {response.ReasonPhrase ?? "☹️ Unknown error..."}",
null,
[],
null,
"Unable to retrieve valid response from the server.");
var errorTitle = $"HTTP {(int)response.StatusCode} : {response.ReasonPhrase ?? "☹️ Unknown error..."}";
var answer = new ChatAppResponseOrError(
Array.Empty<ResponseChoice>(),
errorTitle);

return result with
{
Expand Down
48 changes: 24 additions & 24 deletions app/backend/Extensions/ChatTurnExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,33 @@ namespace MinimalApi.Extensions;

internal static class ChatTurnExtensions
{
internal static string GetChatHistoryAsText(
this ChatTurn[] history, bool includeLastTurn = true, int approximateMaxTokens = 1_000)
{
var historyTextResult = string.Empty;
var skip = includeLastTurn ? 0 : 1;
//internal static string GetChatHistoryAsText(
// this ChatMessage[] history, bool includeLastTurn = true, int approximateMaxTokens = 1_000)
//{
// var historyTextResult = string.Empty;
// var skip = includeLastTurn ? 0 : 1;

foreach (var turn in history.SkipLast(skip).Reverse())
{
var historyText = $"user: {turn.User}";
// foreach (var turn in history.SkipLast(skip).Reverse())
// {
// var historyText = $"user: {turn.User}";

if (turn.Bot is not null)
{
historyText += $"""
<|im_start|>assistant
{turn.Bot}
<|im_end|>
""";
}
// if (turn.Content is not null)
// {
// historyText += $"""
// <|im_start|>assistant
// {turn.Content}
// <|im_end|>
// """;
// }

historyTextResult = historyText + historyTextResult;
// historyTextResult = historyText + historyTextResult;

if (historyTextResult.Length > approximateMaxTokens * 4)
{
return historyTextResult;
}
}
// if (historyTextResult.Length > approximateMaxTokens * 4)
// {
// return historyTextResult;
// }
// }

return historyTextResult;
}
// return historyTextResult;
//}
}
2 changes: 1 addition & 1 deletion app/backend/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "https://localhost:7181;http://localhost:5167"
"applicationUrl": "https://localhost:7181"
},
"Docker": {
"commandName": "Docker",
Expand Down
42 changes: 29 additions & 13 deletions app/backend/Services/ReadRetrieveReadChatService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ public ReadRetrieveReadChatService(
_tokenCredential = tokenCredential;
}

public async Task<ApproachResponse> ReplyAsync(
ChatTurn[] history,
public async Task<ChatAppResponse> ReplyAsync(
ChatMessage[] history,
RequestOverrides? overrides,
CancellationToken cancellationToken = default)
{
Expand All @@ -69,9 +69,11 @@ public async Task<ApproachResponse> ReplyAsync(
var chat = _kernel.GetRequiredService<IChatCompletionService>();
var embedding = _kernel.GetRequiredService<ITextEmbeddingGenerationService>();
float[]? embeddings = null;
var question = history.LastOrDefault()?.User is { } userQuestion
var question = history.LastOrDefault(m => m.IsUser)?.Content is { } userQuestion
? userQuestion
: throw new InvalidOperationException("Use question is null");

string[]? followUpQuestionList = null;
if (overrides?.RetrievalMode != RetrievalMode.Text && embedding is not null)
{
embeddings = (await embedding.GenerateEmbeddingAsync(question, cancellationToken: cancellationToken)).ToArray();
Expand Down Expand Up @@ -126,12 +128,15 @@ standard plan AND dental AND employee benefit.
"You are a system assistant who helps the company employees with their questions. Be brief in your answers");

// add chat history
foreach (var turn in history)
foreach (var message in history)
{
answerChat.AddUserMessage(turn.User);
if (turn.Bot is { } botMessage)
if (message.IsUser)
{
answerChat.AddUserMessage(message.Content);
}
else
{
answerChat.AddAssistantMessage(botMessage);
answerChat.AddAssistantMessage(message.Content);
}
}

Expand Down Expand Up @@ -215,17 +220,28 @@ You answer needs to be a json object with the following format.

var followUpQuestionsJson = followUpQuestions.Content ?? throw new InvalidOperationException("Failed to get search query");
var followUpQuestionsObject = JsonSerializer.Deserialize<JsonElement>(followUpQuestionsJson);
var followUpQuestionsList = followUpQuestionsObject.EnumerateArray().Select(x => x.GetString()).ToList();
var followUpQuestionsList = followUpQuestionsObject.EnumerateArray().Select(x => x.GetString()!).ToList();
foreach (var followUpQuestion in followUpQuestionsList)
{
ans += $" <<{followUpQuestion}>> ";
}

followUpQuestionList = followUpQuestionsList.ToArray();
}
return new ApproachResponse(
DataPoints: documentContentList,
Images: images,
Answer: ans,
Thoughts: thoughts,

var responseMessage = new ResponseMessage("assistant", ans);
var responseContext = new ResponseContext(
DataPointsContent: documentContentList.Select(x => new SupportingContentRecord(x.Title, x.Content)).ToArray(),
DataPointsImages: images?.Select(x => new SupportingImageRecord(x.Title, x.Url)).ToArray(),
FollowupQuestions: followUpQuestionList ?? Array.Empty<string>(),
Thoughts: new[] { new Thoughts("Thoughts", thoughts) });

var choice = new ResponseChoice(
Index: 0,
Message: responseMessage,
Context: responseContext,
CitationBaseUrl: _configuration.ToCitationBaseUrl());

return new ChatAppResponse(new[] { choice });
}
}
2 changes: 1 addition & 1 deletion app/shared/Shared/Models/Approach.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ public enum Approach
{
RetrieveThenRead,
ReadRetrieveRead,
ReadDecomposeAsk
ReadDecomposeAsk,
};
15 changes: 0 additions & 15 deletions app/shared/Shared/Models/ApproachResponse.cs

This file was deleted.

12 changes: 12 additions & 0 deletions app/shared/Shared/Models/ChatMessage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Text.Json.Serialization;

namespace Shared.Models;

public record ChatMessage(
[property:JsonPropertyName("role")] string Role,
[property: JsonPropertyName("content")] string Content)
{
public bool IsUser => Role == "user";
}
10 changes: 6 additions & 4 deletions app/shared/Shared/Models/ChatRequest.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Text.Json.Serialization;

namespace Shared.Models;

public record class ChatRequest(
ChatTurn[] History,
Approach Approach,
RequestOverrides? Overrides = null) : ApproachRequest(Approach)
[property: JsonPropertyName("messages")] ChatMessage[] History,
[property: JsonPropertyName("overrides")] RequestOverrides? Overrides
) : ApproachRequest(Approach.RetrieveThenRead)
{
public string? LastUserQuestion => History?.LastOrDefault()?.User;
public string? LastUserQuestion => History?.Last(m => m.Role == "user")?.Content;
}
5 changes: 0 additions & 5 deletions app/shared/Shared/Models/ChatTurn.cs

This file was deleted.

Loading