Daily/Code Fest

입사하고 4년만에 나간 Gemini 공모전 회고

쟈 미 2024. 10. 3. 02:15
728x90

만든 앱 투표 링크는 여기!!!

https://ai.google.dev/competition/projects/muse-diary?hl=ko

 

뮤즈 일기  |  Gemini API Developer Competition  |  Google AI for Developers

매일 채팅하고, 사운드트랙을 선별하고, 소중한 순간을 간직하세요

ai.google.dev

 

시작

진짜 오랜만에 올리는 포스팅..ㅎㅎ 민망하구만.
24년 6월부터 7월까지 한 달 반의 시간동안 평일 저녁과 주말 시간을 쪼개서 Google Gemini API Developer Competition (Gemini API를 사용한 제품 개발 공모전)에 참여했다. 1명의 PM, 2명의 클라이언트개발자, 1명의 디자이너, 그리고 서버개발자였던 나. 능력있는 멋진 동료들과 함께해서 그런지 힘든 기억보단 새로웠던 시간들이라 블로그로 정리하게 되었다.

어느덧 카카오에 입사한지 4년이 되었는데 (5년차라니...) 회사 일에 적응하고 외부 활동은 거의 안하다보니 슬슬 갇혀있는 기분이 들었었다. 회사에서 하는 AI 수업도 들었는데 결국 하는 일은 비슷비슷하다보니 요즘 세상은 어떻게 돌아가는지가 궁금했었다. (회고라 쓰고 외부 개발세계 탐방기라고 읽는다) 그러던차 PM 언니한테 생일 축하 메시지와 함께! 같이 공모전 할 생각있냐는 연락이왔다..!!! 

회사 일에 지장이 될까 걱정됐는데, 현생에 방해되지 않게 책임진다는 말과 클라이언트 개발자 분의 계획이 담긴 카톡을 보내줬는데 너무 든든해서 결국 참가하게 되었다.  기존 코드 고치는 작업이 아니라 내가 처음부터 코드를 짜는게 얼마만인지 두근두근!!

 

사전 지식 준비 (gen ai, spring-ai)

Gemini API를 사용해서 프로젝트를 제출하는 거였는데, 사실 굉장히 시기가 좋았다. 마침 회사에서 Generative AI(이하Gen AI) 기초반, 심화반 강의도 신청해서 듣고 흥미가 생겼었기 때문이다. AI 개발의 경우엔 대학때부터 느꼈지만 되게 나랑 안맞았다. 왜 그게 그 결과가 나오는지 이해가 안되서인가... 납득이 잘 안됐다. 근데 GPT가 나오고 언젠가는 결국 해야할 것들이라는 생각이 들어서 한번 공부를 하면 좋겠다 싶어서 회사 세션을 시작하게되었고 이 공모전을 시작하게되었다.

대부분의 사이드프로젝트가 그렇듯 사실 클라이언트만 있어도 결과물이 나오는데 서버가 굳이..? 싶었는데, 아무래도 클라에서 하면 API나 프롬프트가 외부에 노출될 가능성도 있고, 개선시 클라이언트 패치의 불편함이 있어서 서버개발자를 구하게 되었다고 하셨다.

사실 서버는 Gen Api 호출 서버를 만드는거라서 간단했다. 그래서 이참에 Spring-AI를 사용해보자는 개인 목표를 잡으며 공모전에 참여하게 되었다. 

우선적으로 회사를 통한 gen ai 공부는 꽤나 신기했다. 여러 개념이나 모델에 대한 설명에서 시작해서 프롬프트 엔지니어링, RAG, ollama, langchain, function call 실습 등 전반적인 방향성을 잡는데 도움이 되었다. 따라서 우리 공모전에서 AI를 사용할때 어떤 선택지가 있는지를 고를 수 있는 기반이 되었던 것 같다. 그리고 끝나고 팀원분들께 간단 공유시간도 가졌었는데 지금 몇개월 지나니까 다시 기억이 잘 안나는 것 같기도..

