Chat gpt와 같은 생성형 AI를 활용해서 기능을 구현하는 작업 시도하는 와중에 스프링에서 Spring AI 기술이 개발되고 있다는 것을 알고 직접 사용해보고 해당 사용에 대한 경험을 남기는 기록을 하려고 한다!
생성형 AI를 통해서 처음 개발을 하려고 했을때는 직접 API를 호출하는 방법을 사용하려고 했었다. chat gpt의 경우도 api를 사용등록하는 페이지에 들어가면 url과 accesstoken 등을 받을 수 있고 다른 일반적인 외부 api를 사용하는것 처럼 사용할 수 있었다.
그러나 결과적으로는 응답값 자체는 단순하게 string 의 message 로 오기 때문에 해당 응답을 어떻게 정제할지 그리고 요청을 어떻게 보내야할지등을 고민해야하고 어떠한 업체의 생성형 ai를 사용하느냐에 따라서 추가적인 작업이 생기게 된다.
Spring AI에 대해서 전체적인 기능을 이해하지는 못했지만 우선 아래의 문제들은 spring ai를 통해서 해결할수 있었다.
- 다양한 생성형 AI 공급자와 연동을 쉽게 할 수 있다
- 요청에 대한 응답을 구조화해서 출력 할 수 있다
현재 막 개발되고 있는 상황으로 2024년 10월 6일 기준으로는 1.0.0 버전까지는 개발이 완료되어서 사용할 수 있는 상황이다.
참고 페이지
Spring AI 설명 문서 : https://docs.spring.io/spring-ai/reference/index.html
Spring 공식 페이지 overview : https://spring.io/projects/spring-ai#overview
Spring AI github : https://github.com/spring-projects/spring-ai
gcloud 관련 명령어 문서 : https://cloud.google.com/sdk/gcloud/reference/config/set
사용 생성형 AI 선택
처음에는 가장 유명한 Chat gpt를 통해서 개발을 진행하려고 했지만 현재 일자 기준으로는 더이상 무료 크레딧을 제공하지 않는것으로 보인다. 이왕이면 무료 서비스를 찾고 싶었기 때문에 3개월간 300달러까지 무료로 사용이 가능한 gemini 를 메인으로 사용하기로 결정했다.
추가로 로컬환경에서는 비용을 최소화 하고 싶었기 때문에 테스트를 진행할때는 무료로 구동이 가능한 Ollama 를 통해서 테스트를 진행하기로 했다.
Ollama 설치 및 사용
Ollama를 사용하면 오픈소스 LLM을 내 로컬 PC에서 쉽게 사용할 수 있다.
(외부에서도 사용을 원한다면 ec2를 별도 서버를 올려서 사용할수도 있을것으로 보인다)
Mac기준으로 설치방법은 brew를 사용한다면 아래의 명령어 하나로 설치가 가능하다.
brew install --cask ollama
설치 이후에 서버를 구동하는 명령어는 아래와 같다
ollama serv
서버가 구동된 다음에는 아래의 명령어를 통해서 생성형 AI를 사용할 수 있게 된다
(run 뒤에는 어떤 모델을 사용할지 선택해서 입력하면 된다. https://github.com/ollama/ollama)
ollama run llama3
Vertex AI 설치 및 사용 (gemini)
처음에는 gemini 를 사용한다고 해서 Google AI Studio(링크)에서 API 키를 발급받고 사용하면 된다고 생각했었는데
Spring AI에서 연동을 하기 위해서는 GCP의 Vertex AI를 사용해야한다.
2024년 10월 6일 기준으로는 신규로 구글에 가입하면 300달러를 무료로 사용할 수 있기 때문에 가입후에
VertexAI (링크)를 연동해서 사용했다.
회원가입 이후에 "AI Platform Training & Prediction API" 를 사용 설정해야지만 정상적으로 연동해서 사용이 가능하다
로컬 환경에서 환경 설정 하기
기본적으로 gcp cli를 설치해서 환경 설정을 해줘야지만 vertex ai를 사용할 수 있다
https://cloud.google.com/sdk/docs/install?hl=ko#mac
MAC환경에서 gcp cli 설치하기
- macOS 플랫폼에 맞는 패키지 설치
- 설치된 압축 파일 해제
- ./google-cloud-sdk/install.sh (초기 설치)
- ./google-cloud-sdk/bin/gcloud init. (초기설정)
PATH설정을 해준다면 압축을 해제한다음에 아래와 같이 설정을 해주면 된다.
- Bash사용하는 경우
echo "source ~/google-cloud-sdk/path.bash.inc" >> ~/.bashrc
source ~/.bashrc
- Zsh 사용하는 경우
echo "source ~/google-cloud-sdk/path.zsh.inc" >> ~/.zshrc
source ~/.zshrc
설치 확인은 gcloud --version을 통해서 확인하면 된다
추가로 아래의 명령어를 통해서 해당 프로젝트 ID를 등록하고 애플리케이션 자격 증명을 진행한다.
gcloud config set project <PROJECT_ID> && gcloud auth application-default login
환경설정이 모두 잘 마무리 되었다면 아래 명령어를 통해 account, project id , region등의 정보가 제대로 등록된걸 확인할 수 있다
gcloud config configurations list
클라우드 환경에서 환경 설정 하기
나 같은 경우는 AWS 의 ec2환경에 docker image 로 내 애플리케이션을 올려서 구동시키려고 했었다.
로컬환경에서 gcp cli의 인증을 하는경우는 브라우저를 통해 직접 로그인을 하면 인증이 가능했지만
배포 과정에서 인증을 하는 경우는 직접 로그인 하는 과정을 거칠 수 가 없기 때문에 gcp 에서 별도의 서비스 계정을 생성하고 생성된 계정의 key를 json으로 저장한뒤에 해당 key를 배포 과정에서 사용하는 방법으로 처리를 진행했었다.
(gcloud auth login --no-launch-browser를 통해서 웹 브라우저 로그인 없이 처리하는 방법도 있지만 이것도 자동화 하기는 애매했다)
1. 서비스계정 key json 파일 다운로드
- IAM 및 관리자 > 서비스 계정 접속
서비스 계정 만들고 난 뒤에 key json 파일 다운로드
2. 해당 서비스 계정 json 파일 aws ec2 내부로 파일 전송
scp -i "test.pem" eastern-haven-1234 ec2-user@ec2-1-1-1-1.ap-northeast-2.compute.amazonaws.com:/tmp
3. Dockerfile 설정
Image를 실행할때 gcp cli를 설치하고 관련 환경 변수 설정을 할 수 있도록 설정
FROM public.ecr.aws/amazoncorretto/amazoncorretto:21 AS builder
# 필요한 패키지 설치 (curl, tar, gzip)
RUN yum update -y && \
yum install -y curl tar gzip
# GCP SDK 설치
RUN curl -O https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-linux-x86_64.tar.gz && \
tar -xf google-cloud-cli-linux-x86_64.tar.gz && \
./google-cloud-sdk/install.sh
# 환경 변수 설정 (GCP SDK)
ENV PATH $PATH:/google-cloud-sdk/bin
# 애플리케이션 JAR 파일 복사
ARG JAR_FILE=build/libs/test.jar
COPY ${JAR_FILE} test.jar
4. docker run 명령어 설정
docker run -v /tmp/eastern-haven.json:/root/.config/gcloud/eastern-haven.json -e GOOGLE_APPLICATION_CREDENTIALS=/root/.config/gcloud/eastern-haven.json \
-e SPRING_AI_VERTEX_AI_GEMINI_PROJECT_ID=eastern-haven-1 \
-e SPRING_AI_VERTEX_AI_GEMINI_LOCATION=asia-northeast3 \
-d --name test -p 80:8081 --restart unless-stopped --entrypoint /bin/sh 111111111.dkr.ecr.ap-northeast-2.amazonaws.com/test:latest \
-c "gcloud auth activate-service-account --key-file=/root/.config/gcloud/eastern-haven.json && \
gcloud config set project eastern-haven-1 && \
gcloud config set compute/region asia-northeast3 && \
gcloud config set compute/zone asia-northeast3-a && \
java -jar /test.jar"
- aws ec2 디렉토리에 존재하는 gcp 사용자 계정 key 파일을 /root/.config/gcloud 폴더로 이동시킨다
- 환경 변수 추가 작업
GOOGLE_APPLICATION_CREDENTIALS (gcp의 key파일 경로를 지정한다)
SPRING_AI_VERTEX_AI_GEMINI_PROJECT_ID (yml파일에 proejct id가 포함되지만 해당 환경설정값을 추가로 안주면 애플리케이션 기동에 문제가 생긴다)
SPRING_AI_VERTEX_AI_GEMINI_LOCATION (yml파일에 location 정보도 포함되지만 해당 환경설정값을 추가로 안주면 애플리케이션 기동에 문제가 생긴다)
- 애플리케이션을 기동시키기 전에 기본적인 gcp 환경 셋팅을 진행해준다
gcloud auth activate-service-account --key-file=/root/.config/gcloud/eastern-haven.json
해당 명령어를 통해서 gcp cli 로그인 처리가 된다 (key 정보 등록 을 통해서 gcloud init, gcloud auth application-default login 등을 대체하기 위함)
gcloud init 등으로 별도 로그인을 하는 경우와 다르게 service account 를 등록하는 과정에서는 아래 정보가 포함이 안되기 때문에 해당 처리과정도 추가로 처리해준다.
gcloud config set project eastern-haven-1
gcloud config set compute/region asia-northeast3
gcloud config set compute/zone asia-northeast3-a
Spring AI 애플리케이션 소스 설정
Gradle 추가
spring ai 를 사용하려고 하는 경우 기본적으로 spring boot 3.2 이상의 버전을 사용해야한다
기본 환경설정 https://docs.spring.io/spring-ai/reference/getting-started.html
dependencies {
implementation 'org.springframework.ai:spring-ai-ollama-spring-boot-starter:1.0.0-SNAPSHOT'
implementation 'org.springframework.ai:spring-ai-vertex-ai-gemini-spring-boot-starter:1.0.0-SNAPSHOT'
}
repositories {
mavenCentral()
maven { url 'https://repo.spring.io/milestone' }
maven { url 'https://repo.spring.io/snapshot' }
}
Application yml 설정
spring:
ai:
ollama:
base-url: http://localhost:11434/
chat:
options:
model: llama3
temperature: 0.7
vertex:
ai:
gemini:
project-id: eastern-haven-1
location: asia-northeast3
ollama의 경우 로컬 환경에서 구동하는 경우 기본 포트가 11434 이다
구조화 출력
관련 문서 https://docs.spring.io/spring-ai/reference/api/structured-output-converter.html
Structured Output Converter :: Spring AI Reference
As of 02.05.2024 the old OutputParser, BeanOutputParser, ListOutputParser and MapOutputParser classes are deprecated in favor of the new StructuredOutputConverter, BeanOutputConverter, ListOutputConverter and MapOutputConverter implementations. the latter
docs.spring.io
chat 형태로 ai에 명령어를 보내고 보내게 되는 text는 유저별로 특정 단어 혹은 문장이 변경되어서 출력이 되어야한다.
이를 위해서 StringTemplate의 st파일을 생성해서(프롬프트 템플릿) Resources의 templates 폴더에 저장한 뒤에 해당 파일을 Resource로 불러와서 사용했다
@Value("classpath:templates/testRequestTemplate.st")
private Resource testRequestTemplate;
testRequestTemplate.st의 내용은 아래와 같다
나는 {style} 한 나라를 를 좋아하는데 이런 나라 3곳을 추천해줄래? {format}
출력되는 형태를 결정하기 위해서 별도의 response 파일을 생성해주면된다
나는 record로 해당 응답 파일을 생성했으며 내가 어떠한 값을 출력받고 싶은지는 @JsonPropertyDescription에 추가적으로 내용을 첨부하였다.
public record Recommend(@JsonPropertyDescription("추천 나라") String countryName,
@JsonPropertyDescription("나라 설명") Integer coutryInformation) {
}
정확하게 정해진 구조를 통해서 출력할 것이기 때문에 위의 파일과 BeenOutputConvert를 사용해서 설정을 해주면된다.
(구조가 정확하게 없으면 Mapout 혹은 Listoutput등으로도 출력 결과를 맵핑할 수 있다)
아래의 소스를 보면 어떠한 방법으로 입력값과 출력값을 지정해서 결과를 가져오는지 확인할 수 있다.
// 이건 spring ai 를 gradle에 의존성 주입하고 나면 사용할 수 있게 된다
private final VertexAiGeminiChatModel vertexAiGeminiChatModel;
@Value("classpath:templates/testRequestTemplate.st")
private Resource testRequestTemplate;
public List<Recommend> generateResponse(String style) {
// 어떠한 객체를 리턴 받기를 원하는지 그 형식을 parser 로 설정해준다
BeanOutputConverter<List<Recommend>> parser = new BeanOutputConverter<>(
new ParameterizedTypeReference<List<Recommend>>() { });
String format = parser.getFormat();
// 내가 기본적으로 설정한 StringTemplat을 가져온다
PromptTemplate promptTemplate = new PromptTemplate(testRequestTemplate);
// 가져온 String에 입력받은 변수를 맵핑하고, 내가 출력하고자 하는 포맷을 지정한다
Prompt prompt = promptTemplate.create(Map.of(
"style", style,
"format", format));
ChatResponse response = vertexAiGeminiChatModel.call(prompt);
return parser.convert(response.getResult().getOutput().getContent());
}
ps. 다른 생성형 ai를 사용한다면 chatModel만 변경해주면된다
예를 들어 Ollama를 사용하는 경우 아래의 모델을 사용해서 위의 코드를 수행하면 된다.
private final OllamaChatModel ollamaChatModel;