diff --git a/sandbox/usecases/sentiment-analysis.ipynb b/sandbox/usecases/sentiment-analysis.ipynb index 44d223a7..5234a928 100644 --- a/sandbox/usecases/sentiment-analysis.ipynb +++ b/sandbox/usecases/sentiment-analysis.ipynb @@ -25,7 +25,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" @@ -37,17 +37,7 @@ "kernelName": "csharp" } }, - "outputs": [ - { - "data": { - "text/html": [ - "
Installed Packages
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "#r \"nuget: Microsoft.SemanticKernel, 0.17.230711.7-preview\"\n", "#r \"nuget: Microsoft.SemanticKernel.Planning.StepwisePlanner, 0.17.230711.7-preview\"\n", @@ -72,7 +62,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" @@ -88,6 +78,8 @@ "var (_, model, azureEndpoint, apiKey, org, bingApiKey) = Settings.LoadFromFile();\n", "builder.WithAzureChatCompletionService(model, azureEndpoint, apiKey);\n", "\n", + "// TODO: Add embedding service for RaG\n", + "\n", "IKernel kernel = builder.Build();" ] }, @@ -100,7 +92,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" @@ -109,19 +101,10 @@ "kernelName": "csharp" } }, - "outputs": [ - { - "ename": "Error", - "evalue": "(34,17): error CS0103: The name 'kernel' does not exist in the current context", - "output_type": "error", - "traceback": [ - "(34,17): error CS0103: The name 'kernel' does not exist in the current context" - ] - } - ], + "outputs": [], "source": [ "string skPrompt = @\"\n", - "This is a chat between a classification system and human looking to rate their stock portfolio. Given a portfolio in valid JSON, respond back a sentiment in JSON for the attribute: sentiment. To determine this attribute, consider Web results from Bing and propietary data below.\n", + "This is a chat between a classification system that responds in valid JSON and human looking to rate their stock portfolio. Given a portfolio in valid JSON, respond back a sentiment in JSON for the attribute: sentiment. To determine this attribute, consider Web results from Bing and propietary data below.\n", "\n", "Example:\n", "\"\"stocks\"\": [\n", @@ -152,15 +135,17 @@ "{{$portfolio}}\n", "\n", "\n", - "Question: {{$input}}\n", - "\n", "Context: \n", "Web Results: {{$web}}\n", - "Propietary data: {{$propietary}}\n", "\";\n", "\n", "// create semantic function\n", - "var textToSQL = kernel.CreateSemanticFunction(skPrompt, maxTokens: 1000, temperature: 0, topP: 0.5);\n", + "var sentimentClassifier = kernel.CreateSemanticFunction(\n", + " skPrompt,\n", + " skillName: \"SentimentClassifier\",\n", + " functionName: \"SentimentClassifer\",\n", + " description: \"Classify sentiment of a stock portfolio\",\n", + " maxTokens: 1000, temperature: 0, topP: 0.5);\n", "Console.WriteLine(textToSQL.ToString());" ] }, @@ -173,7 +158,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" @@ -184,15 +169,34 @@ }, "outputs": [], "source": [ - "var tableInfo = @\"\n", - " Users table has columns: user_id, first_name, last_name, and email.\n", - " Transactions table has columns: transaction_id, user_id, transaction_amount, transaction_date, and transaction_type.\n", + "var portfolio = @\"\n", + " \"\"stocks\"\": [\n", + " {\n", + " \"\"symbol\"\": \"\"AAPL\"\",\n", + " \"\"allocation\"\": 0.2,\n", + " \"\"sentiment\"\": \"\"\"\"\n", + " },\n", + " {\n", + " \"\"symbol\"\": \"\"NVDA\"\",\n", + " \"\"allocation\"\": 0.3,\n", + " \"\"sentiment\"\": \"\"\"\"\n", + " },\n", + " {\n", + " \"\"symbol\"\": \"\"TSLA\"\",\n", + " \"\"allocation\"\": 0.2,\n", + " \"\"sentiment\"\": \"\"\"\"\n", + " },\n", + " {\n", + " \"\"symbol\"\": \"\"T\"\",\n", + " \"\"allocation\"\": 0.3,\n", + " \"\"sentiment\"\": \"\"\"\"\n", + " }\n", + " ]\n", "\";\n", "\n", "// context variables\n", "var context = kernel.CreateNewContext();\n", - "context[\"dialect\"] = \"PostgreSQL\";\n", - "context[\"tableInfo\"] = tableInfo;" + "context[\"portfolio\"] = portfolio;" ] }, { @@ -204,7 +208,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": { "dotnet_interactive": { "language": "csharp" @@ -213,77 +217,34 @@ "kernelName": "csharp" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Question: What was the most expensive purchase made in the last year?\n", - "\n", - "SQLQuery: SELECT \n", - " t.transaction_id,\n", - " t.transaction_date,\n", - " t.transaction_amount,\n", - " t.transaction_type,\n", - " u.first_name,\n", - " u.last_name\n", - " FROM \n", - " transactions t\n", - " JOIN \n", - " users u ON t.user_id = u.user_id\n", - " WHERE \n", - " t.transaction_date >= DATE_TRUNC('year', CURRENT_DATE - INTERVAL '1 year')\n", - " AND t.transaction_date < DATE_TRUNC('year', CURRENT_DATE)\n", - " ORDER BY \n", - " t.transaction_amount DESC\n", - " LIMIT 1;\n", - "\n", - "Answer: The most expensive purchase made in the last year was {transaction_type} for {transaction_amount} by {first_name} {last_name} on {transaction_date}.\r\n" - ] - } - ], + "outputs": [], "source": [ - "context[\"input\"] = \"Most expensive purchase in the last year\";\n", - "var sql = await textToSQL.InvokeAsync(context);\n", - "Console.WriteLine(sql);\n", - "\n", + "var question = \"Determine the sentiment of my stock portfolio based on what the internet says about the stocks in my portfolio.\";\n", "\n", - "using var bingConnector = new BingConnector(Env.Var(\"BING_API_KEY\"));\n", - " var webSearchEngineSkill = new WebSearchEngineSkill(bingConnector);\n", + "var bingConnector = new BingConnector(bingApiKey);\n", + "var webSearchEngineSkill = new WebSearchEngineSkill(bingConnector);\n", "\n", - " kernel.ImportSkill(webSearchEngineSkill, \"WebSearch\");\n", - " kernel.ImportSkill(new LanguageCalculatorSkill(kernel), \"advancedCalculator\");\n", - " // kernel.ImportSkill(new SimpleCalculatorSkill(kernel), \"basicCalculator\");\n", - " kernel.ImportSkill(new TimeSkill(), \"time\");\n", + "kernel.ImportSkill(sentimentClassifier, \"SentimentClassifier\");\n", + "kernel.ImportSkill(webSearchEngineSkill, \"WebSearch\");\n", "\n", - " Console.WriteLine(\"*****************************************************\");\n", - " Stopwatch sw = new();\n", - " Console.WriteLine(\"Question: \" + question);\n", + "var config = new Microsoft.SemanticKernel.Planning.Stepwise.StepwisePlannerConfig();\n", + "config.MinIterationTimeMs = 1500;\n", + "config.MaxTokens = 4000;\n", "\n", - " var config = new Microsoft.SemanticKernel.Planning.Stepwise.StepwisePlannerConfig();\n", - " config.ExcludedFunctions.Add(\"TranslateMathProblem\");\n", - " config.MinIterationTimeMs = 1500;\n", - " config.MaxTokens = 4000;\n", + "StepwisePlanner planner = new(kernel, config);\n", + "var plan = planner.CreatePlan(question);\n", "\n", - " StepwisePlanner planner = new(kernel, config);\n", - " sw.Start();\n", - " var plan = planner.CreatePlan(question);\n", - "\n", - " var result = await plan.InvokeAsync(kernel.CreateNewContext());\n", - " Console.WriteLine(\"Result: \" + result);\n", - " if (result.Variables.TryGetValue(\"stepCount\", out string? stepCount))\n", - " {\n", - " Console.WriteLine(\"Steps Taken: \" + stepCount);\n", - " }\n", - "\n", - " if (result.Variables.TryGetValue(\"skillCount\", out string? skillCount))\n", - " {\n", - " Console.WriteLine(\"Skills Used: \" + skillCount);\n", - " }\n", + "var result = await plan.InvokeAsync(kernel.CreateNewContext());\n", + "Console.WriteLine(\"Result: \" + result);\n", + "if (result.Variables.TryGetValue(\"stepCount\", out string? stepCount))\n", + "{\n", + " Console.WriteLine(\"Steps Taken: \" + stepCount);\n", + "}\n", "\n", - " Console.WriteLine(\"Time Taken: \" + sw.Elapsed);\n", - " Console.WriteLine(\"*****************************************************\");\n", - " }" + "if (result.Variables.TryGetValue(\"skillCount\", out string? skillCount))\n", + "{\n", + " Console.WriteLine(\"Skills Used: \" + skillCount);\n", + "}\n" ] } ], diff --git a/services/recommendation-service/dotnet/Program.cs b/services/recommendation-service/dotnet/Program.cs index 1342eb46..8bd83df9 100644 --- a/services/recommendation-service/dotnet/Program.cs +++ b/services/recommendation-service/dotnet/Program.cs @@ -73,11 +73,8 @@ var app = builder.Build(); // Configure the HTTP request pipeline. -if (app.Environment.IsDevelopment()) -{ - app.UseSwagger(); - app.UseSwaggerUI(); -} +app.UseSwagger(); +app.UseSwaggerUI(); app.UseCors(); // app.UseHttpsRedirection(); // TODO: Issue with Next.js to use https redirection diff --git a/services/recommendation-service/dotnet/controllers/InvestmentsController.cs b/services/recommendation-service/dotnet/controllers/InvestmentsController.cs index db88f926..130a97ee 100644 --- a/services/recommendation-service/dotnet/controllers/InvestmentsController.cs +++ b/services/recommendation-service/dotnet/controllers/InvestmentsController.cs @@ -21,8 +21,8 @@ namespace GBB.Miyagi.RecommendationService.Controllers; public class InvestmentsController : ControllerBase { private readonly IKernel _kernel; - private readonly WebSearchEngineSkill _webSearchEngineSkill; private readonly string _memoryCollection = Env.Var("MEMORY_COLLECTION"); + private readonly WebSearchEngineSkill _webSearchEngineSkill; public InvestmentsController(IKernel kernel, WebSearchEngineSkill webSearchEngineSkill) { @@ -61,29 +61,50 @@ public async Task GetRecommendations([FromBody] MiyagiContext miy log?.LogDebug("Number of Tokens: {N}", numTokens); log?.LogDebug("Context: {S}", context.ToString()); - // ========= Prometheus - RaG with current data ========= - // // TODO: Swap Bing Web Search with News Search. - _kernel.ImportSkill(_webSearchEngineSkill, "bing"); - var question = "What is the current inflation rate?"; - var bingResult = await _kernel.Func("bing", "search").InvokeAsync(question); - context.Set("bingResult", bingResult.Result); - log?.LogDebug("Bing Result: {S}", bingResult.Result); - - var searchResults = _kernel.Memory.SearchAsync(_memoryCollection, "investment advise", 3, 0.8); - - await foreach (var item in searchResults) log?.LogDebug(item.Metadata.Text + " : " + item.Relevance); - - // ========= Orchestrate with LLM using context, connector, and memory ========= - var result = await _kernel.RunAsync( - context, - userProfilePlugin["GetUserAge"], - userProfilePlugin["GetAnnualHouseholdIncome"], - advisorPlugin["InvestmentAdvise"]); - log?.LogDebug("Result: {S}", result.Result); - numTokens = GPT3Tokenizer.Encode(result.Result).Count; - log?.LogDebug("Number of Tokens: {N}", numTokens); - var output = result.Result.Replace("\n", ""); + const int maxRetries = 2; + for (var currentRetry = 0; currentRetry < maxRetries; currentRetry++) + try + { + // ========= Prometheus - RaG with current data ========= + _kernel.ImportSkill(_webSearchEngineSkill, "bing"); + var question = "What is the current inflation rate?"; + var bingResult = await _kernel.Func("bing", "search").InvokeAsync(question); + context.Set("bingResult", bingResult.Result); + log?.LogDebug("Bing Result: {S}", bingResult.Result); + + var searchResults = _kernel.Memory.SearchAsync(_memoryCollection, "investment advise", 3, 0.8); + + await foreach (var item in searchResults) log?.LogDebug(item.Metadata.Text + " : " + item.Relevance); + + // ========= Orchestrate with LLM using context, connector, and memory ========= + var result = await _kernel.RunAsync( + context, + userProfilePlugin["GetUserAge"], + userProfilePlugin["GetAnnualHouseholdIncome"], + advisorPlugin["InvestmentAdvise"]); + log?.LogDebug("Result: {S}", result.Result); + numTokens = GPT3Tokenizer.Encode(result.Result).Count; + log?.LogDebug("Number of Tokens: {N}", numTokens); + var output = result.Result.Replace("\n", ""); + + var jsonDocument = JsonDocument.Parse(output); + + return new JsonResult(jsonDocument); + } + catch (JsonException ex) + { + if (currentRetry == maxRetries - 1) + { + // Handle error gracefully, e.g. return an error response + log?.LogError(ex, "Failed to parse JSON data"); + return BadRequest(new { error = "Failed to parse JSON data after retrying investments" }); + } + + // Log the error and proceed to the next iteration to retry + log?.LogError(ex, $"Failed to parse JSON data, retry attempt {currentRetry + 1}"); + } - return Content(output, "application/json"); + log?.LogError("Failed to parse JSON data, returning 400"); + return BadRequest(new { error = "Unexpected error occurred during processing investments" }); } } \ No newline at end of file diff --git a/services/recommendation-service/dotnet/controllers/RecommendationsController.cs b/services/recommendation-service/dotnet/controllers/RecommendationsController.cs index c35281e0..7d5b87e8 100644 --- a/services/recommendation-service/dotnet/controllers/RecommendationsController.cs +++ b/services/recommendation-service/dotnet/controllers/RecommendationsController.cs @@ -35,45 +35,45 @@ public class RecommendationsController : ControllerBase [HttpPost("/personalize")] public async Task GetRecommendations([FromBody] MiyagiContext miyagiContext) { - var assetsResult = await _assetsController.GetRecommendations(miyagiContext) as ContentResult; - var investmentsResult = await _investmentsController.GetRecommendations(miyagiContext) as ContentResult; - - if (assetsResult == null || investmentsResult == null) - return StatusCode(500, - "Failed to get recommendations"); - const int maxRetries = 2; var currentRetry = 0; - JsonDocument assetsJson = null; - JsonDocument investmentsJson = null; - while (currentRetry <= maxRetries) + { try { - var assetsContent = assetsResult.Content; - var investmentsContent = investmentsResult.Content; + var assetsResult = await _assetsController.GetRecommendations(miyagiContext) as ContentResult; + var investmentsResult = await _investmentsController.GetRecommendations(miyagiContext) as JsonResult; + + if (assetsResult == null || investmentsResult == null) + { + return StatusCode(500, "Failed to get recommendations"); + } - assetsJson = JsonDocument.Parse(assetsContent); - investmentsJson = JsonDocument.Parse(investmentsContent); + var assetsJson = JsonDocument.Parse(assetsResult.Content); + var investmentsJson = investmentsResult.Value as JsonDocument; - break; // If parsing is successful, break out of the loop + var aggregatedResult = new Dictionary + { + { "assets", assetsJson.RootElement }, + { "investments", investmentsJson.RootElement } + }; + + return new JsonResult(aggregatedResult); } catch (JsonException ex) { if (currentRetry == maxRetries) + { // Handle error gracefully, e.g. return an error response - return BadRequest(new { error = "Failed to parse JSON data" }); + return BadRequest(new { error = "Failed to parse JSON data after retries" }); + } + currentRetry++; } + } - var aggregatedResult = new Dictionary - { - { "assets", assetsJson.RootElement }, - { "investments", investmentsJson.RootElement } - }; - - return new JsonResult(aggregatedResult); + return BadRequest(new { error = "Unexpected error occurred during processing recommendations" }); } [HttpPost("/personalize/sample")] diff --git a/services/recommendation-service/dotnet/plugins/AdvisorPlugin/InvestmentAdvise/skprompt.txt b/services/recommendation-service/dotnet/plugins/AdvisorPlugin/InvestmentAdvise/skprompt.txt index 94b6f5b1..7173726c 100644 --- a/services/recommendation-service/dotnet/plugins/AdvisorPlugin/InvestmentAdvise/skprompt.txt +++ b/services/recommendation-service/dotnet/plugins/AdvisorPlugin/InvestmentAdvise/skprompt.txt @@ -3,15 +3,9 @@ System: ONLY USE JSON PROPERTIES IN THIS LIST: portfolio [END LIST] -[CONTENT] -{ - {{$stocks}} -} -[END CONTENT] - -EMIT WELL FORMED JSON ALWAYS. +EMIT WELL FORMED JSON ALWAYS. ONLY POPULATE THE gptRecommendation attribute in valid JSON. BE BRIEF AND TO THE POINT. - +User: This is a chat between systems that respond in valid JSON. You are a financial advisor JSON service that only emits valid financial advise in parsable JSON. For gptRecommendation attribute, use the voice of {{$voice}} and be creative but limit to a sentence less than 20 words. Provide advice based on the given portfolio allocation and user information such as a age, income, and risk. @@ -19,9 +13,15 @@ For someone who is {{UserProfilePlugin.GetUserAge $userId}} years old, with {{Us Return well-formed JSON with a "gptRecommendation" property containing {{$voice}}'s' recommendation in a creative and funny tone. Example: {"portfolio":[{"symbol":"MSFT","gptRecommendation":"Booyah! Hold on, steady growth! Diversify, though!"},{"symbol":"PEP","gptRecommendation":"Buy, buy, buy! Solid dividends, sweet stability!"}]} -Zeitgeist to consider: current inflation and mortgage rates: {{$bingResults}} wisdom of crowds opinions: {{recall "investment advise"} -+++++ \ No newline at end of file + +System: +{ + {{$stocks}} +} + ++++++ + diff --git a/services/recommendation-service/dotnet/plugins/AdvisorPlugin/PortfolioAllocation/skprompt.txt b/services/recommendation-service/dotnet/plugins/AdvisorPlugin/PortfolioAllocation/skprompt.txt index 5ad2c8f8..cf48dc99 100644 --- a/services/recommendation-service/dotnet/plugins/AdvisorPlugin/PortfolioAllocation/skprompt.txt +++ b/services/recommendation-service/dotnet/plugins/AdvisorPlugin/PortfolioAllocation/skprompt.txt @@ -3,12 +3,6 @@ ONLY USE JSON PROPERTIES IN THIS LIST: portfolio [END LIST] -[CONTENT] -{ - {{$portfolio}} -} -[END CONTENT] - EMIT WELL FORMED JSON ALWAYS. BE BRIEF AND TO THE POINT. @@ -21,3 +15,8 @@ Example: {"portfolio":[{"name":"Stocks","gptRecommendation":""},{"name":"Bonds", +++++ +[CONTENT] +{ + {{$portfolio}} +} +[END CONTENT] \ No newline at end of file diff --git a/services/sk-copilot-chat-api/dotnet/Program.cs b/services/sk-copilot-chat-api/dotnet/Program.cs index dd707dfc..eb40d43b 100644 --- a/services/sk-copilot-chat-api/dotnet/Program.cs +++ b/services/sk-copilot-chat-api/dotnet/Program.cs @@ -85,12 +85,9 @@ public static async Task Main(string[] args) app.MapHub("/messageRelayHub"); // Enable Swagger for development environments. - if (app.Environment.IsDevelopment()) - { - app.UseSwagger(); - app.UseSwaggerUI(); - } - + app.UseSwagger(); + app.UseSwaggerUI(); + // Start the service Task runTask = app.RunAsync(); diff --git a/ui/typescript/src/components/personalize/selectors/link-accounts.tsx b/ui/typescript/src/components/personalize/selectors/link-accounts.tsx index 95da3ccc..f92432a2 100644 --- a/ui/typescript/src/components/personalize/selectors/link-accounts.tsx +++ b/ui/typescript/src/components/personalize/selectors/link-accounts.tsx @@ -71,7 +71,8 @@ export function LinkAccounts() { return (

