20230103

LangChain 0.0.x 실전 RAG 파이프라인

연말부터 LangChain 쓰고 있는데 버전이 0.0.120 언저리에서 매일 마이너가 올라오는 중이다. 어제 쓴 import가 오늘 deprecated 되는 느낌.

회사 내부 문서 RAG 한번 붙여보자고 했더니 하루 만에 뚝딱 나오긴 했다. 문제는 그게 "동작"한다는 거랑 "쓸만하다"는 게 완전히 별개라는 것.

파이프라인 구성

from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
from langchain.chains import RetrievalQA

splitter = RecursiveCharacterTextSplitter(
    chunk_size=800, chunk_overlap=120,
    separators=["\n\n", "\n", ". ", " "]
)
docs = splitter.split_documents(raw_docs)

vs = Chroma.from_documents(docs, OpenAIEmbeddings(model="text-embedding-ada-002"))
qa = RetrievalQA.from_chain_type(llm=ChatOpenAI(model="gpt-3.5-turbo"),
                                  retriever=vs.as_retriever(search_kwargs={"k":4}))
주의 — chunk_size=1000으로 했더니 ada-002의 8191 토큰 한도에는 여유 있는데, 답변 품질이 더 떨어졌다. context가 너무 길면 중요 문장이 희석된다. 800 근처가 우리 문서엔 sweet spot.

겪은 것들

  • ada-002 embedding이 $0.0004 / 1K token. 10만 문단 돌렸더니 $7 정도. 생각보다 싸다.
  • gpt-3.5-turbo는 4K context라 context stuffing이 빠듯함. k=4로 해도 가끔 터짐.
  • ChatOpenAI 인터페이스가 저번 주랑 또 다름. from langchain.chat_models로 이동했는데 공식 문서가 아직 안 바뀌어 있다.

RetrievalQA 기본 chain은 stuff 방식인데, 이건 top-k 다 이어붙여서 prompt에 넣는 구조. 문서 많아지면 map_reduce로 가야 한다. 다만 map_reduce는 요청 수가 k+1이라 비용 × latency 둘 다 올라감.

당분간은 프로토타입용으로만 쓰고, 제대로 된 retrieval은 따로 구축해야 할 듯. faiss를 쓸지 pgvector로 갈지 고민 중.