Microsoft MVP성태의 닷넷 이야기
.NET Framework: 2125. C# - Semantic Kernel의 Semantic Memory 사용 예제 [링크 복사], [링크+제목 복사],
조회: 3981
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

(시리즈 글이 8개 있습니다.)
.NET Framework: 2117. C# - (OpenAI 기반의) Microsoft Semantic Kernel을 이용한 자연어 처리
; https://www.sysnet.pe.kr/2/0/13345

.NET Framework: 2118. C# - Semantic Kernel의 Prompt chaining 예제
; https://www.sysnet.pe.kr/2/0/13347

.NET Framework: 2119. C# - Semantic Kernel의 "Basic Loading of the Kernel" 예제
; https://www.sysnet.pe.kr/2/0/13348

.NET Framework: 2120. C# - Semantic Kernel의 Skill과 Function 사용 예제
; https://www.sysnet.pe.kr/2/0/13349

.NET Framework: 2121. C# - Semantic Kernel의 대화 문맥 유지
; https://www.sysnet.pe.kr/2/0/13352

.NET Framework: 2123. C# - Semantic Kernel의 ChatGPT 대화 구현
; https://www.sysnet.pe.kr/2/0/13355

.NET Framework: 2124. C# - Semantic Kernel의 Planner 사용 예제
; https://www.sysnet.pe.kr/2/0/13357

.NET Framework: 2125. C# - Semantic Kernel의 Semantic Memory 사용 예제
; https://www.sysnet.pe.kr/2/0/13358




C# - Semantic Kernel의 Semantic Memory 사용 예제

ASK, Kernel, Planner, Skill을 지나 이제 Memory 예제까지 왔습니다. ^^

samples/notebooks/dotnet/06-memory-and-embeddings.ipynb
; https://github.com/microsoft/semantic-kernel/blob/main/samples/notebooks/dotnet/06-memory-and-embeddings.ipynb

Memory는 간단하게 설명하면 전체적인 문맥을 기억하기 위한 수단입니다. 문맥이라고 하니, 지난 Context 예제가 있었는데요,

C# - Semantic Kernel의 대화 문맥 유지
; https://www.sysnet.pe.kr/2/0/13352

사실 위에서 구현한 ContextVariables는 그다지 특별한 것이 아니고, 지금까지의 대화 내용을 전부 누적시켜 설정하는 것에 지나지 않습니다. 일례로, 이전 예제의 코드를 보면,

var context = new ContextVariables();
var history = "";
context.Set("history", history);

...[생략]...

var bot_answer = await kernel.RunAsync(context, skill["Chat"]);
Console.WriteLine(bot_answer);

history += $"\nHuman: {human_input}\nAssistant: {bot_answer}\n";
context.Update(history);

history 문자열에 대화의 내용을 전부 합쳐 Update 시킨 후 RunAsync에 전달하는 식입니다. 여기서 문제는, 이런 식으로 누적시켜 대화를 이어나가면 결국 Model에서 정한 Token 제약에 금방 걸리고 맙니다.

바로 이런 문제를 해결하기 위해, 단기/장기로 대화 상태를 저장할 수 있는 메모리가 필요한 것입니다. 이 외에도, 메모리를 이용하면 또 다른 활용이 가능합니다. 예를 들어, 사내에서 개발한 제품의 매뉴얼이 있다고 가정해 보겠습니다. 물론, OpenAI가 크롤링한 웹 데이터에 해당 매뉴얼이 있다면 상관없겠지만 그렇지 않을 수도 있습니다. 그런 상황이라면 그 매뉴얼 전체를 Chat 내에서 인식시키려 할 때 또다시 Token 제약에 걸립니다.

이런 문제를 Memory를 이용해 해결할 수 있는데요, 이를 위해서는 2가지 구성요소(Memory Storage와 Embedding backend)를 Kernel에 추가해야 합니다. 방법은 예제 코드에 잘 나옵니다. ^^

우선 Embedding backend는 KernelConfig을 이용해 새롭게 "text-embedding-ada-002" 모델을 추가합니다.

KernelConfig kernelConfig = new KernelConfig();
kernelConfig.AddOpenAITextCompletionService("default", "text-davinci-003", apiKey);
kernelConfig.AddOpenAITextEmbeddingGenerationService("ada", "text-embedding-ada-002", apiKey);

그다음 Memory Storage는, Application이 실행 중일 때만 보관이 되도록 VolatileMemoryStore를 사용합니다.

var kernel = Kernel.Builder
    .WithConfiguration(kernelConfig)
    .WithMemoryStorage(new VolatileMemoryStore())
    .Build();

이 외에도, Azure Cosmos DB, PostgreSQL, SQLite 등으로 유지할 수 있다고 합니다. nuget을 찾아보면 관련 타입들이 아마도 다음의 패키지들에 포함된 것 같습니다.

