메인 콘텐츠로 건너뛰기
page thumbnail

7년 된 Rails 모놀리스 안에 AI 에이전트 넣어보기 (실전기)

“7년 동안 잘 굴러가던 Rails 모놀리스에, 굳이 AI 에이전트를 넣어야 할까?”

Mon Ami 팀이 처음 가졌던 생각은 바로 이거였습니다. 미국에 기반을 둔 스타트업 Mon Ami는 고령자와 장애인을 위한 사례 관리 SaaS를 운영하고 있고, 그 핵심은 7년 동안 커온 아주 묵직한 Ruby on Rails 모놀리스입니다. 멀티 테넌트에, 복잡한 접근 제어, 민감한 데이터까지… 딱 “AI 실험하기 제일 부담스러운” 환경이었죠.

하지만 SF Ruby 컨퍼런스에서 RubyLLM과 RAG 관련 발표를 듣고, 상황이 바뀌었습니다. “이 모놀리스 안에서, 위험하게 데이터를 풀어주지 않으면서도 꽤 똑똑한 AI 에이전트를 만들 수 있겠다”는 감이 온 거죠.

이 글에서는 Mon Ami가 7년 된 Rails 모놀리스 안에 어떻게 AI 에이전트를 심었는지, 구조부터 구현 포인트, 2~3일 만에 MVP를 띄운 실제 접근 방식을 중심으로 정리해 봅니다. 모놀리스 Rails, 멀티 테넌트, 민감 데이터 환경에서 AI를 고민 중이라면 충분히 그대로 가져다 쓸 수 있는 아이디어일 거예요.


1. 모놀리스 Rails와 AI 에이전트, 정말 잘 섞일까?

먼저 배경부터 짚어보겠습니다. Mon Ami의 Rails 앱은 전형적인 “성공한 모놀리스”입니다. 하나의 거대한 코드베이스와 데이터베이스 위에, 수년 간 쌓인 비즈니스 로직이 빽빽하게 들어있습니다. 게다가 고령자·장애인 데이터를 다루는 SaaS라서 규제와 보안 요구도 만만치 않죠.

이 앱의 특징을 정리하면 이렇습니다.

민감한 데이터를 다루는 멀티 테넌트 구조입니다. 하나의 Rails 인스턴스가 여러 기관·고객을 동시에 서비스하고, 각 테넌트의 데이터는 강하게 분리해야 합니다. 흔히 ActsAsTenant 같은 방식으로 테넌트 스코프를 강제하거나, 컨트롤러·서비스 레벨에서 철저하게 “이 사용자가 볼 수 있는 자료인지”를 검사하는 패턴을 씁니다.12

이 말은 곧, “AI에게 데이터를 마음껏 던져줄 수 없다”와 같은 이야기입니다. 프롬프트 한 줄 잘못 쓰면, LLM이 전혀 다른 기관의 클라이언트 정보를 끌고 올 수도 있거든요. 대형 Rails 앱들이 멀티 테넌시·RBAC 등을 온갖 규칙으로 감싸는 이유도 이 때문입니다.34

Mon Ami 역시 비슷했습니다. 이미 Algolia 기반의 검색 인덱스를 구축해, 사례 관리자들이 클라이언트를 빠르게 찾을 수 있었지만, “이 위에 AI를 얹자”는 계획은 우선순위가 아니었습니다. 데이터 접근 제어가 너무 복잡했고, “AI 붙였다가 사고 나면 어떡하지?” 하는 걱정이 더 컸기 때문입니다.

그러던 중 SF Ruby 컨퍼런스에서 RubyLLM과 RAG 관련 발표를 듣고, 관점이 살짝 바뀌게 됩니다. 핵심은 두 가지였습니다.

첫째, LLM에게 “직접 DB를 열어보게” 하지 말고, 이미 애플리케이션 안에 있는 접근 제어 로직을 그대로 재사용하자는 것. 즉, Rails가 가진 모든 권한 체크를 함수로 감싸 도구(tool)로 제공하면, LLM은 그 도구만 통해 데이터를 보게 만들 수 있다는 아이디어입니다.

