from langchain_ollama.chat_models import ChatOllama
from langchain_ollama.embeddings import OllamaEmbeddings
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
# 0) 한국어 PDF 로드
loader = PyPDFLoader("docs/sample-ko.pdf") # PDF 경로
pages = loader.load()
# 1) 텍스트 청크 분할(토큰 대신 '문자 길이' 기준)
splitter = RecursiveCharacterTextSplitter(
chunk_size=800, # 한글은 문자 기준 600~1200 사이가 무난
chunk_overlap=120,
length_function=len # tiktoken 대신 문자 길이 사용
)
docs = splitter.split_documents(pages)
print("청크 개수:", len(docs))
print("예시 청크 길이:", len(docs[0].page_content))
# 2) 임베딩 (Ollama — bge-m3 권장)
emb = OllamaEmbeddings(
model="bge-m3", # 멀티링구얼 무료 임베딩
base_url="http://localhost:11434",
)
# 3) 벡터DB: FAISS (in-memory)
vectorstore = FAISS.from_documents(docs, embedding=emb)
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
# 4) 챗 모델 (Ollama)
llm = ChatOllama(
model="ko_model", # 사용 중인 한글 모델 태그
base_url="http://localhost:11434",
temperature=0.2,
model_kwargs={"num_ctx": 4096} # 문맥창(컨텍스트) 여유
)
# 5) RAG 프롬프트 + 체인
prompt = ChatPromptTemplate.from_messages([
("system", "다음 '컨텍스트'만 근거로 한국어로 정확히 답해. 모르면 모른다고 말해."),
("human", "질문: {question}\n\n컨텍스트:\n{context}")
])
def format_docs(dlist):
return "\n\n".join(d.page_content for d in dlist)
chain = (
{"context": retriever | format_docs, "question": lambda x: x["question"]}
| prompt
| llm
| StrOutputParser()
)
# 6) 질의
q = "문서의 핵심 요점을 3줄로 요약해줘."
print(chain.invoke({"question": q}))