티스토리 뷰

개요

  • LLMRAG을 이용한 커스텀 챗봇 개발은 현재 전지구에서 가장 핫한 주제 중에 하나이다. 이번 글에서는 Azure 인프라에서 안전하게 격리된 GPT-4o모델을 셋업하고, 역시 안전하게 격리된 Azure AI Search를 연동하여 LangChain4j 라이브러리로 커스텀 챗봇을 제작하는 방법을 정리했다.

순서

  • Azure OpenAI Instance 생성
  • Azure OpenAI Deployment 생성
  • Azure AI Search Service 생성
  • LangChain4j 소스 코드를 Azure AI Search에 저장
  • LangChain4j 코딩을 도와주는 챗봇 제작

Azure OpenAI Instance 생성

  • OpenAI의 다양한 모델을 내 인프라에 설치하려면 선호하는 리전에 OpenAI Instance를 생성해야 한다.
Microsoft Azure Portal
→ [Azure OpenAI] 클릭
→ [Create Azure OpenAI] 클릭

# [1] Basics
# Project Details
→ Subscription: {your-subscription} 선택
→ Resource groupo: [Create new] 클릭 → Name: {your-resource-group} (입력) → [OK] 클릭
# Instance Details
→ Region: {your-region} 선택
→ name: {your-instance-name} (입력)
→ Pricing tier: {your-pricing-tier} 선택
→ [Next] 클릭

# [2] Network
→ Type: [All networks, including the internet, can access this resource.] 선택
→ [Next] 클릭
→ [Next] 클릭

# [4] Review + submit
→ [Create] 클릭

Azure OpenAI Deployment 생성: GPT-4o

  • 질문을 전송할 LLM으로 OpenAI의 최신 대화형 멀티 모달 모델인 GPT-4o를 생성한다.
Microsoft Azure Portal
→ [Azure OpenAI] 클릭
→ {your-instance} 클릭
→ [Model deployments] 클릭
→ [Manage Deployments] 클릭

# Azure AI Studio
→ [Create new deployment] 클릭

# Deploy model
→ Select a model: [gpt-4o] 선택
→ Model version: [2024-05-13] 선택
→ Deployment type: [Standard] 선택
→ Deployment name: {your-deployment-name} (입력)
→ Tokens per Minute Rate Limit (thousands): 150K
→ Enable Dynamic Quota: [Enabled] 선택
→ [Create] 클릭

Azure OpenAI Deployment 생성: text-embedding-ada-002

  • 원본 텍스트의 벡터 변환을 담당할 Embedding Model을 생성한다. OpenAI의 최신의 텍스트 임베딩 모델인 text-embedding-ada-002를 선택했다.
Microsoft Azure Portal
→ [Azure OpenAI] 클릭
→ {your-instance} 클릭
→ [Model deployments] 클릭
→ [Manage Deployments] 클릭

# Azure AI Studio
→ [Create new deployment] 클릭

# Deploy model
→ Select a model: [text-embedding-ada-002] 선택
→ Model version: [2] 선택
→ Deployment type: [Standard] 선택
→ Deployment name: {your-deployment-name} (입력)
→ Tokens per Minute Rate Limit (thousands): 350K
→ Enable Dynamic Quota: [Enabled] 선택
→ [Deploy] 클릭

Azure AI Search 생성

  • RAG 저장소 및 검색엔진으로 사용할 Azure AI Search를 생성한다.
Microsoft Azure Portal
→ [Azure AI Search] 클릭
→ [Create Azure AI Search] 클릭

# [1] Basics
# Project Details
→ Subscription: {your-subscription} 선택
→ Resource Group: {your-resource-group} 선택

# Instance Details
→ Service name: {your-ai-search-name} (입력)
→ Location: {your-region} 클릭

→ Pricing tier: {your-pricing-tier} 선택
→ [Next] 클릭

→ [Create] 클릭
  • Pricing tier 선택시 주의할 점은 Hybrid Search에 해당하는 Semantic ranker 기능을 사용하려면 Basic 이상의 티어를 선택해야 한다.

Azure AI Search Index 생성

  • RAG 데이터를 저장할 인덱스를 생성한다.
