LangChain 활용해서 내 글스타일에 맞춰 글쓰는 LLM 구축하기 Ver0.1

LangChain 활용해서 내 글스타일에 맞춰 글쓰는 LLM 구축하기 Ver0.1
Photo by Joshua Hoehne / Unsplash

목적 및 배경

  • 매일 글을 쓰는 입장에서 보다 많은 글을 작성하기 위해서 1차로 글의 구조만 잡아주고, 나머지 내용은 LLM으로 채운 후 퇴고를 하는 형태를 시도해보려고 한다.

Code

01. Obsidian에 있는 내 글을 모아서 전처리하기

import os  
import re  
import time  
import pickle  
  
def preProcessNote(file, path_dir):  
    try:  
        file_path = os.path.join(path_dir, file)  
        with open(file_path, 'r', encoding='utf-8') as f:  
            lines = f.readlines()  
  
        # 지워야 할 지점 체크  
        start_idx = None  
        end_idx = None  
        for idx, line in enumerate(lines):  
            if re.match('---', line):  
                if start_idx is None:  
                    start_idx = idx  
                else:  
                    end_idx = idx  
                    break  
  
        if start_idx is None or end_idx is None:  
            raise ValueError(f"Start or end delimiter not found in file: {file_path}")  
  
        # 삭제하기  
        lines = lines[:start_idx] + lines[end_idx+1:]  
        note = ''.join(lines)  
  
        # 데이터 정제  
        note = re.sub(r'[\n\t]+', '', note)  
  
        return note  
  
    except FileNotFoundError as e:  
        raise FileNotFoundError(f"File not found: {file_path}") from e  
    except PermissionError as e:  
        raise PermissionError(f"Permission denied for file: {file_path}") from e  
    except UnicodeDecodeError as e:  
        raise UnicodeDecodeError(f"Error decoding file: {file_path}. Ensure the file is UTF-8 encoded.") from e  
    except ValueError as e:  
        raise ValueError(f"Processing error in file {file_path}: {str(e)}") from e  
    except OSError as e:  
        raise OSError(f"OS error occurred while processing file: {file_path}") from e  
    except Exception as e:  
        raise Exception(f"Unexpected error while processing file: {file_path}") from e  
  
def preProcessNotes(path_dir="data/md", file_ext="md", output_dir="output", output_filename="notes.pkl"):  
    start_time = time.time()  # 시작 시간 기록  
  
    try:  
        # 파일 리스트 가지고 오기  
        file_list = os.listdir(path_dir)  
        file_list = [file for file in file_list if file.endswith(f'.{file_ext}')]  
    except FileNotFoundError as e:  
        raise FileNotFoundError(f"Directory not found: {path_dir}") from e  
    except PermissionError as e:  
        raise PermissionError(f"Permission denied for directory: {path_dir}") from e  
    except OSError as e:  
        raise OSError(f"OS error occurred while accessing directory: {path_dir}") from e  
    except Exception as e:  
        raise Exception(f"Unexpected error while listing files in directory: {path_dir}") from e  
  
    # 출력 디렉토리 생성  
    if not os.path.exists(output_dir):  
        try:  
            os.makedirs(output_dir)  
        except OSError as e:  
            raise OSError(f"Failed to create output directory: {output_dir}") from e  
  
    notes = []  
  
    for file in file_list:  
        try:  
            note = preProcessNote(file, path_dir)  
            notes.append(note)  
        except Exception as e:  
            print(f"Error occurred while processing {file}: {e}")  
  
    # notes 리스트를 pickle 파일로 저장  
    output_file_path = os.path.join(output_dir, output_filename)  
    try:  
        with open(output_file_path, 'wb') as pkl_file:  
            pickle.dump(notes, pkl_file)  
    except Exception as e:  
        raise Exception(f"Failed to save notes to pickle file: {output_file_path}") from e  
  
    end_time = time.time()  # 종료 시간 기록  
    elapsed_time = end_time - start_time  # 경과 시간 계산  
  
    print(f"Processing completed successfully in {elapsed_time:.2f} seconds. Notes saved to {output_file_path}")  
  
    return notes

02. LangChain 구축

import pickle  
import os  
import ollama  
from langchain.schema import Document  
from langchain_community.embeddings import OllamaEmbeddings  
from langchain_chroma import Chroma

notes = pickle.load(open('output/notes.pkl', 'rb'))  
notes_dict = {index: document for index, document in enumerate(notes)}

# notes_dict에서 각 문서를 Document 객체로 변환  
documents = [Document(page_content=content) for content in notes_dict.values()]  
embeddings = OllamaEmbeddings(model="llama3.1:latest")

output_dir = "db"  
# 출력 디렉토리 생성  
if not os.path.exists(output_dir):  
    try:  
        os.makedirs(output_dir)  
    except OSError as e:  
        raise OSError(f"Failed to create output directory: {output_dir}") from e  
  
collection_name = "obsidian"  
# ChromaDB에 저장  
try:  
    vectordb = Chroma.from_documents(  
        documents=documents,  
        embedding=embeddings,  
        collection_name=collection_name,  # 컬렉션 이름 추가  
        persist_directory=output_dir  
    )  
    print("데이터 저장 성공")  
