새로워지기/마흔의 생활코딩

LLM | ollama 4부. 검색증강 RAG 적용하기

청춘만화 2024. 2. 25. 22:55

LLM | ollama 4부. 검색증강 RAG 적용하기

- ollama 1부. 로컬 터미널에서 실행하기 : Linux(wsl 2), MacOS 
- ollama 2부. 로컬 브라우저에서 실행하기 open-webui
- ollama 3부. 온라인(내 도메인으로) 브라우저에서 실행하기 
👉  ollama 4부. 검색증강 RAG 적용하기
- ollama 5부. 이미지 인식 적용하기 
- (준비 중) ollama 6부. MOE mixture of exports 방식 적용하기

 

 

 

Ollama RAG에 앞서

참고로 RAG라는 보편적 개념과 관련 API를 제공하는 프레임워크가 랭체인 LangChain인 만큼 관련 기본적인 내용과 실습을 선행하는 것을 추천하고 관련 내용은 이전에 포스팅해둔 것이 있어서 먼저 걸어두고 시작한다

https://normalstory.tistory.com/entry/LM-to-RAGfeat-Langchain-01-%EA%B0%9C%EC%9A%94

 

LM to RAG(feat. Langchain) - 01 개요

LM과 LLM 개요 아키텍쳐 - 트랜스포머 Transformer( Decoder, Encorder) 학습 알고리즘 - 랭기지 모델 LM LM 워크 플로 : 파운데이션 모델* > RLHF 기법** 대규모 컴퓨터 리소스와 데이터를 통해 프리 트레이닝

normalstory.tistory.com

https://normalstory.tistory.com/entry/LM-to-RAGfeat-Langchain-02-%EC%8B%A4%EC%8A%B5-%EC%9E%91%EC%84%B1-%EC%A4%91

 

LM to RAG(feat. Langchain) - 02 Colab 실습

실습 링크는 코랩 입니다. 기본 대화 준비, API KEY 발급 가입, 키발급 실습, GPT와 대화하기 OpenAI - Documents Langchain - Langchain(LLM)_실습 GPT 3, 3.5 비교 매개변수 조절 temperature : 0일관적 답변, 2매번 다른

normalstory.tistory.com

*참고로 위 예제는 작년 중후반, 랭체인이 업데이트 되기 전 예제라 실습 코드가 조금 다를 수 있지만 큰 맥락에서는 다르지 않다. 패키지만  최신 예제로 업데이트하면 된다. 관련 위 링크에서는 대략적인 구성만 참고하고 실질적인 코드는 이번 포스팅 내용을 참조하면 된다.

 

 

 

자, 이제 Ollama RAG로 고고 

Ollama에서 RAG를 적용하는 방식은 크게 두가지로 구성할 수 있다. 먼저 [1]LangChain에서 제공하는 ChatOllama 패키지를 사용하거나 [2]Ollama에서 직접 pip, npm등을 통해 제공하는 자체 라이브러리를 사용하는 방법이 있다.  

 

[1] 먼저, LangChain에서 제공하는 ChatOllama 패키지를 사용 예이다.

1. 대부분의 코딩은 쎄팅이 이다.

기본적으로 로컬PC에 Ollama가 설치되어 있어야 한다.( 참고로 별도 서버는 세팅하지 않아도 된다)
오피셜 사이트에서 다운로드 방식으로 Ollama 프로그램을 다운로드 받고 관심있는 모델을 다운로드 받아두면 되는데 관련 설정은 위 목차에 있는 첫번째 글을 참고하면 된다. 

 

2. 사용하는 LLM 모델

이번 RAG 예제에서는 Ollama의 라이브러리에서 제공하는 LLM 모델 중 nomic과 mistral에서 새로 출시한 버전을 이용할 예정이다. 참고2로 nomic 모델은 임베딩 전용 모델이다. 아래 명령어를 입력해서 추가로 설치하면 된다.

ollama pull nomic-embed-text
ollama pull mistral:v0.2

 

3. 결과

이번 실습에서는 이해를 돕기 위해 코드를 실행한 결과를 먼저 보면,

코드 실행 결과.RAG를 적용한 결과와 RAG를 적용하지 않은 결과 비교

