Skip to content
This repository has been archived by the owner on Aug 21, 2023. It is now read-only.

Basic version of create segment using azure open ai and semantic kernel sdk #6

Merged
merged 15 commits into from
Aug 11, 2023
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,59 @@

using System;
using System.Threading.Tasks;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Orchestration;
using Microsoft.SemanticKernel.Planning;
using Microsoft.SemanticKernel.Skills.Core;
using RepoUtils;

public static class Example00_02_PlayFabGenerative
{
public static async Task RunAsync()
{
throw new NotImplementedException();
var goals = new string[]
{
"Create a segment with name NewPlayersSegment for the players first logged in date greater than 2023-08-01?", // Working
"Create a segment with name LegacyPlayersSegment for the players last logged in date less than 2023-05-01?", // Working
"Create a segment with name EgyptNewPlayers for the players located in the Egypt?", // Working
"Create a segment for china for the players logged in the last 30 days and grant them 10 virtual currency?",
"Create a segment with name WelcomeEgyptNewPlayers for the players located in the Egypt with entered segment action of email notification?", // With entered segment action
"Create a segment with name EgyptNewPlayers for the players located in the Egypt?" // If the segment already exist, create a segment with name appended with guid
};
await CreateSegmentExample(goals[0]);
Copy link
Collaborator

@nir-schleyen nir-schleyen Aug 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

await CreateSegmentExample(goals[0]);

loop the questions #Resolved

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since these are create operations, same segment name won't work every time for now. Later I will add logic to rename if the segment already exist.

}

private static async Task CreateSegmentExample(string goal)
{
// Create a segment skill
{
Copy link
Collaborator

@nir-schleyen nir-schleyen Aug 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  { [](http://example.com/codeflow?start=1&length=8)

remove brackets #Resolved

Console.WriteLine("======== Action Planner ========");
var kernel2 = new KernelBuilder()
Copy link
Collaborator

@nir-schleyen nir-schleyen Aug 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kernel2

rename to just 'kernel' instead of kernel2 #Resolved

.WithLogger(ConsoleLogger.Logger)
.WithAzureTextCompletionService("text-davinci-003", TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ApiKey) // Note: Action Planner works with old models like text-davinci-002
Copy link
Collaborator

@nir-schleyen nir-schleyen Aug 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"text-davinci-003"

Use the model name from the Test Configuration (not hard-coded) #Resolved

.Build();

string folder = RepoFiles.SampleSkillsPath();
Copy link
Collaborator

@nir-schleyen nir-schleyen Aug 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

string folder = RepoFiles.SampleSkillsPath();

why do you need this line?
Remove if possible #Resolved

kernel2.ImportSkill(new SegmentSkill(), "SegmentSkill");

// Create an instance of ActionPlanner.
// The ActionPlanner takes one goal and returns a single function to execute.
var planner = new ActionPlanner(kernel2);

// We're going to ask the planner to find a function to achieve this goal.
//var goal = "Create a segment with name NewPlayersSegment for the players first logged in date greater than 2023-08-01?";
Console.WriteLine("Goal: " + goal);

// The planner returns a plan, consisting of a single function
// to execute and achieve the goal requested.
var plan = await planner.CreatePlanAsync(goal);
plan.Steps[0].Parameters = plan.Parameters;

// Execute the full plan (which is a single function)
SKContext result = await plan.InvokeAsync(kernel2.CreateNewContext());

// Show the result, which should match the given goal
Console.WriteLine(result);
}
}
}
122 changes: 122 additions & 0 deletions dotnet/samples/KernelSyntaxExamples/Skills/SegmentSkill.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.SemanticKernel.Orchestration;
using Microsoft.SemanticKernel.SkillDefinition;
using Microsoft.SemanticKernel.Skills.OpenAPI.Authentication;
using Microsoft.SemanticKernel.Skills.OpenAPI.Extensions;
using Newtonsoft.Json;
using RepoUtils;

namespace Microsoft.SemanticKernel.Skills.Core;

/// <summary>
/// Create a segment with given information.
/// </summary>
public sealed class SegmentSkill
{
ContextVariables contextVariables = new ContextVariables();
Copy link
Collaborator

@nir-schleyen nir-schleyen Aug 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ContextVariables contextVariables = new ContextVariables();

remove from here and move into the skil function #Resolved


/// <summary>
/// Read a file
/// </summary>
/// <example>
/// {{file.readAsync $path }} => "hello world"
/// </example>
/// <param name="path"> Source file </param>
/// <returns> File content </returns>
[SKFunction, Description("Create a segment using prompt and parsing prompt")]
public async Task<string> CreateSegment([Description("Name of the segment.")] string segmentname,
[Description("Name of the segment definition. Some of the examples are FirstLoginDateFilter, LastLoginDateFilter, LocationFilter.")] string segmentdefinition,
[Description("Name of the segment comparison. Some of the examples are GreaterThan, LessThan, Equals.")] string segmentcomparison,
[Description("Value of the segment comparison. Some of the examples are 2023-08-01, India, Australia, Kenya.")] string segmentcomparisonvalue
)
{
//ToDo: Create payload json using Playfab dlls/sdk
// Set properties to create a Segment using swagger.json
contextVariables.Set("content_type", "application/json");
contextVariables.Set("server_url", TestConfiguration.PlayFab.Endpoint);
string segmentPayload = GetSegmentPayload(segmentname, segmentdefinition, segmentcomparison, ref segmentcomparisonvalue);

contextVariables.Set("content_type", "application/json");
contextVariables.Set("payload", segmentPayload);
var kernel = new KernelBuilder().WithLogger(ConsoleLogger.Logger).Build();
using HttpClient httpClient = new();
var playfabApiSkills = await GetPlayFabSkill(kernel, httpClient);

// Run operation via the semantic kernel
var result2 = await kernel.RunAsync(contextVariables, playfabApiSkills["CreateSegment"]);

Console.WriteLine("\n\n\n");
var formattedContent = JsonConvert.SerializeObject(JsonConvert.DeserializeObject(result2.Result), Formatting.Indented);
Console.WriteLine("CreateSegment playfabApiSkills response: \n{0}", formattedContent);

return $"Segment {segmentname} created with segment definition {segmentdefinition}";
}

private static string GetSegmentPayload(string segmentname, string segmentdefinition, string segmentcomparison, ref string segmentcomparisonvalue)
{
string segmentPayload = "{\n \"SegmentModel\": {\n \"Name\": \"<SegmentName>\",\n \"SegmentOrDefinitions\": [\n {\n \"SegmentAndDefinitions\": [\n {\n \"<SegmentDefinition>\": {\n \"LogInDate\": \"<SegmentComparisonValue>T00:00:00Z\",\n \"Comparison\": \"<SegmentComparison>\"\n }\n }\n ]\n }\n ]\n }\n }";
string locationPayload = "{\n \"SegmentModel\": {\n \"Name\": \"<SegmentName>\",\n \"SegmentOrDefinitions\": [\n {\n \"SegmentAndDefinitions\": [\n {\n \"<SegmentDefinition>\": {\n \"CountryCode\": \"<SegmentComparisonValue>\",\n \"Comparison\": \"<SegmentComparison>\"\n }\n }\n ]\n }\n ]\n }\n }";

if (segmentdefinition == "LocationFilter")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LocationFilter

With proper documentation this mapping steps won't be needed, and the model should know to return country codes (instead of country names) for that segment definition

Copy link
Collaborator Author

@rosireddyr rosireddyr Aug 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wav.. It worked just by updating description :)

{
segmentcomparisonvalue = GetCountryCode(segmentcomparisonvalue);
segmentPayload = locationPayload;
}

segmentPayload = segmentPayload.Replace("<SegmentName>", segmentname);
segmentPayload = segmentPayload.Replace("<SegmentDefinition>", segmentdefinition);
segmentPayload = segmentPayload.Replace("<SegmentComparison>", segmentcomparison);
segmentPayload = segmentPayload.Replace("<SegmentComparisonValue>", segmentcomparisonvalue);
return segmentPayload;
}

private static async Task<IDictionary<string, ISKFunction>> GetPlayFabSkill(IKernel kernel, HttpClient httpClient)
{
IDictionary<string, ISKFunction> playfabApiSkills;

var titleSecretKeyProvider = new PlayFabAuthenticationProvider(() =>
{
string s = TestConfiguration.PlayFab.TitleSecretKey;
return Task.FromResult(s);
});

bool useLocalFile = true;
if (useLocalFile)
{
var playfabApiFile = "../../../Skills/PlayFabApiSkill/openapi.json";
playfabApiSkills = await kernel.ImportOpenApiSkillFromFileAsync("PlayFabApiSkill", playfabApiFile, new OpenApiSkillExecutionParameters(httpClient, authCallback: titleSecretKeyProvider.AuthenticateRequestAsync));
}
else
{
var playfabApiRawFileUrl = new Uri(TestConfiguration.PlayFab.SwaggerEndpoint);
playfabApiSkills = await kernel.ImportOpenApiSkillFromUrlAsync("PlayFabApiSkill", playfabApiRawFileUrl, new OpenApiSkillExecutionParameters(httpClient, authCallback: titleSecretKeyProvider.AuthenticateRequestAsync));
}

return playfabApiSkills;
}

private static string GetCountryCode(string country)
{
StringDictionary countryCodes = new StringDictionary();
countryCodes.Add("India", "IN");
countryCodes.Add("Israel", "IL");
countryCodes.Add("Australia", "AU");
countryCodes.Add("Kenya", "KE");
countryCodes.Add("Egypt", "EG");
countryCodes.Add("China", "CN");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this shouldn't be needed. With an appropriate description, the model should return an appropriate country codes. Country codes are well known and the model should just have the right documentation to tell it to do so

Copy link
Collaborator Author

@rosireddyr rosireddyr Aug 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wav.. It worked just by updating the description :)


if (countryCodes.ContainsKey(country))
{
return countryCodes[country];
}

return string.Empty;
}
}
Loading