<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ko"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://hyuck0221.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://hyuck0221.github.io/" rel="alternate" type="text/html" hreflang="ko" /><updated>2026-02-25T05:26:43+00:00</updated><id>https://hyuck0221.github.io/feed.xml</id><title type="html">Hshim Blog</title><subtitle>개발 관련 경험과 학습 내용을 공유하는 개발 블로그입니다.</subtitle><author><name>hyuck0221</name></author><entry><title type="html">[APIsis] 오픈소스 openAPI 서버</title><link href="https://hyuck0221.github.io/apisis/api/2026/02/25/apisis.html" rel="alternate" type="text/html" title="[APIsis] 오픈소스 openAPI 서버" /><published>2026-02-25T00:00:00+00:00</published><updated>2026-02-25T00:00:00+00:00</updated><id>https://hyuck0221.github.io/apisis/api/2026/02/25/apisis</id><content type="html" xml:base="https://hyuck0221.github.io/apisis/api/2026/02/25/apisis.html"><![CDATA[<p>이 포스팅은 <a href="https://apisis.dev">APIsis</a>에 대한 설명을 담고 있습니다 (<a href="https://github.com/hyuck0221/apisis">github</a>)</p>

<hr />

<h2 id="apisis-란">APIsis 란?</h2>

<p>생활 속 다양한 데이터를 API 형태로 제공해주는 openAPI 서비스<br />
API 서버, 키 관리 페이지가 하나의 서버로 실행됩니다 <br />
또한 모든게 오픈소스로 도커 파일이 제공되어 API 서버를 로컬에 띄워 내부에서 사용할 수도 있습니다</p>

<h2 id="구성">구성</h2>

<h3 id="api">API</h3>

<p>관리중인 API들을 제공하는 서버입니다<br />
API Key를 통해 계정 인증을 하고 라이센스에 맞게 API 별 사용량을 제공합니다</p>

<h3 id="관리-페이지-api">관리 페이지 API</h3>

<p>제공용 API가 아닌 내부 관리 페이지 전용 API 입니다 <br />
계정 관리와 사용량 통계 및 분석 등 호출에 필요한 키를 관리하는 데 중점인 API 이며, 사용자 입장에선 사용하지 않는 내부용 API입니다</p>

<p>계정의 경우 총 3가지(구글, github, 카카오)의 oauth2 로그인을 지원합니다<br />
이메일 계정이 같을 경우 하나의 계정으로 판별되며 계정 별 라이센스를 가질 수 있습니다</p>

<p>라이센스는 xxxx-xxxx-xxxx-xxxx 형태의 코드를 가지고 있으며, 플랜 별로 만료일과 사용량이 달라집니다 <br />
라이센스는 크게 4가지로 나눠져 있으며 EXTRA 플랜을 제외하곤 모두 월별, 연별 만료일 플랜이 존재합니다</p>

<ul>
  <li>BASIC: 라이센스가 없는 사용량보단 훨씬 많은 사용량을 제공하나 많진 않은 플랜</li>
  <li>PRO: 실 사용에 문제 없는 사용량을 제공하는 플랜</li>
  <li>ENTERPRISE: 대규모 운영 환경 사용에 문제 없는 사용량을 제공하는 플랜</li>
  <li>EXTRA: 무제한 플랜</li>
</ul>

<p>라이센스는 계정 당 하나만 소유할 수 있고 라이센스 자체를 삭제할 순 없습니다 <br />
계정에서 라이센스 해제는 가능하나 다른 계정에서 해당 코드를 적용하면 적용할 수 있는 구조로 설계되어 있습니다</p>

<h3 id="mcp">MCP</h3>

<p>자체 MCP 서버도 자동 실행되어 SSE 방식으로 MCP 연결이 가능합니다 <br />
API 정보들을 AI에게 제공해주어 바이브 코딩 시 API 연결에도 유용하게 사용할 수 있습니다</p>

<h3 id="최초-실행-데이터-세팅-프로세스">최초 실행 데이터 세팅 프로세스</h3>

<p>APIsis Application 최초 실행 시 데이터베이스에 데이터 세팅이 필요합니다 <br />
이를 자동화 하기 위해 자동 데이터 세팅 프로세스가 설계되었으며 서버 실행 시 데이터가 없는 기능에 한하여 실행됩니다</p>

<p>데이터 별로 처리 방식이 모두 다릅니다. 현재까지의 데이터는 아래와 같이 처리됩니다</p>
<ul>
  <li>방탈출
    <ul>
      <li>방식: 외부(빠방) API 호출</li>
      <li>프로세스 시점: 방탈출 table에 데이터가 하나도 없을 경우</li>
      <li>시나리오: Application 실행 &gt; 방탈출 table select &gt; (비어있을 경우) &gt; 빠방 API 호출 (Paging) &gt; DB insert &gt; 반복</li>
    </ul>
  </li>
  <li>법정동 코드
    <ul>
      <li>방식: excel 파싱</li>
      <li>프로세스 시점: 법정동 코드 table에 데이터가 하나도 없을 경우</li>
      <li>시나리오: Application 실행 &gt; 법정동 코드 table select &gt; (비어있을 경우) &gt; /excel/areacode_01.xlsx 파싱 &gt; DB insert</li>
    </ul>
  </li>
  <li>로또
    <ul>
      <li>방식: 외부 API 호출</li>
      <li>프로세스 시점: 로또 table에 데이터가 하나도 없을 경우</li>
      <li>시나리오: Application 실행 &gt; 로또 table select &gt; (비어있을 경우) &gt; 로또 API 호출 &gt; DB insert &gt; 반복</li>
    </ul>
  </li>
