<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Software Engineer, Java, Spring Boot, JAX-RS REST API, OAuth 2.0, Microservice, DevOps</title>
    <link>https://jsonobject.tistory.com/</link>
    <description>미니멀리스트
Senior Software Engineer
Java, Spring Boot, JAX-RS
REST API, OAuth 2.0
Microservice, DevOps</description>
    <language>ko</language>
    <pubDate>Sat, 18 Apr 2026 19:55:50 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>지단로보트</managingEditor>
    <item>
      <title>Kotlin, Spring Boot를 이용하여 OpenAI 호환 커스텀 API 서버 제작하기</title>
      <link>https://jsonobject.tistory.com/641</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;00009-784136035.png&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqLtIv/btsKbHppxTo/UTyD6t17KDMhOdMtkZoTEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqLtIv/btsKbHppxTo/UTyD6t17KDMhOdMtkZoTEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqLtIv/btsKbHppxTo/UTyD6t17KDMhOdMtkZoTEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqLtIv%2FbtsKbHppxTo%2FUTyD6t17KDMhOdMtkZoTEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;624&quot; data-filename=&quot;00009-784136035.png&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;624&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2022년 11월 &lt;b&gt;OpenAI&lt;/b&gt;가 &lt;b&gt;ChatGPT&lt;/b&gt;를 세상에 공개한 후로 &lt;b&gt;OpenAI&lt;/b&gt;의 &lt;b&gt;LLM&lt;/b&gt;는 사실상의 표준으로 자리 잡았다. &lt;b&gt;LLM&lt;/b&gt; 연동을 지원하는 많은 오픈 소스 및 상업 솔루션이 &lt;b&gt;OpenAI&lt;/b&gt;의 &lt;b&gt;API&lt;/b&gt;와 동일하게 작동하는 &lt;b&gt;OpenAI Compatible API&lt;/b&gt;을 지원한다. 이 것의 의미는 많은 기업들이 자사의 내부 보안 환경 및 쓰임새에 맞는 독자적인 &lt;b&gt;OpenAI &lt;/b&gt;호환 서버를 구축하고 운영할 수 있다는 것이다.&lt;/li&gt;
&lt;li&gt;이번 글에서는 &lt;b&gt;LangChain4j&lt;/b&gt;와 &lt;b&gt;Azure OpenAI&lt;/b&gt;를 이용하여 &lt;b&gt;OpenAI &lt;/b&gt;호환 서버를 제작하는 방법을 정리했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;OpenAI Compatible Server 명세&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;OpenAI Compatible Server&lt;/b&gt;의 핵심은 &lt;b&gt;OpenAI Chat Completion API&lt;/b&gt;의 작동을 정확하게 모사하는 것이다. 서버는 아래와 같은 클라이언트의 요청을 받아 &lt;b&gt;LLM&lt;/b&gt;의 동작을 수행할 수 있어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;$ curl -X POST &quot;http://localhost:8080/v1/openai/chat/completions&quot; \
      -H &quot;Content-Type: application/json&quot; \
      -H &quot;Authorization: Bearer {YOUR_API_KEY}&quot; \
      -d '{
            &quot;model&quot;: &quot;gpt4-o&quot;,
            &quot;messages&quot;: [
              {
                &quot;role&quot;: &quot;user&quot;,
                &quot;content&quot;: &quot;Hello, how are you?&quot;
              }
            ],
            &quot;maxTokens&quot;: 4096,
            &quot;temperature&quot;: 0.1,
            &quot;stream&quot;: true
          }'&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스트리밍 응답의 경우 서버는 &lt;b&gt;Server-Sent Event&lt;/b&gt;를 이용하여 각 응답 &lt;b&gt;Chunk&lt;/b&gt;를 아래와 같이 클라이언트에 전송할 수 있어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
   &quot;id&quot;: &quot;unique-emitter-id&quot;,
   &quot;object&quot;: &quot;chat.completion.chunk&quot;,
   &quot;created&quot;: 1633024800,
   &quot;model&quot;: &quot;gpt4-o&quot;,
   &quot;choices&quot;: [
     {
       &quot;delta&quot;: {
         &quot;content&quot;: &quot;Hello&quot;
       }
     }
   ]
 }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스트리밍 응답이 완료되면 서버는 &lt;b&gt;Server-Sent Event&lt;/b&gt;를 이용하여 아래와 같이 완료 메시지를 클라이언트에 전송할 수 있어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[DONE]&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로젝트 생성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Spring Initializr&lt;/code&gt;를 로컬에 설치하고 아래와 같이 신규 프로젝트를 생성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;$ sdk install springboot