Microsoft.SemanticKernel.Connectors.Memory.Sqlite
; https://www.nuget.org/packages/Microsoft.SemanticKernel.Connectors.Memory.Sqlite

Microsoft.SemanticKernel.Connectors.Memory.Qdrant 
; https://www.nuget.org/packages/Microsoft.SemanticKernel.Connectors.Memory.Qdrant

Microsoft.SemanticKernel.Connectors.Memory.AzureCognitiveSearch
; https://www.nuget.org/packages/Microsoft.SemanticKernel.Connectors.Memory.AzureCognitiveSearch

Microsoft.SemanticKernel.Connectors.Memory.Postgres
; https://github.com/microsoft/semantic-kernel/tree/main/dotnet/src/Connectors/Connectors.Memory.Postgres

당연히 모두 현재(2023-05-17)는 Preview 버전이고 PostgreSQL의 경우에는 찾을 수 없었습니다.

자, 그럼 이제 남은 것은 Memory Store에 문맥 정보를 추가하는 일이 남았는데요, 이를 위해 SaveInformationAsync 메서드를 사용할 수 있습니다.

const string MemoryCollectionName = "aboutMe";

await kernel.Memory.SaveInformationAsync(MemoryCollectionName, id: "info1", text: "My name is Andrea");
await kernel.Memory.SaveInformationAsync(MemoryCollectionName, id: "info2", text: "I currently work as a tourist operator");
await kernel.Memory.SaveInformationAsync(MemoryCollectionName, id: "info3", text: "I currently live in Seattle and have been living there since 2005");
await kernel.Memory.SaveInformationAsync(MemoryCollectionName, id: "info4", text: "I visited France and Italy five times since 2015");
await kernel.Memory.SaveInformationAsync(MemoryCollectionName, id: "info5", text: "My family is from New York");

여기서 문맥 정보에 대한 식별자를 줬기 때문에 관련 문맥을 사용할 때도 동일한 식별자를 줘야 합니다. 아래는 위와 같은 문맥이 있는 상태에서 질문하는 코드를 보여줍니다.

var questions = new[]
{
    "what is my name?",
    "where do I live?",
    "where is my family from?",
    "where have I travelled?",
    "what do I do for work?",
};

foreach (var q in questions)
{
    var response = await kernel.Memory.SearchAsync(MemoryCollectionName, q).FirstOrDefaultAsync();
    Console.WriteLine(q + " " + response?.Metadata.Text);
}

/* 출력 결과
what is my name? My name is Andrea
where do I live? I currently live in Seattle and have been living there since 2005
where is my family from? My family is from New York
where have I travelled? I visited France and Italy five times since 2015
what do I do for work? I currently work as a tourist operator
*/

오호~~~ 점점 더 예제가 재미있어지는군요. ^^

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




참고로, 이때 문맥으로 저장하는 텍스트는 단순히 문자열로 저장되는 것이 아니고, 우리가 지정한 Embedding backend, 즉 위의 예제에서는 "text-embedding-ada-002" 모델로 벡터값으로 인코딩된 유형으로 저장됩니다. 관련해서는 아래의 문서를 참조하라고 하는군요. ^^

What are Embeddings?
; https://learn.microsoft.com/en-us/semantic-kernel/concepts-ai/embeddings

그러고 보면, 지난 예제에서 "text-embedding-ada-002" 모델을 사용해 "안녕하세요, 책 좀 추천해 주세요." 문자열을 전송했더니 다음과 같은 결과가 나왔는데요,

( 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0, 0 0 0 0 0 1 0 0, 0 0 0, 0 0 0, 0 0, 0 0, 0 0 0, 0 0 0 0,0 0 0, 0 0 0,0,0 0 0 0, 0 0 0 0, 0 0 0,0,0 0, 0,0,0,,0,0,0,0 1 0, 0,0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,0,0, 0,0,0, 0,0,0,0,0,, 0,0,0, 0,0,0,0,0,,0,0,0,0, 1 0,0,0,0,0,0,0,0, 0,0,0,0, 0,0,0, 0,0,0, 0 1 0,0,0,0,0,,, 0,0,0,0, 0,0,0, 0,0, 0,0,0, 0,0, 0,0, 0,0, 0,0,0, 0,0, 0,0,1, 0,0, 0,0, 0,0,0,0, 0,0,0, 0,0, 1,0,0, 0,0, 0,0,0, 0,0,1,0,0,0,0,1, 0,0,0, 0,0,0,1,1,0, 0,0,0,0, 1,0,0,1,1,0, 1,0,0,0,0,0, 1,0,0,0,0,1,0,1,1, 1,0,0,0,1,1, 1,0,1,1,0, 0,0,0,0,0, 0,0, 1, 0,0,1,0,1,1,1, 0,0,0,1,0,1,1, 0,0,0,0, 0,0, 1,0,1,0, 1,1,1,0, 0,0,0, 1,0,0,0,1,1, 1,0,1,1,1, 0,0,0,0, 0,0,0,1,1,1,1,1,1,1,1, 1,0,0,0,0, 1,0,1,1,1,1, 0,0,0,0, 1,1,0,1,0,1,1, 1,0,0,0, 0,0,0,1,,1,0,0,0,1,1,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,0,1,0,0,0,1,0,0,0,1,1,1,1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,1,0,1,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1

아마도 저것이 "vectors or arrays of numbers that represent the meaning and the context of tokens processed by the model."인 듯합니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 6/5/2023]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2023-06-01 08시59분
정성태