</ul>

<h3 id="데이터-업데이트-스케줄러">데이터 업데이트 스케줄러</h3>

<p>최신 데이터 제공을 위해 특정 데이터들은 스케줄러로 데이터 업데이트를 진행합니다</p>
<ul>
  <li>방탈출
    <ul>
      <li>데이터 추가
        <ul>
          <li>실행주기: 매일 오전 1시</li>
          <li>프로세스: 최근 빠방에 등록된 방탈출 카페/테마 insert</li>
        </ul>
      </li>
      <li>데이터 업데이트
        <ul>
          <li>실행주기: 매일 오전 3시</li>
          <li>프로세스: 매일 500개 치 테마 update. ref_id 기준 1~500, 다음날 501~1000 이런식으로 매일 500개씩 업데이트 하여 별점과 운영 여부 등 데이터를 업데이트</li>
        </ul>
      </li>
    </ul>
  </li>
  <li>로또
    <ul>
      <li>실행주기: 매주 토요일 오후 9시</li>
      <li>프로세스: 최근 공개된 로또번호 insert</li>
    </ul>
  </li>
</ul>

<h3 id="관리-페이지">관리 페이지</h3>

<p>패키지에 html도 포함되어 있어 서버 실행 시 관리 페이지도 함께 띄워집니다<br />
서버 도메인 경로로 접속 시 랜딩페이지가 표시됩니다</p>

<p><img src="../assets/images/post/apisis/page_1.png" alt="page_1.png" />
<img src="../assets/images/post/apisis/page_2.png" alt="page_2.png" />
총 3가지의 oauth2 로그인을 지원합니다 <br />
(직접 서버 실행 시 별도의 oauth 설정이 필요합니다)</p>

<p>로그인 후 접속하면 대시보드가 표시됩니다<br />
전체적인 통계와 API 키 관리 그리고 여러가지 가이드들이 제공됩니다
<img src="../assets/images/post/apisis/dashboard_1.png" alt="dashboard_1.png" /></p>

<ul>
  <li>API Key 관리: 대시보드보다 더욱 자세한 키 별 통계도 확인할 수 있습니다</li>
  <li>놀이터: 서버에 있는 API들을 직접 호출해볼 수 있는 화면입니다. 생성한 API Key로 직접 호출해보며 결과를 바로 확인해보실 수 있습니다 (사용량 소모됨)</li>
  <li>API 목록: 서버에 있는 API들을 찾아볼 수 있는 화면입니다</li>
  <li>문서: API들의 상세한 request, response 정보를 볼 수 있습니다</li>
  <li>사용량 통계: API 호출에 추이를 그래프 등 여러 지표로 제공합니다</li>
  <li>분석: 사용량 통계를 베이스로 AI가 사용 현황 보고서를 작성해줍니다</li>
  <li>설정: 여러 설정들을 관리합니다. 라이센스, 분석, 데이터 삭제 등 옵션을 제공합니다</li>
</ul>

<h2 id="마치며">마치며</h2>

<p>openAPI 서버를 운영해보니 몇몇 개인 프로젝트에도 도입을 해봤습니다<br />
미리 필요한 데이터를 APIsis에 등록해두고 프로젝트에 AI로 연결해달라 하면 뚝딱 연결해주는게 생각보다 편하네요<br />
물론 개인 프로젝트에서 직접 연결해서 써도 되지만, 범용적으로 쓸 수 있게 미리 설계해두면 나중에 유용하게 쓰일 것으로 예상됩니다 (사실상 나만의 API 게이트웨이)</p>

<p>개인 프로젝트 중 완성도 높은 프로젝트가 아닐까 싶습니다 <br />
무엇보다 서비스 이름이 너무 마음에 드는것도 한 몫 하네요. API oasis… 헤헤헤헤</p>

<hr />]]></content><author><name>hyuck0221</name></author><category term="APIsis" /><category term="API" /><category term="개발" /><category term="API" /><category term="MCP" /><summary type="html"><![CDATA[이 포스팅은 APIsis에 대한 설명을 담고 있습니다 (github)]]></summary></entry><entry><title type="html">[kemi] 이미지 생성 업데이트</title><link href="https://hyuck0221.github.io/kemi/%EA%B0%9C%EB%B0%9C/kotlin/%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC/2026/02/19/kemi-0.2.1.html" rel="alternate" type="text/html" title="[kemi] 이미지 생성 업데이트" /><published>2026-02-19T00:00:00+00:00</published><updated>2026-02-19T00:00:00+00:00</updated><id>https://hyuck0221.github.io/kemi/%EA%B0%9C%EB%B0%9C/kotlin/%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC/2026/02/19/kemi-0.2.1</id><content type="html" xml:base="https://hyuck0221.github.io/kemi/%EA%B0%9C%EB%B0%9C/kotlin/%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC/2026/02/19/kemi-0.2.1.html"><![CDATA[<p>이 포스팅은 <a href="https://github.com/hyuck0221/kemi">kemi 라이브러리</a>에 대한 설명을 담고 있습니다 <br />
<a href="https://hyuck0221.github.io/%EA%B0%9C%EB%B0%9C/kotlin/%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC/2025/12/22/kemi.html">지난 포스팅</a>에 이어 0.2.1 버전에 대해 다룹니다</p>

<hr />

<h3 id="추가-기능-021-기준">추가 기능 (0.2.1 기준)</h3>

<ul>
  <li>Image Generator 추가</li>
  <li>이미지와 함께 질의응답</li>
</ul>

<h3 id="지원-버전">지원 버전</h3>

