Skip to content

Commit

Permalink
Add more request options
Browse files Browse the repository at this point in the history
  • Loading branch information
jelledruyts committed Dec 19, 2023
1 parent 0410efe commit bdbe000
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 28 deletions.
2 changes: 2 additions & 0 deletions src/Azure.AISearch.WebApp/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ public static class Defaults
public const double FrequencyPenalty = 0.0;
public const double PresencePenalty = 0.0;
public const string StopSequences = "";
public const int Strictness = 3;
public const int DocumentCount = 5;
public const int HnswParametersM = 4;
public const int HnswParametersEfConstruction = 400;
public const int HnswParametersEfSearch = 500;
Expand Down
2 changes: 2 additions & 0 deletions src/Azure.AISearch.WebApp/Models/SearchRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ public class SearchRequest
public double? FrequencyPenalty { get; set; } = Constants.Defaults.FrequencyPenalty;
public double? PresencePenalty { get; set; } = Constants.Defaults.PresencePenalty;
public string? StopSequences { get; set; } = Constants.Defaults.StopSequences;
public int? Strictness { get; set; } = Constants.Defaults.Strictness;
public int? DocumentCount { get; set; } = Constants.Defaults.DocumentCount;

public bool IsVectorSearch => QueryType == Models.QueryType.Vector || QueryType == Models.QueryType.HybridStandard || QueryType == Models.QueryType.HybridSemantic;
public bool IsSemanticSearch => QueryType == Models.QueryType.TextSemantic || QueryType == Models.QueryType.HybridSemantic;
Expand Down
46 changes: 35 additions & 11 deletions src/Azure.AISearch.WebApp/Pages/Index.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -274,8 +274,8 @@
<label class="form-check-label" for="searchRequest-DataSource-AzureCognitiveSearch">Azure AI Search</label>
<span class="info-tip" data-bs-toggle="popover" data-bs-content="Use source data from an Azure AI Search index. For best results, you should use the <code>Chunks</code> index, as this contains smaller and typically more contextually relevant pieces of information. This makes the prompt that is sent to the AI model better suited to generate a meaningful response from."><i class="bi bi-info-circle"></i></span>
</div>
<div class="form-check form-check-inline form-switch">
<input class="form-check-input" type="checkbox" role="switch" name="@nameof(SearchRequest.LimitToDataSource)" id="searchRequest-LimitToDataSource" value="true" v-model="searchRequest.limitToDataSource" v-bind:disabled="searchRequest.dataSource == '@DataSourceType.None'">
<div class="form-check form-check-inline form-switch" v-show="searchRequest.dataSource != '@DataSourceType.None'">
<input class="form-check-input" type="checkbox" role="switch" name="@nameof(SearchRequest.LimitToDataSource)" id="searchRequest-LimitToDataSource" value="true" v-model="searchRequest.limitToDataSource">
<input type="hidden" name="@nameof(SearchRequest.LimitToDataSource)" value="false" />
<label class="form-check-label" for="searchRequest-LimitToDataSource">Limit to your data</label>
<span class="info-tip" data-bs-toggle="popover" data-bs-content="Limit responses to your data content only, not including the data that was used for training the model."><i class="bi bi-info-circle"></i></span>
Expand Down Expand Up @@ -312,18 +312,18 @@
<label class="form-check-label" for="searchRequest-QueryType-TextSemantic">Semantic keyword</label>
<span class="info-tip" data-bs-toggle="popover" data-bs-content="Use <a href='https://learn.microsoft.com/azure/search/semantic-search-overview' target='_blank'>semantic ranking</a>, which returns more relevant results by applying language understanding to initial search results. It can also return <a href='https://learn.microsoft.com/azure/search/semantic-how-to-query-request' target='_blank'>semantic captions</a> (parts of a document that best summarize the content) and even <a href='https://learn.microsoft.com/azure/search/semantic-answers' target='_blank'>semantic answers</a> (direct answers to queries that look like a question). In all cases, the responses aren't AI-generated but come directly from the source data."><i class="bi bi-info-circle"></i></span>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="@nameof(SearchRequest.QueryType)" id="searchRequest-QueryType-Vector" value="@QueryType.Vector" v-model="searchRequest.queryType" v-bind:disabled="searchRequest.searchIndex == '@SearchIndexType.Documents'">
<div class="form-check form-check-inline" v-show="searchRequest.searchIndex != '@SearchIndexType.Documents'">
<input class="form-check-input" type="radio" name="@nameof(SearchRequest.QueryType)" id="searchRequest-QueryType-Vector" value="@QueryType.Vector" v-model="searchRequest.queryType">
<label class="form-check-label" for="searchRequest-QueryType-Vector">Vector only</label>
<span class="info-tip" data-bs-toggle="popover" data-bs-content="First send the search query to an <a href='https://learn.microsoft.com/azure/ai-services/openai/how-to/embeddings' target='_blank'>embedding model in Azure OpenAI</a> to generate a vector representing the query itself. Then perform a <a href='https://learn.microsoft.com/azure/search/vector-search-overview' target='_blank'>vector search</a> to retrieve the nearest neighbors in vector space from the chunked and vectorized documents in the <code>Chunks</code> index. This should return results that are semantically similar to the query, as determined by the embedding model's vector representations."><i class="bi bi-info-circle"></i></span>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="@nameof(SearchRequest.QueryType)" id="searchRequest-QueryType-HybridStandard" value="@QueryType.HybridStandard" v-model="searchRequest.queryType" v-bind:disabled="searchRequest.searchIndex == '@SearchIndexType.Documents'">
<div class="form-check form-check-inline" v-show="searchRequest.searchIndex != '@SearchIndexType.Documents'">
<input class="form-check-input" type="radio" name="@nameof(SearchRequest.QueryType)" id="searchRequest-QueryType-HybridStandard" value="@QueryType.HybridStandard" v-model="searchRequest.queryType">
<label class="form-check-label" for="searchRequest-QueryType-HybridStandard">Standard hybrid (keyword + vector)</label>
<span class="info-tip" data-bs-toggle="popover" data-bs-content="Combine the results of a <a href='https://learn.microsoft.com/azure/search/vector-search-overview' target='_blank'>vector search</a> with the regular <a href='https://learn.microsoft.com/azure/search/search-lucene-query-architecture' target='_blank'>keyword search</a> results into a single ranked response."><i class="bi bi-info-circle"></i></span>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="@nameof(SearchRequest.QueryType)" id="searchRequest-QueryType-HybridSemantic" value="@QueryType.HybridSemantic" v-model="searchRequest.queryType" v-bind:disabled="searchRequest.searchIndex == '@SearchIndexType.Documents'">
<div class="form-check form-check-inline" v-show="searchRequest.searchIndex != '@SearchIndexType.Documents'">
<input class="form-check-input" type="radio" name="@nameof(SearchRequest.QueryType)" id="searchRequest-QueryType-HybridSemantic" value="@QueryType.HybridSemantic" v-model="searchRequest.queryType">
<label class="form-check-label" for="searchRequest-QueryType-HybridSemantic">Semantic hybrid (keyword + vector)</label>
<span class="info-tip" data-bs-toggle="popover" data-bs-content="Combine the results of <a href='https://learn.microsoft.com/azure/search/vector-search-overview' target='_blank'>vector search</a> and <a href='https://learn.microsoft.com/azure/search/semantic-search-overview' target='_blank'>search with semantic ranking</a> into a single ranked response. Compared to standard hybrid search, this provides even more accuracy with L2 reranking using the same language models that power Bing."><i class="bi bi-info-circle"></i></span>
</div>
Expand All @@ -346,6 +346,30 @@
</div>
</div>

