Skip to content

Commit 5d45ace

Browse files
committed
add new chat feature
1 parent 6287cb4 commit 5d45ace

File tree

7 files changed

+56
-33
lines changed

7 files changed

+56
-33
lines changed

app/src/KnowledgeMining.Application/Common/Options/ChunkSearchOptions.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ public class ChunkSearchOptions
88
public string? IndexName { get; set; }
99

1010
public string? KeyField { get; set; }
11-
public int PageSize { get; set; }
11+
public int PageSize { get; set; }
1212
}
1313
}

app/src/KnowledgeMining.Infrastructure/ConfigureServices.cs

+9-7
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,17 @@ public static IServiceCollection AddInfrastructureServices(this IServiceCollecti
3737
clientBuilder.AddSearchIndexerClient(configuration.GetSection(SearchOptions.Search));
3838
});
3939

40-
//services.Configure<ChunkSearchOptions>(configuration.GetSection(ChunkSearchOptions.ChunkSearch));
41-
//services.AddKeyedScoped("chunk", (services, obje) =>
42-
//{
43-
// var options = services.GetRequiredService<IOptions<ChunkSearchOptions>>().Value;
44-
// return new SearchClient(new Uri(options.Endpoint), options.IndexName, new DefaultAzureCredential());
45-
//});
40+
services.Configure<ChunkSearchOptions>(configuration.GetSection(ChunkSearchOptions.ChunkSearch));
41+
services.AddKeyedScoped("chunk", (services, obje) =>
42+
{
43+
var options = services.GetRequiredService<IOptions<ChunkSearchOptions>>().Value;
44+
var apiKey = configuration.GetSection(ChunkSearchOptions.ChunkSearch)["Credential:Key"] ?? throw new ArgumentNullException("azure search credential key is empty.");
45+
return new SearchClient(new Uri(options.Endpoint), options.IndexName, new AzureKeyCredential(apiKey));
46+
});
4647

4748
services.AddScoped<ISearchService, SearchService>();
48-
services.AddScoped<IChunkSearchService, MockChunkSearchService>();
49+
services.AddScoped<IChunkSearchService, ChunkSearchService
50+
>();
4951
services.AddScoped<IStorageService, StorageService>();
5052

5153
services.Configure<OpenAIOptions>(configuration.GetSection(OpenAIOptions.OpenAI));

app/src/KnowledgeMining.Infrastructure/Services/OpenAI/OpenAIService.cs

+29-14
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,14 @@ public class OpenAIService(OpenAIClient client, IChunkSearchService searchServic
1818
private IList<ChatRequestMessage> _messages = new List<ChatRequestMessage>();
1919

2020
public async Task<string> AskQuestionAboutDocument(string question, string content, string documentId = "", CancellationToken ct = default)
21-
{
22-
var systemPrompt = @$" ## Source ##
23-
{content}
24-
## End ##
21+
{
22+
var questionEmbedding = await _openAIClient.GetEmbeddingsAsync(new EmbeddingsOptions(_options.EmbeddingDeployment, [question]));
23+
24+
var chunks = await _searchService.QueryDocumentChuncksAsync(questionEmbedding.Value.Data[0].Embedding.ToArray(), documentId, ct);
2525

26-
You answer needs to be a valid json object with the following format.
27-
{{
28-
""answer"": ""the answer to the question, add a source reference to the end of each sentence. e.g. Apple is a fruit [reference1.pdf][reference2.pdf]. If no source available, put the answer as I don't know."",
29-
""thoughts"": ""brief thoughts on how you came up with the answer, e.g. what sources you used, what you thought about, etc.""
30-
}}";
26+
var systemPrompt = CreateSystemPromptFromChunks(chunks);
3127

32-
ChatCompletionsOptions chatCompletions = new ChatCompletionsOptions()
28+
var chatCompletions = new ChatCompletionsOptions()
3329
{
3430
DeploymentName = _options.CompletionsDeployment,
3531
Temperature = (float)0.7,
@@ -44,10 +40,6 @@ You answer needs to be a valid json object with the following format.
4440
}
4541
};
4642

47-
var questionEmbedding = await _openAIClient.GetEmbeddingsAsync(new EmbeddingsOptions(_options.EmbeddingDeployment, [question]));
48-
49-
var chunks = await _searchService.QueryDocumentChuncksAsync(questionEmbedding.Value.Data[0].Embedding.ToArray(), documentId, ct);
50-
5143
var answer = await _openAIClient.GetChatCompletionsAsync(chatCompletions);
5244

5345
var answerJson = answer.Value.Choices.FirstOrDefault()?.Message.Content ?? throw new InvalidOperationException("Failed to get search query");
@@ -59,5 +51,28 @@ You answer needs to be a valid json object with the following format.
5951

6052
return ans;
6153
}
54+
55+
private string CreateSystemPromptFromChunks(IEnumerable<string> chunks)
56+
{
57+
var sb = new StringBuilder();
58+
sb.AppendLine("## Sources ##");
59+
60+
for (int i = 0; i < chunks.Count(); i++)
61+
{
62+
sb.AppendLine($"### Source {i + 1} ###");
63+
sb.AppendLine(chunks.ElementAt(i));
64+
sb.AppendLine($"### End Source {i + 1} ###");
65+
}
66+
67+
sb.AppendLine("## End Sources ##");
68+
sb.AppendLine();
69+
sb.AppendLine("You answer needs to be a valid json object with the following format.");
70+
sb.AppendLine("{");
71+
sb.AppendLine(" \"answer\": \"the answer to the question, add a source reference to the end of each sentence. e.g. Apple is a fruit [reference1.pdf][reference2.pdf]. If no source available, put the answer as I don't know.\",");
72+
sb.AppendLine(" \"thoughts\": \"brief thoughts on how you came up with the answer, e.g. what sources you used, what you thought about, etc.\"");
73+
sb.AppendLine("}");
74+
75+
return sb.ToString();
76+
}
6277
}
6378
}