Microsoft Azure Portal
→ [AI Search] 클릭
→ {your-ai-search} 클릭
→ [Add index] 클릭
→ [Add index (JSON)] 클릭
→ (아래 JSON 내용 붙여넣기)
→ [Save] 클릭
  • 아래는 LangChain4j가 사용하는 Azure AI SearchIndex 템플릿인데, 텍스트 원본을 저장하는 content 필드의 최대 크기를 32,766 bytes에서 도큐먼트가 허용하는 최대 크기(16 MB)까지 저장할 수 있도록 수정한 것이다.
{
  "name": "{your-ai-search-index-name}",
  "fields": [
    {
      "name": "id",
      "type": "Edm.String",
      "searchable": true,
      "filterable": true,
      "retrievable": true,
      "stored": true,
      "sortable": true,
      "facetable": true,
      "key": true,
      "indexAnalyzer": null,
      "searchAnalyzer": null,
      "analyzer": null,
      "normalizer": null,
      "dimensions": null,
      "vectorSearchProfile": null,
      "vectorEncoding": null,
      "synonymMaps": []
    },
    {
      "name": "content",
      "type": "Edm.String",
      "searchable": true,
      "filterable": false,
      "retrievable": true,
      "stored": true,
      "sortable": false,
      "facetable": false,
      "key": false,
      "indexAnalyzer": null,
      "searchAnalyzer": null,
      "analyzer": null,
      "normalizer": null,
      "dimensions": null,
      "vectorSearchProfile": null,
      "vectorEncoding": null,
      "synonymMaps": []
    },
    {
      "name": "content_vector",
      "type": "Collection(Edm.Single)",
      "searchable": true,
      "filterable": false,
      "retrievable": true,
      "stored": true,
      "sortable": false,
      "facetable": false,
      "key": false,
      "indexAnalyzer": null,
      "searchAnalyzer": null,
      "analyzer": null,
      "normalizer": null,
      "dimensions": 1536,
      "vectorSearchProfile": "vector-search-profile",
      "vectorEncoding": null,
      "synonymMaps": []
    },
    {
      "name": "metadata",
      "type": "Edm.ComplexType",
      "fields": [
        {
          "name": "source",
          "type": "Edm.String",
          "searchable": true,
          "filterable": true,
          "retrievable": true,
          "stored": true,
          "sortable": true,
          "facetable": true,
          "key": false,
          "indexAnalyzer": null,
          "searchAnalyzer": null,
          "analyzer": null,
          "normalizer": null,
          "dimensions": null,
          "vectorSearchProfile": null,
          "vectorEncoding": null,
          "synonymMaps": []
        },
        {
          "name": "attributes",
          "type": "Collection(Edm.ComplexType)",
          "fields": [
            {
              "name": "key",
              "type": "Edm.String",
              "searchable": true,
              "filterable": true,
              "retrievable": true,
              "stored": true,
              "sortable": false,
              "facetable": true,
              "key": false,
              "indexAnalyzer": null,
              "searchAnalyzer": null,
              "analyzer": null,
              "normalizer": null,
              "dimensions": null,
              "vectorSearchProfile": null,
              "vectorEncoding": null,
              "synonymMaps": []
            },
            {
              "name": "value",
              "type": "Edm.String",
              "searchable": true,
              "filterable": true,
              "retrievable": true,
              "stored": true,
              "sortable": false,
              "facetable": true,
              "key": false,
              "indexAnalyzer": null,
              "searchAnalyzer": null,
              "analyzer": null,
              "normalizer": null,
              "dimensions": null,
              "vectorSearchProfile": null,
              "vectorEncoding": null,
              "synonymMaps": []
            }
          ]
        }
      ]
    }
  ],
  "scoringProfiles": [],
  "corsOptions": null,
  "suggesters": [],
  "analyzers": [],
  "normalizers": [],
  "tokenizers": [],
  "tokenFilters": [],
  "charFilters": [],
  "encryptionKey": null,
  "similarity": {
    "@odata.type": "#Microsoft.Azure.Search.BM25Similarity",
    "k1": null,
    "b": null
  },
  "semantic": {
    "defaultConfiguration": "semantic-search-config",
    "configurations": [
      {
        "name": "semantic-search-config",
        "prioritizedFields": {
          "titleField": null,
          "prioritizedContentFields": [
            {
              "fieldName": "content"
            }
          ],
          "prioritizedKeywordsFields": [
            {
              "fieldName": "content"
            }
          ]
        }
      }
    ]
  },
  "vectorSearch": {
    "algorithms": [
      {
        "name": "vector-search-algorithm",
        "kind": "hnsw",
        "hnswParameters": {
          "metric": "cosine",
          "m": 4,
          "efConstruction": 400,
          "efSearch": 500
        },
        "exhaustiveKnnParameters": null
      }
    ],
    "profiles": [
      {
        "name": "vector-search-profile",
        "algorithm": "vector-search-algorithm",
        "vectorizer": null,
        "compression": null
      }
    ],
    "vectorizers": [],
    "compressions": []
  }
}

