반응형
스프링 AI 시리즈
- [Spring AI] 준비 (기본 개념, OpenAI API Key, 크레딧 충전)
- [Spring AI] 챗봇 만들기 (Kotlin)
- [Spring AI] Vector Store와 RAG를 이용한 할루시네이션 방지
- [Spring AI] OpenAI 비용을 절감하는 방법
이제 본격적으로 OpenAI과 Spring AI를 활용한 챗봇을 만들어보자.
전체 파일 구조
build.gradle.kts
plugins {
kotlin("jvm") version "1.9.25"
kotlin("plugin.spring") version "1.9.25"
id("org.springframework.boot") version "3.3.3"
id("io.spring.dependency-management") version "1.1.6"
}
group = "org.example"
version = "0.0.1-SNAPSHOT"
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
repositories {
mavenCentral()
maven { url = uri("https://repo.spring.io/milestone") }
}
extra["springAiVersion"] = "1.0.0-M2"
dependencies {
implementation("org.springframework.ai:spring-ai-openai-spring-boot-starter")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("org.jetbrains.kotlin:kotlin-reflect")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
dependencyManagement {
imports {
mavenBom("org.springframework.ai:spring-ai-bom:${property("springAiVersion")}")
}
}
kotlin {
compilerOptions {
freeCompilerArgs.addAll("-Xjsr305=strict")
}
}
tasks.withType<Test> {
useJUnitPlatform()
}
application.yml
spring:
application:
name: spring-ai
ai:
openai:
api-key: # 여기에 발급 받은 OpenAI API Key 값을 입력. 절대 외부(깃허브 등)에 노출 금지!!
chat:
options:
model: gpt-3.5-turbo
temperature: 0.7
max-tokens: 200
AiChatConfig.kt
@Configuration
class AiChatConfig(
val chatClient: ChatClient.Builder
) {
@Bean
fun chatClient(): ChatClient {
return chatClient.build()
}
}
OpenAiEmbeddingConfig.kt
@Configuration
@EnableConfigurationProperties(OpenAiEmbeddingProperties::class)
class OpenAiEmbeddingConfig(
private val openAiEmbeddingProperties: OpenAiEmbeddingProperties
) {
@Bean
fun embeddingModel(): EmbeddingModel {
return OpenAiEmbeddingModel(OpenAiApi(openAiEmbeddingProperties.apiKey))
}
}
DTO(AiChatRequest.kt, AiChatResponse.kt)
// AiChatRequest
data class AiChatRequest(
@NotNull(message = "User input must not be null")
val userInput: String = ""
)
// AiChatResponse
data class AiChatResponse(val response: String
AiChatController.kt
@RestController
@RequestMapping("/api/v1/chat")
class AiChatController(
private val aiChatService: AiChatService
) {
@PostMapping
fun chat(@RequestBody aiChatRequest: AiChatRequest): AiChatResponse {
return aiChatService.chat(aiChatRequest)
}
}
AiChatService.kt
@Service
class AiChatService(
private val chatClient: ChatClient,
private val aiPromptService: AiPromptService
) {
fun chat(aiChatRequest: AiChatRequest): AiChatResponse {
val prompt = aiPromptService.createPrompt(aiChatRequest.userInput)
return AiChatResponse(chatClient.prompt(prompt).call().content())
}
}
system-message.st
You are a helpful AI assistant that helps people find information. Your name is {name}
You should reply to the user's request. and do not repeat it.
user-message.st
Tell me {userInput}
AiPromptService.kt
@Service
class AiPromptService(
@Value("classpath:prompts/chat/system-message.st")
private val systemResource: Resource,
@Value("classpath:prompts/chat/user-message.st")
private val userResource: Resource,
) {
// 유저 입력을 메시지로 변환
private fun createUserMessage(userInput: String): Message =
PromptTemplate(userResource).createMessage(
mapOf("userInput" to userInput)
)
// 시스템 메시지 생성
private fun createSystemMessage(): Message =
SystemPromptTemplate(systemResource).createMessage(
mapOf("name" to "Jerry")
)
// 프롬프트 생성
fun createPrompt(userInput: String): Prompt =
Prompt(listOf(createUserMessage(userInput), createSystemMessage()))
}
위의 PromptService는 조금 자세히 살펴보자.
systemResource: 시스템의 역할이나 행동을 정의하는 메시지를 저장한 템플릿 파일인 system-message.st
userResource: 사용자가 입력한 내용을 담는 메시지를 저장한 템플릿 파일인 user-message.st
이 두 리소스는 각각 특정 메시지 패턴을 따르며, 메시지 내부에서 동적으로 값을 바인딩할 수 있도록 구성되어 있다.
- `createUserMessage` 함수의 경우 `userInput`에 사용자에게 입력받은 `userInput`이 바인딩이 된다.
- `createSystemMessage` 함수의 경우 `name`에 `Jerry`가 바인딩 된다.
그래서 시스템의 이름을 물어보면 `Jerry`라고 답을 한다.
대화형 AI에서는 사용자의 질문에 자연스럽고 일관된 답변을 제공하기 위해, 시스템의 역할과 사용자 입력을 효과적으로 전달하는 방법이 중요하다.
이를 위해 템플릿 파일을 서비스에 맞게 변경하거나, 템플릿 메시지를 데이터베이스에 저장하여 동적으로 활용하는 방식이 필요할 것이다.
추가
나는 배우도 아니고, 연기력이 좋지도 않다. 위처럼 AI가 잘못된 정보를 응답해주는 것을 AI 할루시네이션이라고 한다.
AI 할루시네이션
AI 할루시네이션이란 인공지능 모델, 특히 자연어 처리(NLP) 모델이 실제로 존재하지 않거나 잘못된 정보를 마치 사실인 것처럼 생성하거나 제공하는 현상을 의미한다. 이는 인공지능이 주어진 데이터를 바탕으로 패턴을 학습하고 추론을 하지만, 때때로 오류를 범하거나 기존의 정보와 일치하지 않는 새로운 정보를 생성할 때 발생한다.
다음 포스팅에서 Spring AI와 벡터 스토어(Vector Store)를 활용하여 AI 할루시네이션을 줄이고, 보다 신뢰성 있는 답변을 제공하는 방법을 알아보자.
REFERENCES
스프링 ai prompt - https://docs.spring.io/spring-ai/reference/api/prompt.html
반응형