- Published on
Assistants·LangChain로 RAG 없이 챗봇 고도화
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서로 다른 문서 저장소를 붙이고 임베딩을 튜닝하는 RAG는 강력하지만, 모든 챗봇에 필요한 기본값은 아닙니다. 특히 아래 조건이라면 RAG 없이도 충분히 “고도화된” 경험을 만들 수 있습니다.
- 지식이 자주 바뀌지 않거나, 핵심 규칙이 코드/DB에 이미 존재한다
- 사용자의 의도가 “업무 처리(트랜잭션)”에 가깝고, 정답이 외부 시스템에 있다
- 환각을 줄이기 위해 “검색”이 아니라 “검증 가능한 도구 호출”이 더 중요하다
- 대화의 핵심이 문서 요약/질의응답이 아니라, 상태 기반 플로우(가입, 예약, 환불 등)다
이 글에서는 OpenAI Assistants의 스레드/도구 호출 능력과 LangChain의 오케스트레이션을 결합해, RAG 없이도 챗봇을 고도화하는 실전 패턴을 다룹니다.
RAG 없이도 고도화가 가능한 이유
RAG는 “모델이 모르는 지식”을 외부 문서에서 가져와 답하게 만드는 접근입니다. 반면 많은 서비스형 챗봇은 사실 지식보다 다음이 더 중요합니다.
- 상태 관리: 사용자가 직전에 무엇을 했는지, 어떤 제약이 있었는지
- 도구 호출: DB 조회, 결제/환불 API, 일정 등록, 티켓 생성
- 구조화 출력: 사람이 아니라 시스템이 후속 처리할 수 있는 포맷(JSON)
- 안전장치: 정책 위반 방지, 권한 검증, 재시도/백오프, 멱등성
OpenAI Assistants는 스레드 기반 컨텍스트 관리와 도구 호출을 제공하고, LangChain은 이를 애플리케이션 레벨에서 “워크플로우”로 엮기 좋습니다. 즉, 문서 검색 대신 검증 가능한 시스템 레코드를 근거로 답하게 만들 수 있습니다.
전체 아키텍처: Assistants는 대화 OS, LangChain은 오케스트레이터
권장 구조는 다음과 같습니다.
- OpenAI Assistants: 대화 상태(스레드), 도구 호출 요청 생성, 응답 생성
- LangChain: 도구(Functions/Tools) 등록, 라우팅, 에러 처리, 재시도, 로깅
- 백엔드 서비스: 실제 데이터 소스(DB/CRM/결제/티켓), 권한/정책, 멱등성
핵심은 “모델이 답을 만들기 전에 필요한 사실을 도구로 가져오게” 만드는 것입니다. RAG가 문서에서 사실을 가져왔다면, 여기서는 API/DB에서 사실을 가져옵니다.
패턴 1) 도구 호출로 ‘사실’을 강제하기
예를 들어 주문/배송 상담 챗봇을 만든다고 합시다. 사용자가 “내 주문 언제 와?”라고 물었을 때, 모델이 추측해서는 안 됩니다. 주문 상태는 DB가 진실입니다.
LangChain Tool 정의 예시 (Python)
아래 코드는 LangChain 툴로 주문 조회를 감싸고, 모델이 필요할 때 호출하게 하는 형태입니다.
from typing import Optional, Dict, Any
from langchain_core.tools import tool
# 실제로는 DB/내부 API 호출
FAKE_ORDERS = {
"A100": {"status": "SHIPPED", "carrier": "CJ", "eta": "2026-02-27"},
"A101": {"status": "PROCESSING", "carrier": None, "eta": None},
}
@tool
def get_order_status(order_id: str) -> Dict[str, Any]:
"""주문 상태를 조회한다. order_id를 받아 배송 상태/예상 도착일을 반환."""
if order_id not in FAKE_ORDERS:
return {"found": False}
return {"found": True, "order_id": order_id, **FAKE_ORDERS[order_id]}
이제 모델은 “주문 상태를 말하기 위해 get_order_status를 먼저 호출해야 한다”는 규칙을 시스템 프롬프트로 강제합니다.
프롬프트 규칙(중요)
- 상태/가격/정책 등 “사실”은 반드시 도구로 확인
- 도구 결과에 없는 내용은 “알 수 없음”으로 처리
- 사용자에게 추가 정보가 필요하면 질문
이 패턴만으로도 환각이 크게 줄고, 운영 리스크가 감소합니다.
패턴 2) 상태 기반 플로우를 ‘스레드’와 ‘요약 메모리’로 관리
RAG 없이도 대화가 길어지면 컨텍스트가 흔들립니다. Assistants의 스레드가 기본 메모리를 제공하지만, 실무에서는 아래를 함께 권장합니다.
- 대화 요약(서버 저장): “사용자 목적/제약/확정된 값”만 축약해 저장
- 슬롯 필링(slot filling): 필요한 파라미터를 채울 때까지 질문
- 정책/권한 체크: 사용자 토큰의 스코프를 기준으로 도구 호출 허용
예: 환불 플로우라면 order_id, refund_reason, refund_method 같은 슬롯이 채워져야 다음 단계로 진행합니다.
패턴 3) 구조화 출력(JSON)로 후속 처리를 안정화
RAG를 안 쓰더라도, 챗봇이 “말은 잘하는데 실행이 불안정한” 경우가 많습니다. 해결책은 구조화 출력입니다.
- 모델 출력은 사람에게 보여줄 텍스트와
- 시스템이 실행할 액션(JSON)을 분리
아래 글에서 다룬 것처럼 JSON Schema 강제는 환각/형식 오류를 줄이는 데 매우 유용합니다.
예: “액션 플랜” JSON 스키마
다음 스키마는 “사용자에게 답변”과 “도구 호출 계획”을 분리합니다.
{
"type": "object",
"properties": {
"assistant_message": {"type": "string"},
"actions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"tool": {"type": "string"},
"args": {"type": "object"}
},
"required": ["tool", "args"]
}
}
},
"required": ["assistant_message", "actions"]
}
이렇게 만들어두면 UI는 assistant_message만 보여주고, 서버는 actions를 실행한 뒤 결과를 다시 모델에 피드백하는 방식으로 안정적인 루프를 구성할 수 있습니다.
패턴 4) Assistants의 도구 호출과 LangChain의 라우팅 결합
실무에서는 “어떤 도구를 언제 쓸지”를 단순히 모델에게만 맡기면 비용과 실패율이 올라갑니다. LangChain으로 1차 라우팅을 하거나, 최소한 “금지된 도구”를 정책적으로 막는 레이어가 필요합니다.
- 주문 조회는 누구나 가능하지만
- 주소 변경은 본인 인증이 있어야 가능
- 환불은 결제수단/기간/상태 조건을 만족해야 가능
즉, 모델은 의도 파악과 대화에 집중하고, 실행 가능성 판단은 애플리케이션이 담당하는 구조가 안전합니다.
패턴 5) 운영에서 가장 많이 터지는 것: 429 레이트 리밋
RAG를 붙이지 않으면 토큰이 줄어 비용은 내려갈 수 있지만, 트래픽이 늘면 결국 레이트 리밋이 병목이 됩니다. 특히 “도구 호출이 많은 챗봇”은 한 사용자 요청이 여러 번의 모델 호출로 분해되기 때문에 429를 더 자주 만납니다.
- 지수 백오프
- 지터(jitter)
- 큐잉(사용자별/조직별)
- 멱등 키
- 타임아웃/서킷 브레이커
구체적인 대응 패턴은 아래 글을 참고해 설계하는 것이 좋습니다.
Python 재시도 예시 (간단 백오프)
아래 예시는 네트워크/429에 대비한 최소 형태입니다.
import time
import random
class RateLimitError(Exception):
pass
def call_model_with_retry(fn, max_retries: int = 5):
for attempt in range(max_retries):
try:
return fn()
except RateLimitError:
sleep = (2 ** attempt) + random.random()
time.sleep(sleep)
raise
실무에서는 여기서 더 나아가 “요청 단위가 아니라 워크플로우 단위”로 재시도를 설계해야 합니다. 도구 호출이 섞이면 중간 단계만 재실행할지, 전체를 롤백할지 기준이 필요합니다.
RAG 없이도 ‘정책/업무’ 챗봇이 강해지는 설계 체크리스트
다음 체크리스트를 만족하면, 문서 검색이 없어도 상당히 높은 품질의 챗봇을 만들 수 있습니다.
1) 도구 호출 우선 원칙
- 상태/가격/재고/계정/권한 등은 도구로만 확인
- 도구 실패 시 사용자에게 “확인 불가”를 명시하고 다음 액션 제안
2) 구조화 출력
- 화면용 텍스트와 실행용 JSON 분리
- JSON Schema로 필드/타입/필수값 강제
3) 슬롯 필링
- 주문번호/이메일/기간 등 필수 파라미터가 없으면 질문
- 사용자 입력을 그대로 신뢰하지 말고 서버에서 검증
4) 관측 가능성(Observability)
- 스레드 ID, 사용자 ID, 요청 ID를 전 구간에 전파
- 도구 호출 로그(입력/출력/지연/에러)를 구조화 저장
5) 실패 설계
- 429/타임아웃 재시도
- 도구 호출 멱등성 키 부여(특히 결제/환불/티켓 생성)
- 부분 실패 시 사용자에게 “현재 상태”를 명확히 안내
언제 RAG가 다시 필요해지나
RAG 없이 출발하되, 아래 요구가 커지면 RAG를 고려하는 게 좋습니다.
- 사내 위키/정책 문서가 매우 크고 자주 바뀐다
- 긴 문서 근거를 인용해야 컴플라이언스를 만족한다
- 고객이 “문서에 있는 내용 그대로”를 요구한다
다만 그 시점에서도, 이 글의 패턴(도구 호출, 구조화 출력, 상태 관리)은 그대로 유효합니다. RAG는 “추가 지식 소스”일 뿐, 챗봇의 신뢰성과 운영 안정성은 결국 시스템 설계에서 나옵니다.
정리
OpenAI Assistants와 LangChain을 조합하면, RAG 없이도 다음을 통해 챗봇을 충분히 고도화할 수 있습니다.
- 도구 호출로 사실을 강제해 환각을 줄이고
- 스레드/요약 메모리로 상태를 안정적으로 관리하며
- JSON Schema 기반 구조화 출력으로 실행 신뢰성을 높이고
- 429/재시도/큐잉 같은 운영 패턴으로 장애를 줄인다
RAG는 강력한 옵션이지만, “업무 실행형 챗봇”의 첫 번째 정답은 종종 RAG가 아니라 도구 중심 설계입니다.