박종훈 기술블로그

컨트리뷰트로 배운 Spring AI

발표 했던 내용을 정리해보면 좋겠다 싶어 AI로 내용을 정리하여 기록으로 남깁니다. AI로 정리하여 일부 내용은 발표했던 내용과 다를 수 있고, 오류가 있을 수 있습니다.


이 글은 KSUG Spring AI Meetup 에서 발표한 “컨트리뷰트로 배운 Spring AI” 발표 내용을 AI로 정리한 글입니다.

발표 당시 Spring AI 상위 기여자이자 한국인 중 1위였습니다.

왜 Spring AI에 기여를 하게 되었고, 어떻게 기여하였는지 사례를 소개합니다. 이 글을 읽고 ‘나도 기여해볼 수 있겠다’라는 생각을 가지실 수 있기를 기대합니다.

왜 Spring AI에 기여를 하게 되었나

AI 시대를 맞이한 개발자의 고민

AI 시대를 맞이하면서 백엔드 개발자로서 고민이 많았다. “나… AI 시대에 살아남을 수 있을까?” 라는 생각도 했고, 지금이라도 AI를 공부해야 하는 건 아닌지 고민했다.

백엔드 개발자로서, 어떻게 AI를 서비스에 통합할 수 있을지 이해하고 있으면 좋을 것이라고 생각했다. 그런데 현재 회사에서 하는 업무는 AI와 직접적인 관련이 없었다.

멘토님께서 이런 조언을 해주셨다.

회사에서 하지 않는다면, 직접 구축해서 학습해보면 되지 않겠나.

그래서 관련 프로젝트에 직접 기여를 해보기로 했다. 이슈를 해결해가면서 간접적으로 느껴보자는 마음으로 Spring AI에 기여를 시작했다.

Spring AI 소개

Spring AI는 다양한 AI Provider를 쉽게 통합할 수 있도록 돕는 프로젝트이다. 다양한 프로바이더를 통해 AI Model을 제공받을 수 있는데, Spring AI는 이들을 하나의 일관된 인터페이스로 묶어준다.

오픈소스에 기여하는 법

기여는 생각보다 어렵지 않다. 다음 5단계로 이루어진다.

  1. 이슈 생성 또는 선정 - 해결할 이슈를 찾거나 직접 생성한다.
  2. 구현 방향 논의 - 메인테이너와 어떻게 해결할지 논의한다.
  3. 구현 (코드 작성) - 실제 코드를 작성한다.
  4. PR 생성 및 코드 리뷰 - Pull Request를 올리고 리뷰를 받는다.
  5. 반영 (Merge) - 코드가 머지된다.

기여 현황

M6부터 RC를 거쳐 정식 릴리즈(1.0.x)까지의 여정에 함께하였다. 약 40개의 PR을 남겼고, 기여 분류는 다음과 같다.

분류비율
Model36.6%
Vector Store26.8%
Doc24.4%
Testing9.8%
MCP2.4%

이 글에서는 Model과 Vector Store 기여 사례를 소개한다.

Model 기여 사례

AI Model 이란

AI Model은 방대한 정보 데이터 세트로 학습된 컴퓨터 프로그램 또는 알고리즘이다. 다양한 종류의 AI Model들이 있으며, 다양한 프로바이더를 통해 제공받을 수 있다.

Ollama

Ollama는 로컬에 배포되는 AI 모델 실행기로, 사용자가 직접 대규모 언어 모델(LLM)을 다운로드하고 실행할 수 있도록 설계되었다.

Ollama #1: keep-alive 음수값 버그 수정

“왜 Keep-Alive를 -1로 넣으면 죽어요?”

keep-alive 옵션은 모델이 메모리에 올라온 뒤, 얼마나 오랫동안 계속 유지될지를 정하는 설정이다. -1을 넣으면 모델이 계속 메모리에 유지되어야 하는데, 오류가 발생하고 있었다.

원인은 정규식에 있었다. 기존 정규식은 \\d+로 되어 있어 0 이상의 숫자만 입력 가능했다. 음수 값을 허용하지 않았던 것이다.

수정은 간단했다. -?\\d+로 변경하여 음수도 허용되도록 수정했다.

Ollama #2: Builder 패턴 적용

Spring AI의 Model은 일정한 구조로 구성되어 있다. 어느 날 OpenAI 쪽에 Builder 패턴을 적용하는 PR이 올라왔다.

“이거 다른 곳에도 적용하면 좋을 것 같은데?” 라는 생각이 들었다.

