4 Local LLM - OLLAMA
4.1 개요
이 문서는 Streamlit, MySQL 데이터베이스, LangChain과 Ollama LLM을 결합하여 사용자가 데이터베이스와 상호작용할 수 있는 AI 기반 챗봇을 구현한 코드에 대한 설명을 제공한다.
CS 업무와 관련된 모든 구글시트와 아카라 카페의 내용을 MySQL DB에 데이터베이스화 하였다.
파일 또는 웹사이트 기반으로 RAG을 진행할 수 있으나 최종 목적은 DB의 내용 기반으로 RAG하는 것이다.
4.2 OLLAMA 설정 방법
4.2.1 HuggingFaxe-Hub 설치
#pip install huggingface-hub
4.2.2 GGUF파일 다운로드
https://huggingface.co/heegyu/EEVE-Korean-Instruct-10.8B-v1.0-GGUF
#huggingface-cli download \
# heegyu/EEVE-Korean-Instruct-10.8B-v1.0-GGUF \
# ggml-model-Q5_K_M.gguf \
# --local-dir 본인의_컴퓨터_다운로드폴더_경로 \
# --local-dir-use-symlinks False
4.2.3 Modelfile
EEVE-Korean-Instruct-10.8B-v1.0 예시
#FROM ggml-model-Q5_K_M.gguf
#
#TEMPLATE """{{- if .System }}
#<s>{{ .System }}</s>
#{{- end }}
#<s>Human:
#{{ .Prompt }}</s>
#<s>Assistant:
#"""
#
#SYSTEM """A chat between a curious user and an artificial intelligence assistant. The #assistant gives helpful, detailed, and polite answers to the user's questions."""
#
#PARAMETER stop <s>
#PARAMETER stop </s>
4.2.4 OLLAMA 실행
#ollama create EEVE-Korean-10.8B -f EEVE-Korean-Instruct-10.8B-v1.0-GGUF/Modelfile
4.2.4.1 OLLAMA 모델 목록
#ollama list
4.2.4.2 OLLAMA 모델 실행
ollama run EEVE-Korean-10.8B:latest
4.2.4.3 ngrok에서 터널링(포트 포워드)
#streamlit default port: 8501
#ngrok http localhost:8501
4.3 환경 설정 및 Streamlit 페이지 구성
from dotenv import load_dotenv
load_dotenv()
.env
파일에서 환경 변수를 로드하여 코드에서 민감한 정보를 안전하게 불러올 수 있다.
="MySQL DB GPT", page_icon="🔒", layout="wide") st.set_page_config(page_title
- Streamlit 페이지의 제목과 아이콘, 레이아웃을 설정한다.
4.4 ChatCallbackHandler
# Custom callback handler inheriting from BaseCallbackHandler
class ChatCallbackHandler(BaseCallbackHandler):
= ""
message
def on_llm_start(self, *args, **kwargs):
self.message_box = st.empty()
def on_llm_end(self, *args, **kwargs):
self.message, "ai")
save_message(
def on_llm_new_token(self, token, *args, **kwargs):
self.message += token
self.message_box.markdown(self.message)
- LangChain의 콜백 핸들러를 사용하여 챗봇의 실시간 응답을 처리합니다. 메시지를 저장하고 업데이트하며, 실시간으로 토큰이 생성될 때마다 사용자에게 표시한다.
4.5 Ollama LLM 설정
= ChatOllama(
llm ="EEVE-Korean-10.8B:latest",
model=0.1,
temperature=True,
streaming=[ChatCallbackHandler()],
callbacks )
- Ollama의 “EEVE-Korean-10.8B” 모델을 사용하여 질문에 답변한다.
temperature=0.1
: 모델의 응답이 얼마나 창의적인지 조정한다.streaming=True
: 모델의 출력이 실시간으로 스트리밍되어 사용자에게 즉시 표시된다.
4.6 MySQL 테이블 불러오기
def load_database_data(tables):
try:
= mysql.connector.connect(
connection =db_host,
host=db_database,
database=db_user,
user=db_password,
password='utf8mb4',
charset='utf8mb4_unicode_ci'
collation
)
= {}
data_frames for table in tables:
= f"SELECT * FROM {table}"
query = pd.read_sql(query, connection)
df = df
data_frames[table]
return data_frames
except mysql.connector.Error as err:
f"Error connecting to MySQL: {err}")
st.error(return None
finally:
if connection.is_connected():
connection.close()
- MySQL 데이터베이스에서 사용자가 선택한 테이블 데이터를 Pandas DataFrame으로 불러온다. 이 데이터는 AI에게 제공될 “컨텍스트”로 사용된다.
4.7 데이터 테이블 선택 및 로드
def table_selector():
= ["aqara_cafe", "cs_table", "doorlock_malfunction_ledger", "curtain_ledger", "installation_ledger", "service_ledger"]
available_tables = st.sidebar.multiselect("Select tables to load", available_tables, default=available_tables)
selected_tables
= st.sidebar.button("Load selected tables")
load_button
if load_button:
= load_database_data(selected_tables)
data_frames "data_frames"] = data_frames
st.session_state[f"Loaded data for tables: {', '.join(selected_tables)}")
st.success(
return st.session_state.get("data_frames", {})
- 사용자가 MySQL 데이터베이스에서 불러올 테이블을 선택할 수 있게 한다. Streamlit의
multiselect
위젯을 사용하여 여러 테이블을 선택하고, 그 데이터를 로드하여session state
에 저장한다.
4.8 (참고 1) File Embedding 하기
def embed_file(file):
# Define the directory path
= "./private_files/"
directory
# Create the directory if it does not exist
if not os.path.exists(directory):
os.makedirs(directory)
# Save the file to the directory
= os.path.join(directory, file.name)
file_path
with open(file_path, "wb") as f:
file.read())
f.write(
= LocalFileStore(f"./private_embeddings/{file.name}")
cache_dir = CharacterTextSplitter.from_tiktoken_encoder(
splitter ="\n",
separator=600,
chunk_size=100,
chunk_overlap
)
= UnstructuredFileLoader(file_path)
loader = loader.load_and_split(text_splitter=splitter)
docs
= OllamaEmbeddings(model="EEVE-Korean-10.8B:latest")
embeddings = CacheBackedEmbeddings.from_bytes_store(embeddings, cache_dir)
cached_embeddings
= FAISS.from_documents(docs, cached_embeddings)
vectorstore = vectorstore.as_retriever()
retriever
return retriever
4.9 (참고 2) Webpage Embedding 하기
# Function to scrape website pages and extract text
def scrape_website(url):
= set()
visited_urls = url
base_url = []
texts
def scrape_page(current_url):
if current_url in visited_urls or not current_url.startswith(base_url):
return
visited_urls.add(current_url)
# Request the page
= requests.get(current_url)
response = BeautifulSoup(response.content, "html.parser")
soup
# Extract text from the page
= soup.get_text(separator="\n").strip()
page_text
texts.append(page_text)
# Find all links on the page and recursively scrape them
for link in soup.find_all("a", href=True):
= requests.compat.urljoin(base_url, link['href'])
absolute_link if absolute_link not in visited_urls:
scrape_page(absolute_link)
scrape_page(base_url)return texts
4.10 대화 기록 저장 및 표시
# Function to save the conversation history
def save_message(message, role):
if "messages" not in st.session_state:
"messages"] = []
st.session_state["messages"].append({"message": message, "role": role}) st.session_state[
- Streamlit의
session state
를 사용하여 사용자와 AI 간의 대화 기록을 저장하고 페이지를 새로고침해도 대화 기록이 유지되도록 한다.
4.11 질문과 데이터 기반 응답 처리
= ChatPromptTemplate.from_template(
prompt """Answer the question using ONLY the following context and not your training data.
If you don't know the answer just say you don't know. DON'T make anything up.
Context: {context}
Question: {question}
"""
)
- LangChain의
ChatPromptTemplate
을 사용하여 AI가 훈련 데이터가 아닌, 제공된 데이터(컨텍스트)를 기반으로만 질문에 답변하도록 한다.
= st.chat_input("Ask anything about your database...")
message
...= (
chain
{"context": RunnableLambda(lambda _: context),
"question": RunnablePassthrough(),
}| prompt
| llm
)with st.chat_message("ai"):
chain.invoke(message)
- 사용자의 질문을 입력받고, 데이터베이스에서 가져온 컨텍스트와 함께 AI에게 전달하여 답변을 생성한다.
4.12 전체 워크플로우
- 사용자는 MySQL 데이터베이스 테이블을 선택한다..
- 선택된 데이터가 Pandas DataFrame으로 불러온다..
- 사용자가 질문을 입력하면, AI가 데이터베이스에서 가져온 데이터를 바탕으로 답변을 제공한다.
- 대화 기록이 유지되며, 이전 대화 내용을 확인할 수 있다.