본문 바로가기

Spring AI + Bedrock 사용해보기

@정소민fan2026. 1. 16. 21:48

프로젝트 마이그레이션을 하면서, Spring AI를 도입해보기로 했다. Langchain과 Spring AI 둘중에 하나를 써야해서 고민했는데 이전 프로젝트에 만들어져있는 RAG가 그렇게 복잡하지 않아서 Langchain 보다는 간단하게 Spring AI로 만들기로 했다. 추후에 교체가 가능하도록 아키텍처를 구성하면 될거같다.

참고하기

이번에 스프링부트가 4.x.x 버전으로 업데이트 되어서 처음에 써보려고 했는데... Spring AI에서 지원을 안하는것 같았다. 내가 잘못 설정해서 그런건지 모르겠는데 3.5.x 버전으로 다운그레이드하니까 해결됐다.

라이브러리 설치 및 환경변수 설정

Spring AI는 LLM 모델에 따라 다르게 라이브러리를 설치해줘야 한다.

우리는 Bedrock을 사용할 것이기 때문에

implementation 'org.springframework.ai:spring-ai-starter-model-bedrock-converse'

이렇게 추가해주면 된다. Spring AI Bedrock도 일반 버전과 converse 버전이 있던데 둘다 설정방법 같은게 조금씩 달랐다. 나는 converse 버전을 사용했다.

 

설치후에 application.yml에 다음과 같이 세팅을 해주어야 한다.

spring:
  ai:
    bedrock:
      aws:
        region: ${AWS_REGION}
        secret-key: ${AWS_SECRET_KEY}
        access-key: ${AWS_ACCESS_KEY}
  • region : Bedrock 모델을 신청한 region
  • secret-key : aws 시크릿 키
  • access-key : aws 액세스 키

시크릿 키와 액세스 키는 root 유저 말고 IAM을 따로 생성하는 편이 보안에 좋다.

위처럼 설정만 해두면 신청해둔 Bedrock 모델에 접근이 가능하다.

코드를 어떻게 써야할까?

interface AiClientPort {
    fun <T> fetchEntity(promptText: String, modelRequest: AiModelRequest, responseType: Class<T>): T?
}

---
import org.springframework.ai.bedrock.converse.BedrockChatOptions
import org.springframework.ai.chat.client.ChatClient
import org.springframework.ai.chat.prompt.Prompt
import org.springframework.stereotype.Component

@Component
class BedrockClientProvider(
    private val chatClient: ChatClient
) : AiClientPort {

    override fun <T> fetchEntity(promptText: String, modelRequest: AiModelRequest, responseType: Class<T>): T? {
        val options = BedrockChatOptions.builder()
            .model(modelRequest.modelId)
            .temperature(modelRequest.temperature)
            .topP(modelRequest.topP)
            .topK(modelRequest.topK)
            .maxTokens(modelRequest.maxTokens)
            .build()
        val prompt = Prompt(promptText, options)

        return chatClient.prompt(prompt)
            .call()
            .entity(responseType)
    }

}

Spring ai 라이브러리를 추가하면 ChatClient를 사용할 수 있다.

BedrockChatOptions에 다음과 같이 옵션을 지정할 수 있다.

  • model : Bedrock 모델 ID
  • temperature : 단어 선택의 무작위성을 결정. 1.0까지 설정할수 있으며, 높은 값일수록 답변이 창의적이고 다양해진다
  • topP : 후보 단어 확률 범위를 제한한다. topP가 0.9라면 상위 90% 확률 안에 드는 단어들 중에서만 선택한다
  • topK : 확률이 가장 높은 상위 K개의 단어 후보 중에서만 선택한다.
  • maxTokens : 답변의 최대 길이를 제한한다.

위 옵션은 환경변수 파일에 설정할수 있지만, 그러면 프로젝트 전체에서 같은 설정을 사용하게 되어서 나는 쓰지 않았다.

이외에도 cache를 이용해서 대화 컨텍스트를 저장하여 챗봇 기능을 구현할수도 있다. 자세한건 공식 문서를 참조하자.

 

나는 이 Provider를 언제든 교체할 수 있도록 상위 어댑터에서 어떤 모델이나 옵션을 사용할지 정할 수 있도록 AiModelRequest를 따로 설정했다.

data class AiModelRequest(
    val modelId: String,
    val temperature: Double? = null,
    val topP: Double? = null,
    val topK: Int? = null,
    val maxTokens: Int? = null
)

 

프롬프트

나는 StringTemplate로 프롬프트를 관리하기로 했다. resources/prompts/ 경로에 두자.

예를 들어 이렇게

StringTemplate에서는 다음과 같은 형태로 변수를 지정할 수 있다.

당신은 일기 분석 전문가입니다. 
사용자의 일기 내용인 "{content}"를 바탕으로 핵심 키워드 3개를 추출해주세요.

일단 StringTemplate를 불러와보자.

@Component
class DiaryAnalysisAdapter(
    private val aiClient: AiClientPort,
    @Value("classpath:/prompts/diary-analysis.st")
    private val prompt: Resource
) : DiaryAnalysisPort {

    override fun getAnalysis(content: String) : DiaryAnalysisResponse? {

        val template = PromptTemplate(prompt)
        val renderedPrompt = template.render(mapOf("content" to content))

        val request = AiModelRequest(
            modelId = "apac.anthropic.claude-sonnet-4-20250514-v1:0",
            temperature = 0.05,
            topP = 0.9,
            topK = 10,
            maxTokens = 4000
        )

        try {
            return aiClient.fetchEntity(renderedPrompt, request, DiaryAnalysisResponse::class.java)
        } catch (e: Exception) {
            throw DiaryAnalysisFailedException(
                e,
                mapOf(
                    "content" to content
                )
            )
        }
    }

}

@Value를 통해서 resources 내부의 .st 파일을 가져올 수 있다. 이것을 Spring ai의 PromptTemplate에 넣어서 사용할 수 있다.

그 후 render 함수를 사용하면 String을 얻어올 수 있는데, 이 때 map을 넘겨줘서 변수에다가 문자열을 치환해서 넣을 수 있다.

create 함수도 비슷한 역할을 하지만, create의 경우 Prompt 객체를 반환한다.

 

그리고 주의할 점!! claude sonnet 4 모델 ID를 쓰면 맨 앞에 apac 을 반드시 붙여줘야한다.

 

또한 stream 같은 옵션을 사용해서 응답이 바로바로 생성되는 연출을 볼수도 있다.

학습 목적으로 만든 레포지토리도 있는데, clone해서 주석을 보는 것도 추천한다.

https://github.com/goochul-im/Spring-Ai-Bedrock-Chatbot

 

GitHub - goochul-im/Spring-Ai-Bedrock-Chatbot: 0h-my-claude 를 사용해 제작한 프로젝트입니다

0h-my-claude 를 사용해 제작한 프로젝트입니다. Contribute to goochul-im/Spring-Ai-Bedrock-Chatbot development by creating an account on GitHub.

github.com

 

'Spring > Kotlin' 카테고리의 다른 글

Mockmvc로 통합 테스트 시 예외 감지  (0) 2025.12.04
마이그레이션 계획  (0) 2025.12.03
서비스, API 작성  (2) 2025.10.01
리포지토리 만들기  (0) 2025.09.13
도메인 엔티티 작성  (0) 2025.09.01
정소민fan
@정소민fan :: 코딩은 관성이야

코딩은 관성적으로 해야합니다 즐거운 코딩 되세요

목차