- Link Accounts + Accounts (synthetic data w/ GPT)

diff --git a/ui/typescript/src/data/personalize/store.ts b/ui/typescript/src/data/personalize/store.ts index fc43f361..fa52b29b 100644 --- a/ui/typescript/src/data/personalize/store.ts +++ b/ui/typescript/src/data/personalize/store.ts @@ -15,7 +15,7 @@ const getRandomIndex = (arrayLength: number) => Math.floor(Math.random() * array export const selectedAdvisorAtom = atom(AdvisorsList[getRandomIndex(AdvisorsList.length)]); -export const selectedBookAtom = atom(BooksList[getRandomIndex(BooksList.length)]); +export const selectedBookAtom = atom(BooksList[0]); export const selectedRiskLevelAtom = atom(RiskLevelsList[getRandomIndex(RiskLevelsList.length)]); diff --git a/ui/typescript/src/data/static/personalize.tsx b/ui/typescript/src/data/static/personalize.tsx index d22a8a99..29845a68 100644 --- a/ui/typescript/src/data/static/personalize.tsx +++ b/ui/typescript/src/data/static/personalize.tsx @@ -1,6 +1,6 @@ export const BooksList = [ - {id: 1, name: 'common-stocks', description: 'Common Stocks and Uncommon Profits'}, - {id: 2, name: 'intelligent-investor', description: 'The Intelligent Investor'}, + {id: 1, name: 'intelligent-investor', description: 'The Intelligent Investor'}, + {id: 2, name: 'common-stocks', description: 'Common Stocks and Uncommon Profits'}, {id: 3, name: 'random-walk', description: 'A Random Walk Down Wall Street'} ]; export const RiskLevelsList = [