덕분에 우리 공모전에서 필요했던 건 RAG까지 갈 필요도 없이 prompt enginneering 만으로도 충분히 가능하다는걸 이해할 수 있었고, 사실 prompty engineering만 한다면  spring자체에서만 보면 httpClient 만 있어도 괜찮다는걸 스스로도 알았다. 그치만 spring-ai를 써보고싶었고, 오히려 spring-ai를 쓰면서 여기서는 지원하지 않아 생긴 허들도 있었어서 재밌었다. (이건 아래에) spring-ai를 쓰다보니 아직 안정화가 되지않아 구조가 휙휙 바뀌는 프레임워크단 코드를 체감할 수 있었던게 회사에서와는 다른 경험이라 신기했다.

 

그래서 뭘 만들었을까

https://youtu.be/lcMANrDdTSw?feature=shared

우리팀의 핵심 기능은 gemini prompt와 대화하며 일기를 적거나 글로 하루를 회고한 내용을 바탕으로 youtube music을 추천하는걸 주 기능으로 하는 앱이다. 제목은 muse diary 이다. gemini가 추천해주는 음악이 당신의 일상에 한번 더 영감을 주면 좋겠다는 의도로 muse라는 단어가 떠올랐다. 아무래도 gen ai를 사용한다면 이미 검색으로 찾을 수 있는 public한 내용에 대한 정리집 혹은 커뮤니티성 앱보다는 개인화에 집중된 private한 내용이 좀 더 사용성에 맞는 것 같다고 팀원간의 의견 나눔이 있었다.

 

짱 멋진 팀원들 자랑

1. 기획

초반에는 기획을 다같이 정리하는 시간을 가졌는데 PM언니가 떠다니기 쉬운 아이디어들을 잘 정리해주고 다시 리마인딩 하면서 회의를 진행했던게 좋았다.

커뮤니케이션은 보통 슬랙으로 했었는데, 평소에 업무를 할 때 카톡으로 하다보니 슬랙 확인을 좀 늦게하곤했었다ㅠ 그런데 확실히 회사밖의 다른 개발자들은 슬랙을 잘 쓰는구나 싶어서 되게 재밌었다. 퇴근하고 아무래도 밀려있는 내용들을 볼때 언급되어있는 스레드들 모아보기로 빠르게 확인하고 개발하고 배포해서 빠르게 확인하는게 되게 효율적이었다.

간만에 회사 외부의 능력자들과 일하는 경험이 되게 신선하면서도 서로 현생에 영향을 미치지 않게 배려해줘서 무사히 마칠 수 있었다.

 

2. 디자인

항상 나는 디자이너분들을 되게 좋아하는 편인데 (클라 개발자가 아니라 부딪힐일이 없어서그런가) 이번에도 역시 디자이너 너무좋다 디자인이 이뻤다!!! 하루를 마치고 LP바에서 술한잔과 함께 오늘 있었던 일을 얘기하면 그에 맞는 잔잔한 노래를 듣는 느낌을 생각했는데, 바이닐 컨셉의 디자인을 가져와주셨다. 

다들 직장인이라 바쁜 시간을 쪼개서 참여하다보니 사실 아이디에이션 했던 모든 기능을 넣을수가 없었는데, PoC단계에서 GUI 디자인 단계로 넘어가기 직전에 대부분의 앱들에서 사용하는 탭바를 걷어내자고 디자이너분이 제안을 주셨다. 덕분에 핵심기능에 집중해서 공모전을 마무리할 수 있었다. (기한이 쫄린다면 기획을 쳐내는게...) 다들 현생을 놓지않으면서도 새벽시간을 쪼개서 얘기하고 회의하고 밥먹고 되게 기억에 남는 팀이 될 것 같다.

 

3. 클라이언트

https://github.com/geminiApiDevKorea/diary_flutter

 

GitHub - geminiApiDevKorea/diary_flutter: 다이어리 플러터 프로젝트

다이어리 플러터 프로젝트. Contribute to geminiApiDevKorea/diary_flutter development by creating an account on GitHub.

github.com

