From 19ae1170fec871472e750b02c42a02be09ff5e43 Mon Sep 17 00:00:00 2001 From: Xiaoyun Zhang Date: Mon, 25 Sep 2023 13:23:46 -0700 Subject: [PATCH] Fix #135 & #161 & #140 (#162) ## Purpose Clean services that used by /ask Related issue: - #135 - #161 - #140 This PR also remove `ApproachServiceResponseFactory` and GPT model that used for text completion ## Does this introduce a breaking change? ``` [ ] Yes [ ] No ``` ## Pull Request Type What kind of change does this Pull Request introduce? ``` [ ] 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 ``` ``` ## What to Check Verify that the following are valid * ... ## Other Information --- .../Extensions/ServiceCollectionExtensions.cs | 21 -- app/backend/GlobalUsings.cs | 3 - .../ApproachServiceResponseFactory.cs | 96 -------- .../AzureOpenAIChatCompletionService.cs | 4 +- .../AzureOpenAITextCompletionService.cs | 43 ---- app/backend/Services/IApproachBasedService.cs | 13 - .../ReadDecomposeAskApproachService.cs | 231 ------------------ .../ReadRetrieveReadApproachService.cs | 103 -------- .../RetrieveThenReadApproachService.cs | 71 ------ app/backend/Services/Skills/LookupSkill.cs | 24 -- .../Skills/RetrieveRelatedDocumentSkill.cs | 32 --- .../Skills/UpdateContextVariableSkill.cs | 12 - infra/main.bicep | 27 -- 13 files changed, 2 insertions(+), 678 deletions(-) delete mode 100644 app/backend/Services/ApproachServiceResponseFactory.cs delete mode 100644 app/backend/Services/AzureOpenAITextCompletionService.cs delete mode 100644 app/backend/Services/IApproachBasedService.cs delete mode 100644 app/backend/Services/ReadDecomposeAskApproachService.cs delete mode 100644 app/backend/Services/ReadRetrieveReadApproachService.cs delete mode 100644 app/backend/Services/RetrieveThenReadApproachService.cs delete mode 100644 app/backend/Services/Skills/LookupSkill.cs delete mode 100644 app/backend/Services/Skills/RetrieveRelatedDocumentSkill.cs delete mode 100644 app/backend/Services/Skills/UpdateContextVariableSkill.cs diff --git a/app/backend/Extensions/ServiceCollectionExtensions.cs b/app/backend/Extensions/ServiceCollectionExtensions.cs index 94dde189..b1e546e5 100644 --- a/app/backend/Extensions/ServiceCollectionExtensions.cs +++ b/app/backend/Extensions/ServiceCollectionExtensions.cs @@ -65,32 +65,11 @@ internal static IServiceCollection AddAzureServices(this IServiceCollection serv return openAIClient; }); - services.AddSingleton(sp => - { - // Semantic Kernel doesn't support Azure AAD credential for now - // so we implement our own text completion backend - var config = sp.GetRequiredService(); - var azureOpenAiGptDeployment = config["AzureOpenAiGptDeployment"]; - - var openAITextService = sp.GetRequiredService(); - var kernel = Kernel.Builder.Build(); - kernel.Config.AddTextCompletionService(azureOpenAiGptDeployment!, _ => openAITextService); - - return kernel; - }); - services.AddSingleton(); - services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - - services.AddSingleton(); - return services; } diff --git a/app/backend/GlobalUsings.cs b/app/backend/GlobalUsings.cs index 5ccda97f..258b8220 100644 --- a/app/backend/GlobalUsings.cs +++ b/app/backend/GlobalUsings.cs @@ -13,7 +13,6 @@ global using Azure.Storage.Blobs.Models; global using Microsoft.AspNetCore.Mvc; global using Microsoft.AspNetCore.Mvc.RazorPages; -global using Microsoft.Extensions.Caching.Distributed; global using Microsoft.ML; global using Microsoft.ML.Transforms.Text; global using Microsoft.SemanticKernel; @@ -22,10 +21,8 @@ global using Microsoft.SemanticKernel.AI.TextCompletion; global using Microsoft.SemanticKernel.Memory; global using Microsoft.SemanticKernel.Orchestration; -global using Microsoft.SemanticKernel.SkillDefinition; global using MinimalApi.Extensions; global using MinimalApi.Services; -global using MinimalApi.Services.Skills; global using PdfSharpCore.Pdf; global using PdfSharpCore.Pdf.IO; global using Shared; diff --git a/app/backend/Services/ApproachServiceResponseFactory.cs b/app/backend/Services/ApproachServiceResponseFactory.cs deleted file mode 100644 index b162ad41..00000000 --- a/app/backend/Services/ApproachServiceResponseFactory.cs +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace MinimalApi.Services; - -internal sealed class ApproachServiceResponseFactory -{ - private readonly ILogger _logger; - private readonly IEnumerable _approachBasedServices; - private readonly IDistributedCache _cache; - - public ApproachServiceResponseFactory( - ILogger logger, - IEnumerable services, IDistributedCache cache) => (_logger, _approachBasedServices, _cache) = (logger, services, cache); - - internal async Task GetApproachResponseAsync( - Approach approach, - string question, - RequestOverrides? overrides = null, - CancellationToken cancellationToken = default) - { - var service = - _approachBasedServices.SingleOrDefault(service => service.Approach == approach) - ?? throw new ArgumentOutOfRangeException( - nameof(approach), $"Approach: {approach} value isn't supported."); - - var key = new CacheKey(approach, question, overrides) - .ToCacheKeyString(); - - var options = new JsonSerializerOptions(JsonSerializerDefaults.Web); - - // If the value is cached, return it. - var cachedValue = await _cache.GetStringAsync(key, cancellationToken); - if (cachedValue is { Length: > 0 } && - JsonSerializer.Deserialize(cachedValue, options) is ApproachResponse cachedResponse) - { - _logger.LogDebug( - "Returning cached value for key ({Key}): {Approach}\n{Response}", - key, approach, cachedResponse); - - return cachedResponse; - } - - var approachResponse = - await service.ReplyAsync(question, overrides, cancellationToken) - ?? throw new AIException( - AIException.ErrorCodes.ServiceError, - $"The approach response for '{approach}' was null."); - - var json = JsonSerializer.Serialize(approachResponse, options); - var entryOptions = new DistributedCacheEntryOptions - { - // Cache each unique request for 30 mins and log when evicted... - AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30) - }; - - await _cache.SetStringAsync(key, json, entryOptions, cancellationToken); - - _logger.LogDebug( - "Returning new value for key ({Key}): {Approach}\n{Response}", - key, approach, approachResponse); - - return approachResponse; - } -} - -internal readonly record struct CacheKey( - Approach Approach, - string Question, - RequestOverrides? Overrides) -{ - /// - /// Converts the given instance into a - /// that will uniquely identify the approach, question and optional override pairing. - /// - internal string ToCacheKeyString() - { - var (approach, question, overrides) = this; - - string? overridesString = null; - if (overrides is { } o) - { - static string Bit(bool value) => value ? "1" : "0"; - - var bits = $""" - {Bit(o.SemanticCaptions.GetValueOrDefault())}.{Bit(o.SemanticRanker)}.{Bit(o.SuggestFollowupQuestions)} - """; - - overridesString = - $":{o.ExcludeCategory}-{o.PromptTemplate}-{o.PromptTemplatePrefix}-{o.PromptTemplateSuffix}-{bits}"; - } - - return $""" - {approach}:{question}{overridesString} - """; - } -} diff --git a/app/backend/Services/AzureOpenAIChatCompletionService.cs b/app/backend/Services/AzureOpenAIChatCompletionService.cs index d154cee9..77a8c6cf 100644 --- a/app/backend/Services/AzureOpenAIChatCompletionService.cs +++ b/app/backend/Services/AzureOpenAIChatCompletionService.cs @@ -37,8 +37,8 @@ public async Task CompleteAsync( var response = await _openAIClient.GetCompletionsAsync( _deployedModelName, option, cancellationToken); - return response.Value is Completions completions && completions.Choices.Count > 0 - ? completions.Choices[0].Text + return response.Value is Completions completions && completions.Choices.Count > 0 + ? completions.Choices[0].Text : throw new AIException(AIException.ErrorCodes.InvalidConfiguration, "completion not found"); } } diff --git a/app/backend/Services/AzureOpenAITextCompletionService.cs b/app/backend/Services/AzureOpenAITextCompletionService.cs deleted file mode 100644 index f250b3ad..00000000 --- a/app/backend/Services/AzureOpenAITextCompletionService.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace MinimalApi.Services; - -public sealed class AzureOpenAITextCompletionService : ITextCompletion -{ - private readonly OpenAIClient _openAIClient; - private readonly string _deployedModelName; - - public AzureOpenAITextCompletionService(OpenAIClient openAIClient, IConfiguration config) - { - _openAIClient = openAIClient; - - var deployedModelName = config["AZURE_OPENAI_GPT_DEPLOYMENT"]; - ArgumentNullException.ThrowIfNullOrEmpty(deployedModelName); - _deployedModelName = deployedModelName; - } - - public async Task CompleteAsync( - string text, CompleteRequestSettings requestSettings, CancellationToken cancellationToken = default) - { - var option = new CompletionsOptions - { - MaxTokens = requestSettings.MaxTokens, - FrequencyPenalty = Convert.ToSingle(requestSettings.FrequencyPenalty), - PresencePenalty = Convert.ToSingle(requestSettings.PresencePenalty), - Temperature = Convert.ToSingle(requestSettings.Temperature), - Prompts = { text }, - }; - - foreach (var stopSequence in requestSettings.StopSequences) - { - option.StopSequences.Add(stopSequence); - } - - var response = - await _openAIClient.GetCompletionsAsync( - _deployedModelName, option, cancellationToken); - return response.Value is Completions completions && completions.Choices.Count > 0 - ? completions.Choices[0].Text - : throw new AIException(AIException.ErrorCodes.InvalidConfiguration, "completion not found"); - } -} diff --git a/app/backend/Services/IApproachBasedService.cs b/app/backend/Services/IApproachBasedService.cs deleted file mode 100644 index e5713090..00000000 --- a/app/backend/Services/IApproachBasedService.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace MinimalApi.Services; - -public interface IApproachBasedService -{ - Approach Approach { get; } - - Task ReplyAsync( - string question, - RequestOverrides? overrides = null, - CancellationToken cancellationToken = default); -} diff --git a/app/backend/Services/ReadDecomposeAskApproachService.cs b/app/backend/Services/ReadDecomposeAskApproachService.cs deleted file mode 100644 index 171ec5c5..00000000 --- a/app/backend/Services/ReadDecomposeAskApproachService.cs +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Microsoft.SemanticKernel.Planning.Planners; - -namespace MinimalApi.Services; - -internal sealed class ReadDecomposeAskApproachService : IApproachBasedService -{ - private readonly SearchClient _searchClient; - private readonly ILogger _logger; - private readonly IConfiguration _configuration; - private readonly AzureOpenAITextCompletionService _completionService; - - private const string AnswerPromptPrefix = """ - Answer questions using the given knowledge ONLY. For tabular information return it as an HTML table. Do not return markdown format. - Each knowledge has a source name followed by a colon and the actual information, always include the source name for each knowledge you use in the answer. - Don't cite knowledge that is not available in the knowledge list. - If you cannot answer using the knowledge list only, say you don't know. - - ### EXAMPLE - Question: 'What is the deductible for the employee plan for a visit to Overlake in Bellevue?' - - Knowledge: - info1.txt: deductibles depend on whether you are in-network or out-of-network. In-network deductibles are $500 for employees and $1000 for families. Out-of-network deductibles are $1000 for employees and $2000 for families. - info2.pdf: Overlake is in-network for the employee plan. - info3.pdf: Overlake is the name of the area that includes a park and ride near Bellevue. - info4.pdf: In-network institutions include Overlake, Swedish, and others in the region - - Answer: - In-network deductibles are $500 for employees and $1000 for families [info1.txt] and Overlake is in-network for the employee plan [info2.pdf][info4.pdf]. - - Question: 'What happens in a performance review' - - Knowledge: - - Answer: - I don't know - ### - Knowledge: - {{$knowledge}} - - Question: - {{$question}} - - Answer: - """; - - private const string CheckAnswerAvailablePrefix = """ - Use only 0 or 1 to in your reply. return 0 if the answer is unknown, otherwise return 1. - - Answer: - {{$answer}} - - ### EXAMPLE - Answer: I don't know - Your reply: - 0 - - Answer: I don't know the answer - Your reply: - 0 - - Answer: In-network deductibles are $500 for employees and $1000 for families [info1.txt] and Overlake is in-network for the employee plan [info2.pdf][info4.pdf]. - Your reply: - 1 - ### - - Your reply: - """; - - private const string ExplainPrefix = """ - Summarize the knowledge you need to know to answer the question. Please don't include - the existing knowledge in the answer - - ### EXAMPLES: - Knowledge: '' - Question: 'What is the deductible for the employee plan for a visit to Overlake in Bellevue?' - Explain: I need to know the information of employee plan and Overlake in Bellevue. - - Knowledge: '' - Question: 'What happens in a performance review?' - Your reply: I need to know what's performance review. - - Knowledge: 'Microsoft is a software company' - Question: 'When is annual review time for employees in Microsoft' - Explain: I need to know the information of annual review time for employees in Microsoft. - - Knowledge: 'Microsoft is a software company' - Question: 'What is included in my Northwind Health Plus plan that is not in standard?' - Explain: I need to know what's Northwind Health Plus Plan and what's not standard in that plan. - ### - Knowledge: - {{$knowledge}} - - Question: - {{$question}} - - Explain: - """; - - private const string GenerateKeywordsPrompt = """ - Generate keywords from explanation, separate multiple keywords with comma. - - ### EXAMPLE: - Explanation: I need to know the information of employee plan and Overlake in Bellevue - Keywords: employee plan, Overlake Bellevue - - Explanation: I need to know the duty of product manager - Keywords: product manager - - Explanation: I need to know information of annual eye exam. - Keywords: annual eye exam - - Explanation: I need to know what's Northwind Health Plus Plan and what's not standard in that plan. - Keywords: Northwind Health Plus plan - ### - - Explanation: - {{$explanation}} - Keywords: - """; - - private const string ThoughtProcessPrompt = """ - Describe the thought process of answering the question using given question, explanation, keywords, information, and answer. - - ### EXAMPLE: - Question: 'how many employees does Microsoft has now' - - Explanation: I need to know the information of Microsoft and its employee number. - - Keywords: Microsoft, employee number - - Information: [google.pdf]: Microsoft has over 144,000 employees worldwide as of 2019. - - Answer: I don't know how many employees does Microsoft has now, but in 2019, Microsoft has over 144,000 employees worldwide. - - Summary: - The question is about how many employees does Microsoft has now. - To answer the question, I need to know the information of Microsoft and its employee number. - I use keywords Microsoft, employee number to search information, and I find the following information: - - [google.pdf] Microsoft has over 144,000 employees worldwide as of 2019. - Using that information, I formalize the answer as - - I don't know how many employees does Microsoft has now, but in 2019, Microsoft has over 144,000 employees worldwide. - ### - - question: - {{$question}} - - explanation: - {{$explanation}} - - keywords: - {{$keywords}} - - information: - {{$knowledge}} - - answer: - {{$answer}} - - summary: - """; - - private const string PlannerPrefix = """ - do the following steps: - - explain what you need to know to answer the $question. - - generating $keywords from explanation. - - use $keywords to lookup or search information. - - update information to $knowledge. - - summarize the entire process and update $summary. - - answer the question based on the knowledge you have. - """; - - public Approach Approach => Approach.ReadDecomposeAsk; - - public ReadDecomposeAskApproachService( - SearchClient searchClient, - AzureOpenAITextCompletionService completionService, - ILogger logger, - IConfiguration configuration) - { - _searchClient = searchClient; - _completionService = completionService; - _logger = logger; - _configuration = configuration; - } - - public async Task ReplyAsync( - string question, - RequestOverrides? overrides, - CancellationToken cancellationToken = default) - { - var kernel = Kernel.Builder.Build(); - kernel.Config.AddTextCompletionService("openai", (kernel) => _completionService); - kernel.ImportSkill(new RetrieveRelatedDocumentSkill(_searchClient, overrides)); - kernel.ImportSkill(new LookupSkill(_searchClient, overrides)); - kernel.CreateSemanticFunction(ReadDecomposeAskApproachService.AnswerPromptPrefix, functionName: "Answer", description: "answer question with given knowledge", - maxTokens: 1024, temperature: overrides?.Temperature ?? 0.7); - kernel.CreateSemanticFunction(ReadDecomposeAskApproachService.ExplainPrefix, functionName: "Explain", description: "explain", temperature: 0.5, - presencePenalty: 0.5, frequencyPenalty: 0.5); - kernel.CreateSemanticFunction(ReadDecomposeAskApproachService.GenerateKeywordsPrompt, functionName: "GenerateKeywords", description: "Generate keywords for lookup or search from given explanation", temperature: 0, - presencePenalty: 0.5, frequencyPenalty: 0.5); - kernel.CreateSemanticFunction(ReadDecomposeAskApproachService.ThoughtProcessPrompt, functionName: "Summarize", description: "Summarize the entire process of getting answer.", temperature: overrides?.Temperature ?? 0.7, - presencePenalty: 0.5, frequencyPenalty: 0.5, maxTokens: 2048); - - var planner = new SequentialPlanner(kernel, new PlannerConfig - { - RelevancyThreshold = 0.7, - }); - var planInstruction = $"{ReadDecomposeAskApproachService.PlannerPrefix}"; - var plan = await planner.CreatePlanAsync(planInstruction); - plan.State["question"] = question; - _logger.LogInformation("{Plan}", PlanToString(plan)); - - do - { - plan = await kernel.StepAsync(question, plan, cancellationToken: cancellationToken); - } while (plan.HasNextStep); - - return new ApproachResponse( - DataPoints: plan.State["knowledge"].ToString().Split('\r'), - Answer: plan.State["Answer"], - Thoughts: plan.State["SUMMARY"].Replace("\n", "
"), - CitationBaseUrl: _configuration.ToCitationBaseUrl()); - } - - private static string PlanToString(Plan originalPlan) => $"Goal: {originalPlan.Description}\n\nSteps:\n" + string.Join("\n", originalPlan.Steps.Select( - s => - $"- {s.SkillName}.{s.Name} {string.Join(" ", s.NamedParameters.Select(p => $"{p.Key}='{p.Value}'"))}{" => " + string.Join(" ", s.NamedOutputs.Where(s => s.Key.ToUpper(System.Globalization.CultureInfo.CurrentCulture) != "INPUT").Select(p => $"{p.Key}"))}" - )); -} diff --git a/app/backend/Services/ReadRetrieveReadApproachService.cs b/app/backend/Services/ReadRetrieveReadApproachService.cs deleted file mode 100644 index cb0c9092..00000000 --- a/app/backend/Services/ReadRetrieveReadApproachService.cs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Microsoft.SemanticKernel.Planning.Planners; - -namespace MinimalApi.Services; - -internal sealed class ReadRetrieveReadApproachService : IApproachBasedService -{ - private readonly SearchClient _searchClient; - private readonly AzureOpenAITextCompletionService _completionService; - private readonly ILogger _logger; - private readonly IConfiguration _configuration; - - private const string PlanPrompt = """ - Do the following steps: - - Search information for $question and save result to $knowledge - - Answer the $question based on the knowledge you have and save result to $answer. - """; - - private const string Prefix = """ - You are an intelligent assistant helping Contoso Inc employees with their healthcare plan questions and employee handbook questions. - Use 'you' to refer to the individual asking the questions even if they ask with 'I'. - Answer the following question using only the data provided in the sources below. - For tabular information return it as an HTML table. Do not return markdown format. - Each source has a name followed by a colon and the actual information, always include the source name for each fact you use in the response. - If you cannot answer using the sources below, say you don't know. - - ### - Question: 'What is the deductible for the employee plan for a visit to Overlake in Bellevue?' - - Knowledge: - info1.txt: deductibles depend on whether you are in-network or out-of-network. In-network deductibles are $500 for employees and $1000 for families. Out-of-network deductibles are $1000 for employees and $2000 for families. - info2.pdf: Overlake is in-network for the employee plan. - info3.pdf: Overlake is the name of the area that includes a park and ride near Bellevue. - info4.pdf: In-network institutions include Overlake, Swedish, and others in the region - - Answer: - In-network deductibles are $500 for employees and $1000 for families [info1.txt] and Overlake is in-network for the employee plan [info2.pdf][info4.pdf]. - - ### - Question: - {{$question}} - - Knowledge: - {{$knowledge}} - - Answer: - """; - - public Approach Approach => Approach.ReadRetrieveRead; - - public ReadRetrieveReadApproachService( - SearchClient searchClient, - AzureOpenAITextCompletionService service, - ILogger logger, - IConfiguration configuration) - { - _searchClient = searchClient; - _completionService = service; - _logger = logger; - _configuration = configuration; - } - - public async Task ReplyAsync( - string question, - RequestOverrides? overrides, - CancellationToken cancellationToken = default) - { - var kernel = Kernel.Builder.Build(); - kernel.Config.AddTextCompletionService("openai", _ => _completionService); - kernel.ImportSkill(new RetrieveRelatedDocumentSkill(_searchClient, overrides)); - kernel.CreateSemanticFunction(ReadRetrieveReadApproachService.Prefix, functionName: "Answer", description: "answer question", - maxTokens: 1_024, temperature: overrides?.Temperature ?? 0.7); - var planner = new SequentialPlanner(kernel, new PlannerConfig - { - RelevancyThreshold = 0.7, - }); - var sb = new StringBuilder(); - var plan = await planner.CreatePlanAsync(ReadRetrieveReadApproachService.PlanPrompt); - var step = 1; - plan.State["question"] = question; - plan.State["knowledge"] = string.Empty; - _logger.LogInformation("{Plan}", PlanToString(plan)); - - do - { - plan = await kernel.StepAsync(plan, cancellationToken: cancellationToken); - sb.AppendLine($"Step {step++} - Execution results:\n"); - sb.AppendLine(plan.State + "\n"); - } while (plan.HasNextStep); - - return new ApproachResponse( - DataPoints: plan.State["knowledge"].ToString().Split('\r'), - Answer: plan.State["Answer"], - Thoughts: sb.ToString().Replace("\n", "
"), - CitationBaseUrl: _configuration.ToCitationBaseUrl()); - } - - private static string PlanToString(Plan originalPlan) => $"Goal: {originalPlan.Description}\n\nSteps:\n" + string.Join("\n", originalPlan.Steps.Select( - s => - $"- {s.SkillName}.{s.Name} {string.Join(" ", s.NamedParameters.Select(p => $"{p.Key}='{p.Value}'"))}{" => " + string.Join(" ", s.NamedOutputs.Where(s => s.Key.ToUpper(System.Globalization.CultureInfo.CurrentCulture) != "INPUT").Select(p => $"{p.Key}"))}" - )); -} diff --git a/app/backend/Services/RetrieveThenReadApproachService.cs b/app/backend/Services/RetrieveThenReadApproachService.cs deleted file mode 100644 index 52d1b491..00000000 --- a/app/backend/Services/RetrieveThenReadApproachService.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace MinimalApi.Services; - -internal sealed class RetrieveThenReadApproachService : IApproachBasedService -{ - private readonly SearchClient _searchClient; - - private const string SemanticFunction = """ - You are an intelligent assistant helping Contoso Inc employees with their healthcare plan questions and employee handbook questions. - Use 'you' to refer to the individual asking the questions even if they ask with 'I'. - Answer the following question using only the data provided in the sources below. - For tabular information return it as an HTML table. Do not return markdown format. - Each source has a name followed by a colon and the actual information, always include the source name for each fact you use in the response. - If you cannot answer using the sources below, say you don't know. - - ### - Question: 'What is the deductible for the employee plan for a visit to Overlake in Bellevue?' - - Sources: - info1.txt: deductibles depend on whether you are in-network or out-of-network. In-network deductibles are $500 for employees and $1000 for families. Out-of-network deductibles are $1000 for employees and $2000 for families. - info2.pdf: Overlake is in-network for the employee plan. - info3.pdf: Overlake is the name of the area that includes a park and ride near Bellevue. - info4.pdf: In-network institutions include Overlake, Swedish, and others in the region - - Answer: - In-network deductibles are $500 for employees and $1000 for families [info1.txt] and Overlake is in-network for the employee plan [info2.pdf][info4.pdf]. - - ### - Question: {{$question}}? - - Sources: - {{$retrieve}} - - Answer: - """; - - private readonly IKernel _kernel; - private readonly IConfiguration _configuration; - private readonly ISKFunction _function; - - public Approach Approach => Approach.RetrieveThenRead; - - public RetrieveThenReadApproachService(SearchClient searchClient, IKernel kernel, IConfiguration configuration) - { - _searchClient = searchClient; - _kernel = kernel; - _configuration = configuration; - _function = kernel.CreateSemanticFunction( - SemanticFunction, maxTokens: 200, temperature: 0.7, topP: 0.5); - } - - public async Task ReplyAsync( - string question, - RequestOverrides? overrides = null, - CancellationToken cancellationToken = default) - { - var text = await _searchClient.QueryDocumentsAsync(question, cancellationToken: cancellationToken); - var context = _kernel.CreateNewContext(); - context["retrieve"] = text; - context["question"] = question; - - var answer = await _kernel.RunAsync(context.Variables, cancellationToken, _function); - - return new ApproachResponse( - DataPoints: text.Split('\r'), - Answer: answer.ToString(), - Thoughts: $"Question: {question} \r Prompt: {context.Variables}", - CitationBaseUrl: _configuration.ToCitationBaseUrl()); - } -} diff --git a/app/backend/Services/Skills/LookupSkill.cs b/app/backend/Services/Skills/LookupSkill.cs deleted file mode 100644 index afc99218..00000000 --- a/app/backend/Services/Skills/LookupSkill.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace MinimalApi.Services.Skills; - -public sealed class LookupSkill -{ - private readonly SearchClient _searchClient; - private readonly RequestOverrides? _requestOverrides; - - public LookupSkill(SearchClient searchClient, RequestOverrides? requestOverrides) - { - _searchClient = searchClient; - _requestOverrides = requestOverrides; - } - - [SKFunction("Look up knowledge")] - [SKFunctionName("Lookup")] - [SKFunctionInput(Description = "lookup query")] - public async Task ExecAsync(string lookupQuery, SKContext context) => lookupQuery is string query - ? await _searchClient.LookupAsync(query, _requestOverrides) - : throw new AIException( - AIException.ErrorCodes.ServiceError, - "Query skill failed to get query from context"); -} diff --git a/app/backend/Services/Skills/RetrieveRelatedDocumentSkill.cs b/app/backend/Services/Skills/RetrieveRelatedDocumentSkill.cs deleted file mode 100644 index ae536765..00000000 --- a/app/backend/Services/Skills/RetrieveRelatedDocumentSkill.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace MinimalApi.Services.Skills; - -public sealed class RetrieveRelatedDocumentSkill -{ - private readonly SearchClient _searchClient; - private readonly RequestOverrides? _requestOverrides; - - public RetrieveRelatedDocumentSkill(SearchClient searchClient, RequestOverrides? requestOverrides) - { - _searchClient = searchClient; - _requestOverrides = requestOverrides; - } - - [SKFunction("Search more information")] - [SKFunctionName("Search")] - [SKFunctionInput(Description = "search query")] - public async Task QueryAsync(string searchQuery, SKContext context) - { - if (searchQuery is string query) - { - var result = await _searchClient.QueryDocumentsAsync(query, _requestOverrides); - - return result; - } - - throw new AIException( - AIException.ErrorCodes.ServiceError, - "Query skill failed to get query from context"); - } -} diff --git a/app/backend/Services/Skills/UpdateContextVariableSkill.cs b/app/backend/Services/Skills/UpdateContextVariableSkill.cs deleted file mode 100644 index 407b3340..00000000 --- a/app/backend/Services/Skills/UpdateContextVariableSkill.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace MinimalApi.Services.Skills; - -public sealed class UpdateContextVariableSkill -{ - [SKFunction("Update knowledge")] - [SKFunctionName("UpdateKnowledgeVariable")] - [SKFunctionInput(Description = "The value to add or append")] - [SKFunctionContextParameter(Name = "knowledge", Description = "variable to update")] - public void AddOrAppend(string variableValue, SKContext context) => context.Variables["knowledge"] = context.Variables.ContainsKey("knowledge") ? $"{context.Variables["knowledge"]}\r{variableValue}" : variableValue; -} diff --git a/infra/main.bicep b/infra/main.bicep index 79d8d864..d0530ebf 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -45,15 +45,6 @@ param formRecognizerServiceName string = '' @description('SKU name for the Form Recognizer service. Default: S0') param formRecognizerSkuName string = 'S0' -@description('Capacity of the GPT deployment. Default: 30') -param gptDeploymentCapacity int = 30 - -@description('Name of the GPT deployment. Default: turbo') -param gptDeploymentName string = 'turbo' - -@description('Name of the GPT model. Default: gpt-35-turbo') -param gptModelName string = 'gpt-35-turbo' - @description('Name of the Azure Key Vault') param keyVaultName string = '' @@ -182,10 +173,6 @@ module keyVaultSecrets 'core/security/keyvault-secrets.bicep' = { name: 'AzureOpenAiServiceEndpoint' value: openAi.outputs.endpoint } - { - name: 'AzureOpenAiGptDeployment' - value: gptDeploymentName - } { name: 'AzureOpenAiChatGptDeployment' value: chatGptDeploymentName @@ -246,7 +233,6 @@ module web './app/web.bicep' = { searchIndexName: searchIndexName formRecognizerEndpoint: formRecognizer.outputs.endpoint openAiEndpoint: openAi.outputs.endpoint - openAiGptDeployment: gptDeploymentName openAiChatGptDeployment: chatGptDeploymentName serviceBinds: [ redis.outputs.serviceBind ] } @@ -291,18 +277,6 @@ module openAi 'core/ai/cognitiveservices.bicep' = { name: openAiSkuName } deployments: [ - { - name: gptDeploymentName - model: { - format: 'OpenAI' - name: gptModelName - version: '0613' - } - sku: { - name: 'Standard' - capacity: gptDeploymentCapacity - } - } { name: chatGptDeploymentName model: { @@ -493,7 +467,6 @@ output AZURE_KEY_VAULT_RESOURCE_GROUP string = keyVaultResourceGroup.name output AZURE_LOCATION string = location output AZURE_OPENAI_CHATGPT_DEPLOYMENT string = chatGptDeploymentName output AZURE_OPENAI_ENDPOINT string = openAi.outputs.endpoint -output AZURE_OPENAI_GPT_DEPLOYMENT string = gptDeploymentName output AZURE_OPENAI_RESOURCE_GROUP string = openAiResourceGroup.name output AZURE_OPENAI_SERVICE string = openAi.outputs.name output AZURE_REDIS_CACHE string = redis.outputs.name