$ spring init --type gradle-project-kotlin --language kotlin --java-version 21 --dependencies=web openai-comp-demo
$ cd openai-comp-demo&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;build.gradle.kts&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로젝트 루트의 &lt;code&gt;build.gradle.kts&lt;/code&gt; 파일에 &lt;code&gt;LangChain4j&lt;/code&gt; 라이브러리 종속성을 아래와 같이 추가한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;val langChain4jVersion = &quot;0.35.0&quot;
dependencies {
    implementation(&quot;dev.langchain4j:langchain4j-core:$langChain4jVersion&quot;)
    implementation(&quot;dev.langchain4j:langchain4j-azure-open-ai:$langChain4jVersion&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Creating JsonConfig&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;REST API&lt;/b&gt;로부터의 응답을 &lt;b&gt;DTO&lt;/b&gt;로 변환할 &lt;code&gt;ObjectMapper&lt;/code&gt; 빈을 생성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Configuration
class JsonConfig {

    @Bean(&quot;objectMapper&quot;)
    @Primary
    fun objectMapper(): ObjectMapper {

        return Jackson2ObjectMapperBuilder
            .json()
            .serializationInclusion(JsonInclude.Include.ALWAYS)
            .failOnEmptyBeans(false)
            .failOnUnknownProperties(false)
            .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
            .modulesToInstall(kotlinModule(), JavaTimeModule())
            .build()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Creating OpenAiCompatibleChatCompletionDTO&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;OpenAi Compatible API&lt;/b&gt;를 준수하는 DTO를 아래와 같이 제작한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;data class OpenAiCompatibleChatMessage(
    val role: String,
    val content: String
)

data class OpenAiCompatibleChatCompletionRequest(
    val model: String = &quot;gpt4-o&quot;,
    val messages: List&amp;lt;OpenAiCompatibleChatMessage&amp;gt;,
    val maxTokens: Int = 4096,
    val temperature: Float = 0.1f,
    val stream: Boolean = false
)

data class OpenAiCompatibleChatCompletionResponse(
    val id: String,
    val `object`: String,
    val created: Long,
    val model: String,
    val choices: List&amp;lt;OpenAiCompatibleChoice&amp;gt;
)

data class OpenAiCompatibleChoice(
    val message: OpenAiCompatibleChatMessage
)

data class OpenAiCompatibleChatCompletionChunk(
    val id: String,
    val `object`: String,
    val created: Long,
    val model: String,
    val choices: List&amp;lt;OpenAiCompatibleChunkChoice&amp;gt;
)

data class OpenAiCompatibleChunkChoice(
    val delta: OpenAiCompatibleDelta
)

data class OpenAiCompatibleDelta(
    val content: String
)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Creating OpenAiCompatibleService&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스트리밍과 비스트리밍 방식을 모두 지원하는 &lt;b&gt;OpenAiCompatibleService&lt;/b&gt; 빈을 제작한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;import com.fasterxml.jackson.databind.ObjectMapper
import dev.langchain4j.data.message.AiMessage
import dev.langchain4j.data.message.UserMessage
import dev.langchain4j.model.StreamingResponseHandler
import dev.langchain4j.model.azure.AzureOpenAiChatModel
import dev.langchain4j.model.azure.AzureOpenAiStreamingChatModel
import dev.langchain4j.model.output.Response
import org.springframework.http.MediaType
import org.springframework.stereotype.Service
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter
import java.time.Instant
import java.util.*
import java.util.concurrent.ConcurrentHashMap

@Service
class OpenAiCompatibleService(
    private val objectMapper: ObjectMapper
) {
    private val emitters = ConcurrentHashMap&amp;lt;String, SseEmitter&amp;gt;()

    fun createChatCompletion(request: OpenAiCompatibleChatCompletionRequest): OpenAiCompatibleChatCompletionResponse {

        // LangChain4j에서 제공하는 모든 ChatLanguageModel 구현체를 사용 가능
        val chatLanguageModel = AzureOpenAiChatModel.builder()
            .apiKey(&quot;{your-azure-openai-api-key}&quot;)
            .endpoint(&quot;{your-azure-openai-endpoint}&quot;)
            .deploymentName(&quot;{your-azure-openai-deployment-name}&quot;)
            .temperature(request.temperature.toDouble())
            .maxTokens(request.maxTokens)
            .topP(0.3)
            .logRequestsAndResponses(true)
            .build()

        val messages = request.messages.map { UserMessage(it.content) }
        val response = chatLanguageModel.generate(messages)

        return OpenAiCompatibleChatCompletionResponse(
            id = UUID.randomUUID().toString(),
            `object` = &quot;chat.completion&quot;,
            created = Instant.now().epochSecond,
            model = request.model,
            choices = listOf(
                OpenAiCompatibleChoice(
                    OpenAiCompatibleChatMessage(
                        role = &quot;assistant&quot;,
                        content = response.content().text()
                    )
                )
            )
        )
    }

    fun createStreamingChatCompletion(request: OpenAiCompatibleChatCompletionRequest): SseEmitter {

        // LangChain4j에서 제공하는 모든 ChatLanguageModel 구현체를 사용 가능
        val streamingChatLanguageModel = AzureOpenAiStreamingChatModel.builder()
            .apiKey(&quot;{your-azure-openai-api-key}&quot;)
            .endpoint(&quot;{your-azure-openai-endpoint}&quot;)
            .deploymentName(&quot;{your-azure-openai-deployment-name}&quot;)
            .temperature(request.temperature.toDouble())
            .maxTokens(request.maxTokens)
            .topP(0.3)
            .logRequestsAndResponses(true)
            .build()

        val emitter = SseEmitter()
        val emitterId = UUID.randomUUID().toString()
        emitters[emitterId] = emitter

        val messages = request.messages.map { UserMessage(it.content) }

        streamingChatLanguageModel.generate(messages, object : StreamingResponseHandler&amp;lt;AiMessage&amp;gt; {
            override fun onNext(token: String) {
                val chunk = OpenAiCompatibleChatCompletionChunk(
                    id = emitterId,
                    `object` = &quot;chat.completion.chunk&quot;,
                    created = Instant.now().epochSecond,
                    model = request.model,
                    choices = listOf(OpenAiCompatibleChunkChoice(OpenAiCompatibleDelta(content = token)))
                )
                emitter.send(
                    SseEmitter.event().data(objectMapper.writeValueAsString(chunk), MediaType.APPLICATION_NDJSON)
                )
            }

            override fun onComplete(response: Response&amp;lt;AiMessage&amp;gt;) {
                emitter.send(SseEmitter.event().data(&quot;[DONE]&quot;))
                emitter.complete()
                emitters.remove(emitterId)
            }

            override fun onError(error: Throwable) {
                emitter.completeWithError(error)
                emitters.remove(emitterId)
            }
        })

        return emitter
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Creating OpenAiCompatibleController&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마지막으로 &lt;b&gt;OpenAiCompatibleController&lt;/b&gt; 빈을 제작한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping(&quot;/v1/openai&quot;)
class OpenAiCompatibleController(
    private val openAICompatibleService: OpenAiCompatibleService
) {
    @PostMapping(&quot;/chat/completions&quot;, produces = [MediaType.APPLICATION_JSON_VALUE])
    fun chatCompletions(
        @RequestHeader(&quot;Authorization&quot;) authHeader: String?,
        @RequestBody request: OpenAiCompatibleChatCompletionRequest
    ): Any {

        val apiKey = authHeader?.removePrefix(&quot;Bearer &quot;)
        // 획득한 API_KEY로 독자적인 커스텀 인증을 적용할 수 있다.

        return if (request.stream) {
            openAICompatibleService.createStreamingChatCompletion(request)
        } else {
            openAICompatibleService.createChatCompletion(request)
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;OpenAI compatible API 작동 테스트&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;i&gt;OpenAI Compatible Server*&lt;/i&gt;의 제작이 완료되었다. 제작한 서버를 실행하고, 대표적인 &lt;b&gt;AI&lt;/b&gt; 코딩 어시스턴트 도구인 &lt;b&gt;Aider&lt;/b&gt;에 환경변수를 지정하면 작동을 확인할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# 프로젝트 실행
$ ./gradlew bootRun

# Aider 환경 변수에 실행 중인 프로젝트의 API 지정
$ export OPENAI_API_BASE=http://localhost:8080/v1/openai/
$ export OPENAI_API_KEY={YOUR_API_KEY}

# Aider 실행
$ aider --model openai/gpt-4o
Aider v0.59.1
Main model: openai/gpt-4o with diff edit format
&amp;gt; Hello, how are you?

Hello! I'm doing well, thank you. How can I assist you with your project today? If you have any specific changes or questions, feel
free to let me know!&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고 글&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://towardsdatascience.com/how-to-build-an-openai-compatible-api-87c8edea2f06&quot;&gt;How to build an OpenAI-compatible API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>SW 개발</category>
      <author>지단로보트</author>
      <guid isPermaLink="true">https://jsonobject.tistory.com/641</guid>
      <comments>https://jsonobject.tistory.com/641#entry641comment</comments>
      <pubDate>Mon, 21 Oct 2024 01:14:37 +0900</pubDate>
    </item>
    <item>
      <title>FLUX, 로컬 환경에서 텍스트 프롬프트로 이미지 생성하기</title>
      <link>https://jsonobject.tistory.com/640</link>
      <description>&lt;h3&gt;FLUX 소개&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FLUX&lt;/code&gt;는 2024년 8월 출시된 새로운 &lt;strong&gt;text2img&lt;/strong&gt; 모델 제품군이다. 제작사인 &lt;code&gt;Black Forest Labs&lt;/code&gt;은 &lt;strong&gt;Stable Diffusion&lt;/strong&gt;으로 유명한 &lt;strong&gt;Stability AI&lt;/strong&gt; 출신들이 창업한 회사로 생성형 이미지 분야에서 굉장한 노하우를 가진 전문가 집단이다. &lt;strong&gt;FLUX&lt;/strong&gt;를 유명하게 만든 것은 생성된 이미지의 품질이다. 자체적으로 공개한 벤치마킹 결과 &lt;strong&gt;Midjourney-V6.0&lt;/strong&gt;와 &lt;strong&gt;SD3-Ultra&lt;/strong&gt;를 뛰어넘는 성과를 드러냈으며 실제로 커뮤니티의 반응도 굉장히 뜨겁다. &lt;a href=&quot;https://blackforestlabs.ai/announcing-black-forest-labs/&quot;&gt;[관련 링크]&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;이번 글에서는 오픈 소스 모델인 &lt;code&gt;FLUX.1 [dev]&lt;/code&gt;를 이용하여 로컬 환경에서 고품질의 생성형 이미지를 제작하는 방법을 정리했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;준비물&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Package Manager: &lt;strong&gt;Stability Matrix&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Package: &lt;strong&gt;Stable Diffusion WebUI Forge&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Model: &lt;strong&gt;FLUX.1 [dev]&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;VAE: &lt;strong&gt;ae.safetensors&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Text Encoder: &lt;strong&gt;ViT-L-14-TEXT-detail-improved-hiT-GmP-TE-only-HF.safetensors&lt;/strong&gt;, &lt;strong&gt;t5xxl_fp16.safetensors&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Upscaler: &lt;strong&gt;4xFFHQDAT.pth&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Stability Matrix 설치&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/LykosAI/StabilityMatrix&quot;&gt;이 링크&lt;/a&gt;에서 자신의 운영체제에 맞는 파일을 다운로드하여 로컬에 설치한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Stable Diffusion WebUI Forge 설치&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Stability Matrix&lt;/code&gt;을 실행하고 아래 순서로 &lt;code&gt;Stable Diffusion WebUI Forge&lt;/code&gt;을 설치한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;Stability Matrix 실행
→ [Packages] 클릭
→ [Add Package] 클릭
→ [Stable Diffusion WebUI Forge] 클릭
→ [Install] 클릭&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;FLUX.1 [dev] 모델 설치&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FLUX.1 [dev]&lt;/code&gt; 모델은 비상업적 용도에 한해 무료로 사용할 수 있는 오픈 소스 모델로 생성된 결과물은 상업적 용도로 사용할 수 있다. 최소 &lt;strong&gt;6GB VRAM&lt;/strong&gt;에서도 사용 가능 가능하도록 메모리 사용량과 실행 속도를 최적화한 &lt;strong&gt;NF4&lt;/strong&gt; 버전을 추천한다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://huggingface.co/lllyasviel/flux1-dev-bnb-nf4/tree/main&quot;&gt;이 링크&lt;/a&gt;에서 &lt;strong&gt;flux1-dev-bnb-nf4-v2.safetensors&lt;/strong&gt; 파일을 다운로드한 후 &lt;strong&gt;Stability Matrix&lt;/strong&gt; 설치 디렉토리 밑의 &lt;strong&gt;Data/Models/StableDiffusion&lt;/strong&gt; 디렉토리에 저장한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;VAE 설치&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://huggingface.co/black-forest-labs/FLUX.1-dev/tree/main&quot;&gt;이 링크&lt;/a&gt;에서 &lt;strong&gt;ae.safetensors&lt;/strong&gt; 파일을 다운로드 후 &lt;strong&gt;Stability Matrix&lt;/strong&gt; 설치 디렉토리 밑의 &lt;strong&gt;Data/Models/VAE&lt;/strong&gt; 디렉토리에 저장한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Text Encoder 설치&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://huggingface.co/zer0int/CLIP-GmP-ViT-L-14/tree/main&quot;&gt;이 링크&lt;/a&gt;에서 &lt;strong&gt;ViT-L-14-TEXT-detail-improved-hiT-GmP-TE-only-HF.safetensors&lt;/strong&gt; 파일을 다운로드 후 &lt;strong&gt;Stability Matrix&lt;/strong&gt; 설치 디렉토리 밑의 &lt;strong&gt;Data/Models/CLIP&lt;/strong&gt; 디렉토리에 저장한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Upscaler 설치&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://openmodeldb.info/models/4x-FFHQDAT&quot;&gt;이 링크&lt;/a&gt;에서 &lt;strong&gt;4xFFHQDAT.pth&lt;/strong&gt; 파일을 다운로드 후 &lt;strong&gt;Stability Matrix&lt;/strong&gt; 설치 디렉토리 밑의 &lt;strong&gt;Data/Models/ESRGAN&lt;/strong&gt; 디렉토리에 저장한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Stable Diffusion WebUI Forge 실행&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;이미지를 생성할 모든 준비가 완료되었다. 아래와 순서로 &lt;code&gt;Stable Diffusion WebUI Forge&lt;/code&gt;을 실행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;Stability Matrix 실행
→ [Packages] 클릭
→ [Stable Diffusion WebUI Forge] 클릭
→ [Lanunch] 클릭&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;브라우저에 웹 인터페이스가 실행되었으면, 최적의 이미지 생성을 위해 아래 설정을 적용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;Stable Diffusion WebUI Forge 웹 인터페이스
→ UI: [flux]
→ Checkpoint: [flux1-dev-bnb-nf4-v2.safetensors]
→ VAE / Text Encoder: [ae.safetensors], [ViT-L-14-TEXT-detail-improved-hiT-GmP-TE-only-HF.safetensor], [t5xxl_fp16.safetensors]
→ Diffusion in Low Bits: [Automatic (fp16 LoRA)]
→ Sampling method: [[Forge] Flux Realistic]
→ Schedule type: [Beta]
→ Sampling steps: 20
→ Hires. fix: [체크]
→ Upscaler: [4xFFHQDAT]
→ Denosising strength: 0.35
→ Width: 512
→ Height: 512
→ Distilled CFG Scale: 2
→ CFG Scale: 1
→ PerturbedAttentionGuidance Integrated: [Enabled] 체크 → Scale: 3&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;이제 프롬프트에 아래 예시를 입력하고 &lt;strong&gt;Generate&lt;/strong&gt; 버튼을 클릭하면 이미지가 생성된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;nukacola on the table, &amp;quot;nukacola&amp;quot;, fallout, closed shot, nuclear radioactive color, realistic&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d4TOkd/btsJIRrUIpX/nL1Cabs2zLxrLkggqcOIN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d4TOkd/btsJIRrUIpX/nL1Cabs2zLxrLkggqcOIN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d4TOkd/btsJIRrUIpX/nL1Cabs2zLxrLkggqcOIN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd4TOkd%2FbtsJIRrUIpX%2FnL1Cabs2zLxrLkggqcOIN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;1024&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3&gt;FLUX 사용 소감&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;위 설정으로 &lt;strong&gt;RTX 3080 10GB&lt;/strong&gt; 기준으로 수십장의 이미지를 테스트해봤다. 최대 3개의 &lt;strong&gt;LoRA&lt;/strong&gt;를 사용했고 512x768 해상도 기준 1분 45초 내외가 소요되었다. 512x512 또는 512x768에서의 결과물의 품질은 실사와 구분을 못할 정도로 훌륭하다. 하지만 &lt;strong&gt;FLUX&lt;/strong&gt;의 진짜 능력은 768x768 이상부터 발휘된다. 차원이 다른 디테일을 보여주는데 768x1152 해상도 기준 1시간 내외가 소요되어 생성 시간이 상당히 느려서 많은 인내심이 필요했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;결과물 이미지를 3D 애셋으로 변환하기&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;FLUX&lt;/strong&gt;로 생성한 2D 이미지를 3D로 생성하면 게임 개발, 3D 프린터 출력 등 다양한 용도로 활용할 수 있다. 관련 업계는 아직 기술이 성숙되지 않은 상황인데 중국의 &lt;strong&gt;Tripo&lt;/strong&gt;가 현재 가장 앞서 있다. 유료 모델인 &lt;code&gt;Tripo AI v2.0&lt;/code&gt;을 이용하면 &lt;strong&gt;FLUX&lt;/strong&gt;로 생성한 2D 이미지를 3D 애셋으로 손쉽게 변환할 수 있다. 생성된 3D 에셋을 &lt;strong&gt;GLB&lt;/strong&gt; 파일로 저장하면 &lt;strong&gt;Windows 11&lt;/strong&gt;에서 &lt;strong&gt;3D Viewer&lt;/strong&gt;로 확인할 수 있다. &lt;a href=&quot;https://www.tripo3d.ai/app&quot;&gt;[사이트 링크]&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;참고 글&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://blackforestlabs.ai/&quot;&gt;FLUX.1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://education.civitai.com/quickstart-guide-to-flux-1/&quot;&gt;Quickstart Guide to Flux.1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stable-diffusion-art.com/flux-forge/&quot;&gt;How to run Flux AI with low VRAM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://civitai.com/articles/7029&quot;&gt;Lazy FLUX.1d Starter Guide by Makina69&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>SW 개발</category>
      <author>지단로보트</author>
      <guid isPermaLink="true">https://jsonobject.tistory.com/640</guid>
      <comments>https://jsonobject.tistory.com/640#entry640comment</comments>
      <pubDate>Mon, 23 Sep 2024 23:36:27 +0900</pubDate>
    </item>
    <item>
      <title>AWS DNA 6기 Gen AI 프로그램 참가 및 수상 후기</title>
      <link>https://jsonobject.tistory.com/639</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4200&quot; data-origin-height=&quot;2800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Fle4z/btsIyty4NAv/q2Bfgspf1OgM7ngKHLecI1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fle4z/btsIyty4NAv/q2Bfgspf1OgM7ngKHLecI1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fle4z/btsIyty4NAv/q2Bfgspf1OgM7ngKHLecI1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFle4z%2FbtsIyty4NAv%2Fq2Bfgspf1OgM7ngKHLecI1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4200&quot; height=&quot;2800&quot; data-origin-width=&quot;4200&quot; data-origin-height=&quot;2800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;소개&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;AWS DNA&lt;/code&gt;는 기업이 마주하는 문제를 &lt;b&gt;AWS&lt;/b&gt;의 서비스를 활용하여 해결하는 과정을 체계적으로 경험할 수 있게 제작된 &lt;b&gt;AWS Korea&lt;/b&gt;의 트레이닝 및 네트워킹 프로그램이다.&lt;/li&gt;
&lt;li&gt;매 기수마다 프로그램에 참여하는 기업을 엄선하여 선정하고, 약 4주간의 교육, 나머지 2주간의 &lt;b&gt;Pre-POC&lt;/b&gt;를 통한 &lt;b&gt;MVP&lt;/b&gt; 제작, 마지막 발표로 이어지는 쉽지 않은 성격의 프로그램이다.&lt;/li&gt;
&lt;li&gt;2020년 1기를 시작으로, 이번 6기는 &lt;b&gt;Gen AI&lt;/b&gt;를 주제로 현재 재직 중인 &lt;b&gt;젠틀몬스터&lt;/b&gt; 소속으로 참가하게 되었고, 최종 발표에서 &lt;b&gt;Customer Obsession&lt;/b&gt; 부분에서 최고 점수를 받아 상을 수상했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참가 계기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;나는 올해 &lt;b&gt;젠틀몬스터&lt;/b&gt;에 입사하여 선행 기술을 연구하는 &lt;b&gt;기술전략팀&lt;/b&gt;의 일원으로 &lt;b&gt;Gen AI&lt;/b&gt; 관련 주제의 프로젝트를 담당하여 진행 중인 차에, &lt;b&gt;AWS Korea&lt;/b&gt;의 &lt;b&gt;Gen AI on AWS Immersion Day&lt;/b&gt; 교육에 참여하는 중에 당시 세션 발표자이자 우리 회사의 담당 &lt;b&gt;Solutions Architect&lt;/b&gt;인 최승원 님을 통해 &lt;b&gt;AWS DNA&lt;/b&gt; 프로그램을 소개 받고, 참가 희망 의사를 밝혀, 최종적으로 선정되어 우리 팀 전원이 참가하게 되었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;일정&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이번 &lt;b&gt;AWS DNA 6기&lt;/b&gt;는 아래 일정으로 진행되었다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2024-05-29, &lt;b&gt;Gen AI&lt;/b&gt; 개념 이해 및 프로젝트 구체화&lt;/li&gt;
&lt;li&gt;2024-06-05, &lt;b&gt;AWS&lt;/b&gt; 기반 &lt;b&gt;Gen AI&lt;/b&gt; 이론 및 실습 세션&lt;/li&gt;
&lt;li&gt;2024-06-12, &lt;b&gt;Bedrock with RAG&lt;/b&gt; 주요 기법, 사용 사례, &lt;b&gt;Knowledge Base&lt;/b&gt; 소개, &lt;b&gt;Bedrock with Agent&lt;/b&gt; 활용&lt;/li&gt;
&lt;li&gt;2024-06-19, &lt;b&gt;Bedrock Advanced&lt;/b&gt; 아키텍처, &lt;b&gt;Bedrock Production&lt;/b&gt; 적용 팁: &lt;b&gt;Bedrock Security&lt;/b&gt;, &lt;b&gt;Cost Optimization&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;2024-06-26, 팀별 &lt;b&gt;Pre-PoC&lt;/b&gt; 개발 및 오피스아워&lt;/li&gt;
&lt;li&gt;2024-07-03, 팀별 결과 발표 및 시상&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참가 회사&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1팀: 카카오-A (인프라기술조직)&lt;/li&gt;
&lt;li&gt;2팀: 카카오-B (AI플랫폼팀)&lt;/li&gt;
&lt;li&gt;3팀: 그라운드엑스&lt;/li&gt;
&lt;li&gt;4팀: 우아한형제들-A (추천서비스팀)&lt;/li&gt;
&lt;li&gt;5팀: 우아한형제들-B (배달시간예측서비스팀)&lt;/li&gt;
&lt;li&gt;6팀: 인터파크트리플&lt;/li&gt;
&lt;li&gt;7팀: 위대한상상&lt;/li&gt;
&lt;li&gt;8팀: 마이다스아이티&lt;/li&gt;
&lt;li&gt;9팀: 아임웹&lt;/li&gt;
&lt;li&gt;10팀: 컬리&lt;/li&gt;
&lt;li&gt;11팀: 리파인&lt;/li&gt;
&lt;li&gt;&lt;b&gt;12팀: 젠틀몬스터&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;13팀: 현대카드&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참가 주제&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;젠틀몬스터&lt;/b&gt;는 글로벌 하이엔드 패션 브랜드로서 오프라인에서의 다양하고 독특한 고객 경험으로 명성이 높다. 온라인에서도 최고의 고객 경험을 제공해야 하는 니즈를 발굴하게 되었고, &lt;b&gt;Gen AI&lt;/b&gt; 기반의 제품 추천 검색엔진 &amp;amp; 챗봇을 개발하여 이를 증명해보기로 했다.&lt;/li&gt;
&lt;li&gt;고객 니즈 발굴은 아마존의 기업 문화이자 문제 해결 방식인 &lt;b&gt;거꾸로 일하기&lt;/b&gt;(&lt;b&gt;Working Backwards&lt;/b&gt;)를 통해 가상의 젠틀몬스터 충성 고객을 정의하고, 보도자료 작성부터 시작했다. (&lt;b&gt;거꾸로 일하기&lt;/b&gt;는 이번 교육 과정으로 체득했다. 같은 이름의 번역서도 있어 적극 추천한다!) 이를 통해 이 주제를 선정한 것이 과연 타당할지 초기 단계에서 검증할 수 있었다.&lt;/li&gt;
&lt;li&gt;우리는 고객의 자연어 질문에 대해 최적의 상품을 추천하는 서비스를 개발했고, 이를 유연하게 활용하기 위한 &lt;b&gt;LLM&lt;/b&gt; 프롬프트 엔지니어링, &lt;b&gt;RAG&lt;/b&gt; 기법을 사용했다. 이를 위해 &lt;b&gt;Amazon Bedrock Claude 3.5 Sonnet&lt;/b&gt;, &lt;b&gt;Amazon Bedrock TitanText Embedding v2&lt;/b&gt;, &lt;b&gt;Amazon SageMaker&lt;/b&gt;, &lt;b&gt;Amazon OpenSearch&lt;/b&gt;를 적극 사용했다.&lt;/li&gt;
&lt;li&gt;6주 과정을 통해 &lt;b&gt;Gen AI&lt;/b&gt; 도입으로 이전과 다르게 고객이 텍스트와 이미지를 통해 원하는 취향의 제품을 자연어로 질의하여 빠른 시간 안에 추천 받을 수 있는 서비스를 개발할 수 있었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;최종 발표 사진&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4200&quot; data-origin-height=&quot;2800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Fle4z/btsIyty4NAv/q2Bfgspf1OgM7ngKHLecI1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fle4z/btsIyty4NAv/q2Bfgspf1OgM7ngKHLecI1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fle4z/btsIyty4NAv/q2Bfgspf1OgM7ngKHLecI1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFle4z%2FbtsIyty4NAv%2Fq2Bfgspf1OgM7ngKHLecI1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4200&quot; height=&quot;2800&quot; data-origin-width=&quot;4200&quot; data-origin-height=&quot;2800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4200&quot; data-origin-height=&quot;2800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HljSL/btsIygzMRep/Dly8S9vzHPjny6jEJ7zrSk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HljSL/btsIygzMRep/Dly8S9vzHPjny6jEJ7zrSk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HljSL/btsIygzMRep/Dly8S9vzHPjny6jEJ7zrSk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHljSL%2FbtsIygzMRep%2FDly8S9vzHPjny6jEJ7zrSk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4200&quot; height=&quot;2800&quot; data-origin-width=&quot;4200&quot; data-origin-height=&quot;2800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;4200&quot; data-origin-height=&quot;2800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OmZgH/btsIybSL2Lb/3qNNaQ6AL6x0RsKByyeIcK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OmZgH/btsIybSL2Lb/3qNNaQ6AL6x0RsKByyeIcK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OmZgH/btsIybSL2Lb/3qNNaQ6AL6x0RsKByyeIcK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOmZgH%2FbtsIybSL2Lb%2F3qNNaQ6AL6x0RsKByyeIcK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;4200&quot; height=&quot;2800&quot; data-origin-width=&quot;4200&quot; data-origin-height=&quot;2800&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참가 회고&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최종 발표 후 &lt;b&gt;Customer Obsession&lt;/b&gt;을 수상했고 자랑스럽고 기뻤다. 더이상 놀랄 것도 기쁠 것도 슬플 것도 없는 나같은 15년차 엔지니어에게는 정신이 번쩍 드는 최고의 선물이었다!&lt;/li&gt;
&lt;li&gt;무척 좋은 기회였음에도 처음에는 단발성 교육과정이 아니라 이렇게 6주에 걸쳐 적극적으로 참여하여 프로젝트까지 해야 하는 과정은 내심 부담스러웠다. 씨니어 엔지니어로서 회사에서 주어진 시간이 많지 않기 때문이다. 과정을 마친 현재 소감은, 결과적으로 가장 짧은 기간에 높은 수준의 압축된 &lt;b&gt;Gen AI&lt;/b&gt; 노하우를 체득하게 되었고, 실제 &lt;b&gt;Pre-POC&lt;/b&gt;를 넘어선 프로덕션 레벨에 준하는 목표를 달성했다고 생각한다. 프로그램에 참여하지 않았다면 아마 6개월 걸렸을 지식 체득을 6주만에 경험한 느낌이다. 미래의 다음 기수에 참가하시는 분들이 내 글을 읽게 된다면 이 말을 전하고 싶다. &lt;b&gt;&lt;i&gt;&quot;CTO님을 적극적으로 설득하고 참가하여 결과로 증명하세요!&quot;&lt;/i&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;기업 입장에서 &lt;b&gt;AWS&lt;/b&gt;의 기술 지원은 매우 적극적이고 훌륭하다. 그럼에도 이번 과정에서 느꼈던 특별한 느낌이라면, 과정에 참여하는 동안 고객 대 서비스 제공자의 입장이라기 보다 서로가 엔지니어의 입장으로 굉장히 솔직한 교류를 했다는 느낌이다. 당장 해결이 필요한 부분에서 각 분야 최고의 &lt;b&gt;SA&lt;/b&gt;님들이 고군분투하고 함께 트러블슈팅하는 과정을 생생하게 경험할 수 있었다. 특히 &lt;b&gt;Gen AI&lt;/b&gt;는 이제 막 태동하는 기술이기 때문에 아직 다듬어지지 않았고 우여곡절이 많았다. (프로젝트 최종 발표 며칠 전에 &lt;b&gt;Amazon Bedrock&lt;/b&gt;에서 &lt;b&gt;Claude 3.5 Sonnet&lt;/b&gt;을 출시한 것이 어찌나 다행이었던지!)&lt;/li&gt;
&lt;li&gt;프로그램 교육 과정은 머신러닝의 메인스트림 언어인 &lt;b&gt;Python&lt;/b&gt;으로 진행되었고, 젠틀몬스터를 제외한 다른 회사는 모두 &lt;b&gt;Python&lt;/b&gt;으로 &lt;b&gt;Pre-POC&lt;/b&gt; 프로젝트를 발표했다. 유일하게 젠틀몬스터만 &lt;b&gt;Kotlin&lt;/b&gt;으로 진행했는데 익숙한 기술로 처음부터 &lt;b&gt;Pre-POC&lt;/b&gt;를 바로 프로덕션 레벨로 안착하기 위한 결정이었다. 부족하거나 없는 기능은 &lt;b&gt;LangChain4j&lt;/b&gt;에 필요한 기능을 직접 &lt;b&gt;Feature Request&lt;/b&gt;하면서 진행했다.&lt;/li&gt;
&lt;li&gt;이번 과정은 이름만 들으면 알 수 있는 쟁쟁한 기업들이 참가했고 마지막 발표를 통해 각 기업의 고민과 이를 &lt;b&gt;Gen AI&lt;/b&gt;로 해결하는 과정을 생생하게 공유했다. 확실히 회사 내부에서 혼자 고민하는 것보다는 훨씬 넓은 시야를 가질 수 있었고 덤으로 자연스러운 교류의 기회를 얻을 수 있었다. 홈커밍 데이가 기대된다!&lt;/li&gt;
&lt;li&gt;마지막으로 프로젝트를 함께 한 우리 기술전략팀, 흔쾌히 참가를 허락해주신 &lt;b&gt;CTO&lt;/b&gt;님, 프로젝트 및 행사 전반에 걸쳐 최고의 경험을 제공하기 위해 무척 고생하신 &lt;b&gt;AWS Korea&lt;/b&gt;의 윤영주 매니저님, 신혜지 매니저님, 최승원 &lt;b&gt;SA&lt;/b&gt;님에게 감사의 인사를 전한다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>SW 개발</category>
      <category>aws_dna_6th</category>
      <author>지단로보트</author>
      <guid isPermaLink="true">https://jsonobject.tistory.com/639</guid>
      <comments>https://jsonobject.tistory.com/639#entry639comment</comments>
      <pubDate>Mon, 15 Jul 2024 08:37:29 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin, OpenAI LLM, LangChain4j로 인터넷 URL 자동 번역 및 요약 쉘 스크립트 제작하기</title>
      <link>https://jsonobject.tistory.com/638</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;우리는 &lt;b&gt;LLM&lt;/b&gt;의 시대에 살고 있다. &lt;b&gt;LLM&lt;/b&gt;을 응용하여 생산성을 향상시켜주는 방법과 툴이 홍수처럼 공개되고 있다. 이번 글에서는 리눅스 쉘에서 &lt;b&gt;Kotlin Script&lt;/b&gt;, &lt;b&gt;LangChain4j&lt;/b&gt;를 이용하여 인터넷 글을 광고 필터링 후 한글로 번역하고 요약까지 해주는 스크립트를 작성해본다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스크립트 기능&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아규먼트로 인터넷 URL을 전달한다.&lt;/li&gt;
&lt;li&gt;인터넷 URL에서 광고 컨텐츠 및 불필요한 컨텐츠를 제거하고 글의 내용만 한글로 번역하여 Markdown 형식으로 출력한다.&lt;/li&gt;
&lt;li&gt;마지막으로 글 요약을 출력한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사전 준비물&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OpenAI API Key&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Kotlin 설치&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;SDKMAN&lt;/code&gt;을 이용하여 &lt;code&gt;Kotlin&lt;/code&gt;을 설치한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;shell&quot;&gt;&lt;code&gt;# SDKMAN 설치
$ curl -s &quot;https://get.sdkman.io&quot; | bash
$ source &quot;$HOME/.sdkman/bin/sdkman-init.sh&quot;

# Kotlin 설치
$ sdk install kotlin

# 마크다운 콘솔 뷰어 Glow 설치
$ brew install glow&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;URL to Text 쉘 스크립트 제작&lt;/h3&gt;
&lt;pre class=&quot;julia&quot;&gt;&lt;code&gt;$ nano url2text.main.kts
#!/usr/bin/env kotlin

// LangChain4j 라이브러리를 임포트한다.
@file:DependsOn(&quot;dev.langchain4j:langchain4j:0.31.0&quot;, &quot;dev.langchain4j:langchain4j-open-ai:0.31.0&quot;)

import dev.langchain4j.data.document.Document
import dev.langchain4j.data.document.loader.UrlDocumentLoader
import dev.langchain4j.data.document.parser.TextDocumentParser
import dev.langchain4j.data.document.transformer.HtmlTextExtractor
import dev.langchain4j.data.message.AiMessage
import dev.langchain4j.data.message.SystemMessage
import dev.langchain4j.model.openai.OpenAiChatModel
import dev.langchain4j.model.output.Response
import java.time.Duration
import kotlin.system.exitProcess

// 아규먼트 전달 여부를 확인 후 안내 메시지를 출력하고 종료한다.
val params = args
if (params.isEmpty()) {
    println(&quot;&quot;&quot;
Internet URL to Text Tool 0.1 by Tae-hyeong Lee

Usage: url2text.main.kts {url}

Description:
  The 'url2text' command fetches the content from a given internet URL, translates it into Korean, and provides a summary of the translated content.

Example:
  url2text.main.kts https://github.com/langchain-ai/langchain

Note:
Ensure the URL is a valid internet address for accurate translation and summarization.
   &quot;&quot;&quot;.trimIndent())

    exitProcess(1)
}

// LangChain4j를 이용하여 HTML을 Text로 변환한다.
val url = params[0]
val htmlDocument: Document = UrlDocumentLoader.load(url, TextDocumentParser())
val textDocument: Document = HtmlTextExtractor().transform(htmlDocument)

// LangChain4j OpenAI GPT-4o LLM 오브젝트를 생성한다.
val chatModel: OpenAiChatModel = OpenAiChatModel.builder()
    .apiKey(&quot;{your-openai-api-key}&quot;)
    .timeout(Duration.ofSeconds(120))
    .modelName(&quot;gpt-4o-2024-05-13&quot;)
    .temperature(0.3)
    .topP(0.3)
    .build()

// LLM에게 글 정제와 번역 및 요약을 요청한다.
val aiMessage: Response&amp;lt;AiMessage&amp;gt; = chatModel.generate(
    SystemMessage(
        &quot;&quot;&quot;
당신은 인터넷 글을 읽고 한국어로 번역해주는 어시스턴트야. 인터넷 글은 아래 context를 참고해서 대답해주고 아래 내용을 준수해야해.

1. 특정 제품을 홍보하는 등의 광고성 정보 및 헤더, 푸터, 메뉴 정보는 모두 제거하고, 글의 실제 내용만 추출해서 정확히 번역해줘.
2. 번역 결과는 제목과 각 단락을 적절히 판단하여 Markdown 형식으로 변환해줘.
3. 번역이 끝난 후에는 전체 글에서 핵심 내용만 한국어로 요약해줘. 아래 형식을 지켜줘.

# 글 원본
원본 내용

# 글 요약
요약 내용

context: \\\
$textDocument
\\\
    &quot;&quot;&quot;.trimIndent()
    )
)

// LLM 처리 결과를 출력한다.
println(aiMessage.content().text())
exitProcess(0)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실행 예&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이제 작성한 스크립트를 이용하여 실제 일본어로 된 신문 기사를 영어로 번역해보자.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;# 일본어 신문 기사 번역 및 요약 실행
$ ./url2text.main.kts https://news.yahoo.co.jp/articles/f2278281e65df486a61d105184fcd4e977c49601 &amp;gt; article.md

# 실행 결과 출력
$ glow article.md

## 전통총연, 기업용 ChatGPT 솔루션 &quot;Know Narrator&quot;에 최신 LLM 모델 &quot;GPT-4o&quot; 적용 시작

  2023년 6월 6일 (목) 8:01 배포

  주식회사 전통총연은 5일, 마이크로소프트가 제공하는 Azure OpenAI Service를 활용해 개발한 기업용 ChatGPT 솔루션 &quot;Know
  Narrator&quot;에 &quot;GPT-4o&quot;를 적용하고 제공을 시작했다고 발표했다. 이를 통해 기존 모델과 비교해 약 2배의 속도로 더 높은
  정확도의 생성 AI를 활용할 수 있게 되었다고 한다.

  Know Narrator는 ChatGPT 환경을 기업 내에 구축하고 그 활용을 촉진하는 솔루션이다. 지금까지 Know Narrator는 최신 LLM
  모델을 탑재해왔으며, 사용자 채팅 기록을 분석해 더 효율적인 이용 방법을 제안하는 &quot;Know Narrator Insight&quot;, ChatGPT가
  사내 문서를 참조해 답변을 생성할 수 있게 하는 &quot;Know Narrator Search&quot;, API를 통해 다른 시스템과 연계할 수 있는 &quot;Know
  Narrator API&quot;를 독자적으로 개발해왔다.

  이번 GPT-4o의 적용 시작으로, 출력 속도는 질문 후 답변이 돌아오는 시간이 기존 모델과 비교해 절반 이하로 단축되었다.
  또한, 이미지 인식의 정확도가 향상되어 기존 모델에서는 어려웠던 일본어 손글씨 읽기가 가능해졌다.

  더 나아가, GPT-4o는 다국어 데이터 대응이 강화되어 Know Narrator Search에서도 일본어 문서에 대한 읽기 및 답변 성능이
  향상되었다.

  전통총연은 앞으로도 Know Narrator의 기능 확충을 도모하고, 생성 AI의 실업무 적용과 사회적 보급을 추진해 나갈 것이라고
  밝혔다.

## 글 요약

  전통총연은 기업용 ChatGPT 솔루션 &quot;Know Narrator&quot;에 최신 LLM 모델 &quot;GPT-4o&quot;를 적용하여 제공을 시작했다. 이를 통해 기존
  모델보다 약 2배 빠른 속도로 더 높은 정확도의 생성 AI를 활용할 수 있게 되었으며, 이미지 인식과 다국어 데이터 대응
  성능이 향상되었다.&lt;/code&gt;&lt;/pre&gt;</description>
      <category>SW 개발</category>
      <author>지단로보트</author>
      <guid isPermaLink="true">https://jsonobject.tistory.com/638</guid>
      <comments>https://jsonobject.tistory.com/638#entry638comment</comments>
      <pubDate>Fri, 7 Jun 2024 00:30:33 +0900</pubDate>
    </item>
    <item>
      <title>Azure OpenAI LLM, Azure AI Search, LangChain4j로 커스텀 챗봇 제작하기</title>
      <link>https://jsonobject.tistory.com/637</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;LLM&lt;/b&gt;과 &lt;b&gt;RAG&lt;/b&gt;을 이용한 커스텀 챗봇 개발은 현재 전지구에서 가장 핫한 주제 중에 하나이다. 이번 글에서는 &lt;b&gt;Azure&lt;/b&gt; 인프라에서 안전하게 격리된 &lt;code&gt;GPT-4o&lt;/code&gt;모델을 셋업하고, 역시 안전하게 격리된 &lt;code&gt;Azure AI Search&lt;/code&gt;를 연동하여 &lt;code&gt;LangChain4j&lt;/code&gt; 라이브러리로 커스텀 챗봇을 제작하는 방법을 정리했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;순서&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Azure OpenAI Instance 생성&lt;/li&gt;
&lt;li&gt;Azure OpenAI Deployment 생성&lt;/li&gt;
&lt;li&gt;Azure AI Search Service 생성&lt;/li&gt;
&lt;li&gt;LangChain4j 소스 코드를 Azure AI Search에 저장&lt;/li&gt;
&lt;li&gt;LangChain4j 코딩을 도와주는 챗봇 제작&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Azure OpenAI Instance 생성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;OpenAI&lt;/b&gt;의 다양한 모델을 내 인프라에 설치하려면 선호하는 리전에 &lt;code&gt;OpenAI Instance&lt;/code&gt;를 생성해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;Microsoft Azure Portal
&amp;rarr; [Azure OpenAI] 클릭
&amp;rarr; [Create Azure OpenAI] 클릭

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

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

# [4] Review + submit
&amp;rarr; [Create] 클릭&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Azure OpenAI Deployment 생성: GPT-4o&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;질문을 전송할 &lt;b&gt;LLM&lt;/b&gt;으로 &lt;b&gt;OpenAI&lt;/b&gt;의 최신 대화형 멀티 모달 모델인 &lt;code&gt;GPT-4o&lt;/code&gt;를 생성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;Microsoft Azure Portal
&amp;rarr; [Azure OpenAI] 클릭
&amp;rarr; {your-instance} 클릭
&amp;rarr; [Model deployments] 클릭
&amp;rarr; [Manage Deployments] 클릭

# Azure AI Studio
&amp;rarr; [Create new deployment] 클릭

# Deploy model
&amp;rarr; Select a model: [gpt-4o] 선택
&amp;rarr; Model version: [2024-05-13] 선택
&amp;rarr; Deployment type: [Standard] 선택
&amp;rarr; Deployment name: {your-deployment-name} (입력)
&amp;rarr; Tokens per Minute Rate Limit (thousands): 150K
&amp;rarr; Enable Dynamic Quota: [Enabled] 선택
&amp;rarr; [Create] 클릭&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Azure OpenAI Deployment 생성: text-embedding-ada-002&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원본 텍스트의 벡터 변환을 담당할 &lt;b&gt;Embedding Model&lt;/b&gt;을 생성한다. &lt;b&gt;OpenAI&lt;/b&gt;의 최신의 텍스트 임베딩 모델인 &lt;code&gt;text-embedding-ada-002&lt;/code&gt;를 선택했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;Microsoft Azure Portal
&amp;rarr; [Azure OpenAI] 클릭
&amp;rarr; {your-instance} 클릭
&amp;rarr; [Model deployments] 클릭
&amp;rarr; [Manage Deployments] 클릭

# Azure AI Studio
&amp;rarr; [Create new deployment] 클릭

# Deploy model
&amp;rarr; Select a model: [text-embedding-ada-002] 선택
&amp;rarr; Model version: [2] 선택
&amp;rarr; Deployment type: [Standard] 선택
&amp;rarr; Deployment name: {your-deployment-name} (입력)
&amp;rarr; Tokens per Minute Rate Limit (thousands): 350K
&amp;rarr; Enable Dynamic Quota: [Enabled] 선택
&amp;rarr; [Deploy] 클릭&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Azure AI Search 생성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;RAG&lt;/b&gt; 저장소 및 검색엔진으로 사용할 &lt;code&gt;Azure AI Search&lt;/code&gt;를 생성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Microsoft Azure Portal
&amp;rarr; [Azure AI Search] 클릭
&amp;rarr; [Create Azure AI Search] 클릭

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

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

&amp;rarr; Pricing tier: {your-pricing-tier} 선택
&amp;rarr; [Next] 클릭

&amp;rarr; [Create] 클릭&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Pricing tier&lt;/b&gt; 선택시 주의할 점은 &lt;b&gt;Hybrid Search&lt;/b&gt;에 해당하는 &lt;code&gt;Semantic ranker&lt;/code&gt; 기능을 사용하려면 &lt;b&gt;Basic&lt;/b&gt; 이상의 티어를 선택해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Azure AI Search Index 생성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;RAG&lt;/b&gt; 데이터를 저장할 인덱스를 생성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;Microsoft Azure Portal
&amp;rarr; [AI Search] 클릭
&amp;rarr; {your-ai-search} 클릭
&amp;rarr; [Add index] 클릭
&amp;rarr; [Add index (JSON)] 클릭
&amp;rarr; (아래 JSON 내용 붙여넣기)
&amp;rarr; [Save] 클릭&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아래는 &lt;code&gt;LangChain4j&lt;/code&gt;가 사용하는 &lt;b&gt;Azure AI Search&lt;/b&gt;의 &lt;b&gt;Index&lt;/b&gt; 템플릿인데, 텍스트 원본을 저장하는 &lt;b&gt;content&lt;/b&gt; 필드의 최대 크기를 32,766 bytes에서 도큐먼트가 허용하는 최대 크기(16 MB)까지 저장할 수 있도록 수정한 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;{your-ai-search-index-name}&quot;,
  &quot;fields&quot;: [
    {
      &quot;name&quot;: &quot;id&quot;,
      &quot;type&quot;: &quot;Edm.String&quot;,
      &quot;searchable&quot;: true,
      &quot;filterable&quot;: true,
      &quot;retrievable&quot;: true,
      &quot;stored&quot;: true,
      &quot;sortable&quot;: true,
      &quot;facetable&quot;: true,
      &quot;key&quot;: true,
      &quot;indexAnalyzer&quot;: null,
      &quot;searchAnalyzer&quot;: null,
      &quot;analyzer&quot;: null,
      &quot;normalizer&quot;: null,
      &quot;dimensions&quot;: null,
      &quot;vectorSearchProfile&quot;: null,
      &quot;vectorEncoding&quot;: null,
      &quot;synonymMaps&quot;: []
    },
    {
      &quot;name&quot;: &quot;content&quot;,
      &quot;type&quot;: &quot;Edm.String&quot;,
      &quot;searchable&quot;: true,
      &quot;filterable&quot;: false,
      &quot;retrievable&quot;: true,
      &quot;stored&quot;: true,
      &quot;sortable&quot;: false,
      &quot;facetable&quot;: false,
      &quot;key&quot;: false,
      &quot;indexAnalyzer&quot;: null,
      &quot;searchAnalyzer&quot;: null,
      &quot;analyzer&quot;: null,
      &quot;normalizer&quot;: null,
      &quot;dimensions&quot;: null,
      &quot;vectorSearchProfile&quot;: null,
      &quot;vectorEncoding&quot;: null,
      &quot;synonymMaps&quot;: []
    },
    {
      &quot;name&quot;: &quot;content_vector&quot;,
      &quot;type&quot;: &quot;Collection(Edm.Single)&quot;,
      &quot;searchable&quot;: true,
      &quot;filterable&quot;: false,
      &quot;retrievable&quot;: true,
      &quot;stored&quot;: true,
      &quot;sortable&quot;: false,
      &quot;facetable&quot;: false,
      &quot;key&quot;: false,
      &quot;indexAnalyzer&quot;: null,
      &quot;searchAnalyzer&quot;: null,
      &quot;analyzer&quot;: null,
      &quot;normalizer&quot;: null,
      &quot;dimensions&quot;: 1536,
      &quot;vectorSearchProfile&quot;: &quot;vector-search-profile&quot;,
      &quot;vectorEncoding&quot;: null,
      &quot;synonymMaps&quot;: []
    },
    {
      &quot;name&quot;: &quot;metadata&quot;,
      &quot;type&quot;: &quot;Edm.ComplexType&quot;,
      &quot;fields&quot;: [
        {
          &quot;name&quot;: &quot;source&quot;,
          &quot;type&quot;: &quot;Edm.String&quot;,
          &quot;searchable&quot;: true,
          &quot;filterable&quot;: true,
          &quot;retrievable&quot;: true,
          &quot;stored&quot;: true,
          &quot;sortable&quot;: true,
          &quot;facetable&quot;: true,
          &quot;key&quot;: false,
          &quot;indexAnalyzer&quot;: null,
          &quot;searchAnalyzer&quot;: null,
          &quot;analyzer&quot;: null,
          &quot;normalizer&quot;: null,
          &quot;dimensions&quot;: null,
          &quot;vectorSearchProfile&quot;: null,
          &quot;vectorEncoding&quot;: null,
          &quot;synonymMaps&quot;: []
        },
        {
          &quot;name&quot;: &quot;attributes&quot;,
          &quot;type&quot;: &quot;Collection(Edm.ComplexType)&quot;,
          &quot;fields&quot;: [
            {
              &quot;name&quot;: &quot;key&quot;,
              &quot;type&quot;: &quot;Edm.String&quot;,
              &quot;searchable&quot;: true,
              &quot;filterable&quot;: true,
              &quot;retrievable&quot;: true,
              &quot;stored&quot;: true,
              &quot;sortable&quot;: false,
              &quot;facetable&quot;: true,
              &quot;key&quot;: false,
              &quot;indexAnalyzer&quot;: null,
              &quot;searchAnalyzer&quot;: null,
              &quot;analyzer&quot;: null,
              &quot;normalizer&quot;: null,
              &quot;dimensions&quot;: null,
              &quot;vectorSearchProfile&quot;: null,
              &quot;vectorEncoding&quot;: null,
              &quot;synonymMaps&quot;: []
            },
            {
              &quot;name&quot;: &quot;value&quot;,
              &quot;type&quot;: &quot;Edm.String&quot;,
              &quot;searchable&quot;: true,
              &quot;filterable&quot;: true,
              &quot;retrievable&quot;: true,
              &quot;stored&quot;: true,
              &quot;sortable&quot;: false,
              &quot;facetable&quot;: true,
              &quot;key&quot;: false,
              &quot;indexAnalyzer&quot;: null,
              &quot;searchAnalyzer&quot;: null,
              &quot;analyzer&quot;: null,
              &quot;normalizer&quot;: null,
              &quot;dimensions&quot;: null,
              &quot;vectorSearchProfile&quot;: null,
              &quot;vectorEncoding&quot;: null,
              &quot;synonymMaps&quot;: []
            }
          ]
        }
      ]
    }
  ],
  &quot;scoringProfiles&quot;: [],
  &quot;corsOptions&quot;: null,
  &quot;suggesters&quot;: [],
  &quot;analyzers&quot;: [],
  &quot;normalizers&quot;: [],
  &quot;tokenizers&quot;: [],
  &quot;tokenFilters&quot;: [],
  &quot;charFilters&quot;: [],
  &quot;encryptionKey&quot;: null,
  &quot;similarity&quot;: {
    &quot;@odata.type&quot;: &quot;#Microsoft.Azure.Search.BM25Similarity&quot;,
    &quot;k1&quot;: null,
    &quot;b&quot;: null
  },
  &quot;semantic&quot;: {
    &quot;defaultConfiguration&quot;: &quot;semantic-search-config&quot;,
    &quot;configurations&quot;: [
      {
        &quot;name&quot;: &quot;semantic-search-config&quot;,
        &quot;prioritizedFields&quot;: {
          &quot;titleField&quot;: null,
          &quot;prioritizedContentFields&quot;: [
            {
              &quot;fieldName&quot;: &quot;content&quot;
            }
          ],
          &quot;prioritizedKeywordsFields&quot;: [
            {
              &quot;fieldName&quot;: &quot;content&quot;
            }
          ]
        }
      }
    ]
  },
  &quot;vectorSearch&quot;: {
    &quot;algorithms&quot;: [
      {
        &quot;name&quot;: &quot;vector-search-algorithm&quot;,
        &quot;kind&quot;: &quot;hnsw&quot;,
        &quot;hnswParameters&quot;: {
          &quot;metric&quot;: &quot;cosine&quot;,
          &quot;m&quot;: 4,
          &quot;efConstruction&quot;: 400,
          &quot;efSearch&quot;: 500
        },
        &quot;exhaustiveKnnParameters&quot;: null
      }
    ],
    &quot;profiles&quot;: [
      {
        &quot;name&quot;: &quot;vector-search-profile&quot;,
        &quot;algorithm&quot;: &quot;vector-search-algorithm&quot;,
        &quot;vectorizer&quot;: null,
        &quot;compression&quot;: null
      }
    ],
    &quot;vectorizers&quot;: [],
    &quot;compressions&quot;: []
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;build.gradle.kts&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인프라 레벨에서의 준비는 이제 끝났다. 본격적인 코드 작업을 위해 프로젝트에 &lt;code&gt;LangChain4j&lt;/code&gt;를 임포트한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;val langChain4jVersion = &quot;0.31.0&quot;
dependencies {
    implementation(&quot;dev.langchain4j:langchain4j-core:$langChain4jVersion&quot;)
    implementation(&quot;dev.langchain4j:langchain4j-embeddings:$langChain4jVersion&quot;)
    implementation(&quot;dev.langchain4j:langchain4j-easy-rag:$langChain4jVersion&quot;)
    implementation(&quot;dev.langchain4j:langchain4j-open-ai:$langChain4jVersion&quot;)
    implementation(&quot;dev.langchain4j:langchain4j-azure-open-ai:$langChain4jVersion&quot;)
    implementation(&quot;dev.langchain4j:langchain4j-azure-ai-search:$langChain4jVersion&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Embedding, LLM Model 및 RAG 오브젝트 생성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;LangChain4j&lt;/code&gt;는 &lt;code&gt;Azure OpenAI&lt;/code&gt;와 &lt;code&gt;Azure AI Search&lt;/code&gt; 생태계를 완벽 지원한다. 아래와 같이 쉽게 관련 오브젝트를 생성할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// Azure OpenAI text-embedding-ada-002의 EmbeddingModel 오브젝트 생성
val embeddingModel: EmbeddingModel = AzureOpenAiEmbeddingModel.builder()
    .endpoint(&quot;https://{your-azrue-open-ai-text-embedding-ada-002-deployment-name}.openai.azure.com&quot;)
    .serviceVersion(&quot;2023-05-15&quot;)
    .apiKey(&quot;{your-azure-openai-instance-api-key}&quot;)
    .deploymentName(&quot;{your-azrue-open-ai-text-embedding-ada-002-deployment-name}&quot;)
    .build()

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

// Hybrid Search를 적용한 Azure AI Search의 ContentRetriever 오브젝트 생성
val contentRetriever: ContentRetriever = AzureAiSearchContentRetriever.builder()
    .endpoint(&quot;https://{your-azure-ai-search-service-name}.search.windows.net&quot;)
    .apiKey(&quot;{your-azure-ai-search-admin-key}&quot;)
    .dimensions(1536)
    .indexName(&quot;{your-azure-ai-search-index-name}&quot;)
    .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(&quot;https://{your-azrue-open-ai-gpt-4o-deployment-name}.openai.azure.com&quot;)
    .apiKey(&quot;{your-azure-openai-instance-api-key}&quot;)
    .deploymentName(&quot;{your-azrue-open-ai-gpt-4o-deployment-name}&quot;)
    .serviceVersion(&quot;2024-02-01&quot;)
    .timeout(Duration.ofSeconds(360))
    .temperature(0.3)
    .topP(0.3)
    .build()&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데이터를 Azure AI Search에 저장&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Azure AI Search&lt;/code&gt;는 데이터 원본이 되는 &lt;b&gt;Text&lt;/b&gt;와 이를 다차원 소수 배열로 변환한 &lt;b&gt;Vector&lt;/b&gt; 데이터를 같이 하나의 &lt;b&gt;Document&lt;/b&gt;로 저장할 수 있다. 이런 특징 덕분에 &lt;b&gt;Semanctic Search&lt;/b&gt;와 &lt;b&gt;Keyword Search&lt;/b&gt;가 결합된 &lt;b&gt;Hybrid Search&lt;/b&gt;가 가능하다.&lt;/li&gt;
&lt;li&gt;아래는 &lt;code&gt;LangChain4j&lt;/code&gt; 라이브러리 소스 코드를 클로닝하여 &lt;b&gt;Azure AI Search&lt;/b&gt;에 저장하는 예제이다. 먼저 프로젝트 루트 디렉토리에 관련 저장소를 클로닝한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;# 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&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클로닝한 소스 코드 파일을 &lt;code&gt;text-embedding-ada-002&lt;/code&gt; 임베딩 모델을 사용하여 Vector 데이터로 변환하여 원본 텍스트와 함께 &lt;b&gt;Azure AI Search&lt;/b&gt;에 저장한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;// 클로닝한 LangChain4j 라이브러리를 소스 코드 파일만 Azure AI Search에 저장
val embeddings: MutableList&amp;lt;Pair&amp;lt;Embedding, TextSegment&amp;gt;&amp;gt; = mutableListOf()
arrayOf(&quot;langchain4j&quot;, &quot;langchain4j-embeddings&quot;, &quot;langchain4j-examples&quot;).forEach { directory -&amp;gt;
    Files.walk(Paths.get(directory)).use { paths -&amp;gt;
        paths.filter {
            Files.isRegularFile(it) &amp;amp;&amp;amp; arrayOf(
                &quot;md&quot;,
                &quot;xml&quot;,
                &quot;gradle&quot;,
                &quot;kts&quot;,
                &quot;java&quot;,
                &quot;kt&quot;
            ).contains(it.fileName.toString().substringAfterLast('.', &quot;&quot;))
        }
            .forEach { path -&amp;gt;
                val document = Document.document(path.toFile().readText())
                val segments = documentSplitter.split(document)
                segments.forEach { segment -&amp;gt;
                    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)
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;LLM 모델에 RAG 연동 질의&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Azure AI Search&lt;/b&gt;에 &lt;b&gt;LangChain4j&lt;/b&gt; 관련 소스 코드가 저장되었으므로 이제 RAG로 질의한 정보 목록을 LLM의 프롬프트에 포함하여 질의할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;pony&quot;&gt;&lt;code&gt;// [1] LLM 질문 작성
val question = &quot;RAG에서 Semantic Search와 Keyword Search의 장점을 결합한 Hybrid Search에 대해서 자세히 설명해줘. 그리고 LangChain4j에서 Azure AI Search에 데이터를 저장하고 질의하는 예제를 작성해줘.&quot;

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

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

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

// [4] LLM 답변 출력
println(aiMessage.content().text())&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다른 방법으로 &lt;code&gt;AiServices&lt;/code&gt;를 이용하여 제작할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;// [1] LLM 질문 작성
val question = &quot;{your-question}&quot;

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

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

// [4] LLM 답변 출력
println(assistant.ask(question))&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고 글&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.langchain.com/&quot;&gt;LangChain&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.langchain4j.dev/&quot;&gt;LangChain4j&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>SW 개발</category>
      <author>지단로보트</author>
      <guid isPermaLink="true">https://jsonobject.tistory.com/637</guid>
      <comments>https://jsonobject.tistory.com/637#entry637comment</comments>
      <pubDate>Mon, 3 Jun 2024 16:03:20 +0900</pubDate>
    </item>
    <item>
      <title>Lobe Chat, 통합 LLM 프론트엔드 프레임워크 설치하기</title>
      <link>https://jsonobject.tistory.com/636</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대부분의 개발자가 &lt;code&gt;OpenAI API&lt;/code&gt;로 &lt;b&gt;LLM&lt;/b&gt;을 이용한 개발에 입문한다. &lt;b&gt;OpenAI API&lt;/b&gt;는 &lt;code&gt;ChatGPT&lt;/code&gt;와는 별개로 작동하며 최신 모델인 &lt;code&gt;GPT-4 Turbo&lt;/code&gt;(현재 2023년 12월까지 학습된 &lt;code&gt;gpt-4-turbo-2024-04-09&lt;/code&gt;와 동일)을 이용할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;OpenAI API&lt;/b&gt;를 이용하는 개발자들은 &lt;b&gt;ChatGPT&lt;/b&gt;와 같은 프론트엔드의 필요성을 느끼는데, 이번 글에서 소개하는 &lt;code&gt;Lobe Chat&lt;/code&gt;이 바로 &lt;b&gt;OpenAI API&lt;/b&gt;를 &lt;b&gt;ChatGPT&lt;/b&gt;처럼 사용하게 해주는 프론트엔드 역할을 오픈 소스로 제공한다. 내 경우 로컬에 설치하여 상시로 편리하게 이용한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Lobe Chat&lt;/b&gt;의 장점은 &lt;b&gt;OpenAI API&lt;/b&gt; 뿐만 아니라 전세계 거의 대부분의 모델에 대한 통합 프론트엔드를 제공한다는 것이다. &lt;b&gt;API Key&lt;/b&gt;만 입력하면 바로 통일된 &lt;b&gt;UI&lt;/b&gt;로 편리하게 이용할 수 있어 추천한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Lobe Chat 설치&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Lobe Chat&lt;/b&gt;은 Docker 이미지를 제공하기 때문에 매우 편리하게 설치할 수 있다. 별도의 데이터베이스를 요구하지 않으며 서비스 실행 중 차지하는 메모리 용량은 &lt;b&gt;150MB&lt;/b&gt; 내외로 가볍다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;$ mkdir lobe-chat
$ cd lobe-chat

# Docker Compose 파일 작성
$ nano docker-compose.yml
version: '3.8'

services:
  lobe-chat:
    image: lobehub/lobe-chat:latest
    container_name: lobe-chat
    restart: always
    ports:
      - '3210:3210'
    environment:
      OPENAI_API_KEY: {your-openai-api-key}
      ACCESS_CODE: {your-password}

# Lobe Chat 서비스 설치 및 실행
$ docker-compose up -d&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Lobe Chat 실행&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹 브라우저에서 &lt;code&gt;http://localhost:3210&lt;/code&gt;으로 접속하면 &lt;b&gt;Lobe Chat&lt;/b&gt;을 이용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고 글&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/lobehub/lobe-chat&quot;&gt;GitHub - Lobe Chat&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>SW 개발</category>
      <author>지단로보트</author>
      <guid isPermaLink="true">https://jsonobject.tistory.com/636</guid>
      <comments>https://jsonobject.tistory.com/636#entry636comment</comments>
      <pubDate>Sat, 20 Apr 2024 19:30:01 +0900</pubDate>
    </item>
    <item>
      <title>MongoDB Atlas, 개념 및 사용법 정리</title>
      <link>https://jsonobject.tistory.com/542</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;개요&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;MongoDB&lt;/b&gt;는 수년간 대표적인 &lt;b&gt;NoSQL&lt;/b&gt; 제품으로 자리 잡았다. 특히, 전통적인 &lt;b&gt;RDBMS&lt;/b&gt; 대비 우월한 샤드 및 스케일 아웃 능력으로 대량 트래픽, 대량 데이터가 발생하는 모던 웹 시대에 걸맞는 저장소로 널리 쓰이고 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;MongoDB&lt;/b&gt;는 관리 측면에서 굉장한 전문성과 노력이 필요하다. 프로덕션 레벨이라면, 제작사가 직접 제공하는 &lt;code&gt;MongoDB Atlas&lt;/code&gt; 유료 클라우드 서버리스 매니지드 상품 이용을 추천한다. 데이터 증가에 따른 스케일 아웃 같은 까다롭고 어려운 관리 이슈를 대부분 자동으로 수행해주며, 매우 편리한 브라우저 기반의 관리 &lt;b&gt;UI&lt;/b&gt;를 제공한다. 특히 2019년 6월부터 &lt;code&gt;MongoDB Atlas Full-Text Search&lt;/code&gt; 서비스를 개시하였는데, &lt;b&gt;MongoDB&lt;/b&gt; 생태계에서, 복잡한 백엔드 아키텍쳐에 대한 고민 없이 순수히 풀텍스트 검색 자체에 집중할 수 있어 강력히 추천한다. &lt;a href=&quot;https://www.mongodb.com/blog/post/getting-started-with-mongodb-atlas-fulltext-search&quot;&gt;[관련 링크]&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;MongoDB Atlas 클러스터 생성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;MongoDB Atlas&lt;/b&gt; 클러스터는 웹 콘솔을 통해 직관적으로 생성할 수 있다. 아래는 개발 환경에 권장하지만 추후 스케일링의 제한이 없는 &lt;b&gt;M10&lt;/b&gt; 클러스터 티어를 &lt;b&gt;AWS&lt;/b&gt; 클라우드의 &lt;b&gt;서울&lt;/b&gt; 리전에 생성하는 예이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;MongoDB Cloud 콘솔 접속
# Deployment
&amp;rarr; [Database] 클릭
&amp;rarr; [Build a Database] 클릭
&amp;rarr; [Advence Configuration Options] 클릭
&amp;rarr; [Dedicated] 클릭

# Global Cluster Configuration
&amp;rarr; [Enable Global Writes] 체크 해제

# Deployment your database
&amp;rarr; Provider: [AWS] 선택
&amp;rarr; Region: [Seoul] 선택

# Cluster Tier
&amp;rarr; Tier: [M10] 선택 (2GB RAM, 10GB Storage, 2 vCPUs, from $0.10/hr)
&amp;rarr; Storage: 10 (입력)
&amp;rarr; [Cluster Tier Scaling] 체크
&amp;rarr; [Allow cluster to be scaled down] 체크
&amp;rarr; Minimum cluster size: [M10] 선택
&amp;rarr; Maximum cluster size: [M30] 선택
&amp;rarr; [Storage Scaling] 체크

# Additional Settings
&amp;rarr; Select a Version: [MongoDB 7.0] 선택
&amp;rarr; [Turn on Cloud Backup] 체크
&amp;rarr; [Continuous Cloud Backup] 체크
&amp;rarr; [Enable Business Intelligence Connector] 체크 해제

# Cluster Name
&amp;rarr; Cluster Name: foo-dev (입력)
&amp;rarr; [Create Cluster] 클릭

# Security Quickstart
&amp;rarr; How would you like to authenticate your connection?: [Username and Password]
&amp;rarr; Username: {db-admin-username}
&amp;rarr; Password: {db-admin-password}
&amp;rarr; [Create User] 클릭&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;M10&lt;/code&gt;, &lt;code&gt;M20&lt;/code&gt;은 개발 환경에 추천되는 클러스터 티어이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;M30&lt;/code&gt;부터 대용량 트래픽이 발생하는 운영 환경에 추천되는 클러스터 티어이다. 이 티어부터 샤드를 지원한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;M10&lt;/b&gt;부터 다운 타임 없는 오토 스케일링을 지원한다. 조건은 클래스 선택시 &lt;b&gt;General&lt;/b&gt;을 선택해야 하며, 유입량에 따라 자동으로 클러스터 티어와 스토리지 용량을 변경해준다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;M10&lt;/b&gt;부터 기본적으로 레플리카 셋으로 구성되는데, 운영 중에도 샤드로 업그레이드가 가능하다. 유의할 점은 샤드는 최소 &lt;b&gt;M30&lt;/b&gt;으로의 티어 업그레이드를 요구하며, 한번 샤드로 전환한 후에는 &lt;b&gt;M30&lt;/b&gt; 미만의 티어로 다운그레이드가 불가능하다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;M10&lt;/b&gt;부터 &lt;b&gt;VPC 피어링&lt;/b&gt;을 지원하는데 이를 이용하면 애플리케이션이 위치한 &lt;b&gt;VPC&lt;/b&gt;와 &lt;b&gt;MongoDB Atlas&lt;/b&gt;를 같은 내부 네트워크로 취급할 수 있다. 이를 통해 외부 인터넷 경유 없이 순수한 내부 통신이 가능해진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Amazon VPC 피어링 연결 생성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;MongoDB Atlas&lt;/b&gt;는 퍼블릭 연결을 제공하는 서비리스 매니지드 서비스이지만 프라이빗으로 구성된 &lt;b&gt;Amazon VPC&lt;/b&gt;와 &lt;b&gt;VPC 피어링 연결&lt;/b&gt;을 생성하여 마치 격리된 내부 네트워크에 애플리케이션과 함께 위치시키는 것이 가능하다. 이를 통해 중요한 데이터에 대한 접근을 원천 차단하여 보안을 강화할 수 있다. &lt;b&gt;VPC&lt;/b&gt; 피어링 연결을 생성하는 방법은 아래와 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;AWS VPC 콘솔 접속
&amp;rarr; [VPC] 클릭
&amp;rarr; 피어링 연결을 생성할 기존 프라이빗 VPC를 선택
&amp;rarr; [Edit VPC settins] &amp;rarr; [Enable DNS hostnames] 체크 &amp;rarr; [Enable DNS resolution] 체크 &amp;rarr; [Save] 클릭
(해당 VPC의 소유자 ID를 기억: 111111111111)
(해당 VPC의 ID를 기억: vpc-11111111111111111)
(해당 VPC의 CIDR을 기억: 10.0.0.0/16)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이번에는 &lt;b&gt;MongoDB Atlas&lt;/b&gt;에서 아래와 같이 피어링 연결을 생성하고 요청한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;MongoDB Atlas 콘솔 접속
&amp;rarr; [Atlas] 클릭 &amp;rarr; [Network Access] 클릭 &amp;rarr; [Peering] 클릭
&amp;rarr; [Add Peering Connection] 클릭

# Peering Connection
&amp;rarr; Cloud Provider: [AWS] 선택 &amp;rarr; [Next] 클릭

# Your Application VPC
&amp;rarr; Account ID: 111111111111 (앞서 기억해둔 VPC의 소유자 ID를 입력)
&amp;rarr; VPC ID: vpc-11111111111111111 (앞서 기억해둔 VPC의 ID를 입력)
&amp;rarr; VPC CIDR: 10.0.0.0/16 (앞서 기억해둔 VPC의 CIDR을 입력)
&amp;rarr; [Add this CIDR block to my IP whitelist] 체크
&amp;rarr; Application VPC Region: [ap-northeast-2] 선택 (리전 선택)

# Your Atlas VPC
&amp;rarr; Atlas VPC Region: ap-northeast-2 (선택 불가)
&amp;rarr; VPC CIDR: 192.168.120.0/21 (입력 불가, 이후 AWS VPC 콘솔에 입력하기 위해 기억)
&amp;rarr; [Initate Peering] 클릭
(Status: Waiting for Approval로 변경됨을 확인)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다시 &lt;b&gt;AWS VPC&lt;/b&gt; 콘솔로 돌아와서 요청한 피어링 연결을 수락하고, 라우팅 테이블에 추가한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;AWS VPC 콘솔 접속
&amp;rarr; [Peering connections] 클릭
&amp;rarr; (Status: Pending acceptance 피어링 연결 선택)
&amp;rarr; [Actions] 클릭 &amp;rarr; [Accept request] 클릭 &amp;rarr; [Accept request] 클릭

# VPC 피어링 연결 요청 수락
&amp;rarr; [Yes, Accept] 클릭
&amp;rarr; [지금 내 라우팅 테이블 수정] 클릭

# 라우팅 테이블
&amp;rarr; [foo-dev-private] 선택
&amp;rarr; [작업] 클릭 &amp;rarr; [라우팅 편집] 클릭
&amp;rarr; 대상: 192.168.120.0/21 (앞서 기억해둔 VPC의 CIDR을 입력) &amp;rarr; 대상: [Peering Connection] 선택 &amp;rarr; [mongodb-atlas-foo-dev] 선택
&amp;rarr; [라우팅 저장] 클릭&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Amazon EC2 피어링 연결 확인&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앞서 피어링 연결을 생성한 후, 같은 &lt;b&gt;VPC&lt;/b&gt;(또한 서브넷)에 위치한 &lt;b&gt;Amazon EC2&lt;/b&gt; 인스턴스에서 정상 연결을 확인할 수 있다. 방법은 아래와 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;# Amazon Linux에 Atlas CLI 저장소 등록
$ sudo nano /etc/yum.repos.d/mongodb-org-7.0.repo
[mongodb-org-7.0]
name=MongoDB Repository
baseurl=https://repo.mongodb.org/yum/amazon/2/mongodb-org/7.0/x86_64/
gpgcheck=1
enabled=1
gpgkey=https://pgp.mongodb.com/server-7.0.asc

# Atlas CLI 설치
$ sudo yum install mongodb-atlas -y

# Atlas CLI 버전 확인
$ atlas --version
atlascli version: 1.19.0

# MongoDB Atlas 디비 피어링 연결 확인
$ mongosh &quot;mongodb+srv://{db-admin-username}:{db-admin-password}@{db-srv}&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;새 유저 생성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Atlas CLI&lt;/code&gt;를 이용하여 새로운 유저 계정을 생성할 수 있다. 아래는 새로운 유저를 생성하고 &lt;b&gt;readWrite&lt;/b&gt;, &lt;b&gt;dbAdmin&lt;/b&gt; 2개 권한을 부여하는 예이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;# 새 유저 계정 생성
$ atlas dbusers create --username {new_username} --password {password} --role readWrite@{mongodb-atlas-db-name} --role dbAdmin@{mongodb-atlas-db-name} --projectId {mongodb-atlas-project-id}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고 글&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.atlas.mongodb.com/sizing-tier-selection/#cluster-auto-scaling&quot;&gt;Atlas Sizing and Tier Selection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.mongodb.com/blog/post/introducing-vpc-peering-for-mongodb-atlas&quot;&gt;Introducing VPC Peering for MongoDB Atlas&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>SW 개발</category>
      <author>지단로보트</author>
      <guid isPermaLink="true">https://jsonobject.tistory.com/542</guid>
      <comments>https://jsonobject.tistory.com/542#entry542comment</comments>
      <pubDate>Mon, 8 Apr 2024 14:55:14 +0900</pubDate>
    </item>
    <item>
      <title>WSL 2, 안드로이드 개발 환경 구성하기</title>
      <link>https://jsonobject.tistory.com/635</link>
      <description>&lt;h3&gt;개요&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Windows 11&lt;/strong&gt;을 호스트 머신으로 하는 &lt;strong&gt;WSL 2&lt;/strong&gt; 환경에서 안드로이드 앱 개발 환경을 구성하고, 안드로이드 기기를 연결하는 방법을 정리했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;WSL 2 안드로이드 개발 필수 준비물&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;WSL 2&lt;/strong&gt;에서 안드로이드 앱 개발을 하려면 호스트가 되는 &lt;strong&gt;Windows 11&lt;/strong&gt;에는 &lt;code&gt;USBIPD&lt;/code&gt;, &lt;strong&gt;WSL 2&lt;/strong&gt;에는 &lt;code&gt;adb&lt;/code&gt;, &lt;code&gt;Android Studio&lt;/code&gt;를 설치해야 한다.아래와 같이 차례대로 실행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# Android Studio 설치
$ sudo snap install android-studio --classic

# Android Studio 바로가기 생성
$ nano ~/.bash_alises
alias studio=&amp;quot;/snap/bin/android-studio &amp;gt; /dev/null 2&amp;gt;&amp;amp;1 &amp;amp;&amp;quot;
alias studio.=&amp;quot;/snap/bin/android-studio . &amp;gt; /dev/null 2&amp;gt;&amp;amp;1 &amp;amp;&amp;quot;

# Android Studio 첫 실행 후 안내에 따라 Andorid SDK Command-line Tools 설치
Settings &amp;gt; Languange &amp;amp; Frameworks &amp;gt; Android SDK &amp;gt; SDK Tools &amp;gt; Andorid SDK Command-line Tools

# Windows 11에서 Android ADB Interface 드라이버 설치
https://developer.android.com/studio/run/win-usb
https://developer.samsung.com/android-usb-driver

# Windows 11에서 USBIPD 설치
$ winget install usbipd

# WSL 2에서 adb 설치
$ sudo apt-get install adb -y
$ adb kill-server
$ adb start-server

# adb 실행 확인
$ adb devices
List of devices attached&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;WSL 2에서 유선 연결된 안드로이드 기기 인식&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;WSL 2&lt;/strong&gt;는 호스트 &lt;strong&gt;PC&lt;/strong&gt;에 연결된 안드로이드 기기를 인식하지 못한다. &lt;code&gt;USBIPD&lt;/code&gt;를 이용하면 &lt;strong&gt;USB&lt;/strong&gt; 트래픽을 &lt;strong&gt;WSL 2&lt;/strong&gt;로 포워딩하여 기기를 인식할 수 있다. 아래와 같이 차례대로 실행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# 안드로이드 기기에서 개발자 모드 활성화 후 케이블로 PC와 연결
설정 &amp;gt; 휴대전화 정보 &amp;gt; 소프트웨어 정보 &amp;gt; [빌드번호] 7번 연속 탭
설정 &amp;gt; 휴대전화 정보 &amp;gt; 개발자 옵션 &amp;gt; [USB 디버깅] 켜기 &amp;gt; [USB 디버깅 권한 승인 취소]

# Windows 11에서 연결된 안드로이드 기기 확인
$ usbipd list
Connected:
BUSID  VID:PID    DEVICE                                               STATE
1-1    1111:1111  S24, SAMSUNG Mobile USB Modem, SAMSUNG Mobile US...  Not shared

# Windows 11에서 연결된 안드로이드 기기를 WSL 2에 포워딩
# 포워딩 시점에 안드로이드 기기에 권한 승인 창에 뜨면 확인
$ usbipd bind -b 1-1
$ usbipd attach --wsl=Ubuntu --busid=1-1

# WSL 2에서 연결된 안드로이드 기기 확인
$ adb devices
List of devices attached
R3CX308V3AJ     device&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;WSL 2&lt;/strong&gt;에서 &lt;code&gt;adb devices&lt;/code&gt; 명령으로 연결된 안드로이드 기기가 조회되면 인식 성공이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;WSL 2에 Flutter 설치&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Flutter&lt;/code&gt; 개발 환경은 아래와 같이 설치할 수 있다. 앞서 &lt;code&gt;adb devices&lt;/code&gt; 명령어로 확인된 연결 기기를 &lt;code&gt;flutter devices&lt;/code&gt; 명령에서도 확인할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Flutter 설치
$ sudo snap install flutter --classic
$ flutter sdk-path
$ flutter doctor --android-licenses

# 정상 설치 여부 확인
$ flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.19.4, on Ubuntu 22.04.4 LTS 5.15.146.1-microsoft-standard-WSL2, locale C.UTF-8)
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
[✓] Chrome - develop for the web
[✓] Linux toolchain - develop for Linux desktop
[✓] Android Studio (version 2023.2)
[✓] Connected device (3 available)
[✓] Network resources

• No issues found!

# Flutter 연결 기기 확인
$ flutter devices
Found 3 connected devices:
  SM S921N (mobile) • 172.10.5.39:5555 • android-arm64  • Android 14 (API 34)
  Linux (desktop)   • linux            • linux-x64      • Ubuntu 22.04.4 LTS 5.15.146.1-microsoft-standard-WSL2
  Chrome (web)      • chrome           • web-javascript • Google Chrome 123.0.6312.58&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;참고 글&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://devblogs.microsoft.com/commandline/connecting-usb-devices-to-wsl/&quot;&gt;Connecting USB devices to WSL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/dorssel/usbipd-win&quot;&gt;USBIPD&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>SW 개발</category>
      <author>지단로보트</author>
      <guid isPermaLink="true">https://jsonobject.tistory.com/635</guid>
      <comments>https://jsonobject.tistory.com/635#entry635comment</comments>
      <pubDate>Tue, 26 Mar 2024 14:50:23 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin, 쉘 스크립트 작성하기</title>
      <link>https://jsonobject.tistory.com/495</link>
      <description>&lt;h3&gt;개요&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bash&lt;/strong&gt;는 운영 환경에서 시스템 엔지니어 및 개발자들에게 있어 가장 널리 사용되고 익숙한 스크립트 인터프리터이지만 동시에 가장 불편한 스크립트 언어를 제공한다. 수년간 개발 진영은 &lt;strong&gt;Type-Safe&lt;/strong&gt;를 보장하는 언어가 생산성과 유지보수에 있어 강세를 보이면서, 프론트엔드에서는 &lt;strong&gt;TypeScript&lt;/strong&gt;가, 백엔드에서는 &lt;code&gt;Kotlin&lt;/code&gt;이 점유율을 높이고 있다. 그렇다면 &lt;strong&gt;Kotlin&lt;/strong&gt;으로 쉘 스크립트를 작성하면 어떨까? 이미 &lt;strong&gt;Node.js&lt;/strong&gt;는 쉘 스크립트에 널리 사용되고 있다. 이번 글에서는 &lt;strong&gt;Kotlin&lt;/strong&gt;으로 쉘 스크립트를 작성하는 방법을 설명하자고 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;kscript 설치&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;kscript&lt;/code&gt;는 &lt;strong&gt;Kotlin&lt;/strong&gt; 언어로 쉘 스크립트를 작성하게 해주는 유용한 보조 도구이다. &lt;strong&gt;JVM&lt;/strong&gt;의 단점을 극복하기 위한 스크립트 캐시 등 여러 유용한 도구를 제공한다. 아래와 같이 설치한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# SDKMAN 설치
$ curl -s &amp;quot;https://get.sdkman.io&amp;quot; | bash
$ source &amp;quot;$HOME/.sdkman/bin/sdkman-init.sh&amp;quot;

$ nano $HOME/.bash_profile
export SDKMAN_DIR=&amp;quot;$HOME/.sdkman&amp;quot;
[[ -s &amp;quot;$HOME/.sdkman/bin/sdkman-init.sh&amp;quot; ]] &amp;amp;&amp;amp; source &amp;quot;$HOME/.sdkman/bin/sdkman-init.sh&amp;quot;

# kscript 설치
$ sdk install kotlin
$ sdk install kscript

# 설치된 버전 확인
$ kscript -v
Version   : 4.2.3
Build     : 2023-07-22T13:06:02.327407526Z
Kotlin    : 1.9.23-release-779
Java      : JRE 21.0.1+12&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;kscript 예제 작성&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;이제 스크립트를 작성해 볼 차례이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 스크립트 작성
$ nano hello.kts
#!/usr/bin/env kscript
println(&amp;quot;Hello, World!&amp;quot;)

# 스크립트에 실행 권한 부여
$ chmod +x hello.kts

# 스크립트 실행
$ ./hello.kts
Hello, World!

# IntelliJ IDEA로 편집
$ kscript --idea hello.kts&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;라이브러리 추가 예제&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;스크립트의 시작 부분에 &lt;code&gt;@file:DependsOn()&lt;/code&gt;를 사용하면 &lt;strong&gt;Maven&lt;/strong&gt; 저장소의 원격 라이브러리를 사용할 수 있다. 아래는 &lt;strong&gt;TSID&lt;/strong&gt; 클래스를 외부 라이브러리에서 임포트하여 사용한 예제이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;$ nano tsid.kts
#!/usr/bin/env kscript
@file:DependsOn(&amp;quot;io.hypersistence:hypersistence-utils-hibernate-60:3.7.3&amp;quot;)

import io.hypersistence.tsid.TSID

val tsid = TSID.fast()
println(tsid.toLong())
println(tsid.toString())
println(tsid.toLowerCase())
println(tsid.instant)&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;외부 명령어 실행 예제&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;아래와 같이 스크립트를 작성하면 스크립트 내에서 외부 명령어를 실행할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/usr/bin/env kscript
import java.io.File

when (&amp;quot;ls -al&amp;quot;.exec()) {
    0 -&amp;gt; println(&amp;quot;SUCCESS&amp;quot;)
    else -&amp;gt; {
        println(&amp;quot;FAILED&amp;quot;)
        kotlin.system.exitProcess(1) 
    }
}

// 실행 결과를 반환 (0:성공, 나머지:실패)
fun String.exec(cwd: File? = null): Int {
    return ProcessBuilder(*split(&amp;quot; &amp;quot;).toTypedArray())
            .redirectErrorStream(true)
            .inheritIO()
            .directory(cwd)
            .start()
            .waitFor()
}

// 실행 문자열을 반환, 실패시 IOException 발생
fun String.exec(cwd: File? = null): String? {
    val parts = this.split(&amp;quot;\\s&amp;quot;.toRegex())
    val proc = ProcessBuilder(*parts.toTypedArray())
            .directory(cwd)
            .redirectOutput(ProcessBuilder.Redirect.PIPE)
            .redirectError(ProcessBuilder.Redirect.PIPE)
            .start()
    proc.waitFor()
    return proc.inputStream.bufferedReader().readText()
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;참고 글&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/holgerbrandl/kscript&quot;&gt;kscript - Having fun with Kotlin scripting&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/outbrain-engineering/type-safety-in-the-shell-with-kscript-7dd40d022537&quot;&gt;Type-safety in the shell with kscript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://craigrussell.io/2019/02/scripting-with-kotlin-kscript/&quot;&gt;Scripting with Kotlin - kscript&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>SW 개발</category>
      <author>지단로보트</author>
      <guid isPermaLink="true">https://jsonobject.tistory.com/495</guid>
      <comments>https://jsonobject.tistory.com/495#entry495comment</comments>
      <pubDate>Wed, 20 Mar 2024 17:57:20 +0900</pubDate>
    </item>
    <item>
      <title>데이터베이스 PK로 TSID 사용하기</title>
      <link>https://jsonobject.tistory.com/634</link>
      <description>&lt;h3&gt;개요&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TSID&lt;/code&gt;는 &lt;strong&gt;Time-Sorted Unique Identifier&lt;/strong&gt;의 약자로 기존의 &lt;strong&gt;UUID&lt;/strong&gt;를 대체할 목적으로 탄생했다. 이름이 의미하듯이 최소 크기의 랜덤 문자열로서 생성 시간 순으로 정렬 가능한 것이 특징이다. 작은 공간을 차지하면서 동시에 생성 시간순으로 정렬이 가능하기에 데이터베이스의 &lt;strong&gt;AUTO INCREMENT ID&lt;/strong&gt;에 대한 대체제로 적합하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;TSID 특징&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;64 bit 정수(=18자 문자열) 또는 13자의 URL-safe한 &lt;strong&gt;Base 32&lt;/strong&gt; 문자열로 표현할 수 있다. (나는 53 bit 이상의 정수를 처리할 수 없는 &lt;strong&gt;JavaScript&lt;/strong&gt; 기반의 클라이언트를 고려하여 후자를 선호한다. &lt;strong&gt;UUID&lt;/strong&gt; 대비 65%을 절약할 수 있다.)&lt;/li&gt;
&lt;li&gt;값의 생성 주체가 애플리케이션이기 때문에 데이터가 저장되기 전에 값을 미리 알 수 있다.&lt;/li&gt;
&lt;li&gt;1/1000초 단위의 시간 정보와 무작위 정보로 조합되어 고유성이 보장된다. 따라서 데이터베이스의 수평 확장 및 여러 지역에서의 동시 전개가 용이하다.&lt;/li&gt;
&lt;li&gt;무작위 정보가 포함되어 연속적 관계를 파악하기 힘들다. 반면에 생성 일시 순으로 정렬이 가능하여 &lt;strong&gt;B+Tree&lt;/strong&gt; 인덱스의 성능 이점을 누릴 수 있다. 값 자체에서 생성 일시를 획득하는 것도 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;build.gradle.kts&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;프로젝트 루트의 &lt;strong&gt;build.gradle.kts&lt;/strong&gt;에 아래 내용을 추가한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;dependencies {
    implementation(&amp;quot;io.hypersistence:hypersistence-utils-hibernate-60:3.7.3&amp;quot;)
}&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;TSID 생성&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TSID&lt;/strong&gt;는 아래와 같이 생성할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// TSID 생성
// 실행 시점1/1000초 단위로 저장
val tsid = TSID.fast()

// Long 타입으로 출력 (64비트 고정 길이)
// 556028620837202474
tsid.toLong()

// String 타입의 대문자로 출력 (13자 고정 길이)
0FDV92PPZ7AHA
tsid.toString()

// String 타입의 소문자로 출력 (13자 고정 길이)
// 0fdv92ppz7aha
tsid.toLowerCase()

// 저장된 Instant 오브젝트 획득
// 2024-03-14T08:19:13.719Z
tsid.instant

// TSID 문자열로 부터 TSID 오브젝트를 복원
TSID.from(tsid.toString())&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;JPA 엔티티에 TSID 적용&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;JPA&lt;/strong&gt; 엔티티는 아래와 같이 &lt;strong&gt;PK&lt;/strong&gt;로 &lt;strong&gt;TSID&lt;/strong&gt;가 자동 생성되도록 설계할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;import io.hypersistence.utils.hibernate.id.Tsid
import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.Id
import jakarta.persistence.Table
import java.io.Serializable

@Entity
@Table(name = &amp;quot;foo&amp;quot;)
class Foo : Serializable {

    // [방법 1] 64 bit 정수 TSID 생성
    @Id
    @Tsid
    @Column
    var id: Long? = null

    // [방법 2] 13자 Base 32 문자열 TSID 생성
    @Id
    @Tsid
    @Column(columnDefinition = &amp;quot;CHAR(13)&amp;quot;)
    var id: String? = null
    ...
}&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;String&lt;/strong&gt; 타입의 &lt;strong&gt;TSID&lt;/strong&gt;를 사용할 경우 &lt;strong&gt;DDL&lt;/strong&gt;로 표현하면 아래와 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;CREATE TABLE `foo` (
  `id` CHAR(13) NOT NULL,
  ...
  PRIMARY KEY (`id`) USING BTREE
)
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_0900_ai_ci;&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;실제 데이터를 생성해보면 아래와 같이 생성되어 시간 순으로 조회 가능한 것을 확인할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;#id, TSID.from(id).instant
0FGA6Z3X7BRPW, 2024-03-22T00:15:00.072115Z
0FGA6Z3TFBRQR, 2024-03-22T00:15:00.050725Z
0FGA6Z3RKBRPP, 2024-03-22T00:15:00.035553Z
0FGA6Z3PZBRPH, 2024-03-22T00:15:00.022884Z
0FGA6Z3NQBRQQ, 2024-03-22T00:15:00.011920Z&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;TSID 생성 스크립트 제작&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;리눅스 콘솔에서 급하게 &lt;strong&gt;TSID&lt;/strong&gt; 생성이 필요할 수 있다. 평소 아래와 같이 스크립트를 만들어두면 편리하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# SDKMAN으로 kotlin, kscript 설치
$ sdk install kotlin
$ sdk install kscript

$ nano tsid.kts
#!/usr/bin/env kscript
@file:DependsOn(&amp;quot;io.hypersistence:hypersistence-utils-hibernate-60:3.7.3&amp;quot;)

import io.hypersistence.tsid.TSID

val tsid = TSID.fast()
println(tsid.toLong())
println(tsid.toString())
println(tsid.toLowerCase())
println(tsid.instant)

$ chmod +x tsid.kts

$ ./tsid.kts
558185822630215770
0FFRK1HE8H02T
0ffrk1he8h02t
2024-03-20T07:11:10.706Z&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;참고 글&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/vladmihalcea/hypersistence-tsid&quot;&gt;GitHub - TSID Generator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.foxhound.systems/blog/time-sorted-unique-identifiers/&quot;&gt;TSIDs strike the perfect balance between integers and UUIDs for most databases&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>SW 개발</category>
      <author>지단로보트</author>
      <guid isPermaLink="true">https://jsonobject.tistory.com/634</guid>
      <comments>https://jsonobject.tistory.com/634#entry634comment</comments>
      <pubDate>Tue, 19 Mar 2024 09:28:01 +0900</pubDate>
    </item>
  </channel>
</rss>