1. 배경(Problem)
요즘 챗GPT 같은 AI 기술이 워낙 빠르게 발전하고 있죠. 이제는 안 써본 사람이 드물 정도로 우리 일상 속에 깊숙이 들어왔습니다.
그래서 저희는 단순히 AI를 ‘사용’하는 게 아니라, 직접 ‘활용하는 입장’에서 뭔가 만들어보자는 마음으로 프로젝트를 시작했습니다.
마침 저희 백엔드 부트캠프에서는 Spring 기반 개발을 중심으로 진행되고 있었기 때문에 스프링 생태계에 자연스럽게 통합할 수 있는 AI 프레임워크인 Spring AI를 선택하게 되었습니다.
또한, 이번 프로젝트의 주제가 ‘카카오 지도 연동’ 이었기 때문에 AI와 지도 기반 서비스를 연계해 실제로 유의미한 결과를 만들어보자는 방향으로 기획했습니다.
한편, 실제 사용자들은 이런 질문을 많이 하잖아요.
“이번 주 애인이랑 뭐하지?”
“리뷰 좋은 카페 어디 있어?”
“지하철 타고 어디까지 가면 될까?”
이처럼 구체적이고 맥락 있는 질문에 대해, AI가 자연어를 이해하고, 알맞은 장소를 추천해주는 서비스가 있다면 정말 편리하겠다는 생각이 들었습니다.
그래서 저희는 사용자의 자연어 입력을 분석하고, 분위기, 위치, 의도 등을 파악한 뒤, 외부 API와 연동하여 리뷰 기반 장소 추천을 하고, 결과를 카카오 지도에 시각적으로 표현하는 구조를 설계하게 되었습니다.
2. 서비스 소개(Solution)
서비스 설명
마키는 사용자의 자연어 질문을 이해하고, 분위기나 목적에 맞는 장소를 추천한 후, 해당 장소를 카카오 지도에 시각적으로 표시해주는 AI 기반 장소 추천 서비스입니다.
사용자가 입력한 문장 예시
•
“여의도에서 조용한 카페 추천해줘”
•
“데이트하기 좋은 식당 알려줘”
•
“지하철 타고 갈 수 있는 액티비티 없어?”
이런 문장을 입력하면,
1.
Spring AI 기반 챗 모델이 질문을 분석해 사용자의 의도, 위치, 분위기 등을 추출하고,
2.
리뷰 및 평점 데이터를 임베딩한 벡터 DB에서 유사도 검색을 수행한 뒤,
3.
추천 장소를 카카오 지도에 시각적으로 표시합니다.
또한,
•
지하철 경로 탐색
•
주차장 검색,
•
액티비티 추천 등 다양한 기능을 외부 API와 연계하여 제공합니다.
사용자의 이전 대화 맥락도 기억하고 이어서 응답할 수 있도록 ChatMemory 기능도 구현되어 있으며,
모든 응답 흐름은 툴 체이닝 기반으로 유연하게 구성되어 있습니다.
시연 영상
3. 아키텍처 및 핵심 기능
아키텍처 및 주요 사용 기술
•
프론트엔드 (React + Kakao Map API)
◦
사용자의 자연어 질문을 입력 받고,
◦
추천된 장소를 지도에 시각화하여 보여줍니다.
•
백엔드 (Spring Boot + Spring AI)
◦
사용자 입력을 Spring AI의 ChatClient로 전달합니다.
◦
여러 Advisors가 순차적으로 의도, 장소, 분위기 등을 분석합니다.
◦
분석 결과에 따라 적절한 Tool을 호출합니다.
•
툴 (Tools)
◦
Redis GEO 쿼리로 반경 내 장소 필터링
◦
pgvector로 분위기 기반 유사도 검색
◦
외부 API 호출: Tmap, Kakao Map, Google Places
•
DB
◦
PostgreSQL: 장소 정보 및 벡터 데이터 저장
◦
Redis: 위치 기반 빠른 조회, JWT 블랙리스트 처리
◦
Custom Chat Memory Table: 사용자 대화, 질문, 응답, 토큰 사용량 저장
•
AI 모델
◦
GPT-4o: 안정적인 툴 체이닝 및 자연어 응답 생성
◦
nomic-embed-text: 리뷰 데이터 임베딩 수행
•
모니터링 (Grafana + Spring Actuator)
◦
요청 횟수, 응답 시간, 토큰 사용량, 툴 실행 시간 등을 실시간 추적 및 시각화
4. 활용 라이브러리 및 개발 환경
구분 | 사용 기술 | 활용 목적 |
언어/프레임워크 | Java, Spring Boot | 백엔드 전반, AI 연동 처리 |
AI 연동 | Spring AI | LLM 기반 자연어 처리, 툴 체이닝, ChatMemory 등 |
DB | PostgreSQL + pgvector | 벡터 기반 유사도 검색 (장소 임베딩 저장) |
캐시/위치처리 | Redis | JWT 토큰 관리, 반경 기반 장소 필터링 (GEO) |
챗 모델 | OpenAI GPT-4o | 자연어 분석, 툴 호출 흐름 처리 |
임베딩 모델 | nomic-embed-text | 무료 + 빠른 임베딩, 자체 데이터 벡터화에 사용 |
지도 API | Kakao Map, Tmap | 지도 시각화 및 실시간 경로 탐색 |
데이터 수집 | Google Places API | 장소 정보 및 리뷰 데이터 확보 |
모니터링 | Spring Actuator + Grafana | 토큰 사용량, 요청 시간, API 호출 모니터링 |
서버 공유 | Ngrok | 임베딩 서버 외부 노출 및 팀원 협업 테스트 |
프론트엔드 | React + Kakao Map SDK | 사용자 입력 및 시각적 결과 표시 UI 구성 |
5. 주요 사용 기술 및 사용 이유
6. 트러블 슈팅
a. Spring AI 공식 문서와의 괴리
문제: 공식 문서를 보고 CallAroundAdvisor를 사용했는데, 프로젝트 실행 시 제대로 작동하지 않았습니다.
원인: 해당 클래스는 이미 deprecated 상태였고, 최신 버전에서는 CallAdvisor를 사용해야 했습니다.
해결 과정: 공식 문서엔 관련 설명이 없어서, 결국 직접 Spring AI 라이브러리 코드를 열어보고, 어떤 클래스가 실제 동작하는지 테스트하며 해결했습니다.
배운 점: 위 문제 말고도, 여러 부분에서 괴리가 있었습니다. 문서는 참고용일 뿐이고, 실제로는 코드를 직접 보고 확인하는 역량이 중요하다는 걸 느꼈습니다.
b. pgvector의 거리 필터링 한계
문제: pgvector를 이용해 장소 유사도 검색을 했는데, 검색 속도가 너무 느렸습니다.
원인: pgvector는 코사인 유사도 계산에는 강하지만, 반경 내 거리 필터링은 지원하지 않습니다. 모든 데이터에 대해 유사도 계산을 수행해야 하므로 비효율적이었습니다.
해결 과정: Redis의 GEO 쿼리를 이용해 사용자 위치 기준 반경 내 장소만 먼저 필터링한 후, 그 대상에만 pgvector 유사도 검색을 적용했습니다.
배운 점: 각 기술의 장단점을 보완적으로 활용하는 방식이 필요했고, Redis + pgvector 병행 구조가 좋은 해결책이었습니다.
c. 밀집 지역 검색 성능 저하
문제: 명동, 홍대 등 장소가 많은 지역에서는 Redis로 필터링해도 여전히 후보 수가 너무 많아 검색 속도가 느려졌습니다.
원인: 반경 내 업장 수가 수백 개가 넘다 보니, 유사도 검색 자체의 부하가 컸습니다.
해결 과정: 평점이 낮은 업장을 추가로 필터링하는 로직을 도입했고, 검색된 업장 수에 따라 필터링 기준을 유동적으로 조정해 성능을 개선했습니다.
배운 점: 단순히 기술을 적용하는 것이 아니라, 지역 상황에 따라 기준을 동적으로 조절하는 유연한 설계가 중요하다는 걸 깨달았습니다.
d. 임베딩 품질에 따른 추천 정확도 저하
문제: “리뷰 있는 카페 추천해줘”라고 했더니, “0개의 리뷰가 있습니다”라는 문장이 임베딩된 장소가 추천됐습니다.
원인: 임베딩 시 불필요한 문장까지 그대로 포함했기 때문에, 오히려 잘못된 추천 결과가 나왔습니다.
해결 과정: 텍스트 전처리를 강화하고, 사용자 질문 패턴을 예측해 자연어 형태로 가공된 임베딩 문장을 사전에 준비했습니다.
배운 점: 임베딩 품질이 곧 AI 응답 품질이라는 걸 깨달았고, 데이터 전처리의 중요성을 크게 실감했습니다.
e. Bedrock 모델의 툴 체이닝 미지원
문제: Bedrock 문서를 보고 Titan, Claude 모델에 툴 체이닝이 가능할 거라 기대했지만, 실제론 단일 툴만 호출 가능했습니다.
원인: 문서에 단순히 “툴 호출 기능 있음”만 명시되어 있고, 체이닝 지원 여부는 언급되지 않았습니다.
해결 과정: 직접 여러 모델을 테스트하며 체이닝이 가능한 모델을 찾았고, 최종적으로 GPT-4o를 사용하게 됐습니다.
배운 점: 외부 모델 도입 시, 문서보다 직접 테스트가 훨씬 중요한 신뢰 지표가 된다는 것을 경험했습니다.
f. 외부 API 응답 호환성 문제
문제: 지하철 경로 탐색 시, 분명 출발지와 도착지를 정상적으로 입력했는데도 “역이 존재하지 않는다”는 오류가 발생했습니다.
원인: 사용한 API들마다 역 ID 체계가 달라서 서로 매핑이 되지 않았습니다.
해결 과정: 좌표 기반 탐색이 가능한 Tmap API로 변경해 문제를 해결했습니다.
배운 점: 외부 API를 연동할 때는 ID 체계와 응답 포맷의 호환 가능성부터 먼저 확인하는 것이 중요하다는 걸 알게 됐습니다.
g. Google API 비용 및 정책 제약
문제
저희는 장소 추천 서비스에서 사용자 리뷰를 기반으로 유사도 검색을 하기 위해
Google Places API를 사용해 장소 정보와 리뷰 데이터를 수집했습니다.
하지만 실제 사용 과정에서 다음과 같은 심각한 제약이 있었습니다.
1.
API 호출 단가가 매우 높음
•
리뷰 데이터는 1000건당 약 40달러 수준의 비용이 발생했습니다.
•
저희가 수집한 총량은 10만 건이 넘었고, 이에 따른 비용은 30만 원 이상에 달했습니다.
2.
수집 실패 시 재요청 구조의 비효율
•
Google API는 중간에 실패하면,
기존에 수집한 데이터도 다시 처음부터 재호출해야 하는 구조였습니다.
•
이로 인해 불필요한 중복 호출이 발생했고, 비용이 기하급수적으로 늘어났습니다.
해결 과정
이 문제를 해결하기 위해, 저희는 Redis 기반의 캐시 전략을 도입했습니다.
•
각 수집 범위(좌표, 구역 등)를 Redis에 저장하고,
•
이미 수집한 범위에 대해서는 중복 호출하지 않도록 차단했습니다.
이를 통해,
•
API 크레딧 낭비를 줄이고,
•
대량의 데이터를 안정적으로 수집할 수 있었습니다.
h. 좌표 변환 정확도 문제
문제
•
사용자가 “여의도 리뷰 좋은 카페 추천해줘”라고 하면, 좌표가 한강 한가운데로 찍히는 문제가 발생했습니다.
원인
•
장소명이 모호하거나 범위가 넓으면, 지도 API가 잘못된 위치를 추론해버리는 경우가 많았습니다.
해결 과정
•
장소명을 사전에 정의한 카테고리로 매핑하거나,
•
특정 장소에 대해서는 기준 좌표를 직접 지정해서 위치 정확도를 보정했습니다.
배운 점: 자연어 입력은 불완전할 수밖에 없기 때문에, 이를 어떻게 정제하고 기준화할지가 지도 기반 서비스에서 매우 중요했습니다.
i. 한계
공식 문서를 정말 매일 확인했는데, 버전이 수시로 바뀌고,앞서 말씀드린 바와 같이 부정확한 내용도 존재했습니다..
아직은 안정적인 실무용 프레임워크로 사용하기엔 조금 이르다는 인상을 받았습니다.
Spring AI 안정성 부족
공식 문서를 정말 매일 확인했는데,
버전이 수시로 바뀌고,앞서 말씀드린 바와 같이 부정확한 내용도 존재했습니다..
아직은 안정적인 실무용 프레임워크로 사용하기엔 조금 이르다는 인상을 받았습니다.
LangGraph 미지원
또 하나는 Spring AI가 아직 LangGraph를 지원하지 않는 점입니다.
그래서 분기 처리, 상태 저장, 툴 체이닝 로직을 전부 직접 구현해야 했고,
툴 호출도 "이 툴을 호출하세요"라는 시스템 메시지를 프롬프트에 직접 하드코딩해야 했습니다.
기능이 늘어날수록 프롬프트도 점점 복잡해지고 길어졌습니다.
하드코딩의 부작용
이렇게 하드코딩된 구조는 프롬프트 길이 증가 → 토큰 사용량 증가로 이어졌고,
결국엔 비용 증가와 유지보수 어려움으로 연결됐습니다.
Grafana로 확인한 토큰 사용량
실제로 GPT API 요금을 직접 지불하면서 진행했는데,
금액이 너무 많이 나와서 Grafana로 토큰 사용량을 확인해보니,
저희가 삽입한 시스템 메시지조차도 전부 토큰으로 계산되고 있었습니다.
j. 개선 방향
결론적으로 현재 기준으로는 Spring AI는 아직 아쉬운 점이 많았습니다.
시연 영상에서는 응답 속도가 꽤 빨라 보이지만, 실제로는 하나의 요청이 30초 이상 소요되었고,
프롬프트가 길어질수록 지연이 더 심해졌습니다. 어디서 지연이 발생하는지도 분석해보았지만 찾기 힘들었구요.
그래서 프로젝트를 마치고 나서는 Python 기반으로 개발했으면, 더 좋았을 수도 있겠다 라는 생각이 들었습니다.
이번 프로젝트를 진행하면서 Spring AI를 기반으로 서비스를 개발해보았는데, 실무 관점에서는 몇 가지 아쉬운 점들을 직접 경험할 수 있었습니다.
•
먼저, Spring AI의 안정성 문제입니다. 개발 초기부터 공식 문서를 거의 매일 확인하며 기능을 적용했지만, 릴리즈 주기가 매우 빠르고 문서 내용도 자주 바뀌었습니다. 실제로 한 달 사이에 버전이 세 차례 이상 변경되었고, 문서에 안내된 방식대로 구현했는데도 deprecated된 클래스가 포함되어 있는 경우도 있었습니다. 이런 경험을 통해 Spring AI는 아직 실무에서 안정적으로 사용하기엔 다소 이른 단계라는 인상을 받았습니다.
•
또 하나의 큰 제약은 Spring AI가 LangGraph를 지원하지 않는다는 점이었습니다. LangGraph는 흐름 제어, 상태 저장, 조건 분기 등을 코드 수준에서 직관적으로 구성할 수 있는 프레임워크인데, Spring AI에서는 이러한 기능을 모두 직접 구현해야 했습니다. 예를 들어 사용자 요청에 따라 어떤 툴을 호출할지 결정하거나, 이전 상태를 기억해 다음 흐름으로 연결하는 작업을 시스템 메시지 안에서 일일이 하드코딩해야 했습니다. 기능이 많아질수록 프롬프트도 점점 복잡해지고, 유지보수가 어려워졌습니다.
•
이렇게 프롬프트에 모든 흐름을 하드코딩하게 되면 여러 부작용이 발생합니다. 우선 프롬프트 길이가 늘어나면서 GPT에게 전달되는 토큰 수도 함께 증가하게 됩니다. 이로 인해 응답 비용이 높아졌고, 구조가 길고 복잡해질수록 로직을 재사용하거나 수정하기도 어려웠습니다. 단순한 구조에서는 문제 없지만, 여러 기능이 조합되는 고도화된 구조에서는 분명한 한계로 느껴졌습니다.
•
실제로 저희는 GPT API 사용 비용을 직접 지불하며 프로젝트를 진행했는데, 예상보다 훨씬 많은 비용이 발생했습니다. 이상하다고 생각해서 Grafana를 통해 토큰 사용량을 분석해본 결과, 저희가 삽입한 시스템 메시지조차 모두 토큰으로 계산되고 있다는 사실을 알게 되었습니다. 단순한 안내 문장 하나도 비용에 포함되었고, 프롬프트가 길어질수록 응답 속도도 더 느려졌습니다. 일부 요청의 경우 하나의 응답을 받기까지 30초 이상이 소요되기도 했고, 어디서 병목이 생겼는지 파악하기도 쉽지 않았습니다.
•
이런 경험들을 종합해보면, 현재 기준으로는 Spring AI가 실무에서 안정적이고 효율적인 AI 어플리케이션을 만들기에는 아직 부족한 부분이 많다고 느꼈습니다. 특히 복잡한 흐름 처리, 툴 체이닝, 상태 기반 분기 처리가 필요한 구조에서는 오히려 Spring AI보다 Python 기반의 LangChain이나 LangGraph를 사용하는 것이 더 명확하고 직관적인 선택일 수 있다는 생각이 들었습니다.
결국, 프레임워크의 선택은 단순한 편의성만이 아니라 전체 구조의 확장성과 유지보수성, 그리고 비용 효율성까지 함께 고려해야 한다는 것을 다시 한번 실감한 프로젝트였습니다.
k. Google Place 리뷰 데이터 활용의 현실적인 한계
이번 프로젝트를 진행하면서 가장 크게 느낀 현실적인 한계 중 하나는, 바로 Google Place API를 통한 리뷰 데이터 활용 문제였습니다. 특히 비용과 정책 두 측면에서 모두 제약이 컸습니다.
•
우선 비용 문제입니다. 저희는 약 2만 건 정도의 장소 리뷰 데이터를 수집했는데, 그 비용만 해도 30만 원 이상이 들었습니다. 그런데 전체적으로 수집한 데이터는 11만 건이 넘었기 때문에, 만약 이를 전부 유료로 수집했다면 비용은 수백만 원 단위로 치솟았을 것입니다. 실무가 아닌 학습/테스트 단계에서도 이 정도 수준의 비용이 발생한다는 점은, 실제 운영 환경에서는 매우 큰 부담으로 작용할 수밖에 없습니다.
•
이러한 상황에서, 저희는 현실적인 우회 방법으로 무료 계정을 여러 개 생성해가며 데이터를 나눠 수집할 수밖에 없었습니다. 하지만 이는 명백히 지속 불가능한 방식이었고, 장기적으로 서비스화하기엔 안정성도, 윤리적 측면도 부족한 접근이었습니다.
•
더 큰 문제는 Google의 리뷰 데이터 사용 정책입니다. Google Places API는 제공된 리뷰 데이터를 서버에 저장하거나, 재가공하거나, 가공한 결과를 다시 노출하는 행위를 명확하게 금지하고 있습니다. 이 말은 곧, 리뷰 데이터를 기반으로 유사도 검색이나 추천 기능을 구현하려면 반드시 실시간 API 호출을 해야 한다는 뜻입니다. 그리고 이 구조는 사용자 수가 많아질수록 API 호출량과 비용도 비례해서 증가하게 되는 매우 비효율적인 구조입니다.
•
결국, 저희는 이 문제를 통해 Google Place 리뷰 데이터를 기반으로 한 추천 시스템을 그대로 운영하는 것은 현실적으로 불가능하다는 결론을 내렸습니다. 단순한 기술 구현의 문제가 아니라, 비용 + 정책 + 구조적 한계가 복합적으로 얽혀 있었기 때문입니다.
그렇다면 실제 서비스를 운영한다면 어떤 방식으로 이 문제를 해결할 수 있을까? 저희는 이 고민을 바탕으로 다음과 같은 구조를 구상해보았습니다.
1단계: Google API 실시간 호출 기반 서비스 운영 (초기 단계)
서비스 초기에는 어쩔 수 없이 Google API를 실시간으로 호출하여 리뷰 요약을 제공하는 방식으로 시작합니다.
이때는 유사도 검색은 어려우며, 호출 및 응답 비용을 감당해야 하기 때문에 사용량을 엄격히 제한할 수밖에 없습니다.
2단계: 사용자 리뷰 수집 유도 (자체 데이터 확보 단계)
이후, 사용자가 실제로 장소를 방문한 뒤 리뷰를 직접 남기도록 유도합니다.
예를 들어, 리뷰를 작성한 사용자에게는 토큰을 지급하거나 포인트를 제공하는 방식으로 동기부여를 할 수 있습니다.
3단계: 자체 DB 기반 추천 전환 (장기적 구조)
일정량의 리뷰가 쌓이게 되면, 더 이상 Google API에 의존하지 않고 자체 DB에 저장된 리뷰를 기반으로
벡터 임베딩과 유사도 검색을 수행할 수 있게 됩니다.
이렇게 되면 API 호출 비용 없이도 추천 기능을 유지할 수 있고, 정책 위반 문제에서도 자유로워질 수 있습니다.
이러한 구조로 전환된다면, 장기적으로 비용을 절감하고, 서비스 품질도 안정적으로 유지할 수 있을 것으로 예상했습니다. Google API는 초기 데이터 확보나 테스트용으로는 유용할 수 있지만, 운영 환경에서는 자체 리뷰 데이터 확보를 목표로 전략적인 전환이 필요하다는 점을 이번 프로젝트를 통해 분명히 느낄 수 있었습니다.
7. 팀 소개
공통 : Spring AI 학습 , 데이터 수집, 프롬프트 작성, 외부 API 작성, 프론트엔드 , 챗 메모리 구현
•
팀 소개말
저희 팀 이름은 ‘남바완’입니다. 마지막 프로젝트에서 꼭 1등 해보자고 다짐하며 지은 이름입니다.
닉값 제대로 했습니다.
•
회고