Skip to content

Commit e193da4

Browse files
implement chat protocal (#299)
## Purpose <!-- Describe the intention of the changes being proposed. What problem does it solve or functionality does it add? --> * ... This PR implements chat protocal (non-streaming) part. ## Does this introduce a breaking change? <!-- Mark one with an "x". --> ``` [ ] Yes [x] No ``` fix #254 Link to chat protocal: https://github.com/Azure-Samples/ai-chat-app-protocol ## Pull Request Type What kind of change does this Pull Request introduce? <!-- Please check the one that applies to this PR using "x". --> ``` [ ] Bugfix [ ] Feature [ ] Code style update (formatting, local variables) [ ] Refactoring (no functional changes, no api changes) [ ] Documentation content changes [ ] Other... Please describe: ``` ## How to Test * Get the code ``` git clone [repo-address] cd [repo-name] git checkout [branch-name] npm install ``` * Test the code <!-- Add steps to run the tests suite and/or manually test --> ``` ``` ## What to Check Verify that the following are valid * ... ## Other Information <!-- Add any other helpful information that may be needed here. --> --------- Co-authored-by: David Pine <[email protected]>
1 parent eccf87e commit e193da4

22 files changed

+198
-100
lines changed

app/Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<AzureFormRecognizerVersion>4.1.0</AzureFormRecognizerVersion>
5-
<AzureIdentityVersion>1.10.1</AzureIdentityVersion>
5+
<AzureIdentityVersion>1.11.0</AzureIdentityVersion>
66
<AzureSearchDocumentsVersion>11.5.0-beta.4</AzureSearchDocumentsVersion>
77
<AzureStorageBlobsVersion>12.17.0</AzureStorageBlobsVersion>
88
<AzureOpenAIVersion>1.0.0-beta.7</AzureOpenAIVersion>

app/Directory.Packages.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<PackageVersion Include="Azure.AI.FormRecognizer" Version="4.1.0" />
88
<PackageVersion Include="Azure.AI.OpenAI" Version="1.0.0-beta.12" />
99
<PackageVersion Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.3.0" />
10-
<PackageVersion Include="Azure.Identity" Version="1.10.4" />
10+
<PackageVersion Include="Azure.Identity" Version="1.11.0" />
1111
<PackageVersion Include="Azure.Search.Documents" Version="11.5.1" />
1212
<PackageVersion Include="Azure.Storage.Blobs" Version="12.19.1" />
1313
<PackageVersion Include="Azure.Storage.Files.Shares" Version="12.17.1" />

app/SharedWebComponents/Components/Answer.razor

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,20 +46,20 @@
4646
</MudTabPanel>
4747
<MudTabPanel Icon="@Icons.Material.Filled.Lightbulb" Text="Thought process"
4848
ToolTip="Show thought process."
49-
Disabled="@(Retort is { Thoughts: null })">
49+
Disabled="@(Retort is { Context.Thoughts: null } or { Context.Thoughts.Length: 0})">
5050
<ChildContent>
5151
<MudPaper Class="pa-6" Elevation="3">
5252
<pre style="white-space: normal; font-size: 1.2em;">
53-
@(RemoveLeadingAndTrailingLineBreaks(Retort.Thoughts!))
53+
@(RemoveLeadingAndTrailingLineBreaks(Retort.Context.ThoughtsString))
5454
</pre>
5555
</MudPaper>
5656
</ChildContent>
5757
</MudTabPanel>
5858
<MudTabPanel Icon="@Icons.Material.Filled.TextSnippet" Text="Supporting Content"
59-
ToolTip="Show the supporting content." Disabled="@(Retort is { DataPoints: null } or { DataPoints.Length: 0 })">
59+
ToolTip="Show the supporting content." Disabled="@(Retort is { Context.DataPoints.Text: null } or { Context.DataPoints.Text.Length: 0 })">
6060
<ChildContent>
6161
<MudPaper Class="pa-2" Elevation="3">
62-
<SupportingContent DataPoints="Retort.DataPoints" Images="Retort.Images ?? []" />
62+
<SupportingContent DataPoints="Retort.Context.DataPointsContent" Images="Retort.Context.DataPointsImages ?? []" />
6363
</MudPaper>
6464
</ChildContent>
6565
</MudTabPanel>

app/SharedWebComponents/Components/Answer.razor.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ namespace SharedWebComponents.Components;
44

55
public sealed partial class Answer
66
{
7-
[Parameter, EditorRequired] public required ApproachResponse Retort { get; set; }
7+
[Parameter, EditorRequired] public required ResponseChoice Retort { get; set; }
88
[Parameter, EditorRequired] public required EventCallback<string> FollowupQuestionClicked { get; set; }
99

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

1919
base.OnParametersSet();
2020
}

app/SharedWebComponents/Components/AnswerError.razor

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
<MudStack Spacing="4" Class="full-width">
33
<MudIcon Icon="@Icons.Material.Filled.Error" Color="Color.Error" Size="Size.Large" />
44
<MudText Typo="Typo.subtitle2">
5-
@Error.Answer
5+
@Error.Error
66
</MudText>
77
<MudText Typo="Typo.body1">
8-
@Error.Error
8+
Unable to retrieve valid response from the server.
99
</MudText>
1010
<div>
1111
<MudButton Variant="Variant.Filled" Size="Size.Small" Color="Color.Info"

app/SharedWebComponents/Components/AnswerError.razor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace SharedWebComponents.Components;
55
public sealed partial class AnswerError
66
{
77
[Parameter, EditorRequired] public required string Question { get; set; }
8-
[Parameter, EditorRequired] public required ApproachResponse Error { get; set; }
8+
[Parameter, EditorRequired] public required ChatAppResponseOrError Error { get; set; }
99
[Parameter, EditorRequired] public required EventCallback<string> OnRetryClicked { get; set; }
1010

1111
private async Task OnRetryClickedAsync()

app/SharedWebComponents/Models/AnswerResult.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ namespace SharedWebComponents.Models;
44

55
public readonly record struct AnswerResult<TRequest>(
66
bool IsSuccessful,
7-
ApproachResponse? Response,
7+
ChatAppResponseOrError? Response,
88
Approach Approach,
99
TRequest Request) where TRequest : ApproachRequest;

app/SharedWebComponents/Pages/Chat.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
<MudBadge Origin="Origin.TopLeft" Overlap="true" Color="Color.Secondary"
5353
Icon="@Icons.Material.Filled.AutoAwesome"
5454
Style="display:inherit">
55-
<Answer Retort="@answer" FollowupQuestionClicked="@OnAskQuestionAsync" />
55+
<Answer Retort="@answer.Choices[0]" FollowupQuestionClicked="@OnAskQuestionAsync" />
5656
</MudBadge>
5757
}
5858
</div>

app/SharedWebComponents/Pages/Chat.razor.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public sealed partial class Chat
99
private string _lastReferenceQuestion = "";
1010
private bool _isReceivingResponse = false;
1111

12-
private readonly Dictionary<UserQuestion, ApproachResponse?> _questionAndAnswerMap = [];
12+
private readonly Dictionary<UserQuestion, ChatAppResponseOrError?> _questionAndAnswerMap = [];
1313

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

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

49-
history.Add(new ChatTurn(_userQuestion));
49+
history.Add(new ChatMessage("user", _userQuestion));
5050

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

5454
_questionAndAnswerMap[_currentQuestion] = result.Response;

app/SharedWebComponents/Services/ApiClient.cs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -112,21 +112,19 @@ private async Task<AnswerResult<TRequest>> PostRequestAsync<TRequest>(
112112

113113
if (response.IsSuccessStatusCode)
114114
{
115-
var answer = await response.Content.ReadFromJsonAsync<ApproachResponse>();
115+
var answer = await response.Content.ReadFromJsonAsync<ChatAppResponseOrError>();
116116
return result with
117117
{
118118
IsSuccessful = answer is not null,
119-
Response = answer
119+
Response = answer,
120120
};
121121
}
122122
else
123123
{
124-
var answer = new ApproachResponse(
125-
$"HTTP {(int)response.StatusCode} : {response.ReasonPhrase ?? "☹️ Unknown error..."}",
126-
null,
127-
[],
128-
null,
129-
"Unable to retrieve valid response from the server.");
124+
var errorTitle = $"HTTP {(int)response.StatusCode} : {response.ReasonPhrase ?? "☹️ Unknown error..."}";
125+
var answer = new ChatAppResponseOrError(
126+
Array.Empty<ResponseChoice>(),
127+
errorTitle);
130128

131129
return result with
132130
{

app/backend/Extensions/ChatTurnExtensions.cs

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,33 @@ namespace MinimalApi.Extensions;
44

55
internal static class ChatTurnExtensions
66
{
7-
internal static string GetChatHistoryAsText(
8-
this ChatTurn[] history, bool includeLastTurn = true, int approximateMaxTokens = 1_000)
9-
{
10-
var historyTextResult = string.Empty;
11-
var skip = includeLastTurn ? 0 : 1;
7+
//internal static string GetChatHistoryAsText(
8+
// this ChatMessage[] history, bool includeLastTurn = true, int approximateMaxTokens = 1_000)
9+
//{
10+
// var historyTextResult = string.Empty;
11+
// var skip = includeLastTurn ? 0 : 1;
1212

13-
foreach (var turn in history.SkipLast(skip).Reverse())
14-
{
15-
var historyText = $"user: {turn.User}";
13+
// foreach (var turn in history.SkipLast(skip).Reverse())
14+
// {
15+
// var historyText = $"user: {turn.User}";
1616

17-
if (turn.Bot is not null)
18-
{
19-
historyText += $"""
20-
<|im_start|>assistant
21-
{turn.Bot}
22-
<|im_end|>
23-
""";
24-
}
17+
// if (turn.Content is not null)
18+
// {
19+
// historyText += $"""
20+
// <|im_start|>assistant
21+
// {turn.Content}
22+
// <|im_end|>
23+
// """;
24+
// }
2525

26-
historyTextResult = historyText + historyTextResult;
26+
// historyTextResult = historyText + historyTextResult;
2727

28-
if (historyTextResult.Length > approximateMaxTokens * 4)
29-
{
30-
return historyTextResult;
31-
}
32-
}
28+
// if (historyTextResult.Length > approximateMaxTokens * 4)
29+
// {
30+
// return historyTextResult;
31+
// }
32+
// }
3333

34-
return historyTextResult;
35-
}
34+
// return historyTextResult;
35+
//}
3636
}

app/backend/Properties/launchSettings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"ASPNETCORE_ENVIRONMENT": "Development"
1717
},
1818
"dotnetRunMessages": true,
19-
"applicationUrl": "https://localhost:7181;http://localhost:5167"
19+
"applicationUrl": "https://localhost:7181"
2020
},
2121
"Docker": {
2222
"commandName": "Docker",

app/backend/Services/ReadRetrieveReadChatService.cs

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ public ReadRetrieveReadChatService(
5656
_tokenCredential = tokenCredential;
5757
}
5858

59-
public async Task<ApproachResponse> ReplyAsync(
60-
ChatTurn[] history,
59+
public async Task<ChatAppResponse> ReplyAsync(
60+
ChatMessage[] history,
6161
RequestOverrides? overrides,
6262
CancellationToken cancellationToken = default)
6363
{
@@ -69,9 +69,11 @@ public async Task<ApproachResponse> ReplyAsync(
6969
var chat = _kernel.GetRequiredService<IChatCompletionService>();
7070
var embedding = _kernel.GetRequiredService<ITextEmbeddingGenerationService>();
7171
float[]? embeddings = null;
72-
var question = history.LastOrDefault()?.User is { } userQuestion
72+
var question = history.LastOrDefault(m => m.IsUser)?.Content is { } userQuestion
7373
? userQuestion
7474
: throw new InvalidOperationException("Use question is null");
75+
76+
string[]? followUpQuestionList = null;
7577
if (overrides?.RetrievalMode != RetrievalMode.Text && embedding is not null)
7678
{
7779
embeddings = (await embedding.GenerateEmbeddingAsync(question, cancellationToken: cancellationToken)).ToArray();
@@ -126,12 +128,15 @@ standard plan AND dental AND employee benefit.
126128
"You are a system assistant who helps the company employees with their questions. Be brief in your answers");
127129

128130
// add chat history
129-
foreach (var turn in history)
131+
foreach (var message in history)
130132
{
131-
answerChat.AddUserMessage(turn.User);
132-
if (turn.Bot is { } botMessage)
133+
if (message.IsUser)
134+
{
135+
answerChat.AddUserMessage(message.Content);
136+
}
137+
else
133138
{
134-
answerChat.AddAssistantMessage(botMessage);
139+
answerChat.AddAssistantMessage(message.Content);
135140
}
136141
}
137142

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

216221
var followUpQuestionsJson = followUpQuestions.Content ?? throw new InvalidOperationException("Failed to get search query");
217222
var followUpQuestionsObject = JsonSerializer.Deserialize<JsonElement>(followUpQuestionsJson);
218-
var followUpQuestionsList = followUpQuestionsObject.EnumerateArray().Select(x => x.GetString()).ToList();
223+
var followUpQuestionsList = followUpQuestionsObject.EnumerateArray().Select(x => x.GetString()!).ToList();
219224
foreach (var followUpQuestion in followUpQuestionsList)
220225
{
221226
ans += $" <<{followUpQuestion}>> ";
222227
}
228+
229+
followUpQuestionList = followUpQuestionsList.ToArray();
223230
}
224-
return new ApproachResponse(
225-
DataPoints: documentContentList,
226-
Images: images,
227-
Answer: ans,
228-
Thoughts: thoughts,
231+
232+
var responseMessage = new ResponseMessage("assistant", ans);
233+
var responseContext = new ResponseContext(
234+
DataPointsContent: documentContentList.Select(x => new SupportingContentRecord(x.Title, x.Content)).ToArray(),
235+
DataPointsImages: images?.Select(x => new SupportingImageRecord(x.Title, x.Url)).ToArray(),
236+
FollowupQuestions: followUpQuestionList ?? Array.Empty<string>(),
237+
Thoughts: new[] { new Thoughts("Thoughts", thoughts) });
238+
239+
var choice = new ResponseChoice(
240+
Index: 0,
241+
Message: responseMessage,
242+
Context: responseContext,
229243
CitationBaseUrl: _configuration.ToCitationBaseUrl());
244+
245+
return new ChatAppResponse(new[] { choice });
230246
}
231247
}

app/shared/Shared/Models/Approach.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ public enum Approach
66
{
77
RetrieveThenRead,
88
ReadRetrieveRead,
9-
ReadDecomposeAsk
9+
ReadDecomposeAsk,
1010
};

app/shared/Shared/Models/ApproachResponse.cs

Lines changed: 0 additions & 15 deletions
This file was deleted.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using System.Text.Json.Serialization;
4+
5+
namespace Shared.Models;
6+
7+
public record ChatMessage(
8+
[property:JsonPropertyName("role")] string Role,
9+
[property: JsonPropertyName("content")] string Content)
10+
{
11+
public bool IsUser => Role == "user";
12+
}
Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
// Copyright (c) Microsoft. All rights reserved.
22

3+
using System.Text.Json.Serialization;
4+
35
namespace Shared.Models;
46

57
public record class ChatRequest(
6-
ChatTurn[] History,
7-
Approach Approach,
8-
RequestOverrides? Overrides = null) : ApproachRequest(Approach)
8+
[property: JsonPropertyName("messages")] ChatMessage[] History,
9+
[property: JsonPropertyName("overrides")] RequestOverrides? Overrides
10+
) : ApproachRequest(Approach.RetrieveThenRead)
911
{
10-
public string? LastUserQuestion => History?.LastOrDefault()?.User;
12+
public string? LastUserQuestion => History?.Last(m => m.Role == "user")?.Content;
1113
}

app/shared/Shared/Models/ChatTurn.cs

Lines changed: 0 additions & 5 deletions
This file was deleted.

0 commit comments

Comments
 (0)