클라이언트 개발자 두 분은 플러터로 개발을 했었는데, 확실히 크로스 플랫폼의 이점이 있었다. gen ai 프롬프팅을 한 클라개발자분께서 담당하셨었는데 (너무 감사했다.. 백엔드지만 프롬프팅 엔지니어링 까지는 관심이 없었기에 )  flutter로 프롬프팅 관련 PoC를 금방 구현해보시더니 다른 팀원분들도 해보라고 web으로 배포해서 주셨다. 덕분에 일기 추출 프롬프팅 부분을 다같이 테스트해보며 실제로 gpt가 말하는 뉘앙스를 보며 프롬프트를 수정하기도하고, 앱의 디자인적 사용성이나 백엔드에서는 실제로 들어갈 데이터들을 좀 더 고려해 볼 수 있었다.

그리고 클라이언트 각종 인터렉션을 디자인적 완성도를 위해 깡코드로 구현하시고, 많이 있는 library를 쓰지 않고 직접 코드로 구현한다거나.. 이런 부분이 개인적으로 가장 놀라웠다. 바이닐을 넘기는 인터렉션을 하나하나 디자이너분께 컨펌받으면서 섬세하게 디테일을 잡으시는 모습도 인상적이었고, 나는 백엔드 개발자다보니 당연히 달력은 라이브러리를 쓴다는게 고정적인 생각이었어서 그런가 직접 달력부분 코드를 하나하나 짠다는것도 충격이었다

그리고 실제로 보내주신 코드들을 보니 다트가 깔끔한건지 코딩을 잘하신건지.. 보기에도 편했다. 시니어 클라이언트 개발자와 함께하는 사이드프로젝트 꽤나 감격적이었달까

 

서버만 잘하면 되겠네

그래서 이렇게 자기할일 딱딱하는 팀원들과 함께 하기위해서라면 나도 일을 착착 해나가야겠다! 싶었는데 회사 밖에서 코딩을 한다는건 코딩이 문제가 아니었다! 그동안 기본적으로 환경세팅이 다 되어있던 회사 인프라를 벗어나서 외부에서 하나하나 해야하는 상황! 되게 재밌었다 외부 생태계는 이렇구나 요즘 개발자들은 이렇게 개발하는구나를 체험해볼수있어서 디버깅을 하나하나 해나가는게 재밌었다. 퇴근하고 정신없이 코딩하는게 그냥 며칠만 투자하면 됐기 때문에, 금방금방되서 그런가 몇개월이 지난 지금 생각해도 힘든것보단 재밌었다는 기억만 남았다!!!

우선 서버 코드는 아래에 있다.

https://github.com/geminiApiDevKorea/gem-api

 

GitHub - geminiApiDevKorea/gem-api: gem api server

gem api server. Contribute to geminiApiDevKorea/gem-api development by creating an account on GitHub.

github.com

그리고 구조는 아래처럼 잡았다.

코딩보다도 CI/CD구성이나 환경변수 관리 새로운 인프라 api에 대한 러닝커브 등등이 좀 더 기억에 남는다. 사실 사이드 프로젝트다 보니까 api 서버 만드는 건 확장성보다도 기획에서 바로바로 요청이 올 때 빠르게 변경한다거나 이런것들이 더 중요했기 때문에 객체화 클린코드 테스트코드 같은 것 보다 가장먼저 CI/CD부터 완성했다.

 

1. github Action CI/CD

회사에서는 CI/CD를 jenkins로 하고있어서 github action을 처음써봤다. github action에 대해서 좋다고 좋다고 말만들었지 처음 써보는거라 ansible 문법 익히듯이 action 문법들을 익혀야하는건가 싶었는데 와 요즘 개발은 GPT를쓰는구나?

진짜 감격 그자체 dockerfile 부터 github action worflow까지 다 짜주는 이런 세상이라니.. 그래서 action 쓰기는 했지만 썻다고 말할수 없다 내가 만든게 아니라 gpt가 만들었으니까! 근데 사실 여타 ci/cd가 그렇듯 jenkins pipeline문법이나, ansible 문법이나 action 문법이나 그냥 찾아가면서 작성하는거니까.. 라고 생각하면서 패스했다. 