build.gradle.kts

  • 인프라 레벨에서의 준비는 이제 끝났다. 본격적인 코드 작업을 위해 프로젝트에 LangChain4j를 임포트한다.
val langChain4jVersion = "0.31.0"
dependencies {
    implementation("dev.langchain4j:langchain4j-core:$langChain4jVersion")
    implementation("dev.langchain4j:langchain4j-embeddings:$langChain4jVersion")
    implementation("dev.langchain4j:langchain4j-easy-rag:$langChain4jVersion")
    implementation("dev.langchain4j:langchain4j-open-ai:$langChain4jVersion")
    implementation("dev.langchain4j:langchain4j-azure-open-ai:$langChain4jVersion")
    implementation("dev.langchain4j:langchain4j-azure-ai-search:$langChain4jVersion")
}

Embedding, LLM Model 및 RAG 오브젝트 생성

  • LangChain4jAzure OpenAIAzure AI Search 생태계를 완벽 지원한다. 아래와 같이 쉽게 관련 오브젝트를 생성할 수 있다.
// Azure OpenAI text-embedding-ada-002의 EmbeddingModel 오브젝트 생성
val embeddingModel: EmbeddingModel = AzureOpenAiEmbeddingModel.builder()
    .endpoint("https://{your-azrue-open-ai-text-embedding-ada-002-deployment-name}.openai.azure.com")
    .serviceVersion("2023-05-15")
    .apiKey("{your-azure-openai-instance-api-key}")
    .deploymentName("{your-azrue-open-ai-text-embedding-ada-002-deployment-name}")
    .build()

// Document Splitter 오브젝트 생성
val documentSplitter = DocumentSplitters.recursive(
    8191,
    256,
    OpenAiTokenizer("gpt-4o-2024-05-13")
)

// Hybrid Search를 적용한 Azure AI Search의 ContentRetriever 오브젝트 생성
val contentRetriever: ContentRetriever = AzureAiSearchContentRetriever.builder()
    .endpoint("https://{your-azure-ai-search-service-name}.search.windows.net")
    .apiKey("{your-azure-ai-search-admin-key}")
    .dimensions(1536)
    .indexName("{your-azure-ai-search-index-name}")
    .createOrUpdateIndex(false)
    .embeddingModel(embeddingModel)
    .queryType(AzureAiSearchQueryType.HYBRID_WITH_RERANKING)
    .maxResults(50)
    .minScore(0.0)
    .build()

// Azure OpenAI GPT-4o의 ChatLanguageModel 오브젝트 생성
val chatLanguageModel = AzureOpenAiChatModel.builder()
    .endpoint("https://{your-azrue-open-ai-gpt-4o-deployment-name}.openai.azure.com")
    .apiKey("{your-azure-openai-instance-api-key}")
    .deploymentName("{your-azrue-open-ai-gpt-4o-deployment-name}")
    .serviceVersion("2024-02-01")
    .timeout(Duration.ofSeconds(360))
    .temperature(0.3)
    .topP(0.3)
    .build()

