OpenAI 없이 RAG 앱 구축하기 - 1부
OpenAI는 가장 널리 알려진 대규모 언어 모델(LLM)입니다. 하지만 유일한 LLM은 아닙니다. 이 블로그의 꾸준한 독자라면, 우리가 이미 LangChain, Milvus, 그리고 OpenAI를 사용해 많은 RAG 유형 애플리케이션을 구축해 왔다는 것을 알고 계실 것입니다. 이 프로젝트에서는 OpenAI를 대체하기 위해 Nebula(API 키를 요청하려면 Nebula website 클릭)를 도입하고, OpenAI 임베딩 대신 Hugging Face의 임베딩 모델을 사용합니다.
이 글에서는 다음 내용을 다룹니다:
대화형 RAG 기술 스택
LangChain
Symbl AI
Milvus
Hugging Face
기본 대화형 RAG 앱을 구축하는 방법
대화형 RAG 스택 설정
대화 만들기
질문하기
OpenAI 없이 대화형 RAG 구축하기 요약
대화형 RAG 기술 스택
대화형 RAG는 RAG의 가장 일반적인 형태입니다. RAG 챗봇을 생각할 때 우리가 떠올리는 것이 바로 이것입니다. 작년에 우리는 CVP(ChatGPT 같은 LLM + Vector Database + Prompt as code)가 RAG의 표준 스택이라는 점을 논의했습니다. 이 스택에서는 모델을 하나 더 추가합니다 - Hugging Face의 별도 임베딩 모델입니다. 이 스택은 프롬프트 오케스트레이션에 LangChain을, LLM 제공에 Symbl AI를, 벡터 데이터베이스로 Milvus를, 임베딩 모델로 Hugging Face를 사용합니다.
LangChain
아직 들어보지 못했다면, 이제 듣게 되셨습니다: LangChain은 가장 인기 있는 LLM 앱 오케스트레이션 프레임워크입니다. 이 튜토리얼은 LangChain과 OpenAI를 사용해 대화형 메모리를 구현했던 프로젝트를 각색한 것입니다. 이번에는 OpenAI 임베딩이나 GPT 없이 동일한 프로젝트를 진행합니다.
Symbl AI
Symbl AI는 대화 데이터로 학습된 대화형 LLM을 만들었습니다. 이들의 LLM은 Nebula라고 하며, LangChain 통합 기능이 있습니다. 이 튜토리얼에서는 이전에 사용했던 OpenAI의 GPT-3.5 대신 Nebula를 사용합니다.
Milvus
대화형 메모리 부분의 빛나는 주인공은 벡터 데이터베이스입니다. 논의한 것처럼, 벡터 데이터베이스는 비정형 데이터를 벡터로 변환한 뒤 이를 처리하도록 설계되었습니다. 우리는 이 프로젝트의 대화형 메모리 부분을 위한 저장소로 Milvus를 사용합니다.
Hugging Face
Hugging Face는 세계 최대의 모델 허브입니다. 또한 LangChain과 직접 통합됩니다. 지난 프로젝트에서 사용했던 OpenAI 임베딩 대신 Hugging Face의 임베딩 모델을 사용합니다.
기본 대화형 RAG 앱을 구축하는 방법
이제 기술 스택을 알았으니:
약간의 설정 작업이 필요합니다.
앱이 RAG를 수행할 예시 대화를 정의합니다.
예시 질문을 하며 마무리합니다.
대화형 RAG 스택 설정
대화형 RAG 스택을 설정해 보겠습니다. 여섯 개의 라이브러리인 LangChain, Milvus (Lite), PyMilvus, python-dotenv, Sentence Transformers를 설치해야 합니다. 첫 번째 섹션에서는 LangChain의 핵심 요소 중 일부인 Vector Store Retriever Memory, Conversation Chain, Prompt Template 객체를 가져옵니다. 이 요소들을 사용해 대화형 메모리 예제를 만듭니다.
다른 import 두 가지는 `os`와 `load_dotenv`입니다. 이를 사용해 환경 변수를 로드합니다. 우리가 진행했던 다른 튜토리얼을 기억하신다면, 보통 OpenAI API 키를 로드했지만, 이번에는 Nebula API 키를 로드합니다.
! pip install langchain milvus pymilvus python-dotenv sentence_transformers
from langchain.memory import VectorStoreRetrieverMemory
from langchain.chains import ConversationChain
from langchain.prompts import PromptTemplate
import os
from dotenv import load_dotenv
load_dotenv()
api_key = os.getenv("NEBULA_KEY")
다음으로, 벡터 데이터베이스와 임베딩 모델부터 시작해 보겠습니다. Milvus Lite에서 `default_server`를 가져와 벡터 데이터베이스용으로 시작합니다. 다음으로, LangChain에서 Hugging Face 임베딩을 가져와 임베딩 함수로 사용합니다. 기본 모델은 `all-mpnet-base-v2`이며, 차원 수는 768입니다.
from milvus import default_server
default_server.start()
from langchain_community.embeddings import HuggingFaceEmbeddings
# is this model by default: sentence-transformers/all-mpnet-base-v2
embeddings = HuggingFaceEmbeddings()
다음 섹션은 “정리” 섹션에 가깝습니다. 아직 Milvus와 함께 LangChain을 사용해 본 적이 없다면 관련이 없습니다. 여기서는 Milvus에 연결하고 데이터 포이즈닝을 방지하기 위해 기존 LangChain 컬렉션을 삭제합니다.
from pymilvus import utility, connections
connections.connect(host="127.0.0.1", port=default_server.listen_port)
utility.drop_collection('LangChainCollection')
대화 만들기
이제 대화를 만들고 저장할 차례입니다.
먼저 LangChain에서 Milvus 벡터 스토어 컬렉션 객체를 가져와 빈 문서 집합으로 시작합니다. 또한 앞서 만든 Hugging Face 임베딩 함수도 전달합니다. 연결 인수로 Milvus Lite 인스턴스의 호스트와 포트를 전달합니다. 이번에 사용할 이 객체의 마지막 매개변수는 `consistency_level`이며, 이는 데이터 일관성 수준을 설정합니다. 이 경우에는 사용 가능한 가장 높은 데이터 일관성 수준인 “Strong”입니다.
from langchain.vectorstores import Milvus
vectordb = Milvus.from_documents(
{},
embeddings,
connection_args={"host": "127.0.0.1", "port": default_server.listen_port},
consistency_level="Strong")
다음으로, Milvus 컬렉션과 검색 인수를 전달하여 LangChain을 사용해 벡터 스토어 리트리버를 설정하겠습니다. 이 검색에서는 상위 결과 하나만 반환받고자 합니다. 그런 다음 이 리트리버를 LangChain RAG 스택의 일부로 LLM과 함께 사용할 수 있는 메모리 객체에 전달하겠습니다.
또한 시드 대화가 필요합니다. 실제 상황에서는 이 대화가 고객 서비스 전화와 같습니다. 이 예시에서는 제 자신에 대한 정보를 몇 가지 제공하겠습니다. 여러분은 여러분 자신에 대한 정보를 제공해야 합니다. 예시 대화가 만들어졌으면, 이를 컨텍스트로 메모리에 저장해야 합니다.
컨텍스트를 저장할 때는 두 개의 딕셔너리를 메모리 객체에 전달합니다. 이 예시에서는 예시 입력과 출력을 각각 “input”과 “output”으로 전달합니다. 직접 실험해 보고 싶다면 `save_context` 함수의 키와 값에 원하는 것을 무엇이든 전달할 수 있습니다.
retriever = Milvus.as_retriever(vectordb, search_kwargs=dict(k=1))
memory = VectorStoreRetrieverMemory(retriever=retriever)
about_me = [
{"input": "My favorite snack is chocolate",
"output": "Nice"},
{"input": "My favorite sport is swimming",
"output": "Cool"},
{"input": "My favorite beer is Guinness",
"output": "Great"},
{"input": "My favorite dessert is cheesecake",
"output": "Good to know"},
{"input": "My favorite musician is Taylor Swift",
"output": "I also love Taylor Swift"}
]
for example in about_me:
memory.save_context({"input": example["input"]}, {"output": example["output"]})
질문하기
이제 대화형 RAG 앱에 질문할 준비가 모두 되었습니다. 이 경우 지금까지의 대화 메모리를 가진 벡터 데이터베이스, Hugging Face의 임베딩 모델, 그리고 OpenAI가 아닌 LLM인 Nebula를 갖추고 있습니다. 질문을 하기 전에, 앱이 우리의 대화를 기억하는지 빠르게 확인해 보겠습니다.
사전 로드된 대화에서 무언가를 기억하는지 보여 달라고 해봅시다. 이 예시에서는 제가 가장 좋아하는 뮤지션(Taylor Swift)이 누구인지 묻고 있습니다. 여기서 백그라운드에서는 무슨 일이 일어나고 있을까요? memory 객체는 제공된 임베딩 모델을 사용해 프롬프트를 벡터화하고 기록을 검색합니다.
print(memory.load_memory_variables({"prompt": "who is my favorite musician?"})["history"])
아래 예시와 같은 출력을 얻을 수 있을 것입니다.
이제 대화 체인에 입력할 프롬프트 템플릿을 만듭니다. 이 부분에서 LLM이 필요합니다. 여기서는 Nebula를 가져오고 API 키를 전달하기만 하면 됩니다. 프롬프트를 여러 줄 문자열처럼 다루고, f-string에서 하듯 중괄호 표기법을 사용해 변수를 전달할 수 있습니다.
그런 다음 이 문자열과 변수 이름을 LangChain 프롬프트 템플릿 객체에 전달합니다. 프롬프트 템플릿, LLM, memory를 사용하면 conversation chain 객체를 통해 우리의 “대화”를 만들 수 있습니다.
from langchain_community.llms.symblai_nebula import Nebula
llm = Nebula(nebula_api_key=api_key)
_DEFAULT_TEMPLATE = """The following is a friendly conversation between a human and an AI. The AI is talkative and provides lots of specific details from its context. If the AI does not know the answer to a question, it truthfully says it does not know.
Relevant pieces of previous conversation:
{history}
(You do not need to use these pieces of information if not relevant)
Current conversation:
Human: {input}
AI:"""
PROMPT = PromptTemplate(
input_variables=["history", "input"], template=_DEFAULT_TEMPLATE
)
conversation_with_summary = ConversationChain(
llm=llm,
prompt=PROMPT,
memory=memory,
verbose=True
)
첫 번째 질문을 해봅시다. LLM에게 단순히 무슨 일인지 물어봅니다.
conversation_with_summary.predict(input="Hi Nebula, what's up?")
아래와 같은 응답을 볼 수 있을 것입니다. Nebula의 학습 데이터와 우리가 제공한 예시 데이터의 조합을 통해, LLM이 우리, 즉 “Human”이 다음에 무언가를 말할 것이라고 예상한다는 것을 볼 수 있습니다.
다음 질문으로 제가 가장 좋아하는 뮤지션을 알고 있는지 물어봅시다. 앞서 LLM에게 제가 가장 좋아하는 뮤지션이 Taylor Swift라고 말했고, 이 정보를 메모리에 저장했다는 점을 기억하세요.
conversation_with_summary.predict(input="Who did I say was my favorite musician?")
아래와 같은 응답을 볼 수 있을 것입니다. 우리는 방금 GPT 없이 대화형 RAG 앱을 만들었습니다.
OpenAI 없이 대화형 RAG 앱 구축 요약
이 글은 OpenAI 없이 RAG 앱을 구축하는 튜토리얼 시리즈의 첫 번째 편입니다. 이 튜토리얼에서는 Symbl AI가 만든 대화형 LLM인 Nebula를 살펴보았습니다. 벡터 데이터베이스로는 Milvus를, 임베딩 모델로는 Hugging Face의 MPNet V2를, 그리고 전체를 오케스트레이션하기 위해 LangChain을 사용했습니다.
이 예시에서는 대화형 RAG 앱을 만들었습니다. 벡터 데이터베이스로 Milvus를 실행하고 Hugging Face에서 임베딩 모델을 가져와 앱을 설정했습니다. 그런 다음 LangChain을 사용해 Milvus를 memory store로 사용하고, MPNet V2를 memory용 임베딩 모델로 사용합니다.
이전 단계들이 준비되면, 작업할 데이터를 만들기 위해 대화를 생성합니다. 대화를 memory에 로드하고 데이터가 있는지 빠르게 확인합니다. 다음으로 프롬프트와 LLM을 준비합니다. 그런 다음 이 모든 데이터를 conversation chain 객체에 로드하여 질문할 수 있게 합니다.
이 예시를 마무리하기 위해 대화형 RAG 앱에 두 가지 질문을 던집니다. “무슨 일이야?”와 “내가 가장 좋아하는 뮤지션은 누구야?” 이 시리즈를 계속 확장해 나갈 예정이니 계속 지켜봐 주세요!
이 구현의 요약은 Symbl.ai가 게시한 블로그를 참조하세요.
계속 읽기

How to Choose the Best Embedding Model for RAG in 2026: 10 Models Benchmarked
We benchmarked 10 embedding models on cross-modal, cross-lingual, long-document, and dimension compression tasks. See which one fits your RAG pipeline.

A Developer's Guide to Exploring Milvus 2.6 Features on Zilliz Cloud
Milvus 2.6 marks a shift from “vector search + glue code” to a more advanced retrieval engine, and it is now Generally Available (GA) on Zilliz Cloud (a managed Milvus service).

Vector Databases vs. Time Series Databases
Use a vector database for similarity search and semantic relationships; use a time series database for tracking value changes over time.