<p>(0.1.0 과 동일합니다)</p>

<ul>
  <li>JVM 8+</li>
  <li>Kotlin 1.9+</li>
  <li>Spring Boot 2.7+</li>
  <li>Spring 5.3+</li>
</ul>

<h2 id="라이브러리-설정">라이브러리 설정</h2>

<p>jitpack을 통해 배포되었습니다<br />
아래와 같이 jitpack.io repository url 정의가 선행되어야 합니다</p>

<h4 id="buildgradlekts">build.gradle.kts</h4>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">repositories</span> <span class="p">{</span>
    <span class="nf">mavenCentral</span><span class="p">()</span>
    <span class="nf">maven</span> <span class="p">{</span> <span class="n">url</span> <span class="p">=</span> <span class="nf">uri</span><span class="p">(</span><span class="s">"https://jitpack.io"</span><span class="p">)</span> <span class="p">}</span>
<span class="p">}</span>

<span class="nf">dependencies</span> <span class="p">{</span>
    <span class="nf">implementation</span><span class="p">(</span><span class="s">"com.github.hyuck0221:kemi:0.2.1"</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<h4 id="applicationyml">application.yml</h4>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">kemi</span><span class="pi">:</span>
  <span class="na">gemini</span><span class="pi">:</span>
    <span class="na">api-keys</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">your-first-api-key</span>
      <span class="pi">-</span> <span class="s">your-second-api-key</span>
      <span class="pi">-</span> <span class="s">your-third-api-key</span>
    <span class="na">base-url</span><span class="pi">:</span> <span class="s">https://generativelanguage.googleapis.com</span>
    <span class="na">models</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">gemini-2.5-flash</span>
      <span class="pi">-</span> <span class="s">gemini-2.5-flash-lite</span>
      <span class="pi">-</span> <span class="s">gemini-2.0-flash</span>
      <span class="pi">-</span> <span class="s">gemini-2.0-flash-lite</span>
    <span class="na">image-models</span><span class="pi">:</span> <span class="c1"># NEW!</span>
      <span class="pi">-</span> <span class="s">gemini-2.5-flash-image</span>
      <span class="pi">-</span> <span class="s">gemini-3-pro-image-preview</span>
</code></pre></div></div>

<ul>
  <li>image-models (선택)
    <ul>
      <li>Gemini 이미지 생성 AI Model</li>
      <li>사용량 만료 시 다음 model로 자동 fallback</li>
      <li>미입력 시 gemini-2.5-flash-image, gemini-3-pro-image-preview 적용</li>
    </ul>
  </li>
</ul>

<h2 id="이미지-생성-사용법">이미지 생성 사용법</h2>

<p>Bean에 등록된 Generator를 불러와 호출합니다<br /></p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nn">com.hshim.kemi.GeminiImageGenerator</span>
<span class="k">import</span> <span class="nn">org.springframework.stereotype.Service</span>

<span class="nd">@Service</span>
<span class="kd">class</span> <span class="nc">ImageService</span><span class="p">(</span><span class="k">private</span> <span class="kd">val</span> <span class="py">geminiImageGenerator</span><span class="p">:</span> <span class="nc">GeminiImageGenerator</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">fun</span> <span class="nf">generateImage</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// 이미지 생성</span>
        <span class="kd">val</span> <span class="py">images</span> <span class="p">=</span> <span class="n">geminiImageGenerator</span><span class="p">.</span><span class="nf">generateImage</span><span class="p">(</span>
            <span class="n">prompt</span> <span class="p">=</span> <span class="s">"산 위로 지는 아름다운 석양"</span>
        <span class="p">)</span>
    <span class="p">}</span>

    <span class="k">fun</span> <span class="nf">generateWithOptions</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// 커스텀 종횡비와 해상도로 생성</span>
        <span class="kd">val</span> <span class="py">images</span> <span class="p">=</span> <span class="n">geminiImageGenerator</span><span class="p">.</span><span class="nf">generateImage</span><span class="p">(</span>
            <span class="n">prompt</span> <span class="p">=</span> <span class="s">"밤의 미래 도시"</span><span class="p">,</span>
            <span class="n">aspectRatio</span> <span class="p">=</span> <span class="s">"16:9"</span><span class="p">,</span>
            <span class="n">imageSize</span> <span class="p">=</span> <span class="s">"4K"</span>
        <span class="p">)</span>
    <span class="p">}</span>

    <span class="k">fun</span> <span class="nf">generateWithReferenceImages</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// 스타일 가이드를 위한 참조 이미지와 함께 생성</span>
        <span class="kd">val</span> <span class="py">referenceImage</span> <span class="p">=</span> <span class="nc">ImageData</span><span class="p">.</span><span class="nf">fromPath</span><span class="p">(</span><span class="s">"style-reference.jpg"</span><span class="p">)</span>

        <span class="kd">val</span> <span class="py">images</span> <span class="p">=</span> <span class="n">geminiImageGenerator</span><span class="p">.</span><span class="nf">generateImage</span><span class="p">(</span>
            <span class="n">prompt</span> <span class="p">=</span> <span class="s">"이 예술적 스타일로 산 풍경을 그려주세요"</span><span class="p">,</span>
            <span class="n">aspectRatio</span> <span class="p">=</span> <span class="s">"3:2"</span><span class="p">,</span>
            <span class="n">imageSize</span> <span class="p">=</span> <span class="s">"2K"</span><span class="p">,</span>
            <span class="n">referenceImages</span> <span class="p">=</span> <span class="nf">listOf</span><span class="p">(</span><span class="n">referenceImage</span><span class="p">)</span>
        <span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>image-models에 설정된 모델로 이미지를 생성합니다<br />
생성된 이미지는 Base64로 인코딩 된 형태로 전송받습니다</p>

<h2 id="이미지-분석">이미지 분석</h2>

<p>기본 질의응답에 사용되는 GeminiGenerator와 채팅 형태의 GeminiChatGenerator에서 지원합니다
이미지 분석 및 이해를 위해 질문과 함께 이미지를 askWithImages 함수로 전송할 수 있습니다</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import com.hshim.kemi.model.ImageData

@Service
class VisionService(
private val geminiGenerator: GeminiGenerator
) {
fun analyzeImage() {
// 파일에서 이미지 로드
val image = ImageData.fromPath("/path/to/photo.jpg")

        val answer = geminiGenerator.askWithImages(
            question = "이 이미지에 무엇이 있나요?",
            images = listOf(image)
        )
        println(answer)
    }

    fun compareImages() {
        // 여러 이미지를 한 번에 분석
        val image1 = ImageData.fromFile(File("photo1.jpg"))
        val image2 = ImageData.fromFile(File("photo2.png"))

        val answer = geminiGenerator.askWithImages(
            question = "이 두 이미지의 차이점은 무엇인가요?",
            images = listOf(image1, image2)
        )
        println(answer)
    }
}
</code></pre></div></div>

<p><br />
채팅 대화에서 Vision 사용</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Service
class VisionChatService(
private val geminiChatGenerator: GeminiChatGenerator
) {
fun analyzeImagesInConversation() {
val chat = geminiChatGenerator.createSession()

        // 이미지와 함께 첫 메시지
        val image1 = ImageData.fromPath("diagram.png")
        chat.sendMessageWithImages(
            message = "이 다이어그램은 무엇을 보여주나요?",
            images = listOf(image1)
        )

        // 후속 질문 (AI가 이미지 컨텍스트를 기억함)
        chat.sendMessage("세 번째 구성 요소를 설명해 주실 수 있나요?")

        // 같은 대화에서 다른 이미지
        val image2 = ImageData.fromPath("chart.png")
        chat.sendMessageWithImages(
            message = "이 차트는 이전 다이어그램과 어떤 관련이 있나요?",
            images = listOf(image2)
        )
    }

    fun streamVisionResponse() {
        val chat = geminiChatGenerator.createSession()
        val image = ImageData.fromFile(File("complex-image.jpg"))

        // 이미지 분석을 스트리밍 응답으로 받기
        chat.sendMessageStreamWithImages(
            message = "이 이미지를 자세히 설명해주세요",
            images = listOf(image)
        ) { chunk -&gt;
            print(chunk)  // 분석이 생성되는 대로 출력
        }
    }
}
</code></pre></div></div>

<h2 id="마치며">마치며</h2>

<p>개인 프로젝트를 진행하며 이미지 생성이 필요해저 지난번 설계한 kotlin + gemini 라이브러리에 적용 해 봤습니다<br />
kemi 라이브러리의 목적이였던 ‘간단한 AI 호출’ 영역 내에서 이미지 생성까지 지원하게 되니 마음이 한결 편안하네요</p>

<p>이번에 gemini로 이미지 생성을 해보니 이미지 생성은 많이 비싸다는 걸 깨달았습니다<br />
특히 pro 모델은 난발하지 마세요.. (많이 쓰지도 않았는데 금방 10$)</p>

<hr />]]></content><author><name>hyuck0221</name></author><category term="kemi" /><category term="개발" /><category term="Kotlin" /><category term="라이브러리" /><category term="AI" /><category term="Gemini" /><summary type="html"><![CDATA[이 포스팅은 kemi 라이브러리에 대한 설명을 담고 있습니다 지난 포스팅에 이어 0.2.1 버전에 대해 다룹니다]]></summary></entry><entry><title type="html">[callIt] Jetbrains AI 작명 플러그인</title><link href="https://hyuck0221.github.io/callit/%EA%B0%9C%EB%B0%9C/%ED%94%8C%EB%9F%AC%EA%B7%B8%EC%9D%B8/2026/01/05/callit.html" rel="alternate" type="text/html" title="[callIt] Jetbrains AI 작명 플러그인" /><published>2026-01-05T00:00:00+00:00</published><updated>2026-01-05T00:00:00+00:00</updated><id>https://hyuck0221.github.io/callit/%EA%B0%9C%EB%B0%9C/%ED%94%8C%EB%9F%AC%EA%B7%B8%EC%9D%B8/2026/01/05/callit</id><content type="html" xml:base="https://hyuck0221.github.io/callit/%EA%B0%9C%EB%B0%9C/%ED%94%8C%EB%9F%AC%EA%B7%B8%EC%9D%B8/2026/01/05/callit.html"><![CDATA[<p>이 포스팅은 <a href="https://plugins.jetbrains.com/plugin/29428-callit">Jetbrains CallIt Plugin</a>에 대한 설명을 담고 있습니다</p>