예제를 통해 알 수 있는 점은, RAG를 적용하면 (url 또는.file)제공한 문서를 기반으로 찾고 , 만약 문서 내에 없는 질문을 하는 경우엔 ‘제공된 문서 내에서는 찾을 수 없다’는 내용을 전제로 설명을 해줌. 단, 하나의 코드에서 두 케이스의 코드를 섞어두면, 템플릿을 아무리 엄격하게 정의해도.. 할루시에이션 또는 의역이 정도가 심해진다는 점이다. ( 물론 학습량이 많은 모델은 문서를 제공하지 않아도 할루시에이션 없이 정확한 답을 제공할 수 있다. 물론 프롬프트의 작성 방식과 각 모델간의 특성이 다르다는 변수가 존재하기 때문에 사실상 운에 맏기는 것과 다르지않으니 개인적으로는 변인통제를 해야하는 서비스 제공자 입장에서는 권장할 만한 방식은 못되는 것 같다. )

 

4. 코딩

자, 그럼 이제 코딩을 시작하기로 한다. 참고로 코드 구성은 포스팅의 편의상, 크게 - 1)패키징 구성, 2)문서 로드 및 분할, 3)저장(임베딩), 4)RAG 세팅 및 실행 -  네 단계로 나누어 작성했다.

1) 패키징 구성 및 모델 로드

## ollama RAG(URLs) by langchain-ChatOllama

from langchain_community.document_loaders import WebBaseLoader
from langchain_community.document_loaders import PyPDFLoader
from langchain_community.vectorstores import Chroma
from langchain_community import embeddings
from langchain_community.chat_models import ChatOllama
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain.output_parsers import PydanticOutputParser
from langchain.text_splitter import CharacterTextSplitter

model_local = ChatOllama(model="mistral:v0.2")

 

2) 문서 로드 및 분할
여기서 다시, 문서 로드는 상황에 따라 대략 세가지 방식으로 구성할 수 있다
(1) .txt 파일 업로드 

# txt 첨부
print(".txt 첨부) \\n")

## Type. txt
with open('청킹.txt', 'r', encoding='utf-8') as file:
    text = file.read()
    
from langchain.docstore.document import Document
chunks = []
chunk_size = 35 # Characters
for i in range(0, len(text), chunk_size):
    chunk = text[i:i + chunk_size]
    chunks.append(chunk)
doc_splits = [Document(page_content=chunk, metadata={"source": "local"}) for chunk in chunks]

(2) .pdf 파일 업로드 

# pdf 첨부
print(".pdf 첨부) \\n")

loader = PyPDFLoader("청킹.pdf")
doc_splits = loader.load_and_split()

(3) url 첨부

# url 첨부
print("url 첨부) \\n")

urls = [
    '<https://ollama.com/>',
    '<https://python.langchain.com/docs/integrations/llms/ollama/>'
]
docs = [WebBaseLoader(url).load() for url in urls]
docs_list = [item for sublist in docs for item in sublist]

text_splitter = CharacterTextSplitter.from_tiktoken_encoder(chunk_size=7500, chunk_overlap=100)
doc_splits = text_splitter.split_documents(docs_list)

 

3) 저장(임베딩)
앞에 1)번에서 모델을 로드할때는 mistral( 사용자와 커뮤니케이션 용 LLM 모델)를 사용했지만 여기, 임베딩 구간에서는 임베딩 전용 LLM 모델은 nomic으로 적용했다.

# 문서를 임베딩으로 변환하여 저장하기

vectorstore = Chroma.from_documents(
    documents=doc_splits,
    collection_name="rag-chroma",
    embedding=embeddings.ollama.OllamaEmbeddings(model='nomic-embed-text'),
)

*참고로 ollama에서 제공하는 임베딩 모델은 4월 12일 기준으로 총 3개의 모델들이(mxbai, nomic, all-minilm)이 있다. 관련 내용은 아래 링크를 통해 자세히 살펴보자.

 

Embedding models · Ollama Blog

Embedding models are available in Ollama, making it easy to generate vector embeddings for use in search and retrieval augmented generation (RAG) applications.

ollama.com

 

4) RAG의 R.un 하기!
RAG(Retrieval Augmented Generation)를 위해 백터 저장소에 Retrievers(검색도구)를 저장하고 검색의 범위를 문서로 한정하기 위해 프롬프트에 문맥 context(참조 범위) 구성한다. 내용은 별거 없다. 그냥 제공하는 "대답할때는 내가 준 문맥을 벗어나서 말하지마"라는 의미의 자연어( natural language)이다. 참고로 모델별로 해당 프롬프트를 무시하거나 본인의 학습 내용과 적절히? 버무려서 대답하는 경우도 있다. 참고로 학습량이 부쩍 많아지 최신 모델 일수록 그런 경향이 큰 것 같다.  