<div class="mb-2" v-show="!(searchRequest.engine == '@EngineType.AzureOpenAI' && searchRequest.dataSource == '@DataSourceType.None')">
<label class="form-label">Result parameters</label>
<div class="row row-cols-lg-auto g-3 align-items-center">
<div class="col-12">
<div class="input-group">
<div class="input-group-text">
Document count
<span class="info-tip" data-bs-toggle="popover" data-bs-content="The number of documents to return."><i class="bi bi-info-circle"></i></span>
</div>
<input type="number" class="form-control form-control-small" name="@nameof(SearchRequest.DocumentCount)" value="@Model.SearchRequest.DocumentCount" />
</div>
</div>
<div class="col-12" v-show="searchRequest.engine == '@EngineType.AzureOpenAI' && searchRequest.dataSource == '@DataSourceType.AzureCognitiveSearch'">
<div class="input-group">
<div class="input-group-text">
Strictness
<span class="info-tip" data-bs-toggle="popover" data-bs-content="Sets the threshold to categorize documents as relevant to your queries. Raising the value means a higher threshold for relevance and filters out more less-relevant documents for responses. Setting this value too high might cause the model to fail to generate responses due to limited available documents."><i class="bi bi-info-circle"></i></span>
</div>
<input type="number" class="form-control form-control-small" name="@nameof(SearchRequest.Strictness)" value="@Model.SearchRequest.Strictness" />
</div>
</div>
</div>
</div>

