Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

(시리즈 글이 6개 있습니다.)
.NET Framework: 2116. C# - OpenAI API 사용 - 지원 모델 목록
; https://www.sysnet.pe.kr/2/0/13344

닷넷: 2165. C# - Azure OpenAI API를 이용해 ChatGPT처럼 동작하는 콘솔 응용 프로그램 제작
; https://www.sysnet.pe.kr/2/0/13451

닷넷: 2166. C# - Azure OpenAI API를 이용해 사용자가 제공하는 정보를 대상으로 검색하는 방법
; https://www.sysnet.pe.kr/2/0/13452

닷넷: 2167. C# - Qdrant Vector DB를 이용한 Embedding 벡터 값 보관/조회 (Azure OpenAI)
; https://www.sysnet.pe.kr/2/0/13454

닷넷: 2168. C# - Azure.AI.OpenAI 패키지로 OpenAI 사용
; https://www.sysnet.pe.kr/2/0/13455

닷넷: 2169. C# - OpenAI를 사용해 PDF 데이터를 대상으로 OpenAI 챗봇 작성
; https://www.sysnet.pe.kr/2/0/13456




C# - Qdrant Vector DB를 이용한 Embedding 벡터 값 보관/조회 (Azure OpenAI)

지난 글에서,

C# - Azure OpenAI API를 이용해 사용자가 제공하는 정보를 대상으로 검색하는 방법
; https://www.sysnet.pe.kr/2/0/13452

GitHub 이슈 데이터 정보를 벡터 변환 후 로컬 파일에 저장해 재사용을 했는데요, 사실 간단한 경우라면 몰라도 거의 이런 식으로 사용하는 경우는 없을 것입니다.

그보다는 DB를 활용하게 될 텐데요, 이번 글에서 소개하는 Qdrant가 바로 그런 벡터 데이터베이스 중의 하나입니다.

Qdrant
; https://youtu.be/xEFO1sQ2bUc?t=28371

그리고 .NET Conf 2023의 "Build Intelligent Apps with .NET and Azure" 동영상에서 이에 대한 사용법이 나옵니다. ^^ 역시 이번에도, 해당 강의 내용을 그대로 베껴 보겠습니다.




자, 그럼 지난 글에서 GitHub로부터 가져온 이슈 데이터를 Embedding 과정을 거쳐 벡터로 변환을 해 파일로 저장했는데요,

C# - Azure OpenAI API를 이용해 사용자가 제공하는 정보를 대상으로 검색하는 방법
; https://www.sysnet.pe.kr/2/0/13452

이번에는 Qdrant DB에 저장을 해보겠습니다. 이를 위해 docker로 qdrant 컨테이너를 하나 띄워 두시고,

docker run -p 6333:6333 -p 6334:6334 qdrant/qdrant

NuGet으로부터 Qdrant.Client를 참조 후 인스턴스를 생성합니다.

// Install-Package Azure.AI.OpenAI -Pre
// Install-Package Microsoft.DotNet.Interactive.AIUtilities -Pre
// Install-Package Qdrant.Client -Pre

string azureOpenAIKey = "...[azure openai key]..."; // 초기화 참고
string azureOpenAIEndpoint = "...[azure openai endpoint]...";
var embeddingDeployment = "my-embedding";

OpenAIClient openAIClient = new OpenAIClient(new System.Uri(azureOpenAIEndpoint), new AzureKeyCredential(azureOpenAIKey));

string qdrantHost = "localhost";
string collectionName = "github_issues";

QdrantClient qdrantClient = new QdrantClient(qdrantHost, 6334, false);

이후 동작은 지난 글에서 파일로 벡터 데이터를 저장했던 코드를 DB에 저장하게만 바꾸면 됩니다.

