본 내용은 24.05.22 Spring AI에 대해 변경점이 생긴 이유로 최신 버전과 호환되지 않습니다. 대략적인 원리는 같으나, 변경점에 대해 아시고 싶으신 분들은 해당 글도 참고 부탁드립니다.
개요
요새 ChatGPT 등 생성형 AI를 서비스에 접목시키는 기업들이 늘어나고 있고, 이러한 차원에서 생성형 AI를 프로젝트에 넣어보고자 하는 분들이 많은 기류를 느끼고 있습니다.
스프링에서는 아래의 영상을 통해 자체 기술인 Spring AI를 소개하는 영상을 만들기도 하였죠.
나온 지 얼마 되지 않은 기술이다 보니, 아직 이 기술을 블로그에 기록하신 분이 많이 없는 것 같아 프로젝트 상황에서 적용한 과정을 공유하고자 합니다.
본 글에서 진행할 예시는 OpenAI의 ChatGPT를 결합하여 스프링 애플리케이션에서 ChatGPT에게 프롬프트를 전달하고 응답받는 예시입니다.
Spring AI란?
스프링 공식 문서에 따르면, Spring AI는 다음과 같은 특징을 가지고 있습니다.
저희가 API를 이용하여 인공지능 서비스를 이용하기 위해서는 RestTemplate, WebClient 등 HTTP 연결을 직접 지정해야 했습니다. 하지만 Spring AI를 이용하면, 스프링의 추상화를 이용하여 쉽게 요청을 보낼 수 있게 됩니다. 이러한 점에서 도입해보고 싶은 생각이 들었습니다.
Spring AI 적용하기
작성했던 다른 글들처럼, 이번에도 스프링의 공식 문서를 참고하여 적용해 보겠습니다.
build.gradle 수정
먼저, build.gradle 안에 있는 repositories를 아래 사진과 같이 작성해야 합니다. Spring AI는 아직 계속 개발 중이라 마일스톤과 스냅샷에 관리되어 있어 그런 것 같습니다. (스프링 부트 사이트에서 OpenAI 디펜던시를 확인하실 수도 있습니다.)
그다음으로는 dependencies에 (OpenAI를 이용할 경우) org.springframework.ai:spring-ai-openai 등의 의존성을 등록시켜야 합니다. 저는 버전을 구체적으로 명시한 방법 (첫 번째 줄)을 사용하였습니다.
수정: Spring AI 1.0.0 M1 버전이 나오게 되면서 공식적으로 start.spring.io에서 AI 라이브러리를 선택하실 수 있게 되었습니다.
채팅 AI와 관련된 기능은?
그럼 이제 ChatGPT와 같은 채팅 AI를 호출할 수 있는 관련 코드가 어떤 게 있는지 알아봐야겠죠. 공식 문서와 홍보 영상을 참고하면, ChatClient라는 함수형 인터페이스를 통해 이끌어낼 수 있음을 보실 수 있습니다.
- String call(String message)
- ChatResponse 등에 대한 복잡함을 줄이기 위해 사용할 수 있습니다.
- 예시로 "문자열을 프롬프트로 보내고, 문자열로 응답한 값만 받고 싶은 경우" 사용하면 좋습니다.
- 실제 내부 코드를 보면, 아래의 ChatResponse call(Prompt prompt) 메서드를 호출합니다.
- ChatResponse call(Prompt prompt)
- Prompt 타입으로 요청을 보내고, ChatResponse 타입으로 응답을 받고 싶은 경우 사용할 수 있습니다.
- 밑에 후술 할 추가적인 정보를 이용할 수 있어, 공식 문서에서는 실제 애플리케이션 상황에서 더 자주 쓰인다고 합니다.
StreamingChatClient는 Reactive Flux 방식으로 요청과 응답을 주고받을 수 있는 것 같은데, 이 부분에 대해서는 잘 알고 있지 않은 개념이라 본 글에서는 ChatClient를 기반으로 설명드리겠습니다.
흐름표
채팅 흐름을 이해하기 위해서는 ChatClient 이외에도 Prompt와 ChatResponse에 대해 이해하셔야 합니다.
Prompt
Prompt는 메시지 (Message)의 리스트와 ChatOptions (채팅 모델 옵션)을 캡슐화 한 클래스입니다.
- 1️⃣: 모든 요청과 함께 전달되는 채팅 옵션으로, 요청 시 시스템에 전달되며 시작 옵션을 덮어쓸 수 있습니다. (있는 경우)
- 2️⃣: instructions (명령들)은 채팅 완성 및 임베딩 모델의 텍스트 목록, CV 모델의 오디오 또는 이미지/비디오 등이 될 수 있습니다.
메시지 (Message)는 간단히 말해서, 저희가 일상적으로 사용하는 프롬프트 메시지라고 생각하시면 됩니다. 후술 하겠지만, 응답 메시지를 받을 때에도 이 메시지가 사용됩니다.
Message의 구현체로 AbstractMessage가 있고, 이 AbstractMessage를 상속받은 UserMessage를 ChatClient에서 사용합니다.
ChatClient
ChatClient에서는 Prompt를 GPT AI의 API에 맞게 입력 명령들을 변환하고, 채팅 옵션들을 병합한 뒤 GPT에 API 요청을 보내게 됩니다. 그리고 이 응답을 ChatResponse로 받을 수 있게 합니다.
- 3️⃣: ChatClient에서는 입력 명령들을 모델 입력 형식에 맞게 변환하는 과정을 거칩니다.
- 4️⃣: 실행 시에 비어 있지 않은 옵션들은 시작 시 옵션들을 덮어씁니다.
- 5️⃣: 채팅 옵션은 ChatClient 초기화 중에 시작 시 설정됩니다. (모델마다 선택적으로 구현)
- 6️⃣: AI 모델이 반환한 응답을 ModelRequest, ModelResponse로 변환합니다.
ChatResponse
ChatResponse는 채팅 응답에 대한 메타데이터 (ChatResponseMetadata), Generation을 가지는 응답 객체입니다.
ChatResponseMetadata는 AI 모델에 대한 RateLimit (API 호출 제한량), Usage (사용량) 등을 확인할 수 있습니다.
이 인터페이스를 구현한 OpenAiChatResponseMetadata를 보면 더 구체적으로 원리를 이해하실 수 있습니다.
이번에는 Generation에 대해 알아보겠습니다.
Generation 클래스는 모델 결과를 확장하여 보조 메시지 (AssistantMessage) 응답과 관련된 메타데이터를 나타냅니다.
위의 설명을 기억하고 계신 분들이라면, 위의 메시지 (Message)의 하위 타입으로 UserMessage, AssistantMessage가 함께 있는 것을 기억하고 계실 겁니다. 그리고 함께 띄워둔 MessageType을 통해, 메시지의 타입 중에는 USER와 ASSISTANT가 있었음도 아실 겁니다.
GPT에 요청을 보낼 때는 ChatClient에서 UserMessage를 사용하였고, 응답을 받을 때는 AssistantMessage가 Generation 안에 있음을 보았습니다. 즉, 이를 통해 다음 사실을 추론할 수 있습니다.
- 요청을 보낼 때는 Message의 타입이 USER이다. (사람이 보낸 것이므로)
- 응답을 받을 때 AI 모델에서 전달한 Message의 타입은 ASSISTANT이다.
그렇다면 String call(String message)에서 반환 줄이 왜 저렇게 되는지 (getResult().getOutput()...)만 보고, 실제 구동을 보여드리겠습니다.
ChatResponse 실제 코드
공식 문서에서 보여준 ChatResponse의 실제 코드는 아래처럼 되어 있습니다.
- String call(String message) 메서드를 호출합니다.
- 프롬프트가 만들어집니다.
- ChatResponse를 받습니다.
- getResult() 메서드를 통해, 생성된 Generation의 첫 번째 요소를 받습니다.
- Generation의 getOutput 메서드를 통해, Generation이 가지고 있는 AssistantMessage를 받습니다.
- AssistantMessage는 AbstractMessage를 상속받으므로, AbstractMessage의 컨텐츠 (응답 문자열)를 받게 됩니다.
이로써 Spring AI를 이용하여 GPT와 대화할 때의 요청과 응답에 대한 흐름을 알게 되었습니다.
GPT를 결합하자!
이제 흐름을 알았으니, GPT를 결합하여 실제 호출을 해 봅시다.
이 과정에서 과금이 발생할 수 있으나, 저는 직접 API 토큰을 구입하였기에 제 경험을 알려드리겠습니다.
API key 발급
GPT의 API key 발급은 위키독스에서 잘 정리했기 때문에, 링크를 공유드립니다.
yml (or properties)에 키 작성하기
영상을 보면, spring.ai.openai.api-key에 API 키를 작성해 두면 됨을 알 수 있습니다.
ChatClient 호출
이제, 내부적으로 ChatClient를 호출하면 됩니다.
Postman 테스트
Postman으로 테스트해 보면 정상적으로 작동함을 볼 수 있습니다.
결론
Spring AI를 도입하기 이전에는 직접 RestTemplate, WebClient 등으로 GPT의 url을 작성하고, HTTP 연결에 따른 IOException을 캐치 처리해줘야 하는 등의 문제가 있었지만, Spring AI가 가진 추상화와 캡슐화를 통해 훨씬 간단하게 개발할 수 있게 되었습니다.
다음 글에서는 Spring AI를 보며 느낀 장단점에 대해 글을 작성해 보도록 하겠습니다.
추가 정보: OpenAiChatClient
여담으로, ChatClient의 구현체인 OpenAiChatClient의 코드를 보면 call을 어떻게 오버라이딩했는지 보실 수 있습니다.
이상으로 Spring AI 적용기에 대한 글을 마치겠습니다. 긴 글 읽어주셔서 감사합니다!
Reference
'🚀 팁 (기술 적용 방법 등) > 🤖 Spring AI 파헤치기' 카테고리의 다른 글
[Spring AI 🤖] Spring AI에서 프롬프트를 더욱 효과적으로 작성하는 방법 (feat. PromptTemplate) (0) | 2024.06.23 |
---|---|
[Spring AI 🤖] Spring AI 변경점 이슈 알아보기 (24.05.22 Issue) (0) | 2024.05.26 |