1. 도입부 (Introduction)
대규모 언어 모델(LLM)을 사내 데이터나 전문 법률 지식과 결합하기 위해 RAG(Retrieval-Augmented Generation, 검색 증강 생성) 기술을 많이 도입한다. 하지만 단순한 RAG 아키텍처를 적용해보면 이내 한계에 부딪히게 된다. 사용자가 전문 용어를 혼용해서 질문하거나, 이전 대화 맥락을 생략한 채 "그거 세금 계산은 어떻게 해?"와 같이 대명사로 물어보면 검색(Retriever) 단계에서 엉뚱한 정보만 물어오기 때문이다. 게다가 정부 문서나 소득세법과 같은 엄격한 전문 도메인에서는 특유의 정교한 말투와 근거 조항 제시 형식을 AI에게 일관되게 주입해야 신뢰성을 얻을 수 있다.
이러한 현실적인 장벽을 뛰어넘기 위해, 단순히 질문을 벡터 DB에 던져 답변을 유도하는 수준을 넘어선 다단계 랭체인 파이프라인을 구축해야 한다. 사용자의 단어를 사전식(Dictionary)으로 정규화하고, 이전 대화 기록을 파싱해 질문을 다시 쓰고, 전문 법률 서식에 맞게 Few-shot 학습 가방을 씌운 뒤, 개별 사용자 세션까지 유지하는 고난도 RAG 솔루션을 어떻게 코드로 완벽하게 통제할 수 있는지 꼼꼼하게 파헤쳐 본다.
2. 주요 특징 및 데이터 흐름 파이프라인 (Data Lifecycle & Flow)
전체 시스템에서 사용자 질문 한 건이 어떤 데이터 변환(Data Transformation) 단계를 거쳐 최종 답변으로 탄생하는지 파악하는 것은 아키텍처 설계의 기본이다. 입력값의 형태가 매 단계마다 어떻게 가공되고 결합되는지 상세히 뜯어보자.

3. 랭체인 핵심 컴포넌트 및 이론 해부 (Concepts & Theory Deep-Dive)
구현 단계로 진입하기 전에, 이 파이프라인을 구성하는 랭체인의 4대 빌딩 블록(Prompts, Retrievers, Chains, State)의 내부 작동 원리와 실무적 당위성을 아주 상세하게 파헤쳐 보자.
3.1 프롬프트 엔지니어링의 지휘관: Prompts
프롬프트는 단순히 LLM에 던지는 "명령문"이 아니다. 랭체인에서 프롬프트는 입력을 받아 정형화된 모델 입력값으로 포맷팅해 주는 지능형 템플릿(Prompt Template)으로 취급된다.