회사에서는 아무래도 소스코드나 에러메시지를 직접 복사 붙여넣기해서 넣지 못하다보니 gpt한테 질문할때 가공해서 넣어야해서 귀찮았었는데 그냥 넣으면 나오니까 편했다ㅋㅋ. 그리고 Google Cloud Platform을 쓸때도 어떤 버튼을 어떤메뉴에 들어가야하는지까지 알려주고 바깥 세상 개발은 참 좋구나... 회사 내부 인프라를 사용하다보면 내부 인프라 가이드를 찾아보고 그랬었는데, 외부 세상에서 하는 개발은 gpt한테 물어볼 수 있다는 메리트가 있었다. 그동안 회사 개발만 했다는 티가 너무나나ㅠㅠ

 

그러나 바깥 세상의 개발중에 언제나 그렇듯 귀찮았던건 아무래도 secret 관리였다. 회사안에서는 사실 priavet repository라서 일부는  springboot aplication.yml에 박아서 썼었는데 각종 변수 하나하나를 환경변수 처리를 해야한다니.. 그리고 안했을때 혹시라도 과금폭탄이 무서웠다. 물론 private repository를 쓰면 똑같지만 공모전이다보니 open source 여야했다.

이런 민감정보들이 몇개 되지는 않아서 우선은 전부 app 시작시 쓰는 환경변수로 처리했는데, 만약 프로젝트가 좀 더 커진다면 좀 귀찮아 질 것 같다. 그치만 application.yml 파일 자체를 직접 import해서 로딩한다던지 다른 배포방법도 있지 않을까 싶긴하다. 이런 환경변수 처리들을 좀 쉽게하는 오픈소스들이 무조껀 있을텐데.. 라는 생각이 들면서도 개발을 시작해야하다보니 우선은 github action 자체의 secret을 사용했다.

그리고 사실 엄청 간단한 CI/CD 였기 때문에 문법이나 플러그인을 새롭게 도입할 필요가 없었다. 그래서 오히려 github CI/CD 예제를 보고 배포 플랫폼을 정했다ㅋㅋㅋ gemini api를 써야하다보니 Google Cloud Platform을 무조껀 써야하는 상황이었다. 따라서 google기반의 작은 vm기반 서버로하면 돈이 덜 들테니 그렇게 할까는 막연한 생각만 있었는데, action workflow을 쳐보니 cloud run 이란게 있네..? 부터 시작해서 위와 같은 서버 구조를 갖게 되었다. GKE는 쿠버네티스니까 스케일업,아웃 헬스체크까지 필요한 오케스트레이션 스펙은 현재 굳이 쓸 필요가 없다고 판단했다.

딱 빌드한 이미지가 빠르게 즉각적으로 배포되면서, 작은 컴퓨팅 리소스만 쓰는게(replica를 1로 설정했다.) 이런 작은 공모전 프로젝트에 오히려 적합하다는 생각이 들었다. 이런 gcp사용도 하나의 러닝커브였는데 이것 역시 GPT가 친절하게 어떤 버튼을 누르면 되고,, 어떤버튼으로 설정하면 되는지 알려주는 친절한 외부 개발 세상에 감탄했다ㅋㅋㅋ

그렇게 만든 github action flow는 아래와 같이 매우 간단하다 (여러 account key, secret 전달로 인해 길어졌을뿐.)

  1. docker file을 빌드한다 (docker 빌드시 gradle 빌드하여 앱을 실행한다.)
  2. 해당 docker 이미지를 GCP registry에 push한다.
  3. push된 이미지를 Cloud run이 (replica 1)실행시킨다.

cloud run을 사용하니 로깅도 즉각적으로 확인 할 수 있고 트래픽 모니터링도 기본적으로 제공해주는 게 있어서 편했다. 
(알람 시스템까지 따로 달았다면 좀 더 즉각적으로 에러를 볼 수 있었겠지만... 현재 사용자도 없는데 오버스펙이다.)


2. Spring + Kotlin 개발 그리고 Spring-Ai

