Share via


자습서: Azure OpenAI Service 포함 및 문서 검색 살펴보기

이 자습서에서는 Azure OpenAI 포함 API를 사용하여 문서 검색을 수행하는 과정을 안내합니다. 여기에서 기술 자료를 쿼리하여 가장 관련성이 높은 문서를 찾습니다.

이 자습서에서는 다음을 하는 방법을 알아볼 수 있습니다.

  • Azure OpenAI를 설치합니다.
  • 샘플 데이터 세트를 다운로드하고 분석을 위해 준비합니다.
  • 리소스 엔드포인트 및 API 키에 대한 환경 변수를 만듭니다.
  • text-embedding-ada-002(버전 2) 모델 사용
  • 코사인 유사성을 사용하여 검색 결과의 순위를 지정합니다.

필수 조건

  • Azure 구독 - 체험 구독 만들기
  • 원하는 Azure 구독의 Azure OpenAI에 대한 액세스 권한. 현재 이 서비스에 대한 액세스 권한은 애플리케이션에서만 부여됩니다. https://aka.ms/oai/access에서 양식을 작성하여 Azure OpenAI에 대한 액세스를 신청할 수 있습니다. 문제가 있는 경우 이 리포지토리에서 문제를 엽니다.
  • text-embedding-ada-002(버전 2) 모델이 배포된 Azure OpenAI 리소스. 이 모델은 현재 특정 지역에서만 사용할 수 있습니다. 리소스가 없는 경우 만들기 프로세스는 리소스 배포 가이드에 설명되어 있습니다.
  • Python 3.8 이상 버전
  • 다음 Python 라이브러리: openai, num2words, matplotlib, plotly, scipy, scikit-learn, Pandas, tiktoken.
  • Jupyter 노트북

설정

Python 라이브러리

아직 설치하지 않은 경우 다음 라이브러리를 설치해야 합니다.

pip install openai num2words matplotlib plotly scipy scikit-learn pandas tiktoken

BillSum 데이터 세트 다운로드

BillSum은 미국 의회 및 캘리포니아 주 법안의 데이터 세트입니다. 설명을 위해 미국 청구서만 살펴보겠습니다. 코퍼스는 의회의 103-115차(1993-2018) 세션의 법안으로 구성됩니다. 데이터는 18,949개의 학습 청구서와 3,269개의 테스트 청구서로 분할되었습니다. BillSum 코퍼스는 5,000자에서 20,000자 길이의 중간 길이 입법에 중점을 둡니다. 프로젝트에 대한 자세한 정보와 이 데이터 세트가 파생된 원본 학술 논문은 BillSum 프로젝트의 GitHub 리포지토리에서 확인할 수 있습니다.

이 자습서에서는 GitHub 샘플 데이터에서 다운로드할 수 있는 bill_sum_data.csv 파일을 사용합니다.

로컬 컴퓨터에서 다음 명령을 실행하여 샘플 데이터를 다운로드할 수도 있습니다.

curl "https://raw.githubusercontent.com/Azure-Samples/Azure-OpenAI-Docs-Samples/main/Samples/Tutorials/Embeddings/data/bill_sum_data.csv" --output bill_sum_data.csv

키 및 엔드포인트 검색

Azure OpenAI에 대해 성공적으로 호출하려면 엔드포인트가 필요합니다.

변수 이름
ENDPOINT 이 값은 Azure Portal에서 리소스를 검사할 때 키 및 엔드포인트 섹션에서 찾을 수 있습니다. 또는 Azure OpenAI Studio>플레이그라운드>코드 보기에서 값을 찾을 수 있습니다. 예제 엔드포인트는 https://docs-test-001.openai.azure.com/입니다.
API-KEY 이 값은 Azure Portal에서 리소스를 검사할 때 키 및 엔드포인트 섹션에서 찾을 수 있습니다. KEY1 또는 KEY2를 사용할 수 있습니다.