- ChatPromptTemplate:
- 단순한 텍스트 템플릿과 달리, 메시지 단위의 역할(Role)을 엄격하게 구분하여 설계할 수 있는 대화형 API 전용 템플릿이다.
- SystemMessage(LLM의 자아와 행동 강제령 정의), HumanMessage(사용자가 입력한 질의), AIMessage(LLM이 과거에 수행한 대답)를 온전한 리스트 객체 형태로 묶어서 공급자(OpenAI 등)의 API 스펙에 어긋나지 않도록 정렬해 준다.
- FewShotChatMessagePromptTemplate (퓨샷 인프라스트럭처):
- LLM에게 작업 가이드라인을 길게 줄글로 설명하는 것보다, "실제로 완성된 1~2개의 베스트 답변 세트"를 보여주는 것이 컨텍스트 내 학습(In-Context Learning) 효율을 극대화한다.
- 이를 구현하는 것이 퓨샷 프롬프트 템플릿이다. 사용자가 입력할 원본 질문(input)과 모델의 지향점 답변(answer) 쌍을 담은 사전 리스트를 프롬프트 중간에 자연스럽게 삽입해 준다.
- 법률이나 의학 도메인처럼 일정한 서식 준수("소득세법 제XX조에 따르면..."), 특정 종결어미 강제, 답변 길이 조절이 필수적인 업무(Compliance) 환경에서 시스템의 신뢰도를 보장해 주는 수문장이다.
- MessagesPlaceholder:
- 사용자와 AI가 나누는 대화의 턴 수가 길어질수록, 대화 역사(chat_history) 리스트의 길이 역시 2개, 4개, 10개로 동적으로 늘어난다.
- MessagesPlaceholder는 템플릿 내부에 "가변 길이의 대화 이력 메시지 리스트가 안전하게 안착할 동적 슬롯(Placeholder)"을 만들어주는 통로다. 만약 대화 내역이 비어있다면 빈 값으로, 대화가 축적되면 이 자리의 크기를 유연하게 늘려가며 메시지들을 병합해 준다.
3.2 지식 검색의 수색대장: Retrievers & VectorDB
RAG 시스템의 생명은 "얼마나 빠르고 정교하게 연관성 높은 사내 지식을 찾아오는가"에 달려 있다. LLM의 논리 추론 능력이 아무리 신계에 달해 있어도, 애초에 엉뚱한 정보가 담긴 참고서를 건네받으면 거짓 정보를 진짜인 것처럼 이야기하는 환각 현상(Hallucination)을 일으킨다.
- 물리적 저장소(VectorDB)와 논리적 검색기(Retriever)의 분리:
- Vector DB (Pinecone 등): 수만 장의 지식 문서를 임베딩 모델을 통해 숫자 벡터 공간으로 변환하여 실시간 저장 및 인덱싱하고 있는 실제 데이터 저장소다.
- Retriever (검색기): 랭체인의 인터페이스 표준으로, "문자열(Query)을 입력받아 연관된 Document 문서 객체 리스트를 꺼내오는 모든 추상화 모듈"을 의미한다.
- 즉, 데이터베이스 자체는 Pinecone을 쓰더라도 랭체인 파이프라인 안에서는 이를 검색기 프로토콜(.as_retriever())로 래핑하여 다루며, 이는 추후 다른 벡터 DB(Milvus, Chroma 등)로 데이터베이스 엔진을 교체하더라도 상위 체인 로직은 단 한 줄도 건드리지 않아도 되게끔 의존성을 역전(DIP)시켜 준다.
- History-Aware Retriever (과거 맥락 인지형 검색 필터):
- 사용자가 대화 중에 "그럼 그것의 종합소득세율은 어떻게 적용돼?"라고 물어봤다고 가정해 보자.
- 일반적인 리트리버를 쓰면 "그것"이라는 단어로 벡터 DB를 검색하기 때문에 검색 정확도는 0에 수렴한다.
- create_history_aware_retriever는 이 문제를 해결하기 위해 검색 프로세스 내부에서 한 번의 LLM 가벼운 사전 추론을 먼저 실행한다.
- 대화 내역([Human: "거주자 기준이 뭐야?", AI: "국내에 주소를 둔 개인..."])과 현재의 질문("그럼 그것의 세율은?")을 LLM에게 먼저 전달해, "대한민국 소득세법상 거주자에 대한 종합소득세율은 어떻게 적용되는가?"라는 대명사가 정규화된 독립형 질문(Standalone Question)으로 다시 쓴다. 이후 이 정제된 쿼리를 사용해 벡터 DB를 수색한다.
3.3 연결과 흐름의 척추: Chains & LCEL
과거 랭체인 초기 버전(v0.0.x)에서는 SequentialChain, LLMChain 등 복잡하고 상속 구조가 거대한 파이썬 클래스 기반 하드코딩이 주로 쓰였다. 하지만 최신 랭체인은 LCEL(LangChain Expression Language)이라는 혁신적인 선언형 프로그래밍 스타일을 표준 규격으로 제시한다.