private static async Task EmbedAllIssuesAndSaveToDBAsync(
    QdrantClient qdrantClient, string collectionName, OpenAIClient openAIClient, string embeddingDeployment)
{
    GitHubIssue[]? issues = await LoadIssuesFromFileAsync("issues.json");
    if (issues == null)
    {
        Console.WriteLine("Failed to load issues.json");
        return;
    }

    var collections = await qdrantClient.ListCollectionsAsync();
    if (collections.Contains(collectionName))
    {
        // await qdrantClient.DeleteCollectionAsync(collectionName);
        return;
    }

    var issuesWithChunksColleciton =
        issues.Select(issue => new IssueWithChunks(issue, new()))
            .ToArray();

    var tokenizer = await Tokenizer.CreateAsync(TokenizerModel.ada2);

    foreach (var item in issuesWithChunksColleciton)
    {
        var fullText = item.Issue.Text;
        if (string.IsNullOrWhiteSpace(fullText))
        {
            continue;
        }

        var chunks = tokenizer.ChunkByTokenCountWithOverlap(fullText, 3000, 50)
            .Select(t =>
            $"""
            Title: {item.Issue.Title}

            {t}
            """).Chunk(16)
            .ToArray();

        foreach (var chunk in chunks)
        {
            var embeddingResponse = await openAIClient.GetEmbeddingsAsync(
                new EmbeddingsOptions(embeddingDeployment, chunk));

            item.Chunks.AddRange(
                embeddingResponse.Value.Data.Select(d =>
                new TextWithEmbedding(chunk[d.Index], d.Embedding.ToArray())));
        }
    }

    await qdrantClient.CreateCollectionAsync(collectionName,
        new VectorParams { Size = 1536, Distance = Distance.Cosine });

    var vectors = issuesWithChunksColleciton
        .Where(d => d.Chunks.Count > 0)
        .SelectMany(d =>
        d.Chunks.Select(c => new
        {
            Embedding = c.Embedding,
            Text = $"<issuesTitle>{d.Issue.Title}</issueTitle>\n<issueUrl>{d.Issue.Url}</issueUrl><issueContent>{d.Issue.Text}</issueContent>"
        }))
        .ToList();

    var points = vectors.Select(vector =>
    {
        var point = new PointStruct
        {
            Id = new PointId { Uuid = Guid.NewGuid().ToString() },
            Vectors = vector.Embedding,
            Payload =
            {
                ["text"] = vector.Text
            }
        };

        return point;
    }).ToList();

    await qdrantClient.UpsertAsync(collectionName, points);
}

이렇게 저장한 데이터를 다음과 같이 검색할 수 있습니다.

string question = "Are there any questions for mouse?";

string[] results = await SearchWithQdrantAsync(qdrantClient, collectionName,
    openAIClient, embeddingDeployment,
    question, 16);

results.All((text) =>
{
    Console.WriteLine(text);
    Console.WriteLine("-----------------------------------");
    return true;
});

Console.WriteLine($"Found: {results.Length}");

public static async Task<string[]> SearchWithQdrantAsync(
    QdrantClient qdrantClient, string collectionName,
    OpenAIClient openAIClient, string embeddingDeployment,
    string query, int resultLimit = 1)
{
    var embeddingResponse = await openAIClient.GetEmbeddingsAsync(
                    new EmbeddingsOptions(embeddingDeployment, new[] { query }));

    var embeddingVector = embeddingResponse.Value.Data[0].Embedding.ToArray();

    var results = await qdrantClient.SearchAsync(collectionName, embeddingVector, limit: (ulong)resultLimit);
    return results.Select(r => r.Payload["text"].StringValue).ToArray();
}

참고로, 이것 역시 자연어 검색을 하는 것은 아닙니다. DB를 생성하는 시점의 CreateCollectionAsync 코드를 보면 Distance를 Cosine 옵션으로 주고 있는 것을 볼 수 있는데요, 그러니까 이것도 역시 지난번에 설명한 유사도에 따른 검색에 해당합니다.