Azure Portal에서 해당 리소스로 이동합니다. 키 및 엔드포인트 섹션은 리소스 관리 섹션에서 찾을 수 있습니다. 엔드포인트 및 액세스 키를 복사합니다. API 호출을 인증하는 데 모두 필요합니다. KEY1 또는 KEY2를 사용할 수 있습니다. 항상 두 개의 키를 사용하면 서비스 중단 없이 키를 안전하게 회전하고 다시 생성할 수 있습니다.

엔드포인트 및 액세스 키 위치가 빨간색 원으로 표시된 Azure Portal의 OpenAI 리소스에 대한 개요 UI 스크린샷.

환경 변수

setx AZURE_OPENAI_API_KEY "REPLACE_WITH_YOUR_KEY_VALUE_HERE" 
setx AZURE_OPENAI_ENDPOINT "REPLACE_WITH_YOUR_ENDPOINT_HERE" 

환경 변수를 설정한 후 환경 변수에 액세스하려면 Jupyter Notebooks 또는 사용 중인 IDE를 닫았다가 다시 열어야 할 수 있습니다. Jupyter Notebooks를 사용하는 것이 강력히 권장되지만, 어떤 이유로든 할 수 없는 경우 코드 블록의 끝에서 자주 수행되는 것처럼 dataframe_name을 직접 호출하는 대신 print(dataframe_name)을 사용하여 pandas 데이터 프레임을 반환하는 모든 코드를 수정해야 합니다.

기본 설정하는 Python IDE에서 다음 코드를 실행합니다.

라이브러리 가져오기

import os
import re
import requests
import sys
from num2words import num2words
import os
import pandas as pd
import numpy as np
import tiktoken
from openai import AzureOpenAI

이제 csv 파일을 읽고 Pandas DataFrame을 만들어야 합니다. 초기 DataFrame이 만들어진 후 df를 실행하여 테이블의 콘텐츠를 볼 수 있습니다.

df=pd.read_csv(os.path.join(os.getcwd(),'bill_sum_data.csv')) # This assumes that you have placed the bill_sum_data.csv in the same directory you are running Jupyter Notebooks
df

출력:

csv 파일의 초기 DataFrame 테이블 결과 스크린샷.

초기 테이블에는 필요한 것보다 더 많은 열이 있습니다. text, summarytitle에 대한 열만 포함하는 df_bills라는 더 작은 새 DataFrame을 만듭니다.

df_bills = df[['text', 'summary', 'title']]
df_bills

출력:

텍스트, 요약 및 제목 열만 표시된 더 작은 DataFrame 테이블 결과의 스크린샷.

다음으로 불필요한 공백을 제거하고 문장 부호를 정리하여 토큰화를 위한 데이터를 준비하여 간단한 데이터 정리를 수행합니다.

pd.options.mode.chained_assignment = None #https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#evaluation-order-matters

# s is input text
def normalize_text(s, sep_token = " \n "):
    s = re.sub(r'\s+',  ' ', s).strip()
    s = re.sub(r". ,","",s)
    # remove all instances of multiple spaces
    s = s.replace("..",".")
    s = s.replace(". .",".")
    s = s.replace("\n", "")
    s = s.strip()
    
    return s

df_bills['text']= df_bills["text"].apply(lambda x : normalize_text(x))

이제 토큰 제한(8192 토큰)에 비해 너무 긴 청구서를 제거해야 합니다.

tokenizer = tiktoken.get_encoding("cl100k_base")
df_bills['n_tokens'] = df_bills["text"].apply(lambda x: len(tokenizer.encode(x)))
df_bills = df_bills[df_bills.n_tokens<8192]
len(df_bills)
20

참고 항목

이 경우 모든 청구서는 포함 모델 입력 토큰 한도에 속하지만 위의 기술을 사용하여 포함 실패를 유발할 수 있는 항목을 제거할 수 있습니다. 포함 제한을 초과하는 콘텐츠에 직면하면 콘텐츠를 더 작은 조각으로 청크한 다음 한 번에 하나씩 포함할 수 있습니다.