<div class="mb-2" v-show="!(searchRequest.engine == '@EngineType.AzureOpenAI' && searchRequest.dataSource == '@DataSourceType.None') && (searchRequest.queryType == '@QueryType.Vector' || searchRequest.queryType == '@QueryType.HybridStandard' || searchRequest.queryType == '@QueryType.HybridSemantic')">
<label class="form-label">Vector parameters</label>
<div class="row row-cols-lg-auto g-3 align-items-center">
Expand All @@ -359,8 +383,8 @@
</div>
</div>
<div class="col-12">
<div class="form-check form-check-inline form-switch">
<input class="form-check-input" type="checkbox" role="switch" name="@nameof(SearchRequest.UseIntegratedVectorization)" id="searchRequest-UseIntegratedVectorization" value="true" v-model="searchRequest.useIntegratedVectorization" v-bind:disabled="searchRequest.engine == '@EngineType.AzureOpenAI'">
<div class="form-check form-check-inline form-switch" v-show="searchRequest.engine != '@EngineType.AzureOpenAI'">
<input class="form-check-input" type="checkbox" role="switch" name="@nameof(SearchRequest.UseIntegratedVectorization)" id="searchRequest-UseIntegratedVectorization" value="true" v-model="searchRequest.useIntegratedVectorization">
<input type="hidden" name="@nameof(SearchRequest.UseIntegratedVectorization)" value="false" />
<label class="form-check-label" for="searchRequest-UseIntegratedVectorization">Use integrated vectorization</label>
<span class="info-tip" data-bs-toggle="popover" data-bs-content="Use <a href='https://learn.microsoft.com/azure/search/vector-search-integrated-vectorization' target='_blank'>integrated vectorization</a> to let Azure AI Search generate the vector embedding for the search query text, rather than the app doing that upfront and sending the vector directly to the search service."><i class="bi bi-info-circle"></i></span>
Expand All @@ -369,7 +393,7 @@
</div>
</div>

<div class="mb-2" v-show="searchRequest.engine == '@EngineType.CustomOrchestration'">
<div class="mb-2" v-show="searchRequest.engine == '@EngineType.CustomOrchestration' || searchRequest.engine == '@EngineType.AzureOpenAI'">
<label class="form-label">Model parameters</label>
<div class="row row-cols-lg-auto g-3 align-items-center">
<div class="col-12">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public async Task<SearchResponse> SearchAsync(SearchRequest request)
var searchOptions = new SearchOptions
{
QueryType = request.IsSemanticSearch ? SearchQueryType.Semantic : (request.QuerySyntax == QuerySyntax.Lucene ? SearchQueryType.Full : SearchQueryType.Simple),
Size = request.DocumentCount ?? Constants.Defaults.DocumentCount,
HighlightPreTag = "<mark>",
HighlightPostTag = "</mark>"
};
Expand Down
56 changes: 39 additions & 17 deletions src/Azure.AISearch.WebApp/Services/AzureOpenAISearchService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,23 @@ public async Task<SearchResponse> SearchAsync(SearchRequest request)
ArgumentNullException.ThrowIfNull(request.Query);