- 파이썬 비트 연산자 Pipe (|)의 마법:
- LCEL은 파이썬의 객체 간 비트 연산자인 |를 내부적으로 오버라이딩(__or__ 매직 메서드 재정의)하여 설계되었다.
- 앞 컴포넌트의 출력을 뒤 컴포넌트의 입력값으로 스트리밍하듯이 실시간으로 파이핑 처리를 해준다.
- chain = prompt | llm | StrOutputParser()
- 위의 간단한 구문 만으로도 "딕셔너리 인풋 $\rightarrow$ 프롬프트 가공 $\rightarrow$ LLM 전송 및 연산 $\rightarrow$ 출력 문자열 추출"이라는 일련의 파이프라인이 하나의 안전한 독립형 실행 객체로 조립된다.
- create_stuff_documents_chain:
- RAG 파이프라인의 종착역 중 하나로, 검색기(Retriever)가 발굴해 온 지식 문서 조각 리스트(List[Document])들을 하나로 가지런히 뭉쳐서(Stuff), 우리가 사전에 준비한 메인 시스템 프롬프트 내부의 {context} 슬롯 자리에 예쁜 원본 문자열 텍스트 형태로 밀어 넣어 조립하는 특수 가공 체인이다.
- create_retrieval_chain:
- "질문 재작성 및 검색(History-Aware Retriever)" 모듈과 검색 결과를 가져와 답변을 최종적으로 생성하는 "문서 병합 답변기(Stuff Documents Chain)"를 머리와 꼬리로 조화롭게 결합해 완전무결한 RAG 대여정을 종결 짓는 메인 조율 장치다.
3.4 대화의 메모리 파수꾼: Chat Message History
기본적으로 LLM API 호출은 독립적인 단발성 요청이다. API는 방금 전 들어온 질문이 무엇이었는지 전혀 알지 못하는 무상태성(Stateless) 프로토콜로 작동한다. 따라서 실제 서비스 환경에서 멀티 세션 대화(Stateful Conversation)를 완성하기 위해서는 호출할 때마다 매번 과거 대화 리스트를 주입해 줘야 한다.

