Day 25: 미니 프로젝트 — 나만의 AI 어시스턴트
30일간의 여정을 하나의 프로젝트로 통합합니다. 모델 선택부터 파인튜닝, RAG 연결, Ollama 서빙, Gradio UI 구축까지 전체 파이프라인을 완성합니다.
전체 아키텍처
이 프로젝트는 5개의 단계로 구성됩니다. (1) 기본 모델 선택 및 QLoRA 파인튜닝, (2) 도메인 문서 기반 RAG 벡터 스토어 구축, (3) Ollama로 파인튜닝된 모델 서빙, (4) RAG 검색과 LLM 생성 파이프라인 연결, (5) Gradio 웹 UI 구축.
1단계: QLoRA 파인튜닝
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig, TrainingArguments
from peft import LoraConfig, TaskType
from trl import SFTTrainer
from datasets import load_dataset
import torch
# 모델 로드 (QLoRA)
bnb_config = BitsAndBytesConfig(
load_in_4bit=True, bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16, bnb_4bit_use_double_quant=True,
)
model_name = "meta-llama/Llama-3.1-8B-Instruct"
model = AutoModelForCausalLM.from_pretrained(model_name, quantization_config=bnb_config, device_map="auto")
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token
# 데이터 준비 및 학습
dataset = load_dataset("json", data_files="my_domain_data.json", split="train")
lora_config = LoraConfig(
task_type=TaskType.CAUSAL_LM, r=16, lora_alpha=32,
lora_dropout=0.05, target_modules=["q_proj", "v_proj", "k_proj", "o_proj"],
)
training_args = TrainingArguments(
output_dir="./assistant_model", num_train_epochs=3,
per_device_train_batch_size=4, gradient_accumulation_steps=4,
learning_rate=2e-4, bf16=True, logging_steps=10,
save_steps=100, gradient_checkpointing=True,
)
trainer = SFTTrainer(
model=model, args=training_args, train_dataset=dataset,
peft_config=lora_config, processing_class=tokenizer, max_seq_length=1024,
)
trainer.train()
trainer.save_model("./assistant_lora")
2단계: RAG 벡터 스토어 구축
from langchain_community.document_loaders import DirectoryLoader, TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import OllamaEmbeddings
# 도메인 문서 로딩
loader = DirectoryLoader("./docs/", glob="**/*.txt", loader_cls=TextLoader)
documents = loader.load()
# 청크 분할
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
chunks = splitter.split_documents(documents)
# 로컬 임베딩으로 벡터 스토어 생성 (Ollama 활용)
embeddings = OllamaEmbeddings(model="nomic-embed-text")
vectorstore = Chroma.from_documents(
documents=chunks, embedding=embeddings,
persist_directory="./assistant_vectordb",
)
print(f"벡터 스토어 생성 완료: {len(chunks)}개 청크")
3단계: Ollama에 파인튜닝 모델 등록
파인튜닝된 모델을 GGUF로 변환하고 Ollama에 등록합니다.
# 1. LoRA 어댑터를 기본 모델에 병합
# from peft import PeftModel
# base = AutoModelForCausalLM.from_pretrained(model_name, torch_dtype=torch.float16)
# merged = PeftModel.from_pretrained(base, "./assistant_lora").merge_and_unload()
# merged.save_pretrained("./assistant_merged")
# 2. GGUF 변환 (llama.cpp 사용)
# python convert_hf_to_gguf.py ./assistant_merged --outtype f16 --outfile assistant.gguf
# ./llama-quantize assistant.gguf assistant-q4_k_m.gguf Q4_K_M
# 3. Ollama Modelfile 작성
modelfile_content = """FROM ./assistant-q4_k_m.gguf
TEMPLATE \"\"\"{{- if .System }}<|begin_of_text|><|start_header_id|>system<|end_header_id|>
{{ .System }}<|eot_id|>{{- end }}<|start_header_id|>user<|end_header_id|>
{{ .Prompt }}<|eot_id|><|start_header_id|>assistant<|end_header_id|>
\"\"\"
SYSTEM "당신은 도메인 전문 AI 어시스턴트입니다. 정확하고 친절하게 한국어로 답변하세요."
PARAMETER temperature 0.7
PARAMETER top_p 0.9
"""
with open("Modelfile", "w") as f:
f.write(modelfile_content)
# 터미널에서: ollama create my-assistant -f Modelfile
4단계: RAG + LLM 통합 및 Gradio UI
# pip install gradio
import gradio as gr
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import OllamaEmbeddings
from openai import OpenAI
# 벡터 스토어 로드
embeddings = OllamaEmbeddings(model="nomic-embed-text")
vectorstore = Chroma(persist_directory="./assistant_vectordb", embedding_function=embeddings)
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
# Ollama 클라이언트
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
def chat(message, history):
"""RAG 기반 채팅 함수"""
# 관련 문서 검색
docs = retriever.invoke(message)
context = "\n".join([doc.page_content for doc in docs])
# 대화 기록 구성
messages = [{"role": "system", "content": f"참고 문서:\n{context}\n\n위 문서를 참고하여 답변하세요. 문서에 없는 내용은 일반 지식으로 답변하되, 불확실하면 솔직히 말하세요."}]
for user_msg, bot_msg in history:
messages.append({"role": "user", "content": user_msg})
messages.append({"role": "assistant", "content": bot_msg})
messages.append({"role": "user", "content": message})
response = client.chat.completions.create(
model="my-assistant", messages=messages, temperature=0.7, max_tokens=500,
)
return response.choices[0].message.content
# Gradio UI 실행
demo = gr.ChatInterface(
fn=chat,
title="나만의 AI 어시스턴트",
description="도메인 문서 기반 RAG + 파인튜닝된 LLM 어시스턴트",
examples=["프로젝트 개요를 알려주세요", "최근 변경 사항은?", "API 사용법을 설명해주세요"],
theme=gr.themes.Soft(),
)
demo.launch(server_name="0.0.0.0", server_port=7860)
30일 과정을 완주하셨습니다. 이제 LLM의 기초부터 파인튜닝, RAG, 서빙, UI까지 전체 파이프라인을 이해하고 구축할 수 있는 역량을 갖추셨습니다. 이 프로젝트를 시작점으로 삼아 본인의 도메인에 맞게 확장해보세요.
오늘의 연습문제
- 위 전체 파이프라인을 실제로 실행하세요. 파인튜닝 데이터 20개 이상을 직접 작성하고, 도메인 문서 5개 이상을 준비하여 RAG까지 연결합니다.
- Gradio UI에 “참고 문서 표시” 토글을 추가하여, RAG에서 검색된 원본 문서를 사용자가 확인할 수 있게 개선해보세요.
- 파인튜닝 전/후 모델을 동일한 질문 10개로 평가하고, Day 29에서 배운 LLM-as-Judge 방식으로 품질 개선 정도를 정량적으로 측정해보세요.