Rocket Ralph: 당신이 잠든 사이에도 답변하는 지원 시스템
요약
Discord 커뮤니티의 질문에 24시간 대응하기 위해 구축된 멀티모달 RAG 파이프라인 'Rocket Ralph'를 소개합니다. 텍스트, 이미지, 오디오, 비디오 등 다양한 소스 자료를 처리하여 문맥에 맞는 답변을 자동으로 제공합니다.
핵심 포인트
- 멀티모달 RAG를 통한 Discord 내 자동 지원 시스템 구축
- 텍스트, OCR, 오디오 전사, 비디오 프레임 분석 등 다양한 데이터 처리
- RecursiveCharacterTextSplitter를 활용한 정교한 청킹 기술 적용
- miniLM 임베딩과 Qdrant 벡터 데이터베이스를 이용한 검색 최적화
RocketRide Cloud에서 실행되며 Discord에서 커뮤니티의 질문에 24시간 내내 답변하는 멀티모달 RAG (Retrieval-Augmented Generation) 파이프라인입니다.
Krish Garg 및 Mithilesh Gaurihar 작성
몇 달 전, 새로운 멤버가 저희 Discord에 가입하여 그날 저녁 RocketRide를 설치했지만, 자정쯤 문제에 봉착했습니다. 그들은 필요한 설정 파일(configuration file)을 찾을 수 없어서 도움말 채널에 질문을 남겼습니다.
다음 날 아침, 저희 팀원 중 한 명이 적절한 문서(documentation)를 찾아 약간의 문맥(context)을 추가하여 전달했습니다. 덕분에 해당 멤버는 문제를 해결할 수 있었습니다. 일주일 후, 다른 누군가가 동일한 질문을 했습니다. 그러고 나서 팀이 잠든 사이에 또 다른 질문이 도착했습니다. 답변은 대개 이미 작성되어 있었지만, 여전히 사람이 직접 찾아 전달해야 했습니다.
그것이 실제 문제였습니다. 저희에게 필요한 것은 더 많은 문서가 아니었습니다. 문서와 도움을 요청하는 사람 사이의 더 나은 경로가 필요했습니다.
그래서 저희는 기존 자료를 수용하고, 질문에 유용한 문맥을 찾아내며, 동일한 Discord 채널 내에서 답변을 반환할 수 있는 검색 증강 생성 (RAG, Retrieval-Augmented Generation) 파이프라인을 구축했습니다. 저희는 그를 Rocket Ralph라고 부릅니다. RocketRide 부분은 캔버스 위의 21개 컴포넌트로 구성됩니다. 작동하는 경로를 만드는 데는 30분이 걸리지만, 신뢰할 수 있는 경로를 만드는 데는 더 오랜 시간이 걸립니다. 저희의 기존 Discord 봇은 웹훅 (webhook)을 통해 파이프라인으로 메시지를 보내므로, 이를 위해 별도의 애플리케이션을 구축할 필요가 없었습니다.
읽는 것보다 보는 것을 선호하신다면, 구축 과정을 녹화해 두었습니다:
우리가 원했던 것
저희는 "FAQ를 확인해 보셨나요?"라고 답변하는 또 다른 봇을 만들려고 했던 것이 아닙니다.
유용한 버전이 되려면 저희가 이미 보유하고 있는 자료, 즉 작성된 가이드, PDF, 스크린샷, 녹화된 워크스루 (walkthroughs), 그리고 리포지토리 (repositories) 자체와 함께 작동해야 했습니다. 또한 Discord 내에 머물러야 했습니다. 그 누구도 대화를 중단하고 별도의 지원 도구를 열어 똑같은 질문을 다시 던질 필요가 없어야 했습니다.
파이프라인은 두 부분으로 나뉩니다. 하나는 검색을 위해 지식을 준비하는 것이고, 다른 하나는 질문이 도착할 때 이를 처리하는 것입니다.
무질서한 자료를 검색 가능한 문맥으로 전환하기
수집 흐름(ingestion flow)은 RocketRide의 파일 입력 노드인 Dropper에서 시작됩니다. Dropper는 소스 자료를 받아들이며, 하류(downstream) 노드들이 이를 어떻게 처리할지 결정합니다.
문서들은 파싱(parsing)을 거친 후, 문장 중간이 아닌 자연스러운 경계에서 조각이 나도록 RecursiveCharacterTextSplitter를 사용하여 청킹(chunking)됩니다. 스크린샷과 추출된 이미지들은 OCR을 거칩니다. 오디오는 전사(transcribed)됩니다. 비디오는 음성 설명과 화면상의 텍스트가 유실되지 않도록 오디오와 대표 프레임으로 분리될 수 있습니다. 각 경로는 결과적으로 해당 정보가 어디에서 왔는지 이해할 수 있을 만큼 충분한 소스 정보를 포함한 텍스트를 생성합니다.
miniLM 임베딩(embedding) 모델이 이러한 텍스트 청크들을 벡터(vector)로 변환하며, 파이프라인은 이를 Qdrant에 저장합니다. 이렇게 하는 실질적인 이유는 간단합니다. 질문의 표현이 문서의 표현과 정확히 일치하는 경우는 드물기 때문입니다. 누군가는 "인증 문제(authentication problem)"에 대해 물을 수 있지만, 관련 가이드에서는 이를 "로그인 오류(login error)"라고 부를 수 있습니다. 벡터 검색(Vector retrieval)은 정확한 키워드에만 의존하는 대신 유사성(similarity)을 기반으로 가능성 높은 일치 항목을 찾는 방법을 제공합니다.
이 단계는 에이전트를 학습시키거나 올바른 문서를 찾아낼 것을 보장하지는 않습니다. 대신 우리가 제공한 자료의 검색 가능한 인덱스(index)를 생성합니다. 이 차이점은 나중에 중요해지는데, 검색 품질(retrieval quality)은 우리가 검사하고 개선할 수 있는 영역이기 때문입니다.
누군가 질문을 했을 때 일어나는 일
Discord에서 메시지가 도착하면, 우리의 봇은 이를 파이프라인의 웹훅(webhook)으로 전달합니다. 이후 질의(query) 측면은 짧은 경로를 따릅니다:
- 질문은 수집 단계에서 사용된 것과 동일한 miniLM 모델을 사용하여 임베딩됩니다.
- Qdrant는 가장 유사한 청크들을 반환하며, 이때 0.7의 유사도 점수 임계값(similarity score threshold)으로 필터링하여 모호한 일치 항목이 문맥(context)에 포함되지 않도록 합니다.
- 질문과 검색된 문맥은 OpenAI GPT-4.1을 기반으로 하는 CrewAI 에이전트에게 함께 전달됩니다.
- 답변은 Discord의 메시지 제한보다 안전하게 낮은 1,900자로 다듬어져 채널로 반환됩니다.
더 어려운 질문의 경우, Ralph는 우리의 GitHub 저장소(repositories)를 조사하거나 외부 API를 호출할 수 있는 도구(tools)도 갖추고 있습니다. 이러한 도구들은 선택 사항이며, 검색(retrieval)이 여전히 컨텍스트의 첫 번째 소스로 유지됩니다. 만약 인덱싱된 자료에 신뢰할 수 있는 답변이 포함되어 있지 않다면, 에이전트는 기억(memory)으로 공백을 채우기보다 그렇다고 말해야 합니다.
가드레일 (The Guardrails)
도구를 가진 에이전트에게는 더 많은 기능보다 더 많은 제한(limits)이 필요하며, Ralph의 제한 사항은 프롬프트(prompt)에 맡기기보다 파이프라인(pipeline)에 의해 강제됩니다:
- GitHub 도구는 읽기 전용(read-only)입니다. Ralph는 우리의 저장소를 검색하고 읽을 수 있지만, 쓸 수는 없습니다.
- HTTP 도구는 GET 요청만 수행하며, 우리가 제어하는 URL 화이트리스트(whitelist)에 대해서만 작동합니다.
- 도구 호출에는 속도 제한(rate-limited)이 적용되어, 오작동하는 루프가 API를 계속 때리거나 할당량(quota)을 소진할 수 없습니다.
- Ralph는 도구 결과로 보여줄 수 있는 것이 없다면 검색이 수행되었다고 주장할 수 없습니다. 아무것도 반환되지 않았다면, 답변은 그렇다고 명시합니다.
- 답변할 수 없거나 도구에서 인식할 수 없는 방식으로 오류가 발생하면, 그는 즉흥적으로 행동하지 않습니다. 대신 채널에 팀을 언급하여 사람이 개입할 수 있도록 합니다.
- 결제 관련 질문은 그가 답변할 영역이 아닙니다. 그런 질문은 매번 팀 언급(team mention)으로 처리됩니다.
이러한 도구들의 이면에 있는 자격 증명(credentials)은 RocketRide 환경 변수(environment variables)로 저장되어 런타임(runtime)에 주입됩니다. 이는 .pipe 파일에 붙여넣어지지 않음을 의미하며, 따라서 API 키를 포함하지 않고도 파이프라인을 공유하거나 버전 관리할 수 있습니다.
모든 단계 조사하기 (Inspecting Every Step)
첫 번째 버전은 우리가 기대했던 컨텍스트를 항상 검색해내지는 못했습니다. 이는 유용했는데, 에이전트 주변의 툴링(tooling)으로부터 우리에게 정확히 무엇이 필요한지를 보여주었기 때문입니다.
RocketRide의 상태(Status) 뷰는 활성 작업(active task)과 그 노드(nodes)를 보여줍니다. 흐름(Flow) 뷰는 입력값이 파이프라인을 통해 거친 경로를 보여줍니다. 추적(Trace) 탭은 유용한 디버깅 세부 정보를 노출합니다: Qdrant가 무엇을 반환했는지, 에이전트가 어떤 컨텍스트를 받았는지, 어떤 도구를 호출했는지, 각 단계가 무엇을 생성했는지, 그리고 각 단계가 얼마나 걸렸는지 등을 확인할 수 있습니다.
이는 모델의 비공개 사고 체인 (Chain of Thought)을 읽는 것과는 다릅니다. 그것은 운영 측면에서 더 유용한 것, 즉 우리가 실제로 조치를 취할 수 있는 입력값, 검색 결과 (Retrieval results), 도구 활동 (Tool activity), 그리고 출력값 (Outputs)에 대한 기록입니다.
만약 답변이 부실하다면, 문제가 파싱 (Parsing), 청킹 (Chunking), 검색 (Retrieval), 프롬프팅 (Prompting), 또는 도구 호출 (Tool call) 중 어디에서 시작되었는지 확인할 수 있습니다. 단순히 프롬프트를 수정하고 결과가 좋아지기를 바라는 대신, 잘못된 입력을 생성한 단계를 직접 수정할 수 있습니다.
저희의 말만 믿지 마세요
첫 번째 실제 테스트는 성공적이었습니다. 이 프로젝트를 시작하게 만든 것과 동일한 유형의 설정 파일 (Configuration-file) 질문이 Discord를 통해 들어왔고, 임베딩 (Embedding)과 검색 (Retrieval) 과정을 거쳐, 팀원 중 누구의 개입 없이도 적절한 문서가 첨부된 답변으로 돌아왔습니다. 하지만 저희의 말은 바로 이 포스트에서 '안주해서는 안 된다'고 말하는 바로 그 종류의 증거입니다.
그러니 직접 테스트해 보세요. Discord에 참여하여 Ralph에게 실제 질문을 던져보세요. 그런 다음 그가 말을 지어내도록 유도해 보세요. 우리가 출시하지 않은 기능, 존재하지 않는 설정 옵션, 혹은 출시된 적 없는 버전에 대해 물어보십시오. 앞서 언급한 가드레일 (Guardrails) 덕분에 그는 실행하지 않은 검색을 수행했다고 주장할 수 없으며, 그의 지침은 인덱싱된 자료가 답변을 뒷받침하지 못할 경우 이를 말하도록 되어 있습니다. 그가 답변을 거부하든 답변을 하든, 트레이스 (Trace)는 무엇이 검색되었고, 무엇이 주어졌으며, 그가 그것으로 무엇을 했는지를 정확히 보여줍니다. 저희는 그 트레이스들을 읽습니다.
한 번의 성공적인 답변이 지원 봇이 완성되었음을 증명하지는 않습니다. 하지만 그 경로가 엔드 투 엔드 (End-to-end)로 작동한다는 것은 증명합니다. 다음 작업은 덜 화려하지만 더 중요합니다. 대표적인 소스 자료를 추가하고, 사람들이 실제로 묻는 질문을 테스트하며, 실패한 검색을 검토하고, 에이전트 (Agent)가 컨텍스트 (Context)가 불충분할 때 이를 명확하게 인정할 수 있는 방법을 제공하는 것입니다.
왜 클라우드에서 실행하는가
파이프라인을 구축하는 것은 업무의 절반에 불과했습니다. Discord 지원 봇은 아무도 RocketRide 캔버스를 열어두지 않고 모든 개발자의 노트북이 닫혀 있을 때도 안정적인 엔드포인트 (Endpoint)가 필요합니다.
우리는 웹훅 (Webhook)과 파이프라인 (Pipeline)이 계속 사용 가능하도록 RocketRide Cloud에서 .pipe를 실행합니다. Cloud를 사용하면 팀이 런타임 변수 (Runtime variables)를 관리하고 실시간 작업 (Live tasks) 및 트레이스 (Traces)를 조사할 수 있는 단일 지점을 가질 수 있습니다. 아티팩트 (Artifact) 자체는 휴대성을 유지합니다. 이는 로컬에서 실행하거나 오픈 소스 서버와 함께 실행할 수 있는 것과 동일한 .pipe 형식입니다.
이러한 휴대성은 중요합니다. 엄격한 데이터 거주성 (Data-residency) 요구 사항이 있거나 기존 플랫폼 그룹을 가진 팀은 런타임을 자체 호스팅 (Self-host)하고 주변 서비스를 운영할 수 있습니다. 우리가 여기서 Cloud를 사용하는 이유는 Discord 질문에 답변하는 것이 제품 워크플로 (Product workflow)이기 때문이며, 그 워크플로 뒤의 인프라를 유지 관리하는 것은 아니기 때문입니다.
이것을 구축하려는 사람에게 해주고 싶은 말
가지고 싶은 지식 베이스 (Knowledge base)가 아니라, 현재 가지고 있는 자료로 시작하세요. 유용한 컨텍스트 (Context)는 잘 다듬어진 문서뿐만 아니라 스크린샷과 녹화 영상에도 담겨 있는 경우가 많습니다.
검색 (Retrieval)을 신탁 (Oracle)이 아닌 테스트해야 할 하나의 구성 요소 (Component)로 취급하세요. Qdrant는 후보군을 반환합니다. 에이전트 (Agent)는 여전히 이 후보군을 사용하는 방법과 지원하지 않는 질문을 거절하는 방법에 대한 명확한 지침이 필요합니다.
마지막으로, 파이프라인으로부터 증거를 요구하세요. 무엇이 검색되었는지, 무엇이 모델 (Model)에 도달했는지, 어떤 도구 (Tools)가 실행되었는지, 그리고 무엇이 돌아왔는지 확인할 수 있어야 합니다. 그러한 기록이 없다면, 모든 잘못된 답변은 추측의 영역이 됩니다.
그 결과는 우리 커뮤니티의 사람들을 대체하는 것이 아닙니다. 팀이 잠든 사이에 들어오는 질문을 포함하여, 이미 답변이 존재하는 반복적인 질문들을 처리합니다. 이를 통해 실제로 사람이 필요한 문제들을 위한 인간적인 대화의 여지를 남겨둡니다.
직접 구축하기
Ralph가 실행되는 모든 것은 현재 사용 가능합니다:
- RocketRide Cloud에서 파이프라인 구축 및 호스팅
- 직접 실행하고 싶으신가요? rocketride-server는 오픈 소스입니다
- 또는 RocketRide VS Code extension을 사용하여 에디터에서 바로 구축하세요
그리고 진행 중 막히는 부분이 있다면, RocketRide Discord에 접속하여 질문해 보세요. 네, Ralph도 그곳에 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기