둘째, RubyLLM이라는 Gem이 여러 LLM 제공자를 하나의 깔끔한 API로 감싸주기 때문에, “LLM 연동 + 함수 호출 + 타임아웃/키 관리” 같은 번거로운 작업을 한 번에 해결할 수 있다는 점이었습니다. Rails 개발자 입장에서는 “서비스 객체 하나 더 만든다”의 감각에 가깝죠.

이렇게 해서 Mon Ami는 “Rails 모놀리스의 복잡한 접근 로직을 함수 호출로 캡슐화하고, RubyLLM이 그 함수를 사용하는 AI 에이전트를 구동하는” 구조를 택했습니다. AI는 앱의 수많은 데이터에 직접 손대지 않고, Rails가 허락한 통로로만 정보를 볼 수 있게 한 겁니다.


2. RubyLLM으로 LLM과 Rails를 연결하는 방법

이제 구체적인 기술 스택을 살펴보겠습니다. Mon Ami가 선택한 핵심 도구는 RubyLLM입니다. 이름 그대로, 루비에서 LLM과 상호작용하기 위한 래퍼 겸 프레임워크입니다.

RubyLLM의 장점은 크게 세 가지였습니다.

첫째, LLM 제공자 추상화입니다. OpenAI, 기타 LLM 업체들을 한 번에 감쌀 수 있는 공통 API를 제공합니다. 덕분에 개발할 때는 “대화 모델”, “툴”, “메시지” 같은 고수준 개념만 신경 쓰면 되고, 나중에 모델을 교체할 때도 설정만 바꾸면 됩니다.5

둘째, 세밀한 설정입니다. API 키, 타임아웃, 최대 토큰, 온도 등 모델 호출에 필요한 설정을 Ruby 코드와 설정 파일로 깔끔하게 분리할 수 있습니다. Rails의 환경변수·credentials와 잘 맞아떨어지는 구조라, 운영 환경에서 키 관리도 수월합니다.

셋째, 도구(툴) 호출을 지원하는 대화 모델입니다. RubyLLM은 단순히 “질문-답변”만 하는 챗봇이 아니라, LLM이 필요할 때 특정 함수를 호출하도록 유도하는 구조를 지원합니다. 이게 Mon Ami에게는 결정적이었습니다. 접근 제어가 얽힌 클라이언트 검색, 검증 로직 등을 함수로 감싸 툴로 등록해두면, AI는 이 툴을 이용해 안전하게 검색을 수행할 수 있습니다.

실제 구현을 조금 더 풀어보면 이렇습니다.

Rails 안에 서비스 객체를 하나 만듭니다. 이 서비스는 겉으로 보기엔 “API 컨트롤러 액션과 닮은” 역할을 합니다. 즉, 요청을 받아 컨텍스트를 구성하고, RubyLLM을 통해 LLM을 호출하고, 필요한 도구(예: ‘authorized_client_search’)를 호출할 수 있게 설정한 뒤, 결과를 가공해 반환합니다.

서비스 객체 내부에서는 현재 로그인한 사용자, 속한 테넌트, 권한 정보를 바탕으로 “이 사용자는 어떤 필드까지 볼 수 있는가”를 판단하는 기존 로직을 그대로 불러 씁니다. 이 로직을 통과한 데이터만 LLM에게 보여주도록 하는 것이 포인트입니다.

Mon Ami는 여러 OpenAI 모델을 붙여보며 테스트한 끝에 GPT-4o를 선택했습니다. 속도가 빠르면서도, 검색 결과를 정리해 주거나 사용자의 질문을 이해하는 정확도가 가장 만족스러웠기 때문입니다. 다만 RubyLLM으로 추상화해둔 덕분에, 나중에 모델을 바꾸거나 복수 모델을 상황에 따라 섞어 쓰는 것도 어렵지 않습니다.

