Skip to content

Commit

Permalink
Merge pull request #36 from Azure-Samples/gk/26-aggregate-miyagi-skil…
Browse files Browse the repository at this point in the history
…ls-into-a-notebook
  • Loading branch information
thegovind committed Apr 25, 2023
2 parents 480723c + 384fbb4 commit 84ad4fa
Show file tree
Hide file tree
Showing 13 changed files with 230 additions and 21 deletions.
Binary file modified ancillary/images/wip-azure.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion services/frontend/src/layouts/sidebar/left-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export default function Sidebar({ className }: { className?: string }) {
</h2>
<button className="h-12 rounded-lg bg-brand text-white"
onClick={() => openInNewTab(I_APP_URL)}>
Lab Guide {' '}
Workshop Guide {' '}
</button>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,71 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using GBB.Miyagi.RecommendationService.Utils;
using Microsoft.AspNetCore.Mvc;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.Memory.Qdrant;
using Microsoft.SemanticKernel.Skills.Web;

namespace GBB.Miyagi.RecommendationService.Controllers
{
[ApiController]
[Route("[controller]")]
public class RecommendationsController : ControllerBase
{
private static readonly string[] Questions = new[]
{
"Portfolio allocation", "Expenses reduction"
};

private readonly ILogger<RecommendationsController> _logger;
private readonly IKernel _kernel;
private readonly QdrantMemoryStore _memoryStore;
private readonly WebSearchEngineSkill _webSearchEngineSkill;

public RecommendationsController(ILogger<RecommendationsController> logger)
public RecommendationsController(IKernel kernel, QdrantMemoryStore memoryStore, WebSearchEngineSkill webSearchEngineSkill)
{
_logger = logger;
_kernel = kernel;
_memoryStore = memoryStore;
_webSearchEngineSkill = webSearchEngineSkill;
}

[HttpPost(Name = "GetRecommendations")]
public IEnumerable<Response> Post()
[HttpGet("GetRecommendations")]
public async Task<IActionResult> GetRecommendations(string user, string portfolio)
{
return Enumerable.Range(1, 5).Select(index => new Response
{
Date = DateTime.Now.AddDays(index),
InteractionNum = Random.Shared.Next(-20, 55),
Summary = "Todo: SK to LLM flow"
})
.ToArray();

Console.WriteLine("== Printing Collections in DB ==");
var collections = _memoryStore.GetCollectionsAsync();
await foreach (var collection in collections)
{
Console.WriteLine(collection);
}

string memoryCollectionName = Env.Var("QDRANT_MEMORY_COLLECTION"); // Replace with your desired memory collection name
string prompt = $"How should {user} allocate {portfolio}?";

// Get embeddings for the prompt

// var embeddings = await _kernel.RunAsync()

// Search Qdrant vector store
var searchResults = _kernel.Memory.SearchAsync(memoryCollectionName, embeddings, limit: 3, minRelevanceScore: 0.8);
var similarTexts = new List<string>();

await foreach (var item in searchResults)
{
similarTexts.Add(item.Metadata.Text);
}

// Get latest finance news from Bing
var newsResults = await _webSearchEngineSkill.SearchAsync($"latest finance news {portfolio}", count: 3);
var newsTexts = new List<string>();

foreach (var newsItem in newsResults)
{
newsTexts.Add(newsItem.Title);
}

// Build the new prompt with similar texts and news
string newPrompt = $"{prompt}\n\nSimilar recommendations:\n{string.Join("\n", similarTexts)}\n\nLatest news:\n{string.Join("\n", newsTexts)}\n\n";

// Call text completion API
var completionResult = await _kernel.RunAsync(newPrompt, _kernel.Skills.Get("text-davinci-003"));

return Ok(new { Recommendation = completionResult });
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
<ItemGroup>
<PackageReference Include="Confluent.Kafka" Version="2.1.0" />
<PackageReference Include="Microsoft.SemanticKernel" Version="0.12.207.1-preview" />
<PackageReference Include="Microsoft.SemanticKernel.Connectors.Memory.Qdrant" Version="0.12.207.1-preview" />
<PackageReference Include="Microsoft.SemanticKernel.Skills.Web" Version="0.12.207.1-preview" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.18.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
</ItemGroup>
Expand Down
6 changes: 6 additions & 0 deletions services/recommendation-service/Models/Question.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace GBB.Miyagi.RecommendationService.Models;

public class Question
{
public string input { get; set; }
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace GBB.Miyagi.RecommendationService
namespace GBB.Miyagi.RecommendationService.Models
{
public class Response
{
Expand Down
54 changes: 54 additions & 0 deletions services/recommendation-service/Program.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
using System.Globalization;
using GBB.Miyagi.RecommendationService.Utils;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.Memory.Qdrant;
using Microsoft.SemanticKernel.Skills.Web;
using Microsoft.SemanticKernel.Skills.Web.Bing;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
Expand All @@ -7,6 +14,53 @@
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// Register the required services
builder.Services.AddSingleton<IKernel>(provider =>
{
int qdrantPort = int.Parse(Env.Var("QDRANT_PORT"), CultureInfo.InvariantCulture);
QdrantMemoryStore memoryStore = new QdrantMemoryStore(Env.Var("QDRANT_ENDPOINT"), qdrantPort, vectorSize: 1536, ConsoleLogger.Log);
IKernel kernel = Kernel.Builder
.WithLogger(ConsoleLogger.Log)
.Configure(c =>
{
c.AddAzureTextCompletionService(
Env.Var("AZURE_OPENAI_SERVICE_ID"),
Env.Var("AZURE_OPENAI_DEPLOYMENT_NAME"),
Env.Var("AZURE_OPENAI_ENDPOINT"),
Env.Var("AZURE_OPENAI_KEY"));
c.AddAzureTextEmbeddingGenerationService(
Env.Var("AZURE_OPENAI_EMBEDDINGS_SERVICE_ID"),
Env.Var("AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT_NAME"),
Env.Var("AZURE_OPENAI_EMBEDDINGS_ENDPOINT"),
Env.Var("AZURE_OPENAI_EMBEDDINGS_KEY"));
})
.WithMemoryStorage(memoryStore)
.Build();
return kernel;
});

builder.Services.AddSingleton<QdrantMemoryStore>(provider =>
{
int qdrantPort = int.Parse(Env.Var("QDRANT_PORT"), CultureInfo.InvariantCulture);
QdrantMemoryStore memoryStore = new QdrantMemoryStore(Env.Var("QDRANT_ENDPOINT"), qdrantPort, vectorSize: 1536, ConsoleLogger.Log);
return memoryStore;
});

builder.Services.AddSingleton<BingConnector>(provider =>
{
var bingConnector = new BingConnector(Env.Var("BING_API_KEY"));
return bingConnector;
});

builder.Services.AddSingleton<WebSearchEngineSkill>(provider =>
{
var bingConnector = provider.GetRequiredService<BingConnector>();
var bing = new WebSearchEngineSkill(bingConnector);
return bing;
});

var app = builder.Build();

// Configure the HTTP request pipeline.
Expand Down
18 changes: 18 additions & 0 deletions services/recommendation-service/README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,22 @@ Microservice that leverages Semantic Kernel to orchestrate the recommendation fl

![Recommendation Service](../../ancillary/images/sk-memory-orchestration.png)

## Getting Started

Set the following environment variables:

```bash
dotnet user-secrets set "BING_API_KEY" ""
dotnet user-secrets set "AZURE_OPENAI_SERVICE_ID" ""
dotnet user-secrets set "AZURE_OPENAI_DEPLOYMENT_NAME" ""
dotnet user-secrets set "AZURE_OPENAI_ENDPOINT" "https://{}.openai.azure.com/"
dotnet user-secrets set "AZURE_OPENAI_KEY" ""
dotnet user-secrets set "QDRANT_ENDPOINT" "http://localhost"
dotnet user-secrets set "QDRANT_PORT" "6333"
dotnet user-secrets set "QDRANT_MEMORY_COLLECTION" ""
dotnet user-secrets set "AZURE_OPENAI_EMBEDDINGS_SERVICE_ID" ""
dotnet user-secrets set "AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT_NAME" ""
dotnet user-secrets set "AZURE_OPENAI_EMBEDDINGS_ENDPOINT" "https://{}.openai.azure.com/"
dotnet user-secrets set "AZURE_OPENAI_EMBEDDINGS_KEY" ""

```
35 changes: 35 additions & 0 deletions services/recommendation-service/Utils/ConsoleLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using Microsoft.Extensions.Logging;

namespace GBB.Miyagi.RecommendationService.Utils;

/// <summary>
/// Basic logger printing to console
/// </summary>
internal static class ConsoleLogger
{
internal static ILogger Log => LogFactory.CreateLogger<object>();

private static ILoggerFactory LogFactory => s_loggerFactory.Value;

private static readonly Lazy<ILoggerFactory> s_loggerFactory = new(LogBuilder);

private static ILoggerFactory LogBuilder()
{
return LoggerFactory.Create(builder =>
{
builder.SetMinimumLevel(LogLevel.Warning);
// builder.AddFilter("Microsoft", LogLevel.Trace);
// builder.AddFilter("Microsoft", LogLevel.Debug);
// builder.AddFilter("Microsoft", LogLevel.Information);
// builder.AddFilter("Microsoft", LogLevel.Warning);
// builder.AddFilter("Microsoft", LogLevel.Error);
builder.AddFilter("Microsoft", LogLevel.Warning);
builder.AddFilter("System", LogLevel.Warning);
builder.AddConsole();
});
}
}
36 changes: 36 additions & 0 deletions services/recommendation-service/Utils/Env.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using Microsoft.Extensions.Configuration;

namespace GBB.Miyagi.RecommendationService.Utils;

#pragma warning disable CA1812 // instantiated by AddUserSecrets
internal sealed class Env
#pragma warning restore CA1812
{
/// <summary>
/// Simple helper used to load env vars and secrets like credentials,
/// to avoid hard coding them in the sample code
/// </summary>
/// <param name="name">Secret name / Env var name</param>
/// <returns>Value found in Secret Manager or Environment Variable</returns>
internal static string Var(string name)
{
var configuration = new ConfigurationBuilder()
.AddUserSecrets<Env>()
.Build();

var value = configuration[name];
if (!string.IsNullOrEmpty(value))
{
return value;
}

value = Environment.GetEnvironmentVariable(name);
if (string.IsNullOrEmpty(value))
{
throw new MiyagiException($"Secret / Env var not set: {name}");
}

return value;
}
}
16 changes: 16 additions & 0 deletions services/recommendation-service/Utils/MiyagiException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace GBB.Miyagi.RecommendationService.Utils;

public class MiyagiException : Exception
{
public MiyagiException() : base()
{
}

public MiyagiException(string message) : base(message)
{
}

public MiyagiException(string message, Exception innerException) : base(message, innerException)
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@ public class SemanticKernelFacade {
@Value("${endpoints.sk-ping}")
private String pingEndpoint;

@Value("${endpoints.sk-base-url}")
private String SK_BASE_URL;

private final WebClient webClient;

public SemanticKernelFacade(WebClient.Builder webClientBuilder) {
this.webClient = webClientBuilder.baseUrl("http://localhost:7071").build();
this.webClient = webClientBuilder.baseUrl(SK_BASE_URL).build();
}

public Mono<String> executePlan(Integer maxSteps) {
Expand Down
1 change: 1 addition & 0 deletions services/user-service/src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ springdoc:
api-docs:
path: /api-docs
endpoints:
sk-base-url: ${SK_BASE_URL:http://localhost:7071}
execute-plan: ${SK_BASE_URL:http://localhost:7071}/api/planner/execute/{maxSteps?}
invoke-function: ${SK_BASE_URL:http://localhost:7071}/api/skills/{skillName}/invoke/{functionName}
sk-ping: ${SK_BASE_URL:http://localhost:7071}/api/ping
Expand Down

0 comments on commit 84ad4fa

Please sign in to comment.