Synthesizing and Summarizing private data to generate recommendations and insights in your Applications¶
In this section, we delve into the process of synthesizing portfolio recommendations as actionable insights using Project Miyagi. The workflow for this is orchestrated by the Semantic Kernel, as illustrated below:
Tip
For advanced deep learning-based recommender systems, checout microsoft/recommenders for complex DLRMs.
After we initialize the Kernel, we can now begin to create the prompts that will be used to generate the synthesized insights. This can be more art than science, and we encourage you to experiment with different prompts and configurations to see what works best for your use case.
For the purposes of this workshop, we are envisioning a use case where you select a portfolio of stocks, and the system generates a summary of the portfolio, along with a recommendation on whether to buy, sell, or hold the portfolio.
// ========= Import Advisor skill from local filesystem =========// ========= Import semantic functions as plugins =========varpluginsDirectory=Path.Combine(Directory.GetCurrentDirectory(),"plugins");varadvisorPlugin=_kernel.ImportSemanticSkillFromDirectory(pluginsDirectory,"AdvisorPlugin");// This will explained in full later in this section:// ========= Import native function =========varuserProfilePlugin=_kernel.ImportSkill(newUserProfilePlugin(),"UserProfilePlugin");// ========= Set context variables to populate the prompt =========varcontext=_kernel.CreateNewContext();context.Variables.Set("userId",miyagiContext.UserInfo.UserId);context.Variables.Set("portfolio",JsonSerializer.Serialize(miyagiContext.Portfolio));context.Variables.Set("risk",miyagiContext.UserInfo.RiskLevel??DefaultRiskLevel);// ========= Chain and orchestrate with LLM =========varplan=newPlan("Execute userProfilePlugin and then advisorPlugin");plan.AddSteps(userProfilePlugin["GetUserAge"],userProfilePlugin["GetAnnualHouseholdIncome"],advisorPlugin["PortfolioAllocation"]);// Execute planvarask="Using the userId, get user age and household income, and then get the recommended asset allocation";context.Variables.Update(ask);log?.LogDebug("Planner steps: {N}",plan.Steps.Count);varresult=awaitplan.InvokeAsync(context);
{"schema":1,"type":"completion","description":"Gives financial advise on how to allocate portfolio, given a risk tolerance and a set of assets","completion":{"max_tokens":512,"temperature":0.9,"top_p":1.0,"presence_penalty":1.0,"frequency_penalty":1.0},"input":{"parameters":[{"name":"portfolio","description":"Asset portfolio as JSON text","defaultValue":""},{"name":"user","description":"User information as JSON text","defaultValue":""}]}}+++++
With 4 types of planners, you can easily chain and orchestrate your skills. However, if there are times when planner might not suit your needs, you could revert to using RunAsync to call a native function.
varuserProfileSkill=_kernel.ImportSkill(newUserProfileSkill(),"UserProfileSkill");// ========= Orchestrate with LLM using context, connector, and memory =========varresult=await_kernel.RunAsync(context,userProfileSkill["GetUserAge"],userProfileSkill["GetAnnualHouseholdIncome"],advisorSkill["InvestmentAdvise"]);_kernel.Log.LogDebug("Result: {0}",result.Result);
// Copyright (c) Microsoft. All rights reserved.usingSystem.ComponentModel;usingMicrosoft.SemanticKernel.Orchestration;usingMicrosoft.SemanticKernel.SkillDefinition;namespaceGBB.Miyagi.RecommendationService.plugins;/// <summary>/// UserProfilePlugin shows a native skill example to look user info given userId./// </summary>/// <example>/// Usage: kernel.ImportSkill("UserProfilePlugin", new UserProfilePlugin());/// Examples:/// SKContext["userId"] = "000"/// </example>publicclassUserProfilePlugin{/// <summary>/// Name of the context variable used for UserId./// </summary>publicconststringUserId="UserId";privateconststringDefaultUserId="40";privateconstintDefaultAnnualHouseholdIncome=150000;privateconstintNormalize=81;/// <summary>/// Lookup User's age for a given UserId./// </summary>/// <example>/// SKContext[UserProfilePlugin.UserId] = "000"/// </example>/// <param name="context">Contains the context variables.</param>[SKFunction][SKName("GetUserAge")][Description("Given a userId, get user age")]publicstringGetUserAge([Description("Unique identifier of a user")]stringuserId,SKContextcontext){// userId = context.Variables.ContainsKey(UserId) ? context[UserId] : DefaultUserId;userId=string.IsNullOrEmpty(userId)?DefaultUserId:userId;context.Log.LogDebug("Returning hard coded age for {0}",userId);intage;if(int.TryParse(userId,outvarparsedUserId))age=parsedUserId>100?parsedUserId%Normalize:parsedUserId;elseage=int.Parse(DefaultUserId);// invoke a service to get the age of the user, given the userIdreturnage.ToString();}/// <summary>/// Lookup User's annual income given UserId./// </summary>/// <example>/// SKContext[UserProfilePlugin.UserId] = "000"/// </example>/// <param name="context">Contains the context variables.</param>[SKFunction][SKName("GetAnnualHouseholdIncome")][Description("Given a userId, get user annual household income")]publicstringGetAnnualHouseholdIncome([Description("Unique identifier of a user")]stringuserId,SKContextcontext){// userId = context.Variables.ContainsKey(UserId) ? context[UserId] : DefaultUserId;userId=string.IsNullOrEmpty(userId)?DefaultUserId:userId;context.Log.LogDebug("Returning userId * randomMultiplier for {0}",userId);varrandom=newRandom();varrandomMultiplier=random.Next(1000,8000);// invoke a service to get the annual household income of the user, given the userIdvarannualHouseholdIncome=int.TryParse(userId,outvarparsedUserId)?parsedUserId*randomMultiplier:DefaultAnnualHouseholdIncome;returnannualHouseholdIncome.ToString();}}
This is how you prompt engineer in your code
The snippets above highlight some of the primitives of SK that allow you to easily integrate prompt engineering in your existing workflows.