app/src/KnowledgeMining.Infrastructure/Services/Search/ChunkSearchService.cs

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using Azure.Search.Documents;
22
using Azure.Search.Documents.Models;
33
using KnowledgeMining.Application.Common.Interfaces;
4+
using KnowledgeMining.Application.Common.Options;
45
using Microsoft.Extensions.DependencyInjection;
6+
using Microsoft.Extensions.Options;
57
using System;
68
using System.Collections.Generic;
79
using System.Linq;
@@ -18,15 +20,16 @@ public Task<IEnumerable<string>> QueryDocumentChuncksAsync(float[] embedding, st
1820
}
1921
}
2022

21-
public class ChunkSearchService([FromKeyedServices("chunk")]SearchClient searchClient) : IChunkSearchService
23+
public class ChunkSearchService([FromKeyedServices("chunk")]SearchClient searchClient, IOptions<ChunkSearchOptions> options) : IChunkSearchService
2224
{
2325
private readonly SearchClient _searchClient = searchClient;
26+
private readonly ChunkSearchOptions _options = options.Value;
2427

2528
public async Task<IEnumerable<string>> QueryDocumentChuncksAsync(float[] embedding, string document, CancellationToken cancellationToken = default)
2629
{
27-
var filter = $"metadata_storage_path eq '{document}'";
30+
var filter = $"{_options.KeyField} eq '{document}'";
2831

29-
SearchOptions searchOptions = new()
32+
Azure.Search.Documents.SearchOptions searchOptions = new()
3033
{
3134
Filter = filter,
3235
QueryType = SearchQueryType.Semantic,
@@ -44,7 +47,7 @@ public async Task<IEnumerable<string>> QueryDocumentChuncksAsync(float[] embeddi
4447
KNearestNeighborsCount = k
4548
};
4649

47-
vectorQuery.Fields.Add("embedding");
50+
vectorQuery.Fields.Add("text_vector");
4851
searchOptions.VectorSearch = new();
4952
searchOptions.VectorSearch.Queries.Add(vectorQuery);
5053

app/src/KnowledgeMining.UI/Pages/Search/Components/DocumentChatComponent.razor

+7-4
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
@turn.Question
1414
</MudText>
1515
</MudPaper>
16-
@if(@turn.Answer is null)
16+
@if (@turn.Answer is null)
1717
{
1818
<MudPaper Class="pa-6" Elevation="3">
1919
<MudProgressCircular Indeterminate="true" />
@@ -45,8 +45,11 @@
4545
</MudGrid>
4646

4747
@code {
48-
[Parameter]
49-
public string Context { get; set; } = string.Empty;
48+
[Parameter]
49+
public string DocumentId { get; set; } = string.Empty;
50+
51+
[Parameter]
52+
public string Content { get; set; } = string.Empty;
5053

5154
private string _message { get; set; } = string.Empty;
5255
private List<ChatTurn> _messages { get; set; } = [];
@@ -83,7 +86,7 @@
8386
try
8487
{
8588
_isReceivingMessage = true;
86-
var response = await ChatService.AskQuestionAboutDocument(latestMessage, Context);
89+
var response = await ChatService.AskQuestionAboutDocument(latestMessage, Content, DocumentId);
8790

8891
turn.Answer = response;
8992
}

app/src/KnowledgeMining.UI/Pages/Search/Components/DocumentDetailsComponent.razor

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
</MudTable>
4343
</MudTabPanel>
4444
<MudTabPanel Text="Chat">
45-
<DocumentChatComponent Context="@GetDocumentContent()" />
45+
<DocumentChatComponent Content="@GetDocumentContent()" DocumentId="@Document?.Name" />
4646
</MudTabPanel>
4747
</MudTabs>
4848
<MudButton OnClick="@ClosePopover" Class="ml-auto mr-n3 mb-1" Color="Color.Error">Close</MudButton>

app/src/KnowledgeMining.UI/appsettings.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@
4343
"ChunkSearch": {
4444
// Required fields
4545
// "Endpoint": "(value in user secrets)",
46-
"IndexName": "km",
47-
"KeyField": "metadata_storage_path",
46+
"IndexName": "vector-1718910374291",
47+
"KeyField": "title",
4848
"PageSize": 1
4949
},
5050

0 commit comments

Comments
 (0)