어쨌든, 이것으로 .NET Conf 2023에 있었던 "Build Intelligent Apps with .NET and Azure" 내용은 모두 정리했습니다. 해당 동영상의 마지막에는 다음과 같은 학습 자료를 공유하고 있으니 참고하세요. ^^

AI in .NET Collection
; https://aka.ms/ai-dotnet-learn
; https://learn.microsoft.com/en-us/collections/1n31t57k7k6r85

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




그나저나, OpenAI의 ChatGPT는 어떻게 해서 자연어 검색을 할 수 있는 걸까요? 아직 저도 완벽하게 이해하는 것은 아니지만, 대충 어떤 식일지는 짐작이 가는 듯합니다.

가령, 사용자가 질문을 하면, 그에 해당하는 키워드로 기존에 저장해 두었던 스토리지로부터 Vector 검색을 해 적당한 문서를 선별할 것입니다. 그런 다음, 그 문서를 "대화의 문맥"에 저장해 두고, 사용자의 질문을 그 문맥 내에서 다시 수행해 이후 적절한 문장으로 Completion 엔진을 통해 대답하는 식일 것입니다.

따라서, 우리가 가진 별도의 Knowledge base 자료가 있다면 그것을 Storage (VectorDB)에 저장한 후, 사용자가 질의를 하면 그것과 유사도가 높은 문서들을 VectorDB에서 검색한 다음 그 원본 문자열을 담은 문서를 다시 OpenAI API에 "질문"과 함께 전달해 ChatCompletion을 거치면 되는 식일 것입니다.




참고로, 왜 마이크로소프트는 OpenAI 서비스가 있는데, 그걸 굳이 Azure에 올려 Azure OpenAI로 따로 서비스를 하고 있는 걸까요? 사용자 입장에서 Azure OpenAI를 선택하면 어떤 장점이 있을지 궁금하지 않나요? ^^

지금까지의 코드를 보면, 질문뿐만 아니라 GitHub Issue 데이터를 Embedding하기 위해 OpenAI 측에 데이터를 전달해야만 했는데요, 사실 이런 과정이 보안을 중시하는 "기업" 입장에서는 매우 불편할 수가 있습니다. 실제로 얼마 전 삼성 전자가 사내에서 ChatGPT 사용을 금지한 이유가 그것 때문이었습니다.

'챗GPT 사내금지' 삼성전자, 직원업무 도울 자체 AI 도구 만든다
; https://www.yna.co.kr/view/AKR20230502125400003

이런 문제를 Azure OpenAI가 해결하는데요, 다음의 문서에서 이를 찾아볼 수 있습니다.

Data, privacy, and security for Azure OpenAI Service
; https://learn.microsoft.com/en-us/legal/cognitive-services/openai/data-privacy

Your prompts (inputs) and completions (outputs), your embeddings, and your training data:

are NOT available to other customers.
are NOT available to OpenAI.
are NOT used to improve OpenAI models.
are NOT used to improve any Microsoft or 3rd party products or services.
are NOT used for automatically improving Azure OpenAI models for your use in your resource (The models are stateless, unless you explicitly fine-tune models with your training data).
Your fine-tuned Azure OpenAI models are available exclusively for your use.


만약, Azure OpenAI의 비용이 부담스럽다면, 차선책으로 무료 LLM 모델인 LLaMA(라마)를 이용해 구축하는 방안이 있습니다.




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







[최초 등록일: ]
[최종 수정일: 11/23/2023]

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

비밀번호

댓글 작성자
 



2024-03-15 09시49분
정성태

