배경
목차가 있으면 Rag 검색기에 걸려서, 검색기의 성능을 떨어트렸다.
원본을 눈으로 보고 전처리(목차 제거) 해줬다.
대량의 문서를 업로드 할 때는, 목차 분류를 자동화 할 필요성 느낌.
해결과정
시행착오를 거치며 목차를 구별하는 아래 프롬트를 제작해서 LLM을 돌림.
"""You are tasked with identifying whether the provided text is a "목차" or part of the "본문" of a book. Follow these instructions: 1. If the text contains 4 or more numeric indicators like chapter numbers or page numbers, label it as "목차". 2. If the text includes continuous, long sentences with descriptions, examples, or in-depth explanations, label it as "본문". 4. If the text contains a mix of both, prioritize the second rule (numeric indicators). Here is the text: "{context}" Please return only "목차" or "본문" without any extra explanation. """ 원본 정보 : 출판정보(1~5), 프롤로그(6~11), 목차(12~18), 본문(21~) {1: '본문', -> 오답 : 원래 출판정보 3: '본문', -> 오답 : 원래 출판정보 5: '본문', -> 오답 : 원래 출판정보 6: '본문', -> 오답 : 원래 프롤로그 7: '본문', -> 오답 : 원래 프롤로그 8: '본문', -> 오답 : 원래 프롤로그 9: '본문', -> 오답 : 원래 프롤로그 10: '본문', -> 오답 : 원래 프롤로그 11: '본문', -> 오답 : 원래 프롤로그 12: '목차', 13: '목차', 14: '목차', 15: '목차', 16: '목차', 17: '목차', 18: '목차', 21: '본문', 22: '본문', 23: '본문',
결과 : 목차는 잘 구분했지만 필요없는 정보를 본문으로 분류했다.
출판정보가 제목등으로 이루어져 글자수가 작은걸 보고 코드적으로 150자 이하는, 일괄 목차로 분류.
결과 : 출판정보는 잘 구분했지만, 프롤로그는 글자수가 많아서 실패, 그리고 150자 제한은 일부 본문도 제거할 위험이 있음. 이 코드는 폐기하고 적용하지 않도록한다.생각해보니 1번처럼 한번 분석하고, 마지막 목차 이전 페이지를 모두 목차로 한꺼번에 바꾸게 코드처리 하면 됨.
{1: '목차:재설정', 3: '목차:재설정', 5: '목차:재설정', 6: '목차:재설정', 7: '목차:재설정', 8: '목차:재설정', 9: '목차:재설정', 10: '목차:재설정', 11: '목차:재설정', 12: '목차:재설정', 13: '목차:재설정', 14: '목차:재설정', 15: '목차:재설정', 16: '목차:재설정', 17: '목차:재설정', 18: '목차:재설정', 21: '본문', 22: '본문', 23: '본문',
결과 : 목차와 본문이 잘 구분됐다. 나중에 rag에 임베딩할때는 이 목차 페이지를 제거하고 하면된다.
효율화
- 만약 GPT등 api를 사용한다면, 모든 페이지를 돌려서 목차를 구분하면 비효율적이다. 전 페이지의 1/15만 목차 구분 연산을 하게 설정
- 근래에는 rag 제작시 페이지 요약 연산을 따로 하는 경우도 많은데 -> 앞 목차를 제외하고 본문 페이지만 요약한다면, 사용량을 절약할 수 있다.
결론
- 문서의 구조상 거의 대부분이 목차가 본문전에 있다. 추가로 프롤로그 찾아내는 프롬트 제작하는 것보다,마지막 목차 이전을 일괄 제거하는 전처리는 합리적으로 보인다.
- 목차를 구분하는 과정은 추가 요금이 들수 있지만, 일부 페이지로 한정한다면, 추가 비용은 그리 크지 않다.
전체코드
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
# create_stuff_documents_chain은 여러 문서를 결합하고 요약하여 하나의 결과로 만드는
# 작업에 사용됩니다. 특히, 여러 개의 문서를 한꺼번에 처리하고 이를 하나의 통합된 정보로 제공해야 할 때 유용
from langchain.chains.combine_documents import (
create_stuff_documents_chain,
)
from langchain_core.documents import Document
from langchain_community.chat_models import ChatOllama
from dotenv import load_dotenv
import os
# .env 파일 로드
load_dotenv()
# 요약을 위한 프롬프트 템플릿을 정의합니다.
prompt = PromptTemplate.from_template(
"""You are tasked with identifying whether the provided text is a "목차" or part of the "본문" of a book.
Follow these instructions:
1. If the text contains 4 or more numeric indicators like chapter numbers or page numbers, label it as "목차".
2. If the text includes continuous, long sentences with descriptions, examples, or in-depth explanations, label it as "본문".
4. If the text contains a mix of both, prioritize the second rule (numeric indicators).
Here is the text:
"{context}"
Please return only "목차" or "본문" without any extra explanation.
"""
)
# ChatOpenAI 모델의 또 다른 인스턴스를 생성합니다. (이전 인스턴스와 동일한 설정)
llm = ChatOpenAI(model_name="gpt-4o-mini", temperature=0,)
# llm = ChatOllama(model="llama3.1:70b", temperature=0.3)
# 문서 요약을 위한 체인을 생성합니다.
# 이 체인은 여러 문서를 입력받아 하나의 요약된 텍스트로 결합합니다.
text_summary_chain = create_stuff_documents_chain(llm, prompt)
last_page = None # 마지막 목차 페이지를 추적할 변수
def create_text_summary(state: GraphState):
"""
주어진 상태에서 텍스트 데이터를 추출하고 요약을 생성하는 함수.
마지막 목차 페이지 이전의 모든 항목을 '목차:재설정'으로 재분류.
Args:
- state (GraphState): 텍스트 데이터가 포함된 상태 객체.
Returns:
- GraphState: 요약된 텍스트가 포함된 새로운 상태 객체.
"""
# state에서 텍스트 데이터를 가져옴
texts = state["texts"]
# 전체 텍스트 중 1/15 크기로 선택 (너무 많은 데이터를 한꺼번에 처리하지 않도록 설정)
selection_size = round(len(texts) / 15)
selected_texts = dict(list(texts.items())[:selection_size])
# 요약된 텍스트를 저장할 딕셔너리 초기화
text_summary = {}
# 선택된 텍스트를 페이지 번호(키) 기준으로 정렬
sorted_texts = sorted(selected_texts.items(), key=lambda x: x[0])
# 각 페이지의 텍스트를 Document 객체로 변환하고 요약 요청을 만듦
inputs = [
{"context": [Document(page_content=text)]} for page_num, text in sorted_texts
]
# text_summary_chain을 사용하여 일괄 처리로 요약을 생성 (batch 처리)
summaries = text_summary_chain.batch(inputs)
# 생성된 요약을 페이지 번호와 함께 딕셔너리에 저장
for (page_num, _), summary in zip(sorted_texts, summaries):
text_summary[page_num + 1] = summary
# 마지막 목차 페이지를 찾아서 추적
last_page = max((i for i, value in text_summary.items() if value == '목차'), default=None)
# 마지막 목차 페이지 이전의 모든 항목을 '목차:재설정'으로 변경
if last_page is not None:
for i in text_summary:
if i > last_page:
break
text_summary[i] = '목차:재설정'
# 요약된 텍스트를 포함한 새로운 GraphState 객체 반환
return GraphState(text_summary=text_summary)
# create_text_summary 함수를 호출하여 텍스트 요약을 생성합니다.
state_out = create_text_summary(loaded_state)
# 생성된 요약을 기존 state에 업데이트
state_out['text_summary']
'오류 해결 과정' 카테고리의 다른 글
리랭커(Reranker) 사용시 여러 문서 한번에 재정렬 시키기 (0) | 2024.10.18 |
---|---|
BM25를 추가한 리트리버(retriver)로 성능 올리기 (4) | 2024.10.13 |
faiss.write_index 한글 제목 저장 안됨 문제 (0) | 2024.09.21 |
캐시 생성 에러 (module 'win32com.gen_py.7D2B6F3C-1D95-4E0C-BF5A-5EE564186FBCx0x1x0' has no attribute 'CLSIDToClassMap') (0) | 2024.08.22 |
LLM 프로젝트에서 저장되는 로그의 용량이 너무 큰 문제 (0) | 2024.08.21 |