그리고 이제 내부 api를 개발할 때가 되었는데 사실 인증, 데이터베이스, ai 사용. 이렇게 세가지가 주요한 백엔드 기능이었다. 
gemini 개발자 대회에서 firebase 섹션도 있어서, 인증과 데이터베이스를 모두 firebase 기반으로 구현했다.
firebase관련 코드자체는 클라이언트 사이드에서 직접쓰면 좀 더 효율적이었을 거란 생각이 들긴했다. firebase 코드전부가 future로 되어있었는데 spring-webflux도 아니라 spring-mvc 기반으로 사용하고있어서 전부 blocking하게 데이터를 가져올 때 조금 아쉬움이 들었다.

간만에 사용하는 spring-security로 firebase token 기반 인증을 구현하였고, database는 전부 cloud firestore로 구현했다. 그리고 gemini api 호출부는 spring-ai 기반으로 구현하였다. 비즈니스로직이 사실상 db읽기 + gemini api 호출뿐이라 큰 복잡도가 없어 model이 중요한 상황에서 kotlin을 사용하니까 data class를 사용하는게 코드 줄수도 적고 편했다.

이 공모전을 시작하면서 spring-ai를 쓰는게 목표였는데 사실 우리팀의 경우엔 RAG이나 embedding model을 커스텀해서 쓰기보단 gemini api에 호출하고 호출할때의 prompt engineering을 잘 하는게 목표였기 때문에 spring-ai 안에서도 chatClient만을 사용했다. chatClient에서 제공하는 기능을 사용하는건 사실상 httpClient로도 가능하지 않나 싶어서 조금 아쉬운 부분이었다.

그래도 spring-ai덕분에 python 환경에서 langchain, lamaindex를 사용할 때 공통된 포맷으로 gen ai api를 호출하고 embedding model을 변경하는 등의 작업을 할수 있다는 것처럼, jvm 시스템 안에서도 이런 일들이 조금은 가능하다는점에 의의가 있다고 생각했다. 따라서 Spring-AI의 경우에 ChatClient를 기반으로하는 추상화가 중요했고, Gpt, VertexApi,  Azure 등등의 gen ai 모델들을 ChatModel들의 구현체들로 유연하게 사용할 수 있게 했다는 점에서 객체지향을 많이 고려한다는게 느껴졌다.

다만 각 gen ai 모델 구현체들의 업데이트도 빠르게 이루어지고 지원범위가 다양하게 있는 만큼, 그걸 spring-ai 에서 따라가기 힘든경우가 있구나 느꼈는데 그것또한 되게 재밌는 경험이었다.

vertex api 스펙에 보면 response의 출력형식을 결정할 수 있는 요청 필드가 있는데 이 필드가, VertexAiGeminiChatModel 에서는 구현이 되어있지 않은 상황이었다. 우리 공모전에서 사용할 prompt에서는 이 필드를 꼭 넣어야 json 타입으로 응답이 올바르게 오는데 지정을 하지 않으면 parsing 에러로 500에러가 간간히 떨어지는 경우가 있었다.

https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/gemini?hl=ko#request

따라서 이걸 spring-ai의 chatClient를 안쓰고 그냥 httpClient를 사용해서 해결할까..라는 고민을 하다가 이미 어느정도 ChatClient에 맞춰서 개발을 해둔 상황이었고, spring-ai의 공식지원은 아니지만 reflection을 이용해서 config를 직접 주입해 넣으면 될것 같아서 아래와 같이 VertexAiGeminiChatModel의 기본 구현을 무시하고 reflection으로 내가 원하는 기능을 구현했다.

val chatModel = VertexAiGeminiChatModel(
    VertexAI(System.getenv("GEMINI_PROJECT_ID"), System.getenv("GEMINI_LOCATION"))
)

val generationConfig = GenerationConfig.newBuilder()
    .setResponseMimeType("application/json") // responseMimeType 을 설정할 수 없어서. 직접 접근하여 설정하였다.
    .setTemperature(0.8f)
    .build()

val generationConfigField = VertexAiGeminiChatModel::class.java.getDeclaredField("generationConfig")
generationConfigField.isAccessible = true
generationConfigField.set(chatModel, generationConfig)