다시 한 번 df_bills를 검토합니다.

df_bills

출력:

n_tokens라는 새 열이 있는 DataFrame의 스크린샷.

n_tokens 열과 텍스트가 궁극적으로 토큰화되는 방식을 조금 더 이해하려면 다음 코드를 실행하는 것이 도움이 될 수 있습니다.

sample_encode = tokenizer.encode(df_bills.text[0]) 
decode = tokenizer.decode_tokens_bytes(sample_encode)
decode

문서의 경우 의도적으로 출력을 자르지만 환경에서 이 명령을 실행하면 청크로 토큰화된 인덱스 0의 전체 텍스트가 반환됩니다. 어떤 경우에는 전체 단어가 단일 토큰으로 표시되는 반면 다른 경우에는 단어의 일부가 여러 토큰으로 분할되는 것을 볼 수 있습니다.

[b'SECTION',
 b' ',
 b'1',
 b'.',
 b' SHORT',
 b' TITLE',
 b'.',
 b' This',
 b' Act',
 b' may',
 b' be',
 b' cited',
 b' as',
 b' the',
 b' ``',
 b'National',
 b' Science',
 b' Education',
 b' Tax',
 b' In',
 b'cent',
 b'ive',
 b' for',
 b' Businesses',
 b' Act',
 b' of',
 b' ',
 b'200',
 b'7',
 b"''.",
 b' SEC',
 b'.',
 b' ',
 b'2',
 b'.',
 b' C',
 b'RED',
 b'ITS',
 b' FOR',
 b' CERT',
 b'AIN',
 b' CONTRIBUT',
 b'IONS',
 b' BEN',
 b'EF',
 b'IT',
 b'ING',
 b' SC',

그런 다음 decode 변수의 길이를 확인하면 n_tokens 열의 첫 번째 숫자와 일치함을 알 수 있습니다.

len(decode)
1466

이제 토큰화가 작동하는 방식에 대해 더 많이 이해했으므로 포함으로 넘어갈 수 있습니다. 아직 문서를 실제로 토큰화하지 않았다는 점에 유의하는 것이 중요합니다. n_tokens 열은 단순히 토큰화 및 포함을 위해 모델에 전달하는 데이터가 입력 토큰 제한인 8,192를 초과하지 않도록 하는 방법입니다. 포함 모델에 문서를 전달하면 문서를 위의 예와 유사한 토큰(반드시 동일하지는 않음)으로 나눈 다음 토큰을 벡터 검색을 통해 액세스할 수 있는 일련의 부동 소수점 숫자로 변환합니다. 이러한 임베딩은 로컬로 저장하거나 Azure 데이터베이스에 저장하여 벡터 검색을 지원할 수 있습니다. 결과적으로 각 청구서에는 DataFrame의 오른쪽에 있는 새 ada_v2 열에 해당하는 자체 포함 벡터가 포함됩니다.

아래 예제에서는 포함하려는 모든 항목당 한 번씩 포함 모델을 호출합니다. 큰 포함 프로젝트로 작업할 때 한 번에 하나의 입력이 아닌 포함할 입력의 배열을 모델에 전달할 수도 있습니다. 모델에 입력의 배열을 전달하면 포함 엔드포인트에 대한 호출당 최대 입력 항목 수는 2048입니다.

client = AzureOpenAI(
  api_key = os.getenv("AZURE_OPENAI_API_KEY"),  
  api_version = "2024-02-01",
  azure_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT")
)

def generate_embeddings(text, model="text-embedding-ada-002"): # model = "deployment_name"
    return client.embeddings.create(input = [text], model=model).data[0].embedding

df_bills['ada_v2'] = df_bills["text"].apply(lambda x : generate_embeddings (x, model = 'text-embedding-ada-002')) # model should be set to the deployment name you chose when you deployed the text-embedding-ada-002 (Version 2) model
df_bills

출력:

df_bills 명령의 형식화된 결과 스크린샷.

아래의 검색 코드 블록을 실행할 때 동일한 text-embedding-ada-002(버전 2) 모델과 함께 "케이블 회사 세금 수익에 대한 정보를 얻을 수 있나요?" 검색 쿼리를 포함합니다. 다음으로 코사인 유사성으로 순위가 매겨진 쿼리에서 새로 포함된 텍스트에 삽입된 가장 가까운 청구서를 찾습니다.

def cosine_similarity(a, b):
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

def get_embedding(text, model="text-embedding-ada-002"): # model = "deployment_name"
    return client.embeddings.create(input = [text], model=model).data[0].embedding

def search_docs(df, user_query, top_n=4, to_print=True):
    embedding = get_embedding(
        user_query,
        model="text-embedding-ada-002" # model should be set to the deployment name you chose when you deployed the text-embedding-ada-002 (Version 2) model
    )
    df["similarities"] = df.ada_v2.apply(lambda x: cosine_similarity(x, embedding))

    res = (
        df.sort_values("similarities", ascending=False)
        .head(top_n)
    )
    if to_print:
        display(res)
    return res


res = search_docs(df_bills, "Can I get information on cable company tax revenue?", top_n=4)

출력:

검색 쿼리가 실행된 후 서식이 지정된 res 결과의 스크린샷.

마지막으로 전체 기술 자료에 대한 사용자 쿼리를 기반으로 문서 검색의 최상위 결과를 표시합니다. 이는 "1993년 납세자의 조회권법"의 최상위 결과를 반환합니다. 이 문서는 쿼리와 문서 간의 코사인 유사성 점수가 0.76입니다.

res["summary"][9]
"Taxpayer's Right to View Act of 1993 - Amends the Communications Act of 1934 to prohibit a cable operator from assessing separate charges for any video programming of a sporting, theatrical, or other entertainment event if that event is performed at a facility constructed, renovated, or maintained with tax revenues or by an organization that receives public financial support. Authorizes the Federal Communications Commission and local franchising authorities to make determinations concerning the applicability of such prohibition. Sets forth conditions under which a facility is considered to have been constructed, maintained, or renovated with tax revenues. Considers events performed by nonprofit or public organizations that receive tax subsidies to be subject to this Act if the event is sponsored by, or includes the participation of a team that is part of, a tax exempt organization."

필수 조건

  • Azure 구독 - 체험 구독 만들기

  • 원하는 Azure 구독의 Azure OpenAI에 대한 액세스 권한.

    현재 이 서비스에 대한 액세스 권한은 애플리케이션에서만 부여됩니다. https://aka.ms/oai/access에서 양식을 작성하여 Azure OpenAI에 대한 액세스를 신청할 수 있습니다. 문제가 있는 경우 이 리포지토리에서 문제를 엽니다.

  • text-embedding-ada-002(버전 2) 모델이 배포된 Azure OpenAI 리소스.

    이 모델은 현재 특정 지역에서만 사용할 수 있습니다. 리소스가 없는 경우 만들기 프로세스는 리소스 배포 가이드에 설명되어 있습니다.

  • PowerShell 7.4

참고 항목

이 자습서의 많은 예제에서는 이전 단계의 변수를 다시 사용합니다. 동일한 터미널 세션을 계속 열어 두세요. 터미널을 닫는 바람에 이전 단계에서 설정한 변수가 손실된 경우 처음부터 다시 시작해야 합니다.

키 및 엔드포인트 검색

Azure OpenAI에 대해 성공적으로 호출하려면 엔드포인트가 필요합니다.

변수 이름
ENDPOINT 이 값은 Azure Portal에서 리소스를 검사할 때 키 및 엔드포인트 섹션에서 찾을 수 있습니다. 또는 Azure OpenAI Studio>플레이그라운드>코드 보기에서 값을 찾을 수 있습니다. 예제 엔드포인트는 https://docs-test-001.openai.azure.com/입니다.
API-KEY 이 값은 Azure Portal에서 리소스를 검사할 때 키 및 엔드포인트 섹션에서 찾을 수 있습니다. KEY1 또는 KEY2를 사용할 수 있습니다.

Azure Portal에서 해당 리소스로 이동합니다. 키 및 엔드포인트 섹션은 리소스 관리 섹션에서 찾을 수 있습니다. 엔드포인트 및 액세스 키를 복사합니다. API 호출을 인증하는 데 모두 필요합니다. KEY1 또는 KEY2를 사용할 수 있습니다. 항상 두 개의 키를 사용하면 서비스 중단 없이 키를 안전하게 회전하고 다시 생성할 수 있습니다.

엔드포인트 및 액세스 키 위치가 빨간색 원으로 표시된 Azure Portal의 OpenAI 리소스에 대한 개요 UI 스크린샷.

키 및 엔드포인트에 대한 영구 환경 변수를 만들고 할당합니다.

환경 변수

setx AZURE_OPENAI_API_KEY "REPLACE_WITH_YOUR_KEY_VALUE_HERE" 
setx AZURE_OPENAI_ENDPOINT "REPLACE_WITH_YOUR_ENDPOINT_HERE" 

이 자습서에서는 잘 알려진 안전한 샘플 데이터 세트로 PowerShell 7.4 참조 설명서를 사용합니다. 또는 Microsoft Research 도구 샘플 데이터 세트를 탐색할 수 있습니다.

프로젝트를 저장할 폴더를 만듭니다. 위치를 프로젝트 폴더로 설정합니다. Invoke-WebRequest 명령을 사용하여 로컬 컴퓨터에 데이터 세트를 다운로드한 다음, 보관 계층을 확장합니다. 마지막으로 PowerShell 버전 7.4에 대한 참조 정보가 포함된 하위 폴더로 위치를 설정합니다.

New-Item '<FILE-PATH-TO-YOUR-PROJECT>' -Type Directory
Set-Location '<FILE-PATH-TO-YOUR-PROJECT>'

$DocsUri = 'https://github.com/MicrosoftDocs/PowerShell-Docs/archive/refs/heads/main.zip'
Invoke-WebRequest $DocsUri -OutFile './PSDocs.zip'

Expand-Archive './PSDocs.zip'
Set-Location './PSDocs/PowerShell-Docs-main/reference/7.4/'

이 자습서에서는 많은 양의 데이터를 사용하고 있으므로 효율적인 성능을 위해 .NET 데이터 테이블 개체를 사용합니다. 데이터 테이블에는 제목, 콘텐츠, 준비, uri, 파일, 벡터 열이 있습니다. 제목 열은 기본 키입니다.

다음 단계에서는 각 markdown 파일의 콘텐츠를 데이터 테이블에 로드합니다. 또한 PowerShell -match 연산자를 사용하여 알려진 텍스트 줄 title:online version:을 캡처하여 고유 열에 저장합니다. 일부 파일에는 텍스트의 메타데이터 줄이 포함되지 않지만 자세한 참조 문서가 아닌 개요 페이지이기 때문에 데이터 파일에서 제외합니다.

# make sure your location is the project subfolder

$DataTable = New-Object System.Data.DataTable

'title', 'content', 'prep', 'uri', 'file', 'vectors' | ForEach-Object {
    $DataTable.Columns.Add($_)
} | Out-Null
$DataTable.PrimaryKey = $DataTable.Columns['title']

$md = Get-ChildItem -Path . -Include *.md -Recurse

$md | ForEach-Object {
    $file       = $_.FullName
    $content    = Get-Content $file
    $title      = $content | Where-Object { $_ -match 'title: ' }
    $uri        = $content | Where-Object { $_ -match 'online version: ' }
    if ($title -and $uri) {
        $row                = $DataTable.NewRow()
        $row.title          = $title.ToString().Replace('title: ', '')
        $row.content        = $content | Out-String
        $row.prep           = '' # use later in the tutorial
        $row.uri            = $uri.ToString().Replace('online version: ', '')
        $row.file           = $file
        $row.vectors        = '' # use later in the tutorial
        $Datatable.rows.add($row)
    }
}

out-gridview 명령(Cloud Shell에서는 사용할 수 없음)을 사용하여 데이터를 봅니다.

$Datatable | out-gridview

출력

초기 DataTable 결과의 스크린샷.

그런 다음 추가 문자, 빈 공백, 기타 문서 표기법을 제거하여 데이터 정리를 수행하고 토큰화를 위한 데이터를 준비합니다. 샘플 함수 Invoke-DocPrep 은 PowerShell -replace 연산자를 사용하여 콘텐츠에서 제거하려는 문자 목록을 반복하는 방법을 보여 줍니다.

# sample demonstrates how to use `-replace` to remove characters from text content
function Invoke-DocPrep {
param(
    [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
    [string]$content
)
    # tab, line breaks, empty space
    $replace = @('\t','\r\n','\n','\r')
    # non-UTF8 characters
    $replace += @('[^\x00-\x7F]')
    # html
    $replace += @('<table>','</table>','<tr>','</tr>','<td>','</td>')
    $replace += @('<ul>','</ul>','<li>','</li>')
    $replace += @('<p>','</p>','<br>')
    # docs
    $replace += @('\*\*IMPORTANT:\*\*','\*\*NOTE:\*\*')
    $replace += @('<!','no-loc ','text=')
    $replace += @('<--','-->','---','--',':::')
    # markdown
    $replace += @('###','##','#','```')
    $replace | ForEach-Object {
        $content = $content -replace $_, ' ' -replace '  ',' '
    }
    return $content
}

Invoke-DocPrep 함수를 만든 후 ForEach-Object 명령을 사용하여 데이터 세트의 모든 행에 대해 준비된 콘텐츠를 준비 열에 저장합니다. 나중에 검색하려는 경우에 원래 서식을 사용할 수 있도록 새 열을 사용합니다.

$Datatable.rows | ForEach-Object { $_.prep = Invoke-DocPrep $_.content }

데이터 테이블을 다시 보고 변경 내용을 확인합니다.

$Datatable | out-gridview

문서를 포함 모델에 전달하면 모델은 문서를 토큰으로 인코딩한 다음 코사인 유사도 검색에 사용할 일련의 부동 소수점 숫자를 반환합니다. 이러한 포함은 로컬로 또는 Azure AI 검색의 벡터 검색 같은 서비스에 저장할 수 있습니다. 각 문서에는 새 벡터 열에 해당하는 자체 포함 벡터가 있습니다.

다음 예제는 데이터 테이블의 각 행을 반복하고, 전처리된 콘텐츠의 벡터를 검색하여 벡터 열에 저장합니다. OpenAI 서비스는 빈번한 요청을 제한하므로 이 예제에는 설명서가 제안하는 대로 지수 백오프가 포함됩니다.

스크립트가 완료되면 각 행에는 각 문서마다 쉼표로 구분된 1536개의 벡터 목록이 있어야 합니다. 오류가 발생하고 상태 코드가 400인 경우 문제 해결을 위해 파일 경로, 제목, 오류 코드가 $errorDocs라는 변수에 추가됩니다. 가장 일반적인 오류는 토큰 개수가 모델의 프롬프트 한도를 초과하는 경우에 발생합니다.

# Azure OpenAI metadata variables
$openai = @{
    api_key     = $Env:AZURE_OPENAI_API_KEY 
    api_base    = $Env:AZURE_OPENAI_ENDPOINT # should look like 'https://<YOUR_RESOURCE_NAME>.openai.azure.com/'
    api_version = '2024-02-01' # may change in the future
    name        = $Env:AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT # custom name you chose for your deployment
}

$headers = [ordered]@{
    'api-key' = $openai.api_key
}

$url = "$($openai.api_base)/openai/deployments/$($openai.name)/embeddings?api-version=$($openai.api_version)"

$Datatable | ForEach-Object {
    $doc = $_

    $body = [ordered]@{
        input = $doc.prep
    } | ConvertTo-Json

    $retryCount = 0
    $maxRetries = 10
    $delay      = 1
    $docErrors = @()

    do {
        try {
            $params = @{
                Uri         = $url
                Headers     = $headers
                Body        = $body
                Method      = 'Post'
                ContentType = 'application/json'
            }
            $response = Invoke-RestMethod @params
            $Datatable.rows.find($doc.title).vectors = $response.data.embedding -join ','
            break
        } catch {
            if ($_.Exception.Response.StatusCode -eq 429) {
                $retryCount++
                [int]$retryAfter = $_.Exception.Response.Headers |
                    Where-Object key -eq 'Retry-After' |
                    Select-Object -ExpandProperty Value

                # Use delay from error header
                if ($delay -lt $retryAfter) { $delay = $retryAfter++ }
                Start-Sleep -Seconds $delay
                # Exponential back-off
                $delay = [math]::min($delay * 1.5, 300)
            } elseif ($_.Exception.Response.StatusCode -eq 400) {
                if ($docErrors.file -notcontains $doc.file) {
                    $docErrors += [ordered]@{
                        error   = $_.exception.ErrorDetails.Message | ForEach-Object error | ForEach-Object message
                        file    = $doc.file
                        title   = $doc.title
                    }
                }
            } else {
                throw
            }
        }
    } while ($retryCount -lt $maxRetries)
}
if (0 -lt $docErrors.count) {
    Write-Host "$($docErrors.count) documents encountered known errors such as too many tokens.`nReview the `$docErrors variable for details."
}

이제 PowerShell 7.4 참조 문서의 로컬 메모리 내 데이터베이스 테이블이 있습니다.

검색 문자열에 따라서는 PowerShell이 각 문서를 유사도 기준으로 순위를 지정할 수 있도록 다른 벡터 집합을 계산해야 합니다.

다음 예제에서는 검색 문자열 get a list of running processes에 대해 벡터가 검색됩니다.

$searchText = "get a list of running processes"

$body = [ordered]@{
    input = $searchText
} | ConvertTo-Json

$url = "$($openai.api_base)/openai/deployments/$($openai.name)/embeddings?api-version=$($openai.api_version)"

$params = @{
    Uri         = $url
    Headers     = $headers
    Body        = $body
    Method      = 'Post'
    ContentType = 'application/json'
}
$response = Invoke-RestMethod @params
$searchVectors = $response.data.embedding -join ','

마지막으로, Lee Holmes가 작성한 예제 스크립트 Measure-VectorSimilarity의 예제를 차용하는 다음 샘플 함수는 코사인 유사도 계산을 수행한 다음, 데이터 테이블에서 각 행의 순위를 지정합니다.

# Sample function to calculate cosine similarity
function Get-CosineSimilarity ([float[]]$vector1, [float[]]$vector2) {
    $dot = 0
    $mag1 = 0
    $mag2 = 0

    $allkeys = 0..($vector1.Length-1)

    foreach ($key in $allkeys) {
        $dot  += $vector1[$key]  * $vector2[$key]
        $mag1 += ($vector1[$key] * $vector1[$key])
        $mag2 += ($vector2[$key] * $vector2[$key])
    }

    $mag1 = [Math]::Sqrt($mag1)
    $mag2 = [Math]::Sqrt($mag2)

    return [Math]::Round($dot / ($mag1 * $mag2), 3)
}

다음 예제의 명령은 $Datatable의 모든 행을 반복하고 검색 문자열과의 코사인 유사도를 계산합니다. 결과가 정렬되고 상위 3개 결과는 $topThree라는 변수에 저장됩니다. 이 예제는 출력을 반환하지 않습니다.

# Calculate cosine similarity for each row and select the top 3
$topThree = $Datatable | ForEach-Object {
    [PSCustomObject]@{
        title = $_.title
        similarity = Get-CosineSimilarity $_.vectors.split(',') $searchVectors.split(',')
    }
} | Sort-Object -property similarity -descending | Select-Object -First 3 | ForEach-Object {
    $title = $_.title
    $Datatable | Where-Object { $_.title -eq $title }
}

GridView에서 제목url 속성만 사용하여 $topThree 변수의 출력을 검토합니다.

$topThree | Select "title", "uri" | Out-GridView

출력:

검색 쿼리가 완료된 후 형식이 지정된 결과의 스크린샷.

$topThree 변수에는 데이터 테이블에 있는 행의 모든 정보가 포함됩니다. 예를 들어 콘텐츠 속성에는 원본 문서 형식이 포함됩니다. [0]을 사용하여 배열의 첫 번째 항목에 인덱싱합니다.

$topThree[0].content

전체 문서를 봅니다(이 페이지의 출력 코드 조각에서는 잘림).

---
external help file: Microsoft.PowerShell.Commands.Management.dll-Help.xml
Locale: en-US
Module Name: Microsoft.PowerShell.Management
ms.date: 07/03/2023
online version: https://learn.microsoft.com/powershell/module/microsoft.powershell.management/get-process?view=powershell-7.4&WT.mc_id=ps-gethelp
schema: 2.0.0
title: Get-Process
---

# Get-Process

## SYNOPSIS
Gets the processes that are running on the local computer.

## SYNTAX

### Name (Default)

Get-Process [[-Name] <String[]>] [-Module] [-FileVersionInfo] [<CommonParameters>]
# truncated example

마지막으로 데이터 세트를 쿼리해야 할 때마다 포함을 다시 생성하는 대신 디스크에 데이터를 저장하고 나중에 재현할 수 있습니다. 다음 예제에서 DataTable 개체 형식의 WriteXML()ReadXML() 메서드는 프로세스를 간소화합니다. XML 파일의 스키마를 사용하려면 데이터 테이블에 TableName이 있어야 합니다.

<YOUR-FULL-FILE-PATH>를 XML 파일을 쓰고 읽으려는 전체 경로로 바꿉니다. 경로는 .xml로 끝나야 합니다.

# Set DataTable name
$Datatable.TableName = "MyDataTable"

# Writing DataTable to XML
$Datatable.WriteXml("<YOUR-FULL-FILE-PATH>", [System.Data.XmlWriteMode]::WriteSchema)

# Reading XML back to DataTable
$newDatatable = New-Object System.Data.DataTable
$newDatatable.ReadXml("<YOUR-FULL-FILE-PATH>")

데이터를 다시 사용할 때 각 새 검색 문자열의 벡터를 가져와야 합니다(전체 데이터 테이블이 아니라). 학습 연습에서는 PowerShell 스크립트를 만들어 검색 문자열을 매개 변수로 사용하여 Invoke-RestMethod 명령을 자동화해 보세요.

이 방식을 사용하면 기술 자료의 문서 전체에서 포함을 검색 메커니즘으로 사용할 수 있습니다. 그런 다음 사용자는 상위 검색 결과를 가져와 다운스트림 작업에 사용할 수 있으며 이로 인해 초기 쿼리가 표시됩니다.

리소스 정리

이 자습서를 완료하기 위해서 Azure OpenAI 리소스만 만들었고 Azure OpenAI 리소스를 정리하고 제거하려는 경우 배포된 모델을 삭제한 다음 테스트 리소스 전용인 경우 리소스 또는 연결된 리소스 그룹을 삭제해야 합니다. 리소스 그룹을 삭제하면 해당 리소스 그룹에 연결된 다른 모든 리소스가 함께 삭제됩니다.

다음 단계

Azure OpenAI의 모델에 대해 자세히 알아봅니다.