정리하자면, Mon Ami의 AI 에이전트는 이렇게 작동합니다.

사용자의 자연어 질문 → Rails 서비스 객체가 RubyLLM 대화 모델 구성 → LLM이 필요 시 지정된 툴(예: Algolia 클라이언트 검색, 접근 제어 검사 함수)을 호출 → Rails 내부 로직이 실제 검색과 권한 검사를 수행 → 검증된 결과만 LLM에 다시 넘김 → LLM이 답변을 생성 → Rails가 응답을 UI로 스트리밍합니다.

AI는 “좋게 말하면 굉장히 똑똑한 프론트엔드”에 가깝고, 권한과 데이터 통제는 여전히 Rails가 쥐고 있는 구조입니다.


3. Algolia, 접근 제어, 그리고 안전한 RAG 설계

Mon Ami가 이미 갖고 있던 강력한 도구는 Algolia 인덱스였습니다. 수년간 축적된 클라이언트 데이터를 빠르게 검색하기 위한 검색 엔진으로, 다양한 필터와 정렬, 부분 일치 검색 등을 지원합니다.

문제는, 이 인덱스 안에도 민감 정보가 들어 있을 수 있다는 점입니다. 단순히 “AI야, Algolia에 이 쿼리로 검색해봐”라고 하면, 다른 테넌트의 데이터가 튀어나와도 AI 입장에서는 그게 문제인지 모릅니다.

그래서 Mon Ami는 “Algolia 검색 + 접근 제어”를 하나의 함수로 감싸기로 했습니다.

이 함수는 대략 이런 일을 합니다.

첫째, 현재 사용자와 테넌트를 받아 Algolia로 검색합니다. 여기까지는 평소 Rails 앱에서 하던 통합 검색과 크게 다르지 않습니다.

둘째, 검색 결과 각 항목에 대해 “이 사용자가 이 클라이언트를 볼 수 있는가?”를 기존 접근 제어 로직으로 다시 한 번 검사합니다. RBAC·멀티 테넌시·커스텀 권한 규칙 등, 그동안 모놀리스 안에 쌓아둔 모든 규칙이 그대로 동원됩니다.34

셋째, 이 필터링을 통과한 결과만 LLM에게 넘깁니다. 결과가 없다면 “당신은 이 정보에 접근할 수 없다”는 메시지를 AI가 사용자에게 자연스럽게 전달하도록 설계할 수 있습니다.

이렇게 하면, AI 기반 RAG 시스템임에도 불구하고 실제 데이터 접근은 기존 Rails 비즈니스 로직의 통제 아래에 두게 됩니다. RAG에서 중요한 건 “AI가 얼마나 많은 데이터를 볼 수 있느냐”보다 “필요한 정보만, 허용된 범위 안에서만 볼 수 있느냐”에 가깝습니다. Mon Ami는 이 부분을 Rails의 강점(이미 잘 짜인 접근 제어)을 활용해 해결한 셈입니다.

여기서 눈여겨볼 점은 “복잡한 접근 로직을 LLM 프롬프트에 설명해서 맞추려고 하지 않는다”는 겁니다. “너는 절대 다른 테넌트 데이터에 접근하면 안 돼”라고 프롬프트에 적는다고 해서, LLM이 100% 지킬 거라는 보장은 없습니다. 대신 “애초에 도구 함수가 다른 테넌트 데이터를 반환하지 않도록” 서버에서 물리적으로 막아버린 것이죠.

이 패턴은 다른 팀에도 그대로 응용할 수 있습니다.

이미 RBAC·멀티 테넌트·감사 로그 등 온갖 보안 기능이 들어간 Rails 앱이 있다면, 그 규칙을 LLM에게 다시 설명하려고 애쓰기보다, “툴 함수 안으로 규칙을 끌고 와서, 그 함수만 AI에게 오픈”하는 방식이 훨씬 안전합니다.