except ValueError as e:  
    print(f"오류 발생: {e}")  
except Exception as e:  
    print(f"예상치 못한 오류 발생: {e}")  
  
  
[#Defines the retriever
retriever = vectordb.as_retriever(search_type='mmr', search_kwargs ={'k':1})

#Gets the document for the retriever
retriever.get_relevant_documents('사전분포란?')

from langchain_core.output_parsers import StrOutputParser  
from langchain_core.prompts import ChatPromptTemplate  
from langchain_core.runnables import RunnablePassthrough  
from langchain_openai import ChatOpenAI  
from langchain_experimental.llms.ollama_functions import OllamaFunctions  
from operator import itemgetter   
  
# This is the prompt I used  
  
# It takes in the documents as {context} and user provide {topic}  
template = """Mimic the writing style in the context:  
{context} and produce a blog on the topic in Korean.  
The number of character should be more than 2000 characters.  
Topic: {topic}  
  
  
"""  
  
prompt = ChatPromptTemplate.from_template(template)  
  
model = OllamaFunctions(model="llama3.1:latest", format="json")  
# model = ChatOpenAI(api_key = "key")  
  
# Using LangCHain LCEL to supply the prompt and generate output  
chain = (  
    {  
        "context":itemgetter("topic") | retriever,  
        "topic": itemgetter("topic"),  
  
    }  
    | prompt  
    | model  
    | StrOutputParser()  
)  
#running the Chain  
chain.invoke({"topic":  "Airflow "})

수정포인트

  • VectorDB에서 검색해본 겨로가, 우선 임베딩이 충분히 한글을 반영하는 것으로 보이지 않는다 → 모델 교체 필요
  • VectorDB는 sqlite 형태로 저장이 되는데 데이터를 다시 로딩하는 과정에서 에러 발생
  • 기타 Warning에 대한 수정 필요
ValueError: Expected collection name that (1) contains 3-63 characters, (2) starts and ends with an alphanumeric character, (3) otherwise contains only alphanumeric characters, underscores or hyphens (-), (4) contains no two consecutive periods (..) and (5) is not a valid IPv4 address, got ./db/chroma.sqlite3

Read more

Chi-Square Test와 T-Test는 데이터 특성에 따라 선택하여 사용합니다.

Chi-Square Test와 T-Test는 데이터 특성에 따라 선택하여 사용합니다.

Chi-Square Test가 A/B/C Test에서 사용되는 구체적인 경우 * 범주형 데이터 분석: 각 그룹에서 클릭하거나 전환된 사람의 수를 비교할 때, 이 데이터를 범주형 변수로 간주하고 Chi-Square Test를 적용할 수 있습니다. * 다중 그룹 비교: A/B/C 테스트에서 세 개 이상의 그룹을 비교해야 할 때, Chi-Square Test는 모든 그룹 간의 독립성을

OLS 기반 인과추론 시 오차항 관련 체크 필요 가정

OLS 기반 인과추론 시 오차항 관련 체크 필요 가정

배경 * 아래 글을 DANBI에서 보다가 더 알아보게 되었습니다. OLS를 떠받치는 몇 개의 기둥이 있는데 그중 실용적으로 가장 중요한 것이 일치성(consistency)다. 쉽게 말해서 OLS를 통해 도출된 추정량이 있을 때 샘플사이즈가 커지면서 이 값이 참 값으로 접근한다는 것이다. 일치성이 충족되면 우리는 적당하게 큰 표본에 대해서 추정치가 좋은 속성을 지니고 있다고

인공지능이 문제가 아니라 결국 사람이 문제가 될 것입니다.

인공지능이 문제가 아니라 결국 사람이 문제가 될 것입니다.

사람들이 AI가 필요하다고 생각하는 시점 저 판사를 얼른 AI로 교체해야 한다. 유튜브에서 뉴스를 보다 보면 정말 많이 보이는 덧글입니다. 이러한 내용의 덧글이 달릴 때마다, 정말 많은 사람들이 공감을 표하곤 합니다. 왜 이렇게 사람들은 이러한 주장에 공감을 표하는 것일까? AI는 시킨대로 하기 때문에 공정하다는 인식 여러 이유가 있겠지만, 사람들은 아마 AI가

BG/NBD 모델은 고객 생애가치를 추정하는데 사용되는 확률 모델입니다.

BG/NBD 모델은 고객 생애가치를 추정하는데 사용되는 확률 모델입니다.

1. BG/NBD 모델이란? * BG/NBD(Beta-Geometric/Negative Binomial Distribution) 모델은 **고객의 생애 가치(Customer Lifetime Value, CLV)**를 추정하는 데 사용되는 확률적 모델입니다. * 특히 고객이 반복 구매를 할지, 아니면 더 이상 활동하지 않을지를 추정하는 데 유용합니다. 이 모델은 고객의 구매 행태를 두 가지 중요한 개념으로 나눕니다: * 고객은 활성(active)