C# - Semantic Kernel의 Planner 사용 예제
Semantic Kernel의 구성 요소는 다음의 그림에서 잘 확인할 수 있습니다.
[출처: https://devblogs.microsoft.com/semantic-kernel/hello-world/]
ASK는 Kernel에 보내는 요청이고, Kernel은 이미 그동안 우리가 만든 코드의 시작에 해당하는 타입이었습니다.
var kernel = Kernel.Builder.Build(); // C# - Semantic Kernel의 "Basic Loading of the Kernel" 예제
그림에서 S는 Skill인데 이것도 "
C# - Semantic Kernel의 Skill과 Function 사용 예제" 글을 통해 이미 다뤘습니다.
나머지는 M(Memory), C(Connector)와 오늘의 주제인 Planner인데요, 이에 대한 예제를 다음의 글에서 다루고 있습니다.
Introduction to the Planner
; https://github.com/microsoft/semantic-kernel/blob/main/samples/notebooks/dotnet/05-using-the-planner.ipynb
코드는 기존 예제와 동일하게 Kernel을 준비하는 것으로 시작하고,
KernelConfig kernelConfig = new KernelConfig();
kernelConfig.AddOpenAITextCompletionService("default", "text-davinci-003", apiKey);
var kernel = Kernel.Builder
.WithConfiguration(kernelConfig)
.Build();
그다음 planner를 만들고 Kernel에 Skill/Function을 담고 있습니다.
var planner = new SequentialPlanner(kernel);
var skillsDirectory = Path.Combine(Directory.GetCurrentDirectory(), "skills");
kernel.ImportSemanticSkillFromDirectory(skillsDirectory, "SummarizeSkill");
kernel.ImportSemanticSkillFromDirectory(skillsDirectory, "WriterSkill");
/*
SummarizeSkill
Function: MakeAbstractReadable, Notegen, Summarize, Topics
WriterSkill
Function: Acronym, AcronymGenerator, AcronymReverse, Brainstorm, EmailGen, EmailTo,
EnglishImprover, NovelChapter, NovelChapterWithNotes, NovelOutline, Rewrite,
ShortPoem, StoryGen, TellMeMore, Translate, TwoSentenceSummary
*/
해당 스킬들은
github에 예제로 제공되고 있는 것들인데, clone 시킨 디렉터리에서 복사할 수 있습니다.
프로젝트에 파일로 추가하는 경우 각각 CopyToOutputDirectory 설정을 해야 하는데, 일일이 파일 하나씩 하는 것은 번잡하므로 와일드카드 문자열을 사용해 "skills\**\*"로 지정하면 skills 디렉터리 하위에 있는 모든 디렉터리 및 파일들을 복사하게 됩니다. 아래는 csproj에 그 설정을 추가한 것입니다.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SemanticKernel" Version="0.13.277.1-preview" />
</ItemGroup>
<ItemGroup>
<None Update="skills\**\*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
그다음 코드에서는 planner를 이용해 사용자의 요청에 기반한 Planner를 생성하는데요,
var ask = "Tomorrow is Valentine's day. I need to come up with a few date ideas and e-mail them to my significant other.";
var originalPlan = await planner.CreatePlanAsync(ask);
Console.WriteLine("Original plan:\n");
Console.WriteLine(JsonSerializer.Serialize(originalPlan, new JsonSerializerOptions { WriteIndented = true }));
재미있는 건, 어떤 스킬을 사용하라고 지정하지도 않았다는 점입니다. 즉, 단순히 현재 Kernel에 포함된 Skill/Function을 이용해 사용자의 ASK를 만족하는 Planner를 구성하는데요, 아래는 originalPlan의 내부 구조를 JSON 포맷으로 보여줍니다.
Original plan:
{
"state": [
{
"Key": "INPUT",
"Value": ""
}
],
"steps": [
{
"state": [
{
"Key": "INPUT",
"Value": ""
}
],
"steps": [],
"named_parameters": [
{
"Key": "INPUT",
"Value": "Valentine\u0027s day date ideas"
}
],
"named_outputs": [
{
"Key": "DATE_IDEAS",
"Value": ""
},
{
"Key": "INPUT",
"Value": ""
}
],
"next_step_index": 0,
"name": "Brainstorm",
"skill_name": "WriterSkill",
"description": "Given a goal or topic description generate a list of ideas"
},
{
"state": [
{
"Key": "INPUT",
"Value": ""
}
],
"steps": [],
"named_parameters": [
{
"Key": "INPUT",
"Value": ""
}
],
"named_outputs": [
{
"Key": "INPUT",
"Value": ""
}
],
"next_step_index": 0,
"name": "",
"skill_name": "Microsoft.SemanticKernel.Orchestration.Plan",
"description": ""
},
{
"state": [
{
"Key": "INPUT",
"Value": ""
}
],
"steps": [],
"named_parameters": [
{
"Key": "sender",
"Value": "Me"
},
{
"Key": "to",
"Value": "My Significant Other"
},
{
"Key": "INPUT",
"Value": "$DATE_IDEA_1;$DATE_IDEA_2;$DATE_IDEA_3"
}
],
"named_outputs": [
{
"Key": "INPUT",
"Value": ""
}
],
"next_step_index": 0,
"name": "EmailTo",
"skill_name": "WriterSkill",
"description": "Turn bullet points into an email to someone, using a polite tone"
}
],
"named_parameters": [
{
"Key": "INPUT",
"Value": ""
}
],
"named_outputs": [
{
"Key": "INPUT",
"Value": ""
}
],
"next_step_index": 0,
"name": "Tomorrow is Valentine\u0027s day. I need to come up with a few date ideas and e-mail them to my significant other.",
"skill_name": "Microsoft.SemanticKernel.Orchestration.Plan",
"description": "Tomorrow is Valentine\u0027s day. I need to come up with a few date ideas and e-mail them to my significant other."
}
어찌 보면, 저것 하나가 새롭게 "Skill"이 된 것입니다. 결국 이러한 Planner대로 실행을 하면,
var originalPlanResult = await kernel.RunAsync(originalPlan);
Console.WriteLine("Original Plan results:\n");
Console.WriteLine(Utils.WordWrap(originalPlanResult.Result, 80));
다음과 같은 출력을 볼 수 있습니다.
Original Plan results:
Dear My Significant Other,
I wanted to take a moment to thank you for being my
significant other. You have been a source of joy and comfort in my life, and I
am so grateful for your presence. I am so lucky to have you in my life.
Thanks,
Me
그러니까, 사용자의 "Tomorrow is Valentine's day. I need to come up with a few date ideas and e-mail them to my significant other." 입력을 바탕으로, 내부에 구성한 Skill/Function을 활용해 위의 출력을 만들어 낸 것입니다.
그럼, 이 상태에서 Shakespeare 화법으로 말하라는 Skill/Function도 (위에서 만든 Kernel에) 추가해 볼까요? ^^
string skPrompt = """
{{$input}}
Rewrite the above in the style of Shakespeare.
""";
var shakespeareFunction = kernel.CreateSemanticFunction(skPrompt, "shakespeare", "ShakespeareSkill", maxTokens: 2000, temperature: 0.2, topP: 0.5);
이번 Skill/Function은 ImportSemanticSkillFromDirectory가 아닌 동적으로 CreateSemanticFunction을 이용해 추가했습니다. 이러한 변화를 반영해 Planner를 다시 생성하고,
var ask = @"Tomorrow is Valentine's day. I need to come up with a few date ideas.
She likes Shakespeare so write using his style. E-mail these ideas to my significant other";
var newPlan = await planner.CreatePlanAsync(ask);
Planner의 내부 변화를 확인합니다.
Console.WriteLine("Updated plan:\n");
Console.WriteLine(JsonSerializer.Serialize(newPlan, new JsonSerializerOptions { WriteIndented = true }));
Updated plan:
{
"state": [
{
"Key": "INPUT",
"Value": ""
}
],
"steps": [
{
"state": [
{
"Key": "INPUT",
"Value": ""
}
],
"steps": [],
"named_parameters": [
{
"Key": "INPUT",
"Value": "Date ideas for Valentine\u0027s day"
}
],
"named_outputs": [
{
"Key": "INPUT",
"Value": ""
},
{
"Key": "DATE_IDEAS",
"Value": ""
}
],
"next_step_index": 0,
"name": "Brainstorm",
"skill_name": "WriterSkill",
"description": "Given a goal or topic description generate a list of ideas"
},
{
"state": [
{
"Key": "INPUT",
"Value": ""
}
],
"steps": [],
"named_parameters": [
{
"Key": "INPUT",
"Value": "$DATE_IDEAS"
}
],
"named_outputs": [
{
"Key": "INPUT",
"Value": ""
},
{
"Key": "SHAKESPEARE_IDEAS",
"Value": ""
}
],
"next_step_index": 0,
"name": "shakespeare",
"skill_name": "ShakespeareSkill",
"description": "Generic function, unknown purpose"
},
{
"state": [
{
"Key": "INPUT",
"Value": ""
}
],
"steps": [],
"named_parameters": [
{
"Key": "sender",
"Value": "Myself"
},
{
"Key": "INPUT",
"Value": "$SHAKESPEARE_IDEAS"
},
{
"Key": "to",
"Value": "My Significant Other"
}
],
"named_outputs": [
{
"Key": "INPUT",
"Value": ""
}
],
"next_step_index": 0,
"name": "EmailTo",
"skill_name": "WriterSkill",
"description": "Turn bullet points into an email to someone, using a polite tone"
}
],
"named_parameters": [
{
"Key": "INPUT",
"Value": ""
}
],
"named_outputs": [
{
"Key": "INPUT",
"Value": ""
}
],
"next_step_index": 0,
"name": "Tomorrow is Valentine\u0027s day. I need to come up with a few date ideas.\r\nShe likes Shakespeare so write using his style. E-mail these ideas to my significant other",
"skill_name": "Microsoft.SemanticKernel.Orchestration.Plan",
"description": "Tomorrow is Valentine\u0027s day. I need to come up with a few date ideas.\r\nShe likes Shakespeare so write using his style. E-mail these ideas to my significant other"
}
이에 따라 텍스트를 생성하면,
var newPlanResult = await kernel.RunAsync(newPlan);
Console.WriteLine("New Plan results:\n");
Console.WriteLine(newPlanResult.Result);
이런 출력을 확인할 수 있습니다.
New Plan results:
Dear My Significant Other,
I hope you're doing well. I wanted to suggest some activities that we could do together.
We could make a delicious feast together. We could take a romantic walk. We could watch a romantic movie. We could attend a local event. We could take a dancing class. We could visit a museum. We could go to a spa. We could go stargazing. We could go to a concert. We could have a picnic.
I look forward to hearing your thoughts.
Thanks,
Myself
음... 잘하면
시라노 같은 친구도 만들 수 있겠군요. ^^
(
첨부 파일은 이 글의 예제 코드를 포함합니다.)
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]