var searchResponse = new SearchResponse();
var chatCompletionsOptions = new ChatCompletionsOptions { DeploymentName = this.settings.OpenAIGptDeployment };
var chatCompletionsOptions = new ChatCompletionsOptions
{
DeploymentName = this.settings.OpenAIGptDeployment,
MaxTokens = request.MaxTokens ?? Constants.Defaults.MaxTokens,
Temperature = (float)(request.Temperature ?? Constants.Defaults.Temperature),
NucleusSamplingFactor = (float)(request.TopP ?? Constants.Defaults.TopP),
FrequencyPenalty = (float)(request.FrequencyPenalty ?? Constants.Defaults.FrequencyPenalty),
PresencePenalty = (float)(request.PresencePenalty ?? Constants.Defaults.PresencePenalty),
};
var stopSequences = (request.StopSequences ?? Constants.Defaults.StopSequences).Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (stopSequences.Any())
{
foreach (var stopSequence in stopSequences)
{
chatCompletionsOptions.StopSequences.Add(stopSequence);
}
}
chatCompletionsOptions.Messages.Add(new ChatRequestSystemMessage(request.SystemRoleInformation));

if (request.History != null && request.History.Any())
Expand Down Expand Up @@ -66,27 +82,30 @@ public async Task<SearchResponse> SearchAsync(SearchRequest request)
throw new InvalidOperationException("Azure OpenAI didn't return a meaningful response.");
}

// Process citations within the answer, which take the form "[doc1][doc2]..." and refer to the (1-based) index of
// the citations in the tool message.
foreach (var extensionMessage in answerMessage.AzureExtensionsContext.Messages.Where(m => m.Role == ChatRole.Tool))
if (answerMessage.AzureExtensionsContext != null)
{
Console.WriteLine(extensionMessage.Content);
var content = JsonSerializer.Deserialize<ChatResponseMessageContent>(extensionMessage.Content!);
if (content?.Citations != null && content.Citations.Any())
// Process citations within the answer, which take the form "[doc1][doc2]..." and refer to the (1-based) index of
// the citations in the tool message.
foreach (var extensionMessage in answerMessage.AzureExtensionsContext.Messages.Where(m => m.Role == ChatRole.Tool))
{
var citationIndex = 0;
foreach (var citation in content.Citations)
Console.WriteLine(extensionMessage.Content);
var content = JsonSerializer.Deserialize<ChatResponseMessageContent>(extensionMessage.Content!);
if (content?.Citations != null && content.Citations.Any())
{
answerText = answerText.Replace($"[doc{++citationIndex}]", $"<cite>{citation.Title}</cite>", StringComparison.OrdinalIgnoreCase);
searchResponse.SearchResults.Add(new SearchResult
var citationIndex = 0;
foreach (var citation in content.Citations)
{
DocumentId = citation.Id,
DocumentTitle = citation.Title,
Captions = string.IsNullOrWhiteSpace(citation.Content) ? Array.Empty<string>() : new[] { citation.Content }
});
answerText = answerText.Replace($"[doc{++citationIndex}]", $"<cite>{citation.Title}</cite>", StringComparison.OrdinalIgnoreCase);
searchResponse.SearchResults.Add(new SearchResult
{
DocumentId = citation.Id,
DocumentTitle = citation.Title,
Captions = string.IsNullOrWhiteSpace(citation.Content) ? Array.Empty<string>() : new[] { citation.Content }
});
}
// Stop looping through the tool messages once we find the first one holding the citations.
break;
}
// Stop looping through the tool messages once we find the first one holding the citations.
break;
}
}

Expand Down Expand Up @@ -117,6 +136,9 @@ private AzureCognitiveSearchChatExtensionConfiguration GetAzureCognitiveSearchDa
},
ShouldRestrictResultScope = request.LimitToDataSource, // Limit responses to data from the data source only
QueryType = GetQueryType(request),
RoleInformation = request.SystemRoleInformation ?? Constants.Defaults.SystemRoleInformation,
Strictness = request.Strictness ?? Constants.Defaults.Strictness,
DocumentCount = request.DocumentCount ?? Constants.Defaults.DocumentCount,
SemanticConfiguration = request.IsSemanticSearch ? Constants.ConfigurationNames.SemanticConfigurationNameDefault : null,
EmbeddingEndpoint = request.IsVectorSearch ? new Uri(new Uri(this.settings.OpenAIEndpoint), $"openai/deployments/{this.settings.OpenAIEmbeddingDeployment}/embeddings?api-version={this.settings.OpenAIApiVersion}") : null,
EmbeddingKey = request.IsVectorSearch ? this.settings.OpenAIApiKey : null
Expand Down

0 comments on commit bdbe000

Please sign in to comment.