- RunnableWithMessageHistory:
- 매번 대화 내역 배열을 수동으로 관리하고 코드로 묶어 던져주는 비효율을 제거하기 위한 자동화 데코레이터다.
- 비상태성의 특징을 가진 RAG 체인(rag_chain)을 한 번 더 감싸 안으며 작동한다.
- 호출 시 개발자가 넘겨준 설정 맵(config={"configurable": {"session_id": "abc123"}})을 실시간으로 추적하여 가로챈다.
- session_id 값을 key 삼아 메모리 테이블(store) 혹은 데이터베이스에 잠들어 있는 대화 이력 오브젝트를 깨워 프롬프트 내부의 MessagesPlaceholder 자리에 자동으로 바인딩해 준다.
- GPT가 한창 고민 끝에 최종 답변을 생산하여 출력을 끝내는 시점이 오면, 이번 턴의 유저 질문(input)과 GPT의 답변(answer)을 해당 세션 메모리에 조용히 갱신해주고 라이프사이클을 안전하게 끝마친다.
4. 상세 코드 구현 및 심층 분석 (Detailed Guide)
이제 앞서 파악한 아키텍처와 개념적 지식을 바탕으로 전체 파이썬 소스 코드를 면밀하게 살펴보자. 각 함수는 유기적으로 엮여 있으며 체인의 실행부에서 완전히 하나로 통합된다.
# =====================================================================
# 1. 라이브러리 임포트 및 초기 인프라 설정
# =====================================================================
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder, FewShotChatMessagePromptTemplate
from langchain.chains import create_history_aware_retriever, create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_openai import ChatOpenAI
from langchain_openai import OpenAIEmbeddings
from langchain_pinecone import PineconeVectorStore
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
# 외부 설정 파일(config.py)에서 미리 정의된 퓨샷 예시를 불러온다.
from config import answer_examples
# 세션별 대화 메시지 기록을 실시간으로 관리할 메모리 저장소(인메모리 딕셔너리)
store = {}
# =====================================================================
# 2. 세션 히스토리 복원 및 벡터 스토어 Retriever 구축
# =====================================================================
def get_session_history(session_id: str) -> BaseChatMessageHistory:
"""
제공된 session_id를 기반으로 세션 내역을 추적하는 함수다.
등록된 세션이 없다면 새로 만들고, 있다면 기존 히스토리를 반환한다.
"""
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
def get_retriever():
"""
Pinecone 클라우드에 연결하여 벡터 공간에서 검색을 수행할 리트리버를 빌드한다.
"""
# 고성능 3세대 대형 임베딩 모델(text-embedding-3-large)을 선택한다.
embedding = OpenAIEmbeddings(model='text-embedding-3-large')
# 사전에 텍스트 문서들이 조각나 밀도 있게 인덱싱되어 있는 Pinecone 인덱스명 지정
index_name = 'tax-markdown-index'
# 기존에 적재 완료된 파인콘 인덱스를 객체로 복원한다.
database = PineconeVectorStore.from_existing_index(
index_name=index_name,
embedding=embedding
)
# 검색 요청이 올 때마다 가장 정합성이 높은 문서를 4개(k=4)씩 반환하도록 구성한다.
retriever = database.as_retriever(search_kwargs={'k': 4})
return retriever
# =====================================================================
# 3. 맥락 인지형 검색기 (History-Aware Retriever) 설계
# =====================================================================
def get_history_retriever():
"""
단순 키워드 매칭을 극복하기 위해, 대화 이력을 참고하여 사용자 질문을 재작성하는
히스토리 인지형 검색기를 빌드하여 반환한다.
"""
llm = get_llm()
retriever = get_retriever()
# 대화 이력이 섞여 있는 경우, 대명사를 표준 명사로 변경하게 만드는 특수 프롬프트 정의
contextualize_q_system_prompt = (
"Given a chat history and the latest user question "
"which might reference context in the chat history, "
"formulate a standalone question which can be understood "
"without the chat history. Do NOT answer the question, "
"just reformulate it if needed and otherwise return it as is."
)
# - 히스토리 변환용 시스템 프롬프트 조립
contextualize_q_prompt = ChatPromptTemplate.from_messages(
[
("system", contextualize_q_system_prompt),
MessagesPlaceholder("chat_history"), # 과거 대화 이력이 동적으로 주입될 위치
("human", "{input}"), # 사용자의 가공되지 않은 질문이 주입될 위치
]
)
# 랭체인 제공 공식 체인 생성을 통해 독립적인 검색을 지원하는 리트리버 생성
history_aware_retriever = create_history_aware_retriever(
llm, retriever, contextualize_q_prompt
)
return history_aware_retriever
# =====================================================================
# 4. LLM 로더 및 사전식 단어 변환 체인 (Dictionary Chain) 구축
# =====================================================================
def get_llm(model='gpt-4o'):
"""
OpenAI의 최신 플래그십 모델인 gpt-4o 인스턴스를 불러온다.
"""
llm = ChatOpenAI(model=model)
return llm
def get_dictionary_chain():
"""
질문에 섞인 구어체나 은어를 도메인 표준 용어로 매핑해 주는 전처리 체인을 빌드한다.
"""
# '사람'이라는 단어가 소득세법의 공식 용어인 '거주자'로 변환되도록 사전을 작성한다.
dictionary = ["사람을 나타내는 표현 -> 거주자"]
llm = get_llm()
# LLM에게 원본 질문을 사전 규칙에 맞게 고쳐 쓰라고 명령하는 프롬프트 템플릿
prompt = ChatPromptTemplate.from_template(f"""
사용자의 질문을 보고, 우리의 사전을 참고해서 사용자의 질문을 변경해주세요.
만약 변경할 필요가 없다고 판단된다면, 사용자의 질문을 변경하지 않아도 됩니다.
그런 경우에는 질문만 리턴해주세요
사전: {dictionary}
질문: {{question}}
""")
# 문자열로 깔끔하게 파싱해 출력하도록 출력 파서를 파이프(|) 라인으로 결합한다.
dictionary_chain = prompt | llm | StrOutputParser()
return dictionary_chain
# =====================================================================
# 5. 소득세법 전용 RAG 체인 및 Stateful 대화 데코레이터 통합
# =====================================================================
def get_rag_chain():
"""
Few-shot, Retrieval, Stateful 히스토리를 전부 유기적으로 연결한 종합 RAG 체인을 리턴한다.
"""
llm = get_llm()
# 퓨샷 예제에 사용될 개별 메시지 구조를 매핑한다.
example_prompt = ChatPromptTemplate.from_messages(
[
("human", "{input}"),
("ai", "{answer}"),
]
)
# 퓨샷 템플릿을 생성하여 소득세법 전문가 특유의 정합성 높은 어조를 학습시킨다.
few_shot_prompt = FewShotChatMessagePromptTemplate(
example_prompt=example_prompt,
examples=answer_examples, # 외부 config.py에 저장된 엄격하게 정제된 데이터셋
)
# 전문가 답변 제약을 위한 지시사항을 시스템 프롬프트에 단단히 박아둔다.
system_prompt = (
"당신은 소득세법 전문가입니다. 사용자의 소득세법에 관한 질문에 답변해주세요"
"아래에 제공된 문서를 활용해서 답변해주시고"
"답변을 알 수 없다면 모른다고 답변해주세요"
"답변을 제공할 때는 소득세법 (XX조)에 따르면 이라고 시작하면서 답변해주시고"
"2-3 문장정도의 짧은 내용의 답변을 원합니다"
"\n\n"
"{context}" # Pinecone 검색 결과 문서들이 동적으로 바인딩되는 곳
)
# 최종 답변 생성을 유도할 프롬프트 구조 정의
qa_prompt = ChatPromptTemplate.from_messages(
[
("system", system_prompt),
few_shot_prompt, # Few-shot 예제가 이곳에 안정적으로 삽입됨
MessagesPlaceholder("chat_history"), # 대화 히스토리 슬롯 확보
("human", "{input}"),
]
)
# 1. 히스토리 감지 리트리버 획득
history_aware_retriever = get_history_retriever()
# 2. 문서 데이터를 Prompt와 섞어서 LLM에 전달해 줄 결합 문서 체인 생성
question_answer_chain = create_stuff_documents_chain(llm, qa_prompt)
# 3. 검색기와 결합 체인을 엮어 온전한 비상태성 RAG 체인 구축
rag_chain = create_retrieval_chain(history_aware_retriever, question_answer_chain)
# 4. 비상태성 체인을 세션 관리 상태성(Stateful) 래퍼로 안전하게 감싸기
conversational_rag_chain = RunnableWithMessageHistory(
rag_chain,
get_session_history,
input_messages_key="input",
history_messages_key="chat_history",
output_messages_key="answer",
).pick('answer') # 전체 출력 딕셔너리에서 'answer' 문자열 답변만 발라내도록 설정
return conversational_rag_chain
# =====================================================================
# 6. 실시간 스트리밍 답변을 방출하는 API 진입점
# =====================================================================
def get_ai_response(user_message):
"""
클라이언트가 질문을 던졌을 때, 전처리 체인과 RAG 체인을 하나로 결합해 스트리밍 결과를 제공한다.
"""
# 단어 사전을 보정하는 단일 체인 로딩
dictionary_chain = get_dictionary_chain()
# 상태성 세션 RAG 체인 로딩
rag_chain = get_rag_chain()
# [인풋 가공 파이프라인 수립]
# 사용자의 질문 문자열(user_message)을 딕셔너리 체인의 입력값으로 넘겨 먼저 가공하고,
# 그 결과를 RAG 체인의 최종 인풋 키인 'input'에 맵 매핑하여 넘겨준다.
tax_chain = {"input": dictionary_chain} | rag_chain
# 동적 스트리밍 API 가동
ai_response = tax_chain.stream(
{
"question": user_message
},
# 세션 ID를 설정하여 인메모리 대화 이력이 보존되고 갱신되게 설정한다.
config={
"configurable": {"session_id": "abc123"}
},
)
return ai_response
5. 실전 팁 및 예외 상황 대응 (Practical Tips & Edge Cases)
현업에서 이 코드를 그대로 엔터프라이즈 환경에 적용하려다 보면 예상치 못한 몇 가지 문제점을 겪게 된다. 원활한 서빙을 위해 반드시 알아두어야 할 운영 및 리팩터링 전략 세 가지다.
5.1 인메모리 store의 데이터 유실 및 OOM(Out of Memory) 방지
현재 구현된 구조는 전역 Python 딕셔너리(store)에 모든 세션 정보를 담고 있다. 이 방식은 서버가 재부팅되면 모든 대화 내역이 공중 분해되는 치명적인 유실 문제를 안고 있으며, 동시 사용자가 늘어날수록 서버 메모리를 끊임없이 잠식한다.
- 실무적 대안: 랭체인이 제공하는 내장 커넥터를 사용해 Redis나 MongoDB 같은 분산 저장소로 메시지 히스토리를 오프로딩해야 한다.
# Redis를 이용한 영구 히스토리 저장 예시
from langchain_community.chat_message_histories import RedisChatMessageHistory
def get_redis_session_history(session_id: str) -> BaseChatMessageHistory:
return RedisChatMessageHistory(
session_id=session_id,
url="redis://localhost:6379/0"
)
5.2 사전(Dictionary) 체인의 속도 지연 병목 최적화

