엔지니어링
래블업은 AI를 위한 소프트웨어 인프라를 만드는 회사입니다. 하지만 인프라만으로 도메인 문제를 풀 수는 없습니다. 전문 지식을 대규모로 추론할 수 있는 시스템이 필요하기 때문입니다. 지난 1년간 저희는 전혀 다른 과제를 다루는 두 개의 프로덕션 RAG 시스템을 개발했습니다. 하나는 11,000개 이상의 HS 코드를 다루는 한국 관세 품목 분류용 멀티 에이전트 시스템 HSense이고, 다른 하나는 7개 문서 프로젝트를 기반으로 고객 지원 질의를 처리하는 Backend.AI RAG 어시스턴트입니다.
이 글에서는 두 시스템을 만들면서 배운 것들, 아키텍처 선택, 저희가 접근했던 방식의 분석적 특징, 그리고 효과가 없었던 접근법을 다뤄보겠습니다.
단순 RAG의 한계
일반적인 RAG 레시피, 즉 문서를 청킹하고 임베딩한 뒤 상위 k개를 검색해서 생성하는 방식은 단일 말뭉치에 대한 간단한 Q&A에서는 잘 작동합니다. 하지만 다음과 같은 경우에는 그렇지 않습니다.
- 긴 문서가 복잡한 계층 구조를 갖고 있을 때. 한국 관세법은 2,000페이지가 넘는 체계로, 분류 규칙이 중첩(nested)되어 있습니다. 단순 청킹은 섹션 간의 관계를 파괴합니다.
- 분류에 다단계 추론이 필요할 때. 올바른 10자리 HS 코드를 결정하는 것은 단순 조회가 아니라, 통칙(GRI) 적용, 코드 계층 탐색, 품목 분류 사례에 대한 교차 참조가 필요한 작업입니다.
- 지식이 이질적인 출처에 흩어져 있을 때. Backend.AI 문서는 공식 문서와 내부 지식 베이스에 걸쳐 있는데, 각각 형식과 품질, 관련성 패턴이 다릅니다.
이를 극복하기 위해서 검색하고 생성하는 것을 넘어서는 아키텍처가 필요했습니다.
HSense: 관세 품목 분류를 위한 멀티 에이전트 RAG
소개
HSense는 과학기술정보통신부 주관의 XaaS 선도 프로젝트를 통해 개발된 AI 기반 HS 코드 분류 시스템입니다. 텍스트 혹은 이미지 기반의 물품 설명을 입력으로 받아서, 해당 물품에 적합한 HS 코드를 제안하고 분류 근거를 관련 법령에 기반하여 제시합니다.
왜 에이전트인가
HS 코드 분류는 복잡한 의사결정 문제입니다. 단일 제품이 여러 후보 코드에 해당될 수 있고, 올바른 분류는 재료 구성, 용도, GRI 규칙 해석, 프레임워크 정의에 동시에 의존합니다.
모든 것을 하나의 거대한 프롬프트로 처리하는 대신, 작업을 네 명의 전문가 역할로 분해했습니다.
| 에이전트 | 역할 | 도구 |
|---|---|---|
| 관세사 (코디네이터) | 팀을 조율하고, 탐색 방향을 안내하며, 최종 결정을 내림 | 다른 에이전트에게 위임 |
| GRI 규칙 전문가 (해석가) | 분류 규칙을 설명하고 해석 가이드를 제공 | 규칙 기반 추론 |
| 프레임워크 전문가 (학자) | 2,000페이지 이상의 텍스트(약 300만 토큰)를 검색 | FAISS 벡터스토어 RAG + SQLite FTS5 키워드 검색 |
| HS 코드 탐색기 (경로 찾기) | 2자리에서 10자리까지 코드 계층을 탐색 | 코드 트리 SQLite 데이터베이스 |
위의 구조는 실제 관세 전문가들의 업무 방식을 반영한 것입니다. 한 사람이 모든 맥락을 가지고 있지 않기 때문에 코디네이터가 구체적 질문을 하고, 전문가들이 증거를 제공하며, 구조화된 논의를 통해 분류 결과가 도출됩니다.
검색 아키텍처
프레임워크 전문가는 이중 검색 전략을 사용합니다.
- 시맨틱 검색: FAISS 벡터스토어를 활용한 의미 기반 쿼리('동물성과 식물성 지방의 혼합 제품')
- 키워드 검색: SQLite FTS5를 활용한 정밀 조회('제1517호', '소호 주 제2호')
PDF 형식의 한국어 문서를 구조화된 마크다운으로 변환하고(marker 사용), LangChain의 헤더 기반 스플리터로 마크다운 헤더별 청킹을 합니다. 글자 수 기반 분할보다 문서 구조를 보존하는 방식입니다.
HS 코드 탐색기는 전체 코드 계층을 담고 있는 별도의 SQLite 데이터베이스 위에서 동작하는데, 벡터 검색 없이 구조화된 트리 순회만으로 충분합니다.
에이전트 프레임워크
멀티 에이전트 프레임워크로 Agno를 사용합니다. 팀 정의의 간략한 예시를 보면 다음과 같습니다.
from agno.agent import Agent
from agno.team import Team
team = Team(
name="HSense Classification Team",
mode="coordinate", # 코디네이터가 전문가에게 위임
members=[tariff_officer, gri_expert, legal_expert, hs_navigator],
success_criteria="Provide HS code classification with supporting evidence",
max_discussion_rounds=5,
stream_intermediate_steps=True,
)각 에이전트는 Pydantic 모델 기반 출력 스키마를 갖추고 있고, 복잡한 케이스를 위한 추론 모드와 다중 턴 세션을 위한 대화 메모리를 내장하고 있습니다.
성능
한국 관세 품목 분류 데이터베이스에서 추출한 500개 테스트 케이스에서, 멀티모달 이미지 어노테이션을 적용한 Gemma-3-27B-IT의 추론 결과는 다음과 같습니다.
| 지표 | 점수 |
|---|---|
| Top-1 정확도 | 92.40% |
| Top-3 정확도 | 96.40% |
| Top-5 정확도 | 97.40% |
| Top-20 정확도 | 98.40% |
| 2자리(류) 정확도 | 96.60% |
| F1 점수 | 86.17% |
| 케이스당 평균 토큰 | 1,549 |
참고로, 2024년 Bryce Judy의 벤치마크1에 따르면 상용 관세 품목 분류 모델의 10자리 코드 Top-1 정확도는 44~89% 범위였습니다. HSense의 92.4%는 이 범위의 최상단에 해당하는데, GPT-4가 아닌 27B 오픈 웨이트 모델로 달성한 결과입니다.
여러 류(Chapter)에서 100% Top-1 정확도를 달성했지만(Chapters 11, 15, 20, 23, 29, 30, 33, 34, 35, 38, 40, 60, 82, 94), 제품이 모호하거나 규칙이 특히 미묘한 류에서는 50~62.5% 수준을 보였습니다.
효과가 없었던 접근법
계층 구조 문서에 벡터스토어 청킹을 적용한 것. 가장 큰 교훈입니다. 관세법2은 트리 구조를 갖고 있어서, 류(Chapter) 안에 호(Heading)가 있고, 호 안에 소호(Subheading)가 있으며, 소호에는 상위 항목의 해석을 변경하는 주(Note)가 달려 있습니다.
청크를 대상으로 한 플랫 벡터 검색은 이런 계층적 관계를 무시하기 때문에, 코드 계층에 대해서는 SQLite 기반 탐색이 더 잘 동작하였습니다.
단일 에이전트 방식. 하나의 에이전트가 GRI 해석, 코드 탐색, 프레임워크 검색, 최종 결정을 모두 처리하려고 하면 혼란이 생깁니다. 컨텍스트 윈도우가 관련 없는 정보로 채워지면서 모델이 집중력을 유지하지 못했는데, 역할을 분리하니 정확도와 디버깅 용이성이 모두 개선되었습니다.
코디네이터의 자유로운 의사결정. 초기 버전에서는 코디네이터가 잘못된 상위 결정(잘못된 류 선택)을 내려서, 모든 전문가가 엉뚱한 방향으로 노력을 낭비하는 경우가 있었습니다. 이를 해결하기 위해 역추적 기능을 추가했는데, 전문가들의 증거가 초기 방향과 모순되면 코디네이터가 리셋하고 대안을 탐색할 수 있게 한 것이 성능을 개선하는데 유효하였습니다.
Backend.AI RAG 어시스턴트
소개
Backend.AI RAG 어시스턴트는 Backend.AI를 사용하고 있는 고객들의 질문과 요청에 체계적으로 대응하기 위해 개발되었습니다. Backend.AI 문서가 포함된 GitHub 저장소와 래블업 내부의 지식베이스 일부를 기반으로 사용자의 질의에 응답합니다.
아키텍처
어시스턴트는 7개 문서 프로젝트를 검색하여 고객 질문에 답변합니다.
사용자 쿼리
→ RequestClassifier (GPT-4.1-mini, 멀티 라벨 라우팅)
→ VectorDBManager (7개 FAISS 인덱스, 프로젝트별 k=10)
→ 신뢰도 필터 (L2 > 1.5 제거)
→ 퓨전 날짜 재랭킹 (유사도 우선, 날짜를 타이브레이커로)
→ 글로벌 상위 15개 선택
→ RAGManager (Qwen3.5-35B-A3B, LangChain v0.3, 스트리밍)
→ 응답
RequestClassifier: 검색 전에 쿼리를 관련 프로젝트로 라우팅합니다. '컴퓨트 세션 생성 방법'에 대한 질문은 webui와 backendai 문서를 검색해야지, enterprise-guide나 realworld_data_support를 검색할 필요가 없습니다. 분류는 GPT-4.1-mini의 구조화된 출력 파싱을 사용하여 8개 카테고리(7개 문서 프로젝트 및 1개 대화 카테고리)로 수행하며, 맥락 파악을 위해 최근 3개 대화 메시지를 반영합니다.
신뢰도 필터: L2 거리가 1.5를 초과하는 청크를 제거합니다. 관련성이 낮은 결과가 컨텍스트를 희석시키는 것을 방지하기 위해서입니다.
퓨전 날짜 재랭킹: 유사도를 주요 신호로, 청크 날짜를 타이브레이커로 사용합니다. 두 청크의 L2 점수가 비슷하면 더 최근 것이 상위에 랭크됩니다.
환각 방지 시스템 프롬프트: 컨텍스트에 있는 내용만으로 답변하도록 엄격하게 강제합니다. 모델은 IP, 포트, 경로, 서버명, 명령어를 절대 지어내서는 안 됩니다. 서로 다른 시기의 솔루션이 여러 개 존재할 경우, 가장 최근 것을 주요 권장 사항으로 먼저 제시한 뒤 대안을 날짜와 함께 나열합니다.
평가 결과
25K건의 실세계 데이터에서 샘플링한 100개의 기술 Q&A 테스트 케이스로 파이프라인을 평가했습니다. 생성 모델은 Backend.AI 인프라에서 서빙되는 Qwen3.5-35B-A3B입니다. 두 가지 검색 모드를 비교했습니다.
| 지표 | 샘플 데이터 | 전체 데이터셋 |
|---|---|---|
| 관련성 | 0.809 | 0.824 |
| 정보성 | 0.775 | 0.792 |
| 정보 보안 | 0.950 | 0.960 |
| 실용성 | 0.766 | 0.778 |
| 장황함 | 0.847 | 0.845 |
| SemScore | 0.703 | 0.704 |
| 종합 점수 | 0.813 | 0.823 |
| 검색 top-1 적중률 | 45% | 45% |
| 검색 top-3 적중률 | 60% | 59% |
| 평균 응답 시간 | 3.02초 | 3.32초 |
전체 데이터셋 모드가 샘플 데이터보다 일관되게 높은 성능을 보여, 프로젝트 간 컨텍스트가 가치를 더한다는 것을 확인했습니다. 정보 보안은 0.95 이상으로, 환각 방지 시스템 프롬프트가 개인정보 유출과 허위 정보 생성을 효과적으로 차단하고 있습니다.
평가 방법론
LLM을 심사위원으로 활용하는 평가 방식을 사용하며, 6개 지표를 동시에 평가하는 단일 구조화 출력 호출을 사용합니다. 이전의 지표별 DeepEval GEval 방식보다 25배 빠릅니다 (200건 평가 기준 약 90분 vs 약 28시간).
지표 (모두 0.0~1.0):
- 관련성: 응답이 기술적으로 문제를 해결하는지 여부. 정답과 정확히 일치하지 않더라도 유효한 대안적 접근을 인정.
- 정보성: 제공된 정보의 완성도와 깊이.
- 정보 보안: 고객명, IP, 계정 ID, 내부 인프라 정보 등 개인정보 유출 여부를 검사.
- 실용성: 조언이 실제 환경에서 실행 가능하고 적용할 수 있는지 여부.
- 장황함: 응답 길이의 적절성. 너무 짧거나 너무 긴 경우 모두 감점.
- SemScore:
text-embedding-3-small임베딩을 활용한 생성 답변과 기대 답변 간의 코사인 유사도. 객관적이고 재현 가능하며 LLM 심사위원의 변동성이 없음.
종합 점수는 가중 평균입니다. 관련성(0.25), 정보 보안(0.20), 정보성(0.15), 실용성(0.15), SemScore(0.15), 장황함(0.10).
테스트 케이스는 2단계 품질 필터를 거칩니다: 태그 사전 필터(25K건 → 19K건), LLM 분류기(500건 → 323건 유효 → 100건 샘플링). 검색 후 정확히 일치하는 소스 청크는 제거하여 데이터 유출을 방지합니다. LLM 심사위원의 평가 기준은 다음과 같습니다: "DevOps 엔지니어가 이 응답을 받았을 때 문제를 해결할 수 있는가?"
두 시스템에서 공통으로 배운 것들
- 가장 취약한 고리는 검색
테스트 케이스의 45%만이 정답 소스 문서를 top-1 검색 결과로 가져옵니다(top-3 기준 60%). 절반 이상의 경우에 생성 모델이 최적이 아닌 컨텍스트로 작업하고 있다는 뜻입니다. 하이브리드 BM25 + 시맨틱 검색, 크로스 인코더 재랭킹, 또는 더 나은 청킹을 통해 검색을 개선하는 것이 모델을 교체하는 것보다 더 큰 효과를 가져올 것입니다. 검색 품질은 모델 선택보다 더 많은 엔지니어링 노력을 쏟을 가치가 있습니다.
- 심사 프롬프트가 생성 모델보다 더 중요
가장 큰 정확도 향상(+0.163)은 생성 모델이나 검색 파라미터를 바꿔서가 아니라, 심사 프롬프트를 수정해서 얻었습니다. 이전 심사 프롬프트는 기대 텍스트와 정확히 일치하지 않는다는 이유로 기술적으로 유효한 대안 답변에 감점을 부과했습니다. 새로운 심사 프롬프트는 이렇게 묻습니다: "DevOps 엔지니어가 이 응답으로 문제를 해결할 수 있는가?" 예를 들어, Docker 레이어 다운로드 문제를 해결할 때 registry-1.docker.io와 docker.io를 동등하게 인정합니다.
- 구조화된 데이터에는 벡터 검색보다 더 나은 구조화된 검색
HSense의 HS 코드 탐색기는 FAISS가 아니라 SQLite를 사용하고, Backend.AI 분류기는 임베딩이 아니라 LLM을 사용합니다. 데이터에 알려진 구조가 있다면, 즉 계층, 카테고리, 메타데이터 필드가 있다면 그 구조를 직접 활용하는 편이 낫습니다. 벡터 검색은 비정형 콘텐츠를 위한 대안이지, 범용 솔루션이 아닙니다.
- 날짜 인식 검색이 바꾸는 응답 품질
서로 다른 시기의 청크가 같은 주제를 다룰 때, 모델은 오래된 솔루션을 주요 권장 사항으로 제시할 수 있습니다. 퓨전 날짜 재랭킹과 컨텍스트 헤더의 날짜 메타데이터를 통해 모델이 솔루션에 날짜를 표기하고 가장 최근 방법을 먼저 추천할 수 있게 됩니다. 테스트 케이스의 51%에서 모델이 날짜 컨텍스트와 함께 여러 솔루션 접근법을 제시합니다.
- 멀티 에이전트 시스템에서는 관측 가능성이 필수
에이전트 하나가 잘못된 결정을 내리면 에이전트팀 전체가 연쇄적으로 잘못된 방향으로 흘러갈 수 있습니다. 가령, HSense 코디네이터가 잘못된 제품 류를 선택한 적이 있었는데, 그 때문에 세 명의 전문가 에이전트가 틀린 증거를 생성하는 상황으로 이어졌습니다. 단계별 로깅과 의사결정 체인 추적 기능이 없으면 이런 실패를 피할 수 있습니다.
- 오픈 웨이트 모델은 이미 프로덕션 준비 완료
Qwen3.5-35B-A3B는 자체 GPU 인프라에서 서빙하여 3초 응답 시간으로 0.823 종합 점수를 달성합니다. 검색된 컨텍스트가 대부분의 무거운 작업을 처리하는 도메인 특화 RAG에서는 오픈 웨이트 모델과 상업용 모델의 격차가 작고 계속 줄어들고 있습니다.
향후 계획
HSense:
- 관세 문서 검색을 플랫 벡터스토어에서 계층 인식 검색으로 전환
- 자동화된 A/B 테스트와 피드백 루프를 활용한 프롬프트 최적화
- 규칙 기반 분류를 보완하기 위한 제품 품목 분류 사례 RAG 파이프라인 추가
Backend.AI 어시스턴트:
- 청킹 파이프라인 수정(적절한 분할 지점 탐지, 인덱스 재구축)
- 하이브리드 BM25 + 시맨틱 검색으로 45% top-1 검색 적중률 개선
- 크로스 인코더 재랭킹으로 더 정밀한 top-k 선택
- 트러블슈팅 및 추론 쿼리를 포함하도록 평가 범위 확장
- 현재의 G-Eval과 함께 RAGAS 지표(충실도, 컨텍스트 정밀도/재현율) 구현
- Streamlit 기반 A/B 테스트 플랫폼을 활용한 사람 평가
두 시스템 모두 Backend.AI 인프라 위에서 동작하기 때문에, GPU 할당, 모델 서빙, 스케일링을 래블업이 만드는 플랫폼에서 처리합니다. 이 글에 담긴 내용 외에도, 합성 데이터 생성과 도메인 전문가와의 피드백을 통한 RAG 시스템 고도화 작업에 대해서는 아래 영상에서 더욱 자세히 확인하실 수 있습니다.
Backend.AI에 관심이 있거나, GPU 인프라 위에서 RAG 시스템을 구축하고 싶으시다면 backend.ai를 방문하시거나 info@lablup.com으로 연락주세요.
Footnotes
-
[Benchmarking Harmonized Tariff Schedule Classification Models] (https://arXiv:2412.14179), arXive ↩