빠르게 작업하여 OllamaApi에도 Builder 패턴을 적용하는 PR을 등록했다.

AS-IS: 각 상황별로 생성자를 직접 구현하는 방식이었다.

TO-BE: Builder 패턴을 사용하여 유연하게 설정할 수 있도록 변경했다.

Ollama #3: 오래된 PR 머지로 인한 파일 경로 문제

“이 파일은 왜 혼자 덩그라니 있지…?”

6개월 동안 묵혀진 오래된 PR이 머지되면서 파일의 이전 경로에서 업데이트가 된 것이었다. 프로젝트 구조가 변경되면서 파일 경로가 바뀌었는데, 오래된 PR은 이전 경로 기준으로 작성되어 있었던 것이다.

이전 경로새 경로
spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/ollama/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/java/org/springframework/ai/model/ollama/autoconfigure/

약 10일 동안 PR 작성자도, 메인테이너도 이 문제를 인지하지 못하고 있었다. CI 테스트에서는 코드에 영향이 없기 때문에 그냥 넘어간 것이다.

이동된 경로에서 변경 사항을 반영하는 PR을 생성했다. 코드 자체는 짧았으나, 업데이트한 내용으로 인해 문제가 없는지 테스트를 수행하는 데 많은 시간이 들었다.

Testcontainer 이야기

Spring AI에서는 Testcontainer를 사용한 통합 테스트를 적극적으로 활용한다. 일반적인 경우에는 크게 문제 없겠지만, Ollama의 경우 테스트할 때마다 새로운 Container가 띄워지면 매번 모델을 다시 다운로드해야 한다. 테스트는 얼마 안 걸리는데 모델을 다운로드하는 데 한세월 걸린다.

이를 방지하기 위해 CI 시에는 Testcontainer가 아닌 별도의 Ollama 컨테이너를 활용한다.

Vector Store 기여 사례

Vector Store 란

“우리 회사에서 정산과 관련된 파트의 담당자는 누구인가요?” 라는 질문을 AI에게 한다고 가정해보자.

이런 내부 정보는 AI 모델이 학습하지 않은 데이터이다. 이때 필요한 것이 바로 검색 증강 생성(RAG, Retrieval-Augmented Generation) 이다. RAG는 대규모 언어 모델의 출력을 최적화하여, 응답을 생성하기 전에 외부의 신뢰할 수 있는 기술 자료를 참조하도록 하는 프로세스이다.

예를 들어 사내 문서에 아래와 같은 정보가 있다고 하자.

[업무 담당자 정리] 개발 담당자 : 박종훈 정산 담당자 : 김OO

이 문서를 벡터화하여 Vector Store에 저장해두면, “정산 담당자가 누구인가요?”라는 질문이 들어왔을 때 관련 문서를 검색하여 “정산과 관련된 파트의 담당자는 김OO입니다.”라고 답변할 수 있게 된다.

Vector Store는 이러한 RAG에서 사용되는 벡터를 저장하는 역할을 한다. Spring AI는 다양한 Vector Store를 지원하고 있다.

Elasticsearch: embedding field 하드코딩 문제

“왜 embedding field가 하드코딩 되어있는 거에요?”

Elasticsearch Vector Store의 코드를 살펴보니, 읽어올 때(doSimilaritySearch)와 기록할 때(doAdd) 모두 embedding 필드명이 하드코딩되어 있었다. 이 필드명을 dynamic하게 처리할 필요가 있었다.

메인테이너와의 논의

“record 말고 Map을 이용해서 key를 dynamic하게 처리하는 건 어떨까요?” 라고 제안했다.

메인테이너의 반응:

우리는 Map을 사용하는 것보다 구체적인 타입을 사용하는 방향을 선호해요. Record에서 Map으로 변경하려는 이유가 있을까요?

embedding 필드의 이름을 동적으로 받아야 한다고 설명하고, 더 좋은 방법이 있는지 물었다. 결국 메인테이너도 Map을 쓰면 쉽게 해결할 수 있다는 것에 동의했고, PR이 머지되었다.

이후 발생한 이슈: Document 구조 변경

PR이 머지된 후 어느 날 메일이 한 통 온다.

ElasticSearch doSimilaritySearch broken after recent changes in Document