4. Turbo Streams로 대화형 AI UI 빠르게 붙이기

백엔드 준비가 끝났다면, 이제 사용자와 AI 에이전트가 대화할 UI가 필요합니다. Mon Ami는 이것도 Rails가 잘하는 방식을 그대로 가져왔습니다. 바로 Turbo Streams 기반의 대화형 UI입니다.

구조는 생각보다 단순합니다.

사용자가 질문을 입력하면, Rails 컨트롤러 액션이 이를 받아 아까 이야기한 서비스 객체를 호출합니다. 이 서비스는 RubyLLM을 통해 LLM과 도구를 적절히 연동하고, 점진적으로 도착하는 응답(스트리밍 응답)을 받아 처리합니다.

이때 Turbo Streams를 이용해, LLM이 답변을 생성해 나가는 대로 채팅 창을 실시간으로 갱신합니다. Rails 7 이후로 많은 앱이 도입한 이 패턴은, 별도의 SPA를 만들지 않고도 “마치 Slack이나 ChatGPT처럼” 부드러운 대화 인터페이스를 만들 수 있게 해 줍니다.

Mon Ami가 선택한 GPT-4o 모델은 속도가 빠른 편이라, 사용자 입장에서는 거의 실시간에 가까운 응답을 경험할 수 있었습니다. 특히 사례 관리 업무처럼 “실무자가 바로 참고해서 행동해야 하는” 환경에서는 답변 속도가 꽤 중요한 UX 요소입니다.

재미있는 점은, 이 전체 흐름이 “Rails 방식”에서 크게 벗어나지 않는다는 것입니다. 여전히 컨트롤러·서비스 객체·뷰(또는 partial)·Turbo Stream 응답이라는 익숙한 구성이며, 여기에 RubyLLM과 LLM 호출이 살짝 끼어든 정도입니다.

덕분에 Mon Ami 팀은 완전히 새로운 FE 스택을 도입하지 않고도, 그리고 백엔드를 미분해(microservices)하지 않고도, “Rails 모놀리스 안에 자연스럽게 녹아든 AI 챗봇”을 만들어낼 수 있었습니다.


5. 2~3일 만에 MVP: “서비스 객체 하나 더 만든 기분”

이제 실제 개발 경험에 대한 이야기입니다.

Mon Ami의 AI 에이전트 초기 버전은 2~3일 만에 만들어졌습니다. 물론 “완벽한 프로덕션 기능”이라기보다, 팀이 내부에서 테스트하고 빠르게 학습해 보기 위한 MVP 버전이었습니다. 하지만 이 짧은 기간 동안 꽤 많은 걸 해볼 수 있었습니다.

이들이 특히 흥미롭게 느꼈던 지점은 “서비스 객체가 사실상 하나의 API 컨트롤러 액션처럼 동작한다”는 점이었습니다. 외부에 노출된 REST 엔드포인트를 하나 만든다고 생각해 보면, 요청 파라미터를 검증하고, 권한을 확인하고, 도메인 서비스를 호출하고, 응답을 JSON으로 포맷팅하는 흐름을 구성하죠.

이번에는 그 “API 클라이언트”가 브라우저나 모바일이 아니라 LLM일 뿐입니다. LLM은 자연어로 질문을 전달하고, 서비스는 이를 해석해 도구를 호출하고, 답을 다시 자연어로 만들어 반환합니다. 구조적으로는 “조금 더 말 많은 클라이언트”에 불과합니다.

Rails 개발자 입장에서는 이게 굉장히 편합니다. 새로운 패러다임을 배우기보다는, 기존의 패턴 위에 LLM 레이어를 얹는 느낌이기 때문입니다. 테스트도 마찬가지입니다. 서비스 객체의 기능은 평소 하던 것처럼 Ruby 테스트로 검증하고, LLM 호출만 개발·테스트·운영 환경에 따라 모킹하거나 샌드박스 키를 사용하면 됩니다.

