Ruby와 Milvus로 End-to-End GenAI 앱 구축하기
LangChain과 같은 전문화된 GenAI 프레임워크의 등장은 ChatGPT 및 LLaMA와 같은 강력한 대규모 언어 모델(LLMs)을 활용하여 정교한 AI 애플리케이션을 빠르고 쉽게 구축할 수 있게 해주었습니다. 예를 들어 LangChain을 사용하면 깊은 이론적 AI 지식이 필요 없이 단 몇 줄의 코드만으로 강력한 Retrieval Augmented Generation(RAG) 애플리케이션을 만들 수 있습니다.
이러한 추세는 오늘날 데이터 과학자와 머신러닝 엔지니어만이 GenAI 애플리케이션을 구축할 수 있는 것이 더 이상 아니라는 의미입니다. 이제 풀스택 엔지니어 또는 소프트웨어 개발자도 LangChain을 사용하여 GenAI 앱을 구축할 수 있습니다.
그러나 이러한 GenAI 프레임워크는 일반적으로 Python으로 작성되어 있으며, 일부 풀스택 엔지니어와 소프트웨어 개발자는 프로젝트에서 Python을 거의 사용하지 않는다는 것을 알고 있습니다. 따라서 이러한 풀스택 엔지니어들이 강력한 LLMs를 활용하여 소프트웨어 프로젝트에서 GenAI 애플리케이션을 구축할 수 있도록 다른 프로그래밍 언어로 된 이러한 GenAI 프레임워크의 확장이 필요합니다.
최근 강연에서 Source Labs LLC의 Solution Architect인 Andrei Bondarev는 풀스택 엔지니어들이 소프트웨어 프로젝트에서 GenAI 애플리케이션을 더 쉽게 구축할 수 있도록 LangChain.rb라는 LangChain의 Ruby 확장을 소개했습니다.
하지만 Ruby로 GenAI 애플리케이션을 구축하는 방법을 논의하기 전에, GenAI의 인기 있는 사용 사례인 Retrieval Augmented Generation(RAG)의 내부 작동 방식을 간단히 살펴보겠습니다.
RAG의 작동 방식
데이터가 모든 GenAI 앱의 금광이라는 것은 비밀이 아닙니다. 데이터는 GenAI가 사실적이고 정확한 응답을 생성하는 데 사용하는 정보의 원천 역할을 합니다. 현재 사용 가능한 모든 데이터 중 80%는 비정형 데이터로 분류될 수 있습니다.
비정형 데이터는 미리 정의된 데이터 형식을 따르지 않는 데이터를 의미합니다. 이러한 유형의 데이터에는 이미지, 텍스트, 사운드, 비디오가 포함됩니다. 머신이 이러한 비정형 데이터 유형을 이해하게 하려면 이를 벡터 임베딩이라고 하는 수치 형식으로 변환해야 합니다.
벡터 임베딩의 기본 개념
임베딩은 n차원 벡터로 구성되며, 여기서 n은 임베딩의 차원 수를 의미합니다. 차원 수는 데이터를 임베딩으로 변환하는 딥러닝 모델에 따라 달라집니다. 임베딩은 그것이 나타내는 데이터의 의미론적 의미를 담고 있습니다.
다양한 데이터 양식을 임베딩으로 변환하기 위해 딥러닝 모델을 사용할 수 있습니다. 예를 들어 텍스트 데이터가 있는 경우 OpenAI 또는 Sentence Transformer 모델을 사용하여 이 텍스트 데이터를 임베딩으로 변환할 수 있습니다. 이미지 데이터가 있는 경우 Vision Transformer와 같이 이미지 특징을 추출할 수 있는 전문 사전 학습 모델을 임베딩 모델로 사용할 수 있습니다.
임베딩은 그것이 나타내는 데이터의 의미론적 의미를 담고 있으므로, 이른바 벡터 공간에서 해당 임베딩과 다른 임베딩 간의 유사도를 계산할 수 있습니다. 의미론적 의미가 유사한 임베딩은 아래 시각화에서 볼 수 있듯이 벡터 공간에서 서로 가까이 배치됩니다:
Vector embeddings in a vector space.png
벡터 공간에서 관련 단어의 임베딩
위 이미지에 표시된 것처럼, "queen"과 "king"의 임베딩은 서로 가까이 배치되어 있으며, "woman"과 "man"도 마찬가지입니다. "queen-king"과 "woman-man" 사이의 유클리드 거리도 유사한 의미를 담고 있기 때문에 대략 동일할 것입니다.
이 개념은 벡터 검색 연산의 기반으로, 하나의 임베딩과 여러 임베딩 간의 유사도를 계산합니다.
벡터 검색 및 RAG 애플리케이션에서 벡터 데이터베이스의 역할
소량의 임베딩만 다룬다면 벡터 검색을 구현하는 것은 간단합니다. 하지만 실제 사례에서는 보통 수천, 수백만, 심지어 수십억 개의 임베딩을 다룹니다. 따라서 임베딩을 효율적으로 저장하고 그 위에서 빠른 벡터 검색을 수행할 수 있는 솔루션이 필요합니다.
여기서 Milvus와 같은 벡터 데이터베이스가 중요한 역할을 합니다. Milvus는 방대한 양의 임베딩을 저장하고 이러한 임베딩에 대해 순식간에 벡터 검색을 수행할 수 있는 오픈 소스 벡터 데이터베이스입니다.
비정형 데이터를 임베딩으로 변환하고 Milvus에 저장하는 워크플로
비정형 데이터를 임베딩으로 변환하고 Milvus에 저장하는 워크플로
벡터 데이터베이스는 RAG와 같은 인기 있는 GenAI 앱에서도 중요한 역할을 합니다. 이미 알고 계실 수 있듯이, RAG의 주요 목표는 사용자의 쿼리에 답하는 데 유용할 수 있는 컨텍스트를 제공함으로써 ChatGPT 및 LLaMA와 같은 LLM에서 생성된 응답의 정확도를 향상시키는 것입니다.
RAG 애플리케이션에서는 사용자의 쿼리가 수신되면 임베딩 모델을 사용해 임베딩으로 변환됩니다. 다음으로 벡터 검색이 수행되며, 이때 쿼리 임베딩은 Milvus와 같은 벡터 데이터베이스 내부에 저장된 컨텍스트 임베딩과 비교됩니다. 그런 다음 가장 유사한 컨텍스트 데이터가 검색되어 쿼리와 함께 LLM에 전달됩니다. 이후 LLM은 컨텍스트의 정보를 사용하여 사용자의 쿼리에 답하는 맥락화된 응답을 생성할 수 있습니다.
RAG
RAG 워크플로
인기 있는 GenAI 프레임워크로서의 LangChain
LangChain은 최첨단 LLM 모델을 사용하여 GenAI 앱을 쉽게 구축하고 개발할 수 있게 해주는 프레임워크입니다. OpenAI, Anthropic, Google과 같은 인기 있는 LLM 제공업체뿐만 아니라 Zilliz와 같은 벡터 데이터베이스 제공업체와도 쉽게 통합됩니다.
LangChain은 또한 LLM 기반 AI 애플리케이션 개발을 위한 유연한 추상화를 제공하여, 데이터 과학자와 소프트웨어 개발자가 단 몇 줄의 코드만으로 RAG와 같은 정교한 시스템을 구축하기 쉽게 만듭니다.
예를 들어, GPT-4를 사용하여 이 블로그 게시물의 내용을 요약하고 싶다고 가정해 보겠습니다. 다음 코드로 이 작업을 완료할 수 있습니다:
import os
from langchain.chains.summarize import load_summarize_chain
from langchain_community.document_loaders import WebBaseLoader
from langchain_openai import ChatOpenAI
os.environ["LANGCHAIN_TRACING_V2"] = "True"
loader = WebBaseLoader("https://lilianweng.github.io/posts/2023-06-23-agent/")
docs = loader.load()
llm = ChatOpenAI(temperature=0, model_name="gpt-3.5-turbo-1106")
chain = load_summarize_chain(llm, chain_type="stuff")
result = chain.invoke(docs)
"""
출력: 이 글은 계획, 메모리, 도구 사용의 구성 요소에 초점을 맞춰 LLM 기반 자율 에이전트의 개념을 논의합니다. 사례 연구와 개념 증명 예시뿐만 아니라 과제와 관련 연구에 대한 참고 문헌도 포함합니다. 저자는 강력한 문제 해결 에이전트를 만드는 데 있어 LLM의 잠재력을 강조하는 동시에, 제한된 컨텍스트 길이와 자연어 인터페이스의 신뢰성과 같은 한계도 부각합니다.
"""
보시다시피, 대략 10줄의 코드만으로도 GPT-4 모델을 활용하여 긴 블로그 게시물을 정확하게 요약할 수 있습니다.
LangChain을 사용하면 더 복잡한 작업도 수행할 수 있습니다. 예를 들어, PDF 문서의 긴 텍스트를 청크로 분할하고, 선택한 임베딩 모델을 사용해 각 청크를 임베딩으로 변환한 뒤, 해당 청크의 임베딩을 벡터 데이터베이스 안에 저장하고, 이후 RAG를 수행할 수 있습니다.
import getpass
import os
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores.milvus import Milvus
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
from langchain.embeddings import SentenceTransformerEmbeddings
from langchain_openai import ChatOpenAI
# Set the API key
os.environ["OPENAI_API_KEY"] = getpass.getpass("Enter your OpenAI API key: ")
llm = ChatOpenAI(model="gpt-4")
# Text to be processed
texts = "This is a very long text.....:"
# Split text into chunks
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100) # Example chunk size and overlap
chunk_texts = text_splitter.split_text(texts)
# Instantiate embedding model
embeddings = SentenceTransformerEmbeddings(model_name="all-MiniLM-L6-v2")
# Store embeddings of chunks inside Milvus DB
vector_db = Milvus.from_texts(texts=chunk_texts, embedding=embeddings, collection_name="rag_milvus")
retriever = vector_db.as_retriever()
# Define function to format documents
def format_docs(docs):
return "\\n\\n".join(doc.page_content for doc in docs)
# Perform RAG (Retrieval Augmented Generation)
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| llm
| StrOutputParser()
)
# Example question
question = "What is the main idea of the text?"
# Execute RAG chain and print the result
for chunk in rag_chain.stream(question):
print(chunk, end="", flush=True)
위에 제공된 데모 외에도, LangChain은 다양한 기능을 제공합니다. 예를 들어, 날씨 앱, 계산기, Google Search와 같은 외부 소스의 LLM 및 API를 통합합니다. 이러한 접근 방식은 LLM이 해당 소스의 정보를 활용하여 더 정확하고 맥락에 맞는 응답을 생성할 수 있게 해줍니다. 이 접근 방식의 자세한 구현은 이어지는 섹션에서 살펴보겠습니다.
LangChain의 모든 기능은 해당 문서 페이지에서도 살펴볼 수 있습니다.
Ruby와 Milvus를 활용한 GenAI 앱 개발
Python은 LangChain을 포함한 AI 연구 및 개발 프레임워크에서 사실상의 표준 프로그래밍 언어가 되었습니다. 한편, Ruby는 빠른 소프트웨어 및 웹 애플리케이션 개발에 여전히 널리 사용되고 있습니다.
하지만 이전 섹션에서 보셨듯이, LangChain의 등장은 소프트웨어 개발자들이 LLM과 AI 전반의 세부 이론을 알지 못해도 LLM의 힘을 웹 앱에 통합할 수 있는 가능성을 열어주었습니다.
이러한 역량은 Ruby처럼 풀스택 개발자에게 더 익숙한 다른 언어로 이러한 GenAI 개발 프레임워크를 확장하려는 수요를 증가시켰습니다. 이러한 수요를 충족하기 위해 Andrei Bondarev는 원래 LangChain 프레임워크의 Ruby 확장인 LangChain.rb를 소개했습니다.
LangChain.rb는 Ruby 풀스택 개발자가 프로젝트에 여러 프로그래밍 언어를 통합하는 번거로움 없이 LLM 기반 웹 앱을 구축할 수 있게 해줍니다. 이를 통해 인기 있는 벡터 데이터베이스, LLM, 외부 리소스를 LLM 웹 앱에 쉽게 통합할 수 있습니다.
LangChain.rb는 원본 LangChain과 동일한 일반 기능을 갖추고 있습니다. 예를 들면 다음과 같습니다:
프롬프트 관리: 원하는 LLM을 위한 프롬프트 템플릿을 생성, 로드 및 저장
컨텍스트 길이 검증: 원하는 LLM 및 임베딩 모델의 컨텍스트 길이에 따라 입력의 컨텍스트 길이를 검증
데이터 청킹: 원하는 벡터 데이터베이스에 수집하기 전에 미리 정의된 규칙으로 데이터를 청크로 분할
대화 메모리: LLM과의 채팅을 메모리에 지속적으로 저장
다음 섹션에서는 LangChain.rb의 도움을 받아 간단한 LLM 기반 앱 개발을 시연하겠습니다.
LangChain.rb를 사용한 일반 RAG 앱
이 첫 번째 예제에서는 LangChain.rb를 사용해 간단하고 빠른 RAG 애플리케이션을 구축하겠습니다. Ruby 프로젝트에서 LangChain.rb를 사용하기 전에 다음 명령을 실행하여 gem을 설치해야 합니다:
gem install langchainrb
이 프로젝트에서는 Milvus를 벡터 데이터베이스로 사용하고, OpenAI의 모델을 LLM 및 임베딩 모델로 모두 사용할 것입니다. Milvus를 실행하려면 Docker에서 Milvus를 설치하고 다음 명령으로 컨테이너를 시작해야 합니다:
# 설치 스크립트 다운로드
curl -sfL https://raw.githubusercontent.com/milvus-io/milvus/master/scripts/standalone_embed.sh -o standalone_embed.sh
# Docker 컨테이너 시작
bash standalone_embed.sh start
이제 Docker 컨테이너를 시작했으니, RAG 애플리케이션에 사용할 Milvus와 모델을 인스턴스화해 보겠습니다.
require 'langchain'
milvus = Langchain::Vectorsearch::Milvus.new(
url: ENV["MILVUS_URL"],
index_name: "Documents",
llm: Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
)
가장 먼저 해야 할 일은 Milvus 벡터 데이터베이스 내부에 스키마와 해당 인덱싱 메서드를 생성하는 것입니다. 다음으로, 이를 사용해 벡터 검색을 수행하기 전에 해당 스키마를 로드해야 합니다.
# 기본 스키마 생성
milvus.create_default_schema
# 기본 인덱스 생성
milvus.create_default_index
# 기본 스키마 로드
milvus.load_default_schema
이제 스키마에 일부 데이터를 수집할 수 있습니다. 직원 복리후생에 대한 정보가 담긴 PDF가 로컬 디렉터리에 있다고 가정해 보겠습니다. 이 PDF의 모든 텍스트를 Milvus 데이터베이스 안에 저장하고 싶다면, 다음 명령을 실행하면 됩니다:
pdf = Langchain.root.join("path/to/my.pdf")
# Milvus 안에 PDF 추가
milvus.add_data(path: pdf)
위 명령을 실행하면 LangChain이 내부적으로 모든 전처리 작업을 수행합니다. PDF 파일 안의 텍스트를 파싱하고, 여러 청크로 나누고, 각 청크를 임베딩으로 변환한 다음, 임베딩을 Milvus 벡터 데이터베이스 안에 저장합니다.
Milvus 벡터 데이터베이스 안에 데이터를 저장한 후에는 PDF 문서와 관련된 질문을 할 수 있습니다. “회사의 휴가 정책은 무엇인가요? 얼마나 쉴 수 있나요?”라고 묻고 싶다고 해보겠습니다. 그러면 다음 한 줄의 코드를 실행하는 것만으로 RAG 시스템에서 LLM에 질문할 수 있습니다:
response = milvus.ask(question: "What’s the company’s vacation policy? How much can I take off?")
puts response
"""
Response:
=> The company's vacation policy allows employees to take any reasonable amount of time off with pay,
as long as they consult with their manager in advance and get their work done.
"""
이게 전부입니다! 일반 RAG 애플리케이션을 구축하는 것 외에도, LangChain.rb로 에이전트형 RAG 애플리케이션을 구축할 수도 있습니다. 이에 대해서는 다음 섹션에서 다루겠습니다.
에이전트를 활용하여 서드파티 도구와 상호작용하기
많은 LLM의 주요 한계는 지식 컷오프 날짜입니다. 예를 들어 GPT-4는 컷오프 날짜가 2023년 4월입니다. 이는 2023년 4월 이후의 일반적이거나 사실적인 사건에 대해 질문하고 싶을 경우, LLM으로부터 정확한 응답을 얻지 못한다는 의미입니다.
이 문제를 해결하기 위해 LangChain.rb는 에이전틱 RAG 애플리케이션을 구축할 수 있게 해줍니다. 이러한 유형의 RAG 애플리케이션은 의사결정자 역할을 하는 "agent"를 포함하는 또 다른 지능 계층을 추가합니다. agent는 사용자 쿼리를 분석한 다음, 쿼리에 답하기에 가장 적합한 컨텍스트를 제공할 수 있는 가장 효과적인 서드파티 도구를 결정합니다.
뉴욕의 현재 날씨에 대해 LLM에 질문하고 싶다고 해봅시다. 일반적인 RAG 시스템에서는 LLM이 뉴욕의 실시간 날씨를 알 수 없습니다. 대부분 환각을 시작해 임의의 날씨 예측을 제공할 것입니다.
에이전틱 RAG 워크플로 (1).png
에이전틱 RAG의 워크플로
에이전틱 RAG는 OpenWeather API와 같은 도구나 API를 시스템에서 사용하여 뉴욕의 실시간 날씨를 가져올 수 있게 함으로써 이 문제를 해결합니다. agent는 먼저 사용자 쿼리를 처리한 다음, 컨텍스트를 정확한 답변으로 종합하기 전에 쿼리에 답하는 데 관련 컨텍스트를 제공할 수 있는 도구를 결정합니다.
다음 데모에서는 계산기, OpenWeather 앱, Google Search와 같은 서드파티 도구를 RAG 시스템 내부에서 사용할 것입니다.
weather = Langchain::Tool::Weather.new(api_key: ENV["OPEN_WEATHER_API_KEY"])
google_search = Langchain::Tool::GoogleSearch.new(api_key: ENV["SERPAPI_API_KEY"])
calculator = Langchain::Tool::Calculator.new
다음으로, 다음 명령을 사용해 이 세 가지 도구를 RAG 시스템 내부에 추가해야 합니다:
openai = Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
agent = Langchain::Agent::ReActAgent.new(
llm: openai,
tools: [weather, google_search, calculator]
)
이제 LLM에 질문을 시작할 수 있습니다. 다음 질문을 하고 싶다고 해봅시다: “Boston, MA와 Washington, D.C.의 현재 날씨를 찾아 평균을 내세요.”
response = agent.run(question: "Find current weather in Boston, MA and Washington, D.C. and take an average")
도구 통합을 사용한 RAG의 출력
도구 통합을 사용한 RAG의 출력
위 스크린샷에서 볼 수 있듯이, 우리의 에이전틱 RAG 시스템은 쿼리에 정확하게 답할 수 있었습니다. 이 RAG 시스템의 워크플로를 자세히 살펴보겠습니다:
쿼리는 먼저 OpenAI LLM으로 전송되었습니다.
agent는 Boston과 Washington D.C.의 현재 날씨를 가져오기 위해 OpenWeather API를 활용해야 한다는 것을 인식했습니다.
날씨 데이터를 가져온 후, agent는 쿼리가 두 도시의 날씨 평균을 요구한다는 것을 확인했습니다.
그런 다음 agent는 날씨 평균을 계산하기 위해 계산기 도구를 호출했습니다.
마지막으로 LLM은 결과를 하나의 일관된 답변으로 종합하여 사용자에게 반환했습니다.
이 예시는 에이전틱 RAG 접근 방식의 강력함을 보여줍니다. 외부 도구와 API를 통합함으로써, 시스템은 LLM의 지식 컷오프 날짜라는 한계를 극복하고 사용자의 쿼리에 정확하고 최신의 응답을 제공했습니다.
내부 데이터베이스와 상호작용하기 위해 Agent 활용하기
에이전틱 RAG를 사용해 내부 데이터베이스와 상호작용할 수도 있습니다. 이는 전통적인 SQL 쿼리에 의존하는 대신 인간과 유사한 언어를 사용해 데이터에 대한 인사이트를 요청할 수 있기 때문에 매우 유용합니다.
온라인 쇼핑몰이 있고 사용자 데이터가 데이터베이스에 저장되어 있다고 해봅시다. 일반적으로 해당 데이터에서 인사이트를 추출하려면 SQL 쿼리를 작성해야 합니다. 에이전틱 RAG를 사용하면, 원하는 인사이트에 대해 LLM에 질문하기만 하면 답변이 즉시 반환됩니다.
예를 들어, 데이터베이스에 저장된 사용자 레코드 수를 알고 싶다고 해봅시다. 간단히 "사용자가 몇 명 있나요?"라고 묻고 다음 명령을 실행할 수 있습니다:
require 'langchain'
# Instantiate the database connection
database = Langchain::Tool::Database.new(connection_string: "postgres://localhost:5432/my_database")
# Create OpenAI LLM instance
openai = Langchain::LLM::OpenAI.new(api_key: ENV["OPENAI_API_KEY"])
# Create SQLAgent with the LLM and database connection
agent = Langchain::Agent::SQLAgent.new(
llm: openai,
db: database
)
# Ask a question to the agent
response = agent.run("How many users are there?")
아래는 명령의 예시 출력입니다:
SQL 통합을 사용한 RAG의 출력
SQL 통합을 사용한 RAG의 출력
보시다시피, 우리의 에이전트형 RAG 시스템은 데이터베이스 데이터와 관련된 특정 질문에 정확하게 답할 수 있었습니다. 에이전트의 워크플로는 이전 예시와 유사합니다:
쿼리가 OpenAI LLM으로 전송되었습니다.
에이전트는 쿼리를 분석하고 사용자 수를 세기 위해 데이터베이스 조회가 필요하다고 판단했습니다.
LLM은 데이터베이스 테이블 스키마를 기반으로 적절한 SQL 쿼리를 생성했습니다.
SQL 쿼리가 데이터베이스에 대해 실행되어 결과를 반환했습니다.
데이터베이스 출력이 LLM으로 다시 전송되었습니다.
LLM은 데이터베이스 결과를 일관되고 사람이 읽을 수 있는 답변으로 종합하여 최종 응답으로 제공했습니다.
결론
LangChain의 도입으로 AI와 데이터 과학 이론에 대한 깊은 지식이 없을 수 있는 전문가들도 LLM에 접근할 수 있게 되었습니다. 우리는 단 몇 줄의 코드만으로 LangChain을 사용해 강력한 RAG 애플리케이션을 구축할 수 있습니다.
이러한 접근성 때문에 Andrei Bondarev는 Ruby용 LangChain 확장인 LangChain.rb를 도입했습니다. 이 프레임워크를 통해 풀스택 개발자는 광범위한 AI 전문 지식 없이도 LLM의 강력한 성능을 웹 애플리케이션에 통합할 수 있습니다. 또한 LangChain.rb는 풀스택 개발자가 웹 애플리케이션에서 LLM을 활용하고자 할 때 다른 프로그래밍 언어로 전환해야 하는 번거로움을 없애 줍니다.
계속 읽기

Notion's Vector Search Is Excellent. Their Next Problem Is Harder.
Notion solved vector search scaling in two years. The next bottleneck — offline context engineering, unified data, and the real-time/offline gap — is harder.

DeepSeek-OCR Explained: Optical Compression for Scalable Long-Context and RAG Systems
Discover how DeepSeek-OCR uses visual tokens and Contexts Optical Compression to boost long-context LLM efficiency and reshape RAG performance.

Selecting the Right ETL Tools for Unstructured Data to Prepare for AI
Learn the right ETL tools for unstructured data to power AI. Explore key challenges, tool comparisons, and integrations with Milvus for vector search.