1  2  3  4  5  6  7  [8]  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13443정성태11/14/20232836개발 환경 구성: 687. OpenSSL로 생성한 사용자 인증서를 ASP.NET Core 웹 사이트에 적용하는 방법
13442정성태11/13/20232599개발 환경 구성: 686. 비주얼 스튜디오로 실행한 ASP.NET Core 사이트를 WSL 2 인스턴스에서 https로 접속하는 방법
13441정성태11/12/20233002닷넷: 2159. C# - ASP.NET Core 프로젝트에서 서버 Socket을 직접 생성하는 방법파일 다운로드1
13440정성태11/11/20232583Windows: 253. 소켓 Listen 시 방화벽의 Public/Private 제어 기능이 비활성화된 경우
13439정성태11/10/20233198닷넷: 2158. C# - 소켓 포트를 미리 시스템에 등록/예약해 사용하는 방법(Port Exclusion Ranges)파일 다운로드1
13438정성태11/9/20232817닷넷: 2157. C# - WinRT 기능을 이용해 윈도우에서 실행 중인 Media App 제어
13437정성태11/8/20232968닷넷: 2156. .NET 7 이상의 콘솔 프로그램을 (dockerfile 없이) 로컬 docker에 배포하는 방법
13436정성태11/7/20233201닷넷: 2155. C# - .NET 8 런타임부터 (Reflection 없이) 특성을 이용해 public이 아닌 멤버 호출 가능
13435정성태11/6/20233104닷넷: 2154. C# - 네이티브 자원을 포함한 관리 개체(예: 스레드)의 GC 정리
13434정성태11/1/20232928스크립트: 62. 파이썬 - class의 정적 함수를 동적으로 교체
13433정성태11/1/20232583스크립트: 61. 파이썬 - 함수 오버로딩 미지원
13432정성태10/31/20232673오류 유형: 878. 탐색기의 WSL 디렉터리 접근 시 "Attempt to access invalid address." 오류 발생
13431정성태10/31/20233091스크립트: 60. 파이썬 - 비동기 FastAPI 앱을 gunicorn으로 호스팅
13430정성태10/30/20232889닷넷: 2153. C# - 사용자가 빌드한 ICU dll 파일을 사용하는 방법
13429정성태10/27/20233140닷넷: 2152. Win32 Interop - C/C++ DLL로부터 이중 포인터 버퍼를 C#으로 받는 예제파일 다운로드1
13428정성태10/25/20233268닷넷: 2151. C# 12 - ref readonly 매개변수
13427정성태10/18/20233462닷넷: 2150. C# 12 - 정적 문맥에서 인스턴스 멤버에 대한 nameof 접근 허용(Allow nameof to always access instance members from static context)
13426정성태10/13/20233591스크립트: 59. 파이썬 - 비동기 호출 함수(run_until_complete, run_in_executor, create_task, run_in_threadpool)
13425정성태10/11/20233354닷넷: 2149. C# - PLinq의 Partitioner<T>를 이용한 사용자 정의 분할파일 다운로드1
13423정성태10/6/20233342스크립트: 58. 파이썬 - async/await 기본 사용법
13422정성태10/5/20233470닷넷: 2148. C# - async 유무에 따른 awaitable 메서드의 병렬 및 예외 처리
13421정성태10/4/20233559닷넷: 2147. C# - 비동기 메서드의 async 예약어 유무에 따른 차이
13420정성태9/26/20235996스크립트: 57. 파이썬 - UnboundLocalError: cannot access local variable '...' where it is not associated with a value
13419정성태9/25/20233347스크립트: 56. 파이썬 - RuntimeError: dictionary changed size during iteration
13418정성태9/25/20234113닷넷: 2146. C# - ConcurrentDictionary 자료 구조의 동기화 방식
13417정성태9/19/20233636닷넷: 2145. C# - 제네릭의 형식 매개변수에 속한 (매개변수를 가진) 생성자를 호출하는 방법
1  2  3  4  5  6  7  [8]  9  10  11  12  13  14  15  ...