“너가 수정한 내용 때문에 toDocument 기능이 더 복잡해졌어 :-(“

내 코드 때문에 문제가 발생한다면 어떻게 해야 할까? 우선 record로 처리하는 방식으로 PR을 새로 생성하고 물었다. “그럼 만족해?” 이슈 제기자는 “응, 도움이 될 것 같아”라고 했지만, 이어서 “그런데 문제는 끝나지 않았어”라고 했다.

“…? 그럼 이 분은 뭘 원하고 계신거지?”

문제 다시 파악하기

이슈 내용을 다시 정리해보니, serialize/deserialize 과정에서 에러가 발생 중이었다. 이슈 제기자에게 확인차 물어보았다.

옛날 버전에서 저장한 데이터를 새 버전에서 불러오려는 거야? 아니면 새 버전에서 저장한 데이터를 새 버전에서 불러왔을 때도 문제가 있다는 거야?

두 케이스 모두 에러가 발생되고 있어. 저장할 때는 “content” 프로퍼티로 저장되어 있는데 불러올 때는 “text” 프로퍼티로 불러와지고 있어서 그래.

기존에는 content라는 필드명을 사용하고 있었는데, 미디어에 대한 임베딩 처리가 요구되면서 text 필드와 media 필드로 나눠지게 된 것이 원인이었다. 기존 코드와의 호환성을 위해 데이터 매핑 시에는 content라는 필드를 유지하고 있었지만, 실제로는 필드 불일치가 발생하고 있었던 것이다.

확인해보니 이슈 제기자는 과거 버전(M5 이전)을 사용 중이셨던 것으로 보였고, 최신 버전으로 마이그레이션 시 에러가 발생하고 있었다.

두 케이스 모두 확인한 후 답변했다.

첫 번째 케이스는 필요하다면 데이터 마이그레이션을 하면 될 것 같고, 두 번째 케이스는 정상적으로 데이터가 저장되고 불러오도록 처리되어 있어. 혹시 문제가 되는 코드를 제공해줄 수 있어?

최종적으로 이슈 제기자도 “너의 말이 맞아, 데이터 마이그레이션이 필요하겠다”라며 동의하면서 해결되었다.

기여하면서 느낀 것

AI 업계에 대해서

  • 빠른 변화: 많은 기술들이 나오지만 살아남는 게 쉽지 않다. 얼마 지나지 않아 또 새로운 기능이 나온다.
  • 대세의 중요성: 하나의 확실한 대세가 생기고 나면 그 후에 다른 것들은 쉽지 않다. 대부분의 Model API에서 OpenAI 호환 API를 제공하고 있으며, 작은 규모의 프로젝트에서는 이 전략이 맞을지도 모른다. Gemini도 결국 OpenAI 호환 API를 제공하게 되었다. MCP 출시 후 A2A도 나왔지만 여전히 주도권에서는 다소 뒤처진 모습이다.
  • 돈이 많으면 간단하게 갈 수 있다: 리소스가 충분하다면 선택지가 넓어진다.
  • RAG는 생각보다 잘 되지 않는다: 다양한 방법을 연구해야 한다.
  • 핫한 것 vs 실제 사용: 핫한 것과 실제 사용되는 것은 다른 이야기이다. “MCP는 쓰는 사람보다 만드는 사람이 많은 유일한 기술일 것이다.”

커리어 관점에서

  • 글로벌 협업 경험: 다른 회사는 어떻게 일할까? 빅테크, 글로벌 IT 기업은 어떻게 일할까? 오픈소스 기여를 통해 실제로 경험해볼 수 있는 기회가 된다. “이 사람들도 사람이구나”라고 느낄 수도 있다.
  • 프로젝트 방향성 체감: 해당 프로젝트의 방향성을 직접 느껴볼 수 있다. 기여를 하다 보면 또 다른 기여와 연결되고, 프로젝트에 참여하면서 나의 지식도 확장된다. 프로젝트의 깊은 부분까지 알아볼 수 있으며, 내 의견을 직접 어필할 수 있는 기회가 된다.
  • 도움을 주는 입장으로 전환: 도움만 받는 입장에서 도움을 주는 입장으로 전환된다. 기여한 코드로 다른 개발자들의 편의성을 올려줄 수 있고, 리뷰어로 다른 기여자들에게 도움을 드리기도 하며, 직접 이슈를 남기기도 한다.

마무리

왜 Spring AI에 기여를 하게 되었고, 어떻게 기여하였는지 사례를 소개하였다.

‘나도 기여해볼 수 있겠다’라고 생각이 드셨다면, 여러분들도 Spring AI에 기여해보시길 기대한다. 오픈소스 기여모임에 참여해보시는 것도 좋은 방법이다. 다양한 프로젝트에 함께 기여하고 있다.

categories: 오픈소스

tags: Spring AI , 오픈소스 , 기여 , Ollama , Elasticsearch , Vector Store , RAG