생각해봤을때 spring-ai에서 default로 제공하는 gemini 버전은 gemini-1.5-pro 였고, 이 요청필드의 경우엔 1.5부터 지원을 했기에 공식적으로 지원을 하는게 좋지 않을까 싶어서. spring-ai에 해당 필드를 추가하여 PR도 넣어보았는데 승인되었다 :)
공모전덕분에 spring 관련 레포에 처음으로 contribution 하는 경험을 얻었다.

 

https://github.com/spring-projects/spring-ai/pull/1185#issuecomment-2305876768

 

feat: Add responseMimeType option in vertexAiGeminiChatOptions by jyami-kim · Pull Request #1185 · spring-projects/spring-ai

The Gemini model provides the responseMimeType parameter, as documented in the Gemini Model Reference. However, when I attempting to call the Gemini model using Spring AI, there is no direct option...

github.com

 

머지 됐다 :)

 

3. 문서화 : swagger

CI/CD를 완성하면서 인증, 데이터베이스, gen ai 호출까지 왠만한 기능들의 api를 구현하는것만 남았었다. 처음부터 swagger는 붙였었지만 실제로 해당 api를 사용할때 파라미터는 어떻게 해야하고, 어떤의미이고 이런것들을 처음엔 거의다 slack으로 안내를 드렸다.
근데 하다보니.. 같이 작업하시는 클라이언트 개발자분들의 편의가 걱정이 되었고 이에 따라 swagger에 documentation을 제대로 달아서 소통을 하는 방향으로 작업 방향을 바꾸게 되었다.

그런데 이런부분을 되게 좋아하셨어서 다음에 또 사이드프로젝트를 하게된다면 그때는 swagger기반으로한 문서화를 기본으로하고, 스웨거에서 api 호출도 가능하게까지 지원해야겠다고 생각했다. spring-security + swagger를 같이 사용하면 그만큼 스웨거를 이용한 api 호출을 이용할 때 추가 설정을 해줘야하는데, 허허 퇴근후 개발 쉽지않다더라. 

 

마치며

회고이기 때문에 내가 느낀점들을 위주로 글을 흐물흐물 쓴거같다. 그동안 퇴근하면 운동하고, 친구들 만나던 삶을 반복하다가 간만에 개발을 하다보니 리프레쉬가 되던 경험이었다. 스펙을 간단하게 잡기도했고 아무래도 서버사이드에서 크게 챌린징할만한 부분이 없었던(?) 작고 소소한 개발이었을지라도 퇴근하고 시간을 쪼개서 작은 프로젝트를 좋은 사람들과 협업해서 만들었다는게 되게 기분이 좋았기 때문에 간만에 블로그를 쓰게 되었다.

사실 아쉬운건 언제나 있을 수 밖에 없다. 사이드 프로젝트다 보니 코드를 너무 일회성으로 짜버렸을까. spring-ai 사용할때 테스트코드도 한번 짜볼껄, 슬랙으로 알람 연결해서 클라 개발자분들의 제보없이도 빠르게 고칠 수 있게 만들어볼껄 그랬나. 가장 간단한 인증방식으로 우선 구현을 위해서 했는데 jwt 기반으로 해볼껄 등등 여러가지 생각나는 것들이 있긴하다.

또한 서버에서 챌린징하는건 아무래도 정말 운영환경에 들어갔을때 정말 일이 시작되는구나도 느끼게되었다. 사실 api를 개발하는건 어떤 개발자든 금방 할테지만, 그 api를 얼마나 확장성있게, 그리고 안전하게, 빠르게 탐지하고 운영할 수 있게 만드는건 더더욱 중요한 요소라는 걸 반증하는 것 같았다. 놀랍게도 그래서 사이드 프로젝트를 하면서 회사 내부에서 사용하는 여러 운영 스킬들에 대해서도 한번 더 중요함을 느끼게되었다.

여튼 주절주절은 여기서 끝!
혹시라도 읽으시는 분이 있다면 현재 피플스초이스어워드가 진행중이니, 소중한 한표 부탁드립니다~! 🙇‍♀️

https://ai.google.dev/competition/projects/muse-diary?hl=ko

 

뮤즈 일기  |  Gemini API Developer Competition  |  Google AI for Developers

매일 채팅하고, 사운드트랙을 선별하고, 소중한 순간을 간직하세요

ai.google.dev