데이터를 Azure AI Search에 저장

  • Azure AI Search는 데이터 원본이 되는 Text와 이를 다차원 소수 배열로 변환한 Vector 데이터를 같이 하나의 Document로 저장할 수 있다. 이런 특징 덕분에 Semanctic SearchKeyword Search가 결합된 Hybrid Search가 가능하다.
  • 아래는 LangChain4j 라이브러리 소스 코드를 클로닝하여 Azure AI Search에 저장하는 예제이다. 먼저 프로젝트 루트 디렉토리에 관련 저장소를 클로닝한다.
# LangChain4j 라이브러리 저장소 클로닝
$ git clone https://github.com/langchain4j/langchain4j.git
$ git clone https://github.com/langchain4j/langchain4j-embeddings.git
$ git clone https://github.com/langchain4j/langchain4j-examples.git
  • 클로닝한 소스 코드 파일을 text-embedding-ada-002 임베딩 모델을 사용하여 Vector 데이터로 변환하여 원본 텍스트와 함께 Azure AI Search에 저장한다.
// 클로닝한 LangChain4j 라이브러리를 소스 코드 파일만 Azure AI Search에 저장
val embeddings: MutableList<Pair<Embedding, TextSegment>> = mutableListOf()
arrayOf("langchain4j", "langchain4j-embeddings", "langchain4j-examples").forEach { directory ->
    Files.walk(Paths.get(directory)).use { paths ->
        paths.filter {
            Files.isRegularFile(it) && arrayOf(
                "md",
                "xml",
                "gradle",
                "kts",
                "java",
                "kt"
            ).contains(it.fileName.toString().substringAfterLast('.', ""))
        }
            .forEach { path ->
                val document = Document.document(path.toFile().readText())
                val segments = documentSplitter.split(document)
                segments.forEach { segment ->
                    embeddings.add(Pair(embeddingModel.embed(segment).content(), segment))
                }
            }
    }
    try {
        contentRetriever.addAll(embeddings.map { it.first }, embeddings.map { it.second })
    } catch (ex: IndexBatchException) {
        ex.indexingResults.filter { !it.isSucceeded }.forEach {
            // Azure AI Search 저장 중에 발생한 에러 메시지 출력
            println(it.errorMessage)
        }
    }

LLM 모델에 RAG 연동 질의

  • Azure AI SearchLangChain4j 관련 소스 코드가 저장되었으므로 이제 RAG로 질의한 정보 목록을 LLM의 프롬프트에 포함하여 질의할 수 있다.
// [1] LLM 질문 작성
val question = "RAG에서 Semantic Search와 Keyword Search의 장점을 결합한 Hybrid Search에 대해서 자세히 설명해줘. 그리고 LangChain4j에서 Azure AI Search에 데이터를 저장하고 질의하는 예제를 작성해줘."

// [2] Azure AI Search로부터 Re-ranking 적용된 Hybrid Search 검색 결과 획득
val contents: List<Content> = contentRetriever.retrieve(Query.from(question))

// [3] LLM 답변 획득
val aiMessage = chatLanguageModel.generate(
    SystemMessage(
"""
당신은 LangChain4j 라이브러리 사용법 안내 및 예제 작성을 도와주는 코딩 어시스턴트야. 예제 작성은 Kotlin 언어로 제작해줘. 질문에 대한 데이터는 아래 Information을 참고해서 대답해줘.

Information: \\\
${contents.take(25).joinToString("\n\n") { it.textSegment().text() }}
\\\
""".trimIndent()
    ),
    UserMessage(question)
)

// [4] LLM 답변 출력
println(aiMessage.content().text())
  • 다른 방법으로 AiServices를 이용하여 제작할 수 있다.
// [1] LLM 질문 작성
val question = "{your-question}"

// [2] 커스텀 어시스턴트 생성
interface Assistant {
    fun ask(userMessage: String): String
}
val assistant = AiServices.builder(Assistant::class.java)
    .chatLanguageModel(chatLanguageModel)
    .contentRetriever(contentRetriever)
    .systemMessageProvider { "당신은 LangChain4j 라이브러리 사용법 안내 및 예제 작성을 도와주는 코딩 어시스턴트야. 예제 작성은 Kotlin 언어로 제작해줘." }
    .build()

// [3] LLM 답변 획득
val answer = assistant.ask(question)

// [4] LLM 답변 출력
println(assistant.ask(question))

참고 글

댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함