... 16  17  18  19  [20]  21  22  23  24  25  26  27  28  29  30  ...
NoWriterDateCnt.TitleFile(s)
13153정성태11/2/20227380.NET Framework: 2062. C# - 코드로 재현하는 소켓 상태(SYN_SENT, SYN_RECV)
13152정성태11/1/20225977.NET Framework: 2061. ASP.NET Core - DI로 추가한 클래스의 초기화 방법 [1]
13151정성태10/31/20226158C/C++: 161. Windows 11 환경에서 raw socket 테스트하는 방법파일 다운로드1
13150정성태10/30/20226066C/C++: 160. Visual Studio 2022로 빌드한 C++ 프로그램을 위한 다른 PC에서 실행하는 방법
13149정성태10/27/20226058오류 유형: 825. C# - CLR ETW 이벤트 수신이 GCHeapStats_V1/V2에 대해 안 되는 문제파일 다운로드1
13148정성태10/26/20225975오류 유형: 824. msbuild 에러 - error NETSDK1005: Assets file '...\project.assets.json' doesn't have a target for 'net5.0'. Ensure that restore has run and that you have included 'net5.0' in the TargetFramew
13147정성태10/25/20225047오류 유형: 823. Visual Studio 2022 - Unable to attach to CoreCLR. The debugger's protocol is incompatible with the debuggee.
13146정성태10/24/20225895.NET Framework: 2060. C# - Java의 Xmx와 유사한 힙 메모리 최댓값 제어 옵션 HeapHardLimit
13145정성태10/21/20226239오류 유형: 822. db2 - Password validation for user db2inst1 failed with rc = -2146500508
13144정성태10/20/20226119.NET Framework: 2059. ClrMD를 이용해 윈도우 환경의 메모리 덤프로부터 닷넷 모듈을 추출하는 방법파일 다운로드1
13143정성태10/19/20226625오류 유형: 821. windbg/sos - Error code - 0x000021BE
13142정성태10/18/20225985도서: 시작하세요! C# 12 프로그래밍
13141정성태10/17/20227228.NET Framework: 2058. [in,out] 배열을 C#에서 C/C++로 넘기는 방법 - 세 번째 이야기파일 다운로드1
13140정성태10/11/20226591C/C++: 159. C/C++ - 리눅스 환경에서 u16string 문자열을 출력하는 방법 [2]
13139정성태10/9/20226259.NET Framework: 2057. 리눅스 환경의 .NET Core 3/5+ 메모리 덤프로부터 모든 닷넷 모듈을 추출하는 방법파일 다운로드1
13138정성태10/8/20227665.NET Framework: 2056. C# - await 비동기 호출을 기대한 메서드가 동기로 호출되었을 때의 부작용 [1]
13137정성태10/8/20225963.NET Framework: 2055. 리눅스 환경의 .NET Core 3/5+ 메모리 덤프로부터 닷넷 모듈을 추출하는 방법
13136정성태10/7/20226543.NET Framework: 2054. .NET Core/5+ SDK 설치 없이 dotnet-dump 사용하는 방법
13135정성태10/5/20226813.NET Framework: 2053. 리눅스 환경의 .NET Core 3/5+ 메모리 덤프를 분석하는 방법 - 두 번째 이야기
13134정성태10/4/20225490오류 유형: 820. There is a problem with AMD Radeon RX 5600 XT device. For more information, search for 'graphics device driver error code 31'
13133정성태10/4/20225861Windows: 211. Windows - (commit이 아닌) reserved 메모리 사용량 확인 방법 [1]
13132정성태10/3/20225764스크립트: 42. 파이썬 - latexify-py 패키지 소개 - 함수를 mathjax 식으로 표현
13131정성태10/3/20228587.NET Framework: 2052. C# - Windows Forms의 데이터 바인딩 지원(DataBinding, DataSource) [2]파일 다운로드1
13130정성태9/28/20225452.NET Framework: 2051. .NET Core/5+ - 에러 로깅을 위한 Middleware가 동작하지 않는 경우파일 다운로드1
13129정성태9/27/20225761.NET Framework: 2050. .NET Core를 IIS에서 호스팅하는 경우 .NET Framework CLR이 함께 로드되는 환경
13128정성태9/23/20228449C/C++: 158. Visual C++ - IDL 구문 중 "unsigned long"을 인식하지 못하는 #import파일 다운로드1
... 16  17  18  19  [20]  21  22  23  24  25  26  27  28  29  30  ...