# RAG 
 
retriever = vectorstore.as_retriever()
after_rag_template = """
Answer only with reference to what is given in context:
{context}
Question: {question}
"""
after_rag_prompt = ChatPromptTemplate.from_template(after_rag_template)
after_rag_chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | after_rag_prompt
    | model_local
    | StrOutputParser()
)

print(after_rag_chain.invoke("인지심리학에 대해 알려줘?"))

 

{부록}

비교를 위해 RAG를 적용하지 않고 바로 LLM에게 질문을 던지는 코드

print("RAG 미적용 예: \\n")

before_rag_template = "What is {topic}"
before_rag_prompt = ChatPromptTemplate.from_template(before_rag_template)
before_rag_chain = before_rag_prompt | model_local | StrOutputParser()

print(before_rag_chain.invoke({"topic": "청킹(Chunking)"}))

 

 

 

[2]Ollama 자체 라이브러리 Libraries 사용 예 

1. 관련 상세 내용은 ollama 오피셜 문서를 참조하자 

ollama 오피셜 라이브러리

 

Python & JavaScript Libraries · Ollama Blog

The initial versions of the Ollama Python and JavaScript libraries are now available, making it easy to integrate your Python or JavaScript, or Typescript app with Ollama in a few lines of code. Both libraries include all the features of the Ollama REST AP

ollama.com

ollama의 오피셜 라이브러리는 Python(PIP) & JavaScript(NPM) 두가지를 제공하고 있다. 

 

2. 바로 코딩

이번 포스팅의 예제는 위와 같이 일단 Python 베이스로 진행한다. 

1) 가상환경 세팅 
venv 생성 후 진입

2) 라이브러리 설치   

pip install ollama
pip install langchain beautifulsoup4 chromadb gradio

3) 코드  작성 
함수로 위주로 구성되어 있다는 점만 다르고 거의 유사하다

import ollama
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import WebBaseLoader
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import OllamaEmbeddings
from langchain.text_splitter import CharacterTextSplitter

urls = [
    '<https://ollama.com/>',
    '<https://python.langchain.com/docs/integrations/llms/ollama/>'
]
docs = [WebBaseLoader(url).load() for url in urls]
docs_list = [item for sublist in docs for item in sublist]

text_splitter = CharacterTextSplitter.from_tiktoken_encoder(chunk_size=7500, chunk_overlap=100)
doc_splits = text_splitter.split_documents(docs_list)

# 저장
vectorstore = Chroma.from_documents(
    documents=doc_splits,
    collection_name="rag-chroma",
    embedding=OllamaEmbeddings(model='nomic-embed-text'),
)

# RAG 적용 
retriever = vectorstore.as_retriever()

def format_docs(docs):
    return "\\n\\n".join(doc.page_content for doc in docs)

# Define the Ollama LLM function
def ollama_llm(question, context):
    formatted_prompt = f"Answer the question based only on the following context: {context}\\n\\nQuestion: {question}"
    response = ollama.chat(model='mistral:v0.2', messages=[{'role': 'user', 'content': formatted_prompt}])
    return response['message']['content']

# Use the RAG chain
result = ollama_llm("인지심리학에 대해 알려줘", retriever)
print(result)

Ollama Libraries( pip)로 실행한 결과, 여러번 실행해도 제공된 문서에서는 관련 내용이 없음을 답변하고 있다.

 

 

 

 

( 추가로 update 예정인 내용 )

pdf 파싱과 관련된 내용
PDF 문서는 .txt나 .MD와 달리 문장이 줄 글 형태가 아니라 중간 중간 이미지나 표도 들어가고 글의 레이아웃도 자주 바뀌는 현상이 있어서 그동안 많은 경우 강제로 랭체인을 튜닝하거나 패키지를 여러개 붙이거나 아니면, 강제로 txt로 변형해버리는 경우가 있었는데.. 최근에.. 문서를 vision 형태로 먼저 레이아웃을 나누고 각 상세 내용을 나눠서 파싱하는 오픈소스가 출시했다. 이전에 계획했던 포스팅을 추가로 업로드하고 and PDF 파싱 실습이 정리되는 데로 관련 내용을 추가할 예정이다.