<hr />

<h2 id="call-it-플러그인이란">Call It 플러그인이란?</h2>

<p>Jetbrains 기반 IDE 에서 사용할 수 있는 AI 변수명 생성 플러그인입니다<br />
Gemini API Key를 입력하여 빠른 변수명 작명이 가능합니다</p>

<h3 id="지원-기능-10-기준">지원 기능 (1.0 기준)</h3>

<ul>
  <li>최신 Gemini 모델 사용 가능</li>
  <li>5가지 다국어 지원</li>
  <li>프로그래밍 언어 감지하여 맞춤 추천</li>
  <li>동시 n개 변수명 추천</li>
</ul>

<h3 id="지원-ide">지원 IDE</h3>

<p>Android Studio, AppCode, Aqua, CLion, Code With Me Guest, DataSpell, DataGrip, JetBrains Gateway, GoLand, IntelliJ IDEA, JetBrains Client, MPS, PhpStorm, PyCharm, Rider, RubyMine, RustRover, WebStorm, Writerside</p>

<h2 id="플러그인-다운로드">플러그인 다운로드</h2>

<p><a href="https://plugins.jetbrains.com/plugin/29428-callit">Jetbrains CallIt Plugin</a></p>

<p>IDE plugin 화면에서도 찾아보실 수 있습니다</p>