무엇보다 중요한 건, 이 접근 방식이 “모놀리스를 갈아엎지 않아도 된다”는 점이었습니다. 요즘 레거시 Rails를 다루는 글들을 보면, 마이크로서비스 전환, 모듈러 모놀리스, Bounded Context 설계 등 거대한 리팩터링 이야기가 자주 나오죠.12 하지만 Mon Ami가 AI 에이전트를 붙인 시도는, 그런 대역사와는 다른 종류의 실험입니다.

기존 모놀리스 구조를 그대로 유지한 채, 그 안에서 가장 잘 정리된 비즈니스 로직과 접근 제어를 “AI의 도구”로 승격시킨 것에 가깝습니다. 그리고 이는 많은 Rails 팀에게 꽤 현실적인 전략일 수 있습니다. 대규모 분산 시스템으로 갈아타기 전에, 지금 있는 모놀리스에서부터 AI의 실용성을 검증해 볼 수 있으니까요.


시사점: 레거시 Rails 앱에서 AI를 시작하고 싶다면

Mon Ami의 사례에서 Rails 개발자가 가져갈 수 있는 메시지를 정리해 보겠습니다.

첫째, 모놀리스라고 해서 AI가 안 붙는 건 아닙니다. 오히려 수년간 다듬어 온 비즈니스 규칙과 접근 제어 로직이 탄탄할수록, 그걸 도구로 재사용하는 AI 설계가 더 안전하고 강력해집니다.

둘째, LLM에게 “모든 걸 맡기려고” 하지 마세요. RBAC, 멀티 테넌트, 감사 규칙 같은 건 Rails가 계속 통제하는 게 좋습니다. LLM은 자연어를 이해하고, 질문을 구조화하고, 도구를 적절히 호출하고, 결과를 사람이 읽을 수 있게 정리하는 역할에 집중시키는 쪽이 안전합니다.

셋째, RubyLLM처럼 LLM과의 상호작용을 추상화해 주는 레이어를 도입하면, 모델 선택·교체·실험이 훨씬 쉬워집니다. GPT-4o가 지금은 좋더라도, 내년에 더 나은 모델이 나오면 설정만 바꾸고 갈아탈 수 있으니까요.

넷째, UI는 복잡하게 갈 필요가 없습니다. Turbo Streams를 활용한 대화형 UI만으로도 “실시간 AI 어시스턴트” 느낌을 충분히 낼 수 있습니다. 기존 Rails 앱에 자연스럽게 녹아드는 경험을 우선으로 두면, 팀 전체의 러닝커브도 줄어듭니다.

마지막으로, “처음부터 완벽하게” 만들 필요는 없습니다. Mon Ami처럼 2~3일짜리 실험으로 시작해, 팀 내부에서 써보고, 보안·UX·성능에 대한 감을 먼저 잡는 것이 좋습니다. 그 과정에서 “진짜로 AI가 잘하는 일”과 “여전히 사람이 통제해야 하는 영역”이 자연스럽게 구분될 겁니다.

7년 된 Rails 모놀리스 안에 AI 에이전트를 넣는다는 건, 거대한 재건축이 아니라 “한 칸 더 유용한 방을 만들고, 거기에 똑똑한 어시스턴트를 앉혀두는” 일에 가깝습니다. 이미 단단히 지어진 집이 있다면, 지금 있는 기둥과 벽을 최대한 활용해보는 것도 좋은 전략입니다.

당신의 Rails 모놀리스에도, 그런 방 하나쯤은 만들어볼 만하지 않을까요?


참고

1Untangling the Rails Monolith - quick look at the database - DEV Community

2Untangling the Rails Monolith - quick look at the code - DEV Community

3Mastering Multi-Tenancy in Rails: Scalable Architecture with ActsAsTenant 🚀

4Managing Multiple Brands in Rails: Multi-Tenant Patterns from RobinReach - DEV Community

이 노트는 요약·비평·학습 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.