이 포스팅은 kemi 라이브러리에 대한 설명을 담고 있습니다


Kemi 라이브러리란?

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

지원 기능 (0.1.0 기준)

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

지원 버전

  • JVM 8+
  • Kotlin 1.9+
  • Spring Boot 2.7+
  • Spring 5.3+

라이브러리 설정

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

build.gradle.kts

repositories {
    mavenCentral()
    maven { url = uri("https://jitpack.io") }
}

dependencies {
    implementation("com.github.hyuck0221:kemi:0.1.0")
}

application.yml

kemi:
  gemini:
    api-keys: # 필수
      - your-first-api-key
      - your-second-api-key
      - your-third-api-key
    base-url: https://generativelanguage.googleapis.com  # 선택
    models: # 선택
      - gemini-2.5-flash
      - gemini-2.5-flash-lite
      - gemini-2.0-flash
      - gemini-2.0-flash-lite
  • api-keys (필수)
    • Google AI Studio에서 발급한 API Key
    • 1개 이상 필수로 등록 필요
    • 사용량 만료 시 다음 key로 자동 fallback
  • base-url (선택)
    • Gemini API 기본 url
  • models (선택)
    • Gemini AI Model
    • 사용량 만료 시 다음 model로 자동 fallback
    • 미입력 시 gemini-2.5-pro 적용

기본 사용법

Bean에 등록된 Generator를 불러와 호출합니다
Generator는 GeminiGeneratorGeminiChatGenerator 로 구분됩니다

import com.hshim.kemi.GeminiGenerator
import com.hshim.kemi.GeminiChatGenerator
import org.springframework.stereotype.Service

@Service
class MyService(
    private val geminiGenerator: GeminiGenerator,
    private val geminiChatGenerator: GeminiChatGenerator,
)

GeminiGenerator

1회성 질문 -> 출력이 필요한 경우 사용합니다

val result = geminiGenerator.ask("너는 어떤 모델이야?")

println(result) // ex. 반가워요! 저는 구글에서 만든 Gemini 2.5 Flash 모델입니다.

GeminiChatGenerator

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

val session = geminiChatGenerator.createSession()

val response1 = session.sendMessage("안녕하세요, 제 이름은 John입니다")
println(response1) // "안녕하세요 John님! 무엇을 도와드릴까요?"

val response2 = session.sendMessage("제 이름이 뭐죠?")
println(response2) // "당신의 이름은 John입니다"

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

// 세션 저장 과정
val sessionState = session.exportSession()
// ..세션 저장..

// 세션 불러오기 과정
// ..세션 불러오기..
val newSession = geminiChatGenerator.restoreSession(sessionState)

프롬프트 관리

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

정적 시스템 프롬프트 설정

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

@Configuration
@EnableConfigurationProperties(GeminiProperties::class)
class GeminiConfiguration {

    private val prompt = "무조건 한국어로 답할 것"

    @Bean
    fun geminiGenerator(properties: GeminiProperties): GeminiGenerator {
        return GeminiGenerator(properties, prompt)
    }

    @Bean
    fun geminiChatGenerator(properties: GeminiProperties): GeminiChatGenerator {
        return GeminiChatGenerator(properties, prompt)
    }
}

동적 시스템 프롬프트 설정

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

// GeminiGenerator
geminiGenerator.ask(
    question = "양자 컴퓨팅에 대해 설명해줘",
    prompt = "너는 친절한 물리학 교수야"
)

// GeminiChatGenerator
geminiChatGenerator.createSession("욕쟁이 할머니처럼 대답해")

Stream 응답

GeminiGenerator: askStream()
GeminiChatGenerator: sendMessageStream()

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

GeminiGenerator

val result = geminiGenerator.askStream("소설 하나만 만들어줘") { chunk ->
    print(chunk)  // 각 단어가 도착하는 즉시 출력
}

// result 에는 전체 응답 결과가 들어있음

GeminiChatGenerator

session.sendMessageStream("이야기를 들려주세요") { chunk ->
    print(chunk)  // 각 단어가 도착하는 즉시 출력
}

session.sendMessageStream("주인공의 이름이 뭐였죠?") { chunk ->
    print(chunk)
}

Data Class 매핑

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

  • GeminiPrompt: Class 단위에서 적용되는 프롬프트
  • GeminiField: Class Field 단위에 적용되는 프롬프트
@GeminiPrompt("전자상거래를 위한 정확하고 현실적인 제품 정보를 생성하세요")
data class Product(
    @GeminiField("제품명, 명확하고 간결해야 함")
    val name: String,

    @GeminiField("USD 가격, 양의 정수여야 함")
    val price: Int,

    @GeminiField("상세한 제품 설명, 2-3문장")
    val description: String,

    @GeminiField("제품 카테고리 (예: 전자제품, 의류, 식품)")
    val category: String
)
val product = geminiGenerator.askWithClass<Product>("프리미엄 스마트폰 제품을 생성해주세요")

Auto Fallback

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

fallback 우선순위는 다음과 같습니다

  1. API key 변경
  2. model 변경
  3. 모두 한도 초과 시 Exception

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

마치며

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