<p><img src="/assets/images/post/callit/plugin.png" alt="plugin.png" /></p>

<h2 id="설정">설정</h2>

<p>Settings &gt; Tools &gt; CallIt &gt; Gemini API Key 입력 &gt; 확인</p>

<p><img src="/assets/images/post/callit/setting.png" alt="setting.png" /></p>

<ul>
  <li>Gemini API Key: google ai studio에서 발급받은 api key</li>
  <li>Gemini Model: 사용 가능한 모델들</li>
  <li>Max Suggestions: 추천 내역 수 <br />(1로 설정 시 즉시 적용)</li>
  <li>message Language: 설정 언어</li>
</ul>

<h2 id="사용법">사용법</h2>

<p>변경하고 싶은 문장 드래그 우클릭 &gt; Suggest Name with CallIt 클릭</p>

<p><img src="/assets/images/post/callit/suggest_photo_1.png" alt="Suggest_photo_1.png" /></p>

<p>원하는 제안 선택 후 OK<br />(Max Suggestions가 1이면 아래 창은 뜨지 않습니다)</p>

<p><img src="/assets/images/post/callit/suggest_photo_2.png" alt="suggest_photo_2.png" /></p>

<h2 id="마치며">마치며</h2>

<p>회사에서 회계 용어들을 변수명으로 지어야 하다 보니 머리를 싸매는 일이 많아졌습니다<br />
이 기능을 하는 플러그인이 이미 많이 존재하겠지만 직접 만들고 싶어 간단하게 구현해본 프로젝트입니다</p>

<p>만들어보면서 플러그인에 구성 방식과 구현 방법 등 어떻게 흘러가는지 알 수 있는 좋은 계기가 되었습니다</p>

<hr />]]></content><author><name>hyuck0221</name></author><category term="callIt" /><category term="개발" /><category term="플러그인" /><category term="AI" /><category term="Gemini" /><category term="Jetbrains" /><summary type="html"><![CDATA[이 포스팅은 Jetbrains CallIt Plugin에 대한 설명을 담고 있습니다]]></summary></entry><entry><title type="html">[kemi] Kotlin + Gemini Support Library</title><link href="https://hyuck0221.github.io/kemi/%EA%B0%9C%EB%B0%9C/kotlin/%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC/2025/12/22/kemi.html" rel="alternate" type="text/html" title="[kemi] Kotlin + Gemini Support Library" /><published>2025-12-22T00:00:00+00:00</published><updated>2025-12-22T00:00:00+00:00</updated><id>https://hyuck0221.github.io/kemi/%EA%B0%9C%EB%B0%9C/kotlin/%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC/2025/12/22/kemi</id><content type="html" xml:base="https://hyuck0221.github.io/kemi/%EA%B0%9C%EB%B0%9C/kotlin/%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC/2025/12/22/kemi.html"><![CDATA[<p>이 포스팅은 <a href="https://github.com/hyuck0221/kemi">kemi 라이브러리</a>에 대한 설명을 담고 있습니다</p>

<hr />

<h2 id="kemi-라이브러리란">Kemi 라이브러리란?</h2>

<p>SpringBoot 환경에서 적은 설정으로 쉽고 빠르게 Gemini 호출을 도와줍니다<br />
간편함을 최우선으로 하여 설계되었고, 빠른 설정과 빠른 호출/관리가 필요한 경우 적합합니다</p>

<h3 id="지원-기능-010-기준">지원 기능 (0.1.0 기준)</h3>

<ul>
  <li>Gemini Auto Configuration 지원</li>
  <li>API key, Model fallback 지원</li>
  <li>Default Prompt 지원</li>
  <li>응답 Class 매핑과 어노테이션 형태의 프롬프트 작성 지원</li>
  <li>채팅형 Generator 지원</li>
  <li>stream 응답 지원</li>
</ul>

<h3 id="지원-버전">지원 버전</h3>

<ul>
  <li>JVM 8+</li>
  <li>Kotlin 1.9+</li>
  <li>Spring Boot 2.7+</li>
  <li>Spring 5.3+</li>
</ul>

<h2 id="라이브러리-설정">라이브러리 설정</h2>

<p>jitpack을 통해 배포되었습니다<br />
아래와 같이 jitpack.io repository url 정의가 선행되어야 합니다</p>

<h4 id="buildgradlekts">build.gradle.kts</h4>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">repositories</span> <span class="p">{</span>
    <span class="nf">mavenCentral</span><span class="p">()</span>
    <span class="nf">maven</span> <span class="p">{</span> <span class="n">url</span> <span class="p">=</span> <span class="nf">uri</span><span class="p">(</span><span class="s">"https://jitpack.io"</span><span class="p">)</span> <span class="p">}</span>
<span class="p">}</span>