현재 파이프라인은 질문 한 번이 들어왔을 때, 사전 확인용으로 LLM에 질문을 한 번 던지고, 변환된 질문을 들고 Standalone 질문을 만들기 위해 한 번 더 던진 뒤, 최종적으로 답변 생성을 위해 또다시 본 체인으로 전달하는 구조다. 즉, 한 번의 응답을 만들기 위해 LLM을 최대 3회나 연쇄적으로 거치게 되므로 속도가 글느려지고 레이턴시가 폭증한다.
6. 결론: 실전 RAG 파이프라인의 완성 (Conclusion)
성공적인 RAG 시스템은 단순히 거대 언어 모델(LLM)에 질문을 던지는 것으로 완성되지 않는다. 사용자의 의도 파악, 대화 맥락의 보존, 도메인 전문 지식의 정교한 주입, 그리고 사용자별 세션 상태 관리가 유기적으로 맞물려 돌아가야 비로소 실무에서 신뢰할 수 있는 AI 어드바이저가 탄생한다.
이번 포스팅에서 다룬 실전 RAG 파이프라인을 지탱하는 4가지 핵심 설계 원칙을 다시 한번 정리한다.
- 사전 필터링 (Dictionary Chain): 사용자의 원시 구어체 질문을 도메인 표준 용어로 정규화하여, 벡터 검색의 출발점인 '입력 품질'을 극대화한다.
- 맥락 인지 검색 (History-Aware Retrieval): 이전 대화 기록을 활용해 모호한 대명사를 명확한 주어로 재작성(Standalone Question)함으로써, 벡터 데이터베이스가 연관 문서를 정확하게 수색하도록 돕는다.
- 퓨샷 학습 (Few-Shot Prompting): 전문가의 모범 답변 패턴을 컨텍스트에 포함해, 모델이 답변의 형식, 어조, 근거 제시 방식 등 비즈니스 요구사항을 완벽히 준수하게 한다.
- 상태 관리 (Stateful Session): 비상태성 API의 한계를 극복하고, session_id를 기반으로 대화 이력을 자동으로 로드하고 저장하여 개별 사용자의 대화 맥락을 영구적으로 보존한다.
결국 RAG 아키텍처는 단순히 정보를 찾아와 던져주는 기술이 아니다. 검색(Retrieval)과 생성(Generation) 사이의 연결 고리를 논리적으로 제어하는 공학적 설계이다. 오늘 정리한 컴포넌트 조합을 주춧돌 삼아, 각자의 도메인 환경에 맞는 견고하고 지능적인 AI 인프라를 구축해 보길 바란다.
💡 학습 노트 요약 (Key Takeaways)
| 핵심 기술 | 역할 | 실무적 의의 |
| Dictionary Chain | 사용자 질문 정규화 | 불필요한 검색 오류 방지 |
| History-Aware Retriever | 맥락 반영 쿼리 재작성 | 모호한 질문의 검색 성공률 향상 |
| Few-Shot Prompting | 도메인 어조 및 형식 강제 | 답변 신뢰도 및 일관성 확보 |
| RunnableWithMessageHistory | 대화 세션 상태 관리 | 자동화된 히스토리 전파 및 데이터 동기화 |
이 아키텍처를 기반으로 엔터프라이즈 레벨의 AI 서비스를 설계한다면, 초기 모델링 단계부터 운영 단계까지 발생할 수 있는 많은 기술적 병목을 미리 차단할 수 있을 것이다. 이 학습 노트가 여러분의 실무 구현에 탄탄한 밑거름이 되길 응원한다.
'LLM > RAG' 카테고리의 다른 글
| [RAG] 검색 성능 극대화 Dense(Chroma/FAISS)와 Sparse(Kiwi-BM25) 정리 (0) | 2026.06.01 |
|---|---|
| [RAG] 문서 Loader부터 다국어 검색 챗봇 정리 (0) | 2026.06.01 |
| [RAG] LCEL(랭체인 표현 언어)과 Runnable을 이용한 RAG 파이프라인 (0) | 2026.05.31 |
| [RAG] LLM과 LangChain 프레임워크 기초 및 RAG 흐름 파악하기 (0) | 2026.05.31 |