<span class="nf">dependencies</span> <span class="p">{</span>
    <span class="nf">implementation</span><span class="p">(</span><span class="s">"com.github.hyuck0221:kemi:0.1.0"</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<h4 id="applicationyml">application.yml</h4>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">kemi</span><span class="pi">:</span>
  <span class="na">gemini</span><span class="pi">:</span>
    <span class="na">api-keys</span><span class="pi">:</span> <span class="c1"># 필수</span>
      <span class="pi">-</span> <span class="s">your-first-api-key</span>
      <span class="pi">-</span> <span class="s">your-second-api-key</span>
      <span class="pi">-</span> <span class="s">your-third-api-key</span>
    <span class="na">base-url</span><span class="pi">:</span> <span class="s">https://generativelanguage.googleapis.com</span>  <span class="c1"># 선택</span>
    <span class="na">models</span><span class="pi">:</span> <span class="c1"># 선택</span>
      <span class="pi">-</span> <span class="s">gemini-2.5-flash</span>
      <span class="pi">-</span> <span class="s">gemini-2.5-flash-lite</span>
      <span class="pi">-</span> <span class="s">gemini-2.0-flash</span>
      <span class="pi">-</span> <span class="s">gemini-2.0-flash-lite</span>
</code></pre></div></div>

<ul>
  <li>api-keys (필수)
    <ul>
      <li>Google AI Studio에서 발급한 API Key</li>
      <li>1개 이상 필수로 등록 필요</li>
      <li>사용량 만료 시 다음 key로 자동 fallback</li>
    </ul>
  </li>
  <li>base-url (선택)
    <ul>
      <li>Gemini API 기본 url</li>
    </ul>
  </li>
  <li>models (선택)
    <ul>
      <li>Gemini AI Model</li>
      <li>사용량 만료 시 다음 model로 자동 fallback</li>
      <li>미입력 시 gemini-2.5-pro 적용</li>
    </ul>
  </li>
</ul>

<h2 id="기본-사용법">기본 사용법</h2>

<p>Bean에 등록된 Generator를 불러와 호출합니다<br />
Generator는 <strong>GeminiGenerator</strong> 와 <strong>GeminiChatGenerator</strong> 로 구분됩니다</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nn">com.hshim.kemi.GeminiGenerator</span>
<span class="k">import</span> <span class="nn">com.hshim.kemi.GeminiChatGenerator</span>
<span class="k">import</span> <span class="nn">org.springframework.stereotype.Service</span>

<span class="nd">@Service</span>
<span class="kd">class</span> <span class="nc">MyService</span><span class="p">(</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">geminiGenerator</span><span class="p">:</span> <span class="nc">GeminiGenerator</span><span class="p">,</span>
    <span class="k">private</span> <span class="kd">val</span> <span class="py">geminiChatGenerator</span><span class="p">:</span> <span class="nc">GeminiChatGenerator</span><span class="p">,</span>
<span class="p">)</span>
</code></pre></div></div>

<h3 id="geminigenerator">GeminiGenerator</h3>

<p>1회성 <strong>질문 -&gt; 출력</strong>이 필요한 경우 사용합니다</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">result</span> <span class="p">=</span> <span class="n">geminiGenerator</span><span class="p">.</span><span class="nf">ask</span><span class="p">(</span><span class="s">"너는 어떤 모델이야?"</span><span class="p">)</span>

<span class="nf">println</span><span class="p">(</span><span class="n">result</span><span class="p">)</span> <span class="c1">// ex. 반가워요! 저는 구글에서 만든 Gemini 2.5 Flash 모델입니다.</span>
</code></pre></div></div>

<h3 id="geminichatgenerator">GeminiChatGenerator</h3>

<p>대화 컨텍스트를 유지해야 할 경우 사용합니다<br />
<strong>질문 -&gt; 출력</strong> 과정을 모두 하나의 세션에 저장되고 다음 질문에선 세션에 저장된 히스토리를 포함하여 요청합니다</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">session</span> <span class="p">=</span> <span class="n">geminiChatGenerator</span><span class="p">.</span><span class="nf">createSession</span><span class="p">()</span>

<span class="kd">val</span> <span class="py">response1</span> <span class="p">=</span> <span class="n">session</span><span class="p">.</span><span class="nf">sendMessage</span><span class="p">(</span><span class="s">"안녕하세요, 제 이름은 John입니다"</span><span class="p">)</span>
<span class="nf">println</span><span class="p">(</span><span class="n">response1</span><span class="p">)</span> <span class="c1">// "안녕하세요 John님! 무엇을 도와드릴까요?"</span>

<span class="kd">val</span> <span class="py">response2</span> <span class="p">=</span> <span class="n">session</span><span class="p">.</span><span class="nf">sendMessage</span><span class="p">(</span><span class="s">"제 이름이 뭐죠?"</span><span class="p">)</span>
<span class="nf">println</span><span class="p">(</span><span class="n">response2</span><span class="p">)</span> <span class="c1">// "당신의 이름은 John입니다"</span>
</code></pre></div></div>

<p>저장, 불러오기를 지원하기 위해 세션 단위로 restore, export가 지원됩니다<br />
세션에서 사용한 api-keys, models, 대화 histories 등 모든 정보가 하나의 object로 관리됩니다</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 세션 저장 과정</span>
<span class="kd">val</span> <span class="py">sessionState</span> <span class="p">=</span> <span class="n">session</span><span class="p">.</span><span class="nf">exportSession</span><span class="p">()</span>
<span class="c1">// ..세션 저장..</span>

<span class="c1">// 세션 불러오기 과정</span>
<span class="c1">// ..세션 불러오기..</span>
<span class="kd">val</span> <span class="py">newSession</span> <span class="p">=</span> <span class="n">geminiChatGenerator</span><span class="p">.</span><span class="nf">restoreSession</span><span class="p">(</span><span class="n">sessionState</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="프롬프트-관리">프롬프트 관리</h2>

<p>모든 질문에 기본으로 적용되는 <strong>정적 시스템 프롬프트</strong>와<br />
매 질문마다 적용할 수 있는 <strong>동적 시스템 프롬프트</strong> 총 두가지로 설정할 수 있습니다</p>

<h3 id="정적-시스템-프롬프트-설정">정적 시스템 프롬프트 설정</h3>

<p>Configuration 에서 Bean 등록 중 prompt를 지정하여 포함시킵니다</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>
<span class="nd">@EnableConfigurationProperties</span><span class="p">(</span><span class="nc">GeminiProperties</span><span class="o">::</span><span class="k">class</span><span class="p">)</span>
<span class="kd">class</span> <span class="nc">GeminiConfiguration</span> <span class="p">{</span>

    <span class="k">private</span> <span class="kd">val</span> <span class="py">prompt</span> <span class="p">=</span> <span class="s">"무조건 한국어로 답할 것"</span>

    <span class="nd">@Bean</span>
    <span class="k">fun</span> <span class="nf">geminiGenerator</span><span class="p">(</span><span class="n">properties</span><span class="p">:</span> <span class="nc">GeminiProperties</span><span class="p">):</span> <span class="nc">GeminiGenerator</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nc">GeminiGenerator</span><span class="p">(</span><span class="n">properties</span><span class="p">,</span> <span class="n">prompt</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="nd">@Bean</span>
    <span class="k">fun</span> <span class="nf">geminiChatGenerator</span><span class="p">(</span><span class="n">properties</span><span class="p">:</span> <span class="nc">GeminiProperties</span><span class="p">):</span> <span class="nc">GeminiChatGenerator</span> <span class="p">{</span>
        <span class="k">return</span> <span class="nc">GeminiChatGenerator</span><span class="p">(</span><span class="n">properties</span><span class="p">,</span> <span class="n">prompt</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="동적-시스템-프롬프트-설정">동적 시스템 프롬프트 설정</h3>

<p>GeminiGenerator: 질문 전송 시 질문과 함께 프롬프트를 전송합니다<br />
GeminiChatGenerator: session 생성 시 프롬프트를 지정하여 메세지 전송 마다 프롬프트를 적용시킵니다</p>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// GeminiGenerator</span>
<span class="n">geminiGenerator</span><span class="p">.</span><span class="nf">ask</span><span class="p">(</span>
    <span class="n">question</span> <span class="p">=</span> <span class="s">"양자 컴퓨팅에 대해 설명해줘"</span><span class="p">,</span>
    <span class="n">prompt</span> <span class="p">=</span> <span class="s">"너는 친절한 물리학 교수야"</span>
<span class="p">)</span>

<span class="c1">// GeminiChatGenerator</span>
<span class="n">geminiChatGenerator</span><span class="p">.</span><span class="nf">createSession</span><span class="p">(</span><span class="s">"욕쟁이 할머니처럼 대답해"</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="stream-응답">Stream 응답</h2>

<p>GeminiGenerator: askStream()<br />
GeminiChatGenerator: sendMessageStream()</p>

<p>handler를 통해 응답을 chunk 단위로 돌려받습니다</p>

<h4 id="geminigenerator-1">GeminiGenerator</h4>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">result</span> <span class="p">=</span> <span class="n">geminiGenerator</span><span class="p">.</span><span class="nf">askStream</span><span class="p">(</span><span class="s">"소설 하나만 만들어줘"</span><span class="p">)</span> <span class="p">{</span> <span class="n">chunk</span> <span class="p">-&gt;</span>
    <span class="nf">print</span><span class="p">(</span><span class="n">chunk</span><span class="p">)</span>  <span class="c1">// 각 단어가 도착하는 즉시 출력</span>
<span class="p">}</span>

<span class="c1">// result 에는 전체 응답 결과가 들어있음</span>
</code></pre></div></div>

<h4 id="geminichatgenerator-1">GeminiChatGenerator</h4>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">session</span><span class="p">.</span><span class="nf">sendMessageStream</span><span class="p">(</span><span class="s">"이야기를 들려주세요"</span><span class="p">)</span> <span class="p">{</span> <span class="n">chunk</span> <span class="p">-&gt;</span>
    <span class="nf">print</span><span class="p">(</span><span class="n">chunk</span><span class="p">)</span>  <span class="c1">// 각 단어가 도착하는 즉시 출력</span>
<span class="p">}</span>

<span class="n">session</span><span class="p">.</span><span class="nf">sendMessageStream</span><span class="p">(</span><span class="s">"주인공의 이름이 뭐였죠?"</span><span class="p">)</span> <span class="p">{</span> <span class="n">chunk</span> <span class="p">-&gt;</span>
    <span class="nf">print</span><span class="p">(</span><span class="n">chunk</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="data-class-매핑">Data Class 매핑</h2>

<p>geminiGenerator에선 askWithClass 함수로 요청 시 적용한 Class 형태로 역직렬화를 지원합니다<br />
추가로 응답에 적용되는 Class 와 Field에 어노테이션을 통해 각각 프롬프트를 적용할 수 있습니다</p>

<ul>
  <li>GeminiPrompt: Class 단위에서 적용되는 프롬프트</li>
  <li>GeminiField: Class Field 단위에 적용되는 프롬프트</li>
</ul>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@GeminiPrompt</span><span class="p">(</span><span class="s">"전자상거래를 위한 정확하고 현실적인 제품 정보를 생성하세요"</span><span class="p">)</span>
<span class="kd">data class</span> <span class="nc">Product</span><span class="p">(</span>
    <span class="nd">@GeminiField</span><span class="p">(</span><span class="s">"제품명, 명확하고 간결해야 함"</span><span class="p">)</span>
    <span class="kd">val</span> <span class="py">name</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span>

    <span class="nd">@GeminiField</span><span class="p">(</span><span class="s">"USD 가격, 양의 정수여야 함"</span><span class="p">)</span>
    <span class="kd">val</span> <span class="py">price</span><span class="p">:</span> <span class="nc">Int</span><span class="p">,</span>

    <span class="nd">@GeminiField</span><span class="p">(</span><span class="s">"상세한 제품 설명, 2-3문장"</span><span class="p">)</span>
    <span class="kd">val</span> <span class="py">description</span><span class="p">:</span> <span class="nc">String</span><span class="p">,</span>

    <span class="nd">@GeminiField</span><span class="p">(</span><span class="s">"제품 카테고리 (예: 전자제품, 의류, 식품)"</span><span class="p">)</span>
    <span class="kd">val</span> <span class="py">category</span><span class="p">:</span> <span class="nc">String</span>
<span class="p">)</span>
</code></pre></div></div>

<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">product</span> <span class="p">=</span> <span class="n">geminiGenerator</span><span class="p">.</span><span class="n">askWithClass</span><span class="p">&lt;</span><span class="nc">Product</span><span class="p">&gt;(</span><span class="s">"프리미엄 스마트폰 제품을 생성해주세요"</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="auto-fallback">Auto Fallback</h2>

<p>Google Gemini 사용 정책은 프로젝트 별, 모델 별 사용량 한도가 제한되어 있습니다<br />
여러개의 API key, model들을 미리 설정하여 사용량 한도가 초과된 경우 자동적으로 변경하여 호출합니다</p>

<p>fallback 우선순위는 다음과 같습니다</p>

<ol>
  <li>API key 변경</li>
  <li>model 변경</li>
  <li>모두 한도 초과 시 Exception</li>
</ol>

<p>모든 사용량 한도가 초과될 경우 “All fallback options exhausted.” Runtime Exception이 발생합니다</p>

<h2 id="마치며">마치며</h2>

<p>개인 프로젝트에서 Kemi 라이브러리를 아주 유용하게 사용하고 있는 입장으로써 특히 class mapping이 진짜 편합니다<br />
보통 json으로 응답할 것을 프롬프트에 따로 기재하고, AI 응답을 직접 역직렬화 하는 작업이 필요한데 솔직히 이 작업.. 너무 귀찮아요<br />
사실상 이 기능 때문에 Kemi 라이브러리 사용할 정도로 아주 편리합니다</p>

<hr />]]></content><author><name>hyuck0221</name></author><category term="kemi" /><category term="개발" /><category term="Kotlin" /><category term="라이브러리" /><category term="AI" /><category term="Gemini" /><summary type="html"><![CDATA[이 포스팅은 kemi 라이브러리에 대한 설명을 담고 있습니다]]></summary></entry><entry><title type="html">블로그를 시작하며</title><link href="https://hyuck0221.github.io/%EC%9D%BC%EC%83%81/%EA%B0%9C%EB%B0%9C/2025/12/17/welcome.html" rel="alternate" type="text/html" title="블로그를 시작하며" /><published>2025-12-17T00:00:00+00:00</published><updated>2025-12-17T00:00:00+00:00</updated><id>https://hyuck0221.github.io/%EC%9D%BC%EC%83%81/%EA%B0%9C%EB%B0%9C/2025/12/17/welcome</id><content type="html" xml:base="https://hyuck0221.github.io/%EC%9D%BC%EC%83%81/%EA%B0%9C%EB%B0%9C/2025/12/17/welcome.html"><![CDATA[<p>첫 포스팅으로 블로그의 방향성과 목표를 설정합니다</p>

<h3 id="블로그의-목표">블로그의 목표</h3>

<ul>
  <li><strong>프로젝트 기록</strong>: 진행중인 프로젝트 들의 설명과 방향성을 기록</li>
  <li><strong>학습 기록</strong>: 새로운 기술을 배우면서 얻은 인사이트 공유</li>
  <li><strong>문제 해결</strong>: 개발 중 마주친 문제와 해결 과정 정리</li>
  <li><strong>지식 공유</strong>: 다른 개발자들과 경험을 나누고 함께 성장</li>
</ul>

<h3 id="앞으로의-계획">앞으로의 계획</h3>

<p>다양한 주제의 글을 작성할 예정입니다:</p>

<ol>
  <li><strong>개발 경험담</strong>: 프로젝트를 진행하며 배운 점들</li>
  <li><strong>기술 튜토리얼</strong>: 유용한 기술과 도구 사용법</li>
  <li><strong>코드 리뷰</strong>: 흥미로운 코드 패턴과 베스트 프랙티스</li>
  <li><strong>개발 생각</strong>: 개발자로서의 고민과 성찰</li>
</ol>

<h3 id="마치며">마치며</h3>

<p>이 블로그는 저 자신의 기록이 되어줄 뿐 아니라 모두에게 유용한 도구가 될 수 있었으면 좋겠습니다</p>

<hr />]]></content><author><name>hyuck0221</name></author><category term="일상" /><category term="개발" /><category term="블로그" /><category term="시작" /><summary type="html"><![CDATA[첫 포스팅으로 블로그의 방향성과 목표를 설정합니다]]></summary></entry></feed>