ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [챗봇] PDF QA 챗봇 개발하기 (1)
    카테고리 없음 2023. 4. 17. 00:28

    목적

    PDF 문서 내용을 기반으로 질의응답(QA)를 할 수 있는 인트라넷에서 사용가능한 챗봇 개발

     

    준비물

    python

    langchain

    openai api key

     

    과정

    전체적인 플로우

    1. PDF 에서 텍스트 추출하기

    langchain에서 제공하는 pdf loader를 이용해 pdf에서 text를 추출한다.

    langchain에서는 다양한 방법을 제공하므로 각자 상황에 맞는 방법을 사용하도록 한다.

    (현재 글쓴이도 적절한 방법을 모색하고 있다.)

    from langchain.document_loaders import UnstructuredPDFLoader
    from langchain.text_splitter import RecursiveCharacterTextSplitter
    
    loader = UnstructuredPDFLoader('../약관.pdf')
    data = loader.load()
    
    print(f"{len(data)}개의 문서를 가지고 있습니다.")
    print(f"문서에 {len(data[0].page_content)}개의 단어를 가지고 있습니다.")
    1개의 문서를 가지고 있습니다.
    문서에 281012개의 단어를 가지고 있습니다.

     


    2. 텍스트를 chunk로 나누기

    embedding을 만들기 위해선 embedding이 모델이 소화할 수 있는 양만큼의 입력만 넣어줘야 한다. 그러기에 281012개의 단어를 가진 값을 통채로 넣어줄 수 없으므로 더 작은 단위로 묶어서 넣어주자.

    text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
    texts = text_splitter.split_documents(data)
    print(f"문서에 {len(texts)}개의 문서를 가지고 있습니다.")
    문서에 286개의 문서를 가지고 있습니다.

    이 때 1000개씩 쪼겠기에 282개의 texts가 생성 될 것이라 기대되었으나 실제로는 286개의 texts가 생성되었다. 

    실제로 texts의 원소들의 길이를 출력해보면 

    983, 991, 952, 988, 979, 959, 944, 972...

    와 같은데 그 이유는 RecursiveCharacterTextSplitter는 의미적으로 가장 연관성이 강한 텍스트 조각으로 보이는 문장, 단어를 가장 길게 유지하고자 하기 떄문이다.

     

    3. Vector Store 만들기

    이제 texts를 embedding으로 만들어 store를 구성하도록 하자.

    vector store를 만드는 라이브러리는 다양하나(pinecone 등)  여기선 Facebook에서 개발한 faiss를 사용한다.

    이때 embedding 모델은 openai의 text-embedding-ada-002(default)를 사용한다.

    from langchain.embeddings import OpenAIEmbeddings
    from langchain.vectorstores import FAISS
    import faiss
    
    print(f'예상되는 토큰 수 {num_tokens_from_string(data[0].page_content, "cl100k_base")}')
    
    # Create the vector store
    store = FAISS.from_texts([text.page_content for text in texts], OpenAIEmbeddings(openai_api_key=openai_api_key))
    예상되는 토큰 수 258751

    openai api를 태우기전 예상 토큰 수를 계산해보고, 실제 태운 후 토큰 수를 확인해본다.

    오전 7:30에서 볼 수 있는 실제 발생 토큰
    발생 비용

    139개의 토큰 수가 차이 있음을 확인 할 수 있다.

     

    import pickle
    faiss.write_index(store.index, "docs.index")
    with open("faiss_store.pkl", "wb") as f:
        pickle.dump(store, f)

     

    이후 만들어진 값을 로컬 디스크에 저장하도록 한다.

     

    4. 사용자 입력 받아 결과 생성하기

    query = "보상하지 않는 손해에 대해 알려줘"
    docs = store.similarity_search(query, k = 4)

     

    위와 같이 보장하지 않는 손해에 대해 질문한다는 가정하에, 기 만든 vector store에서 가장 유사한 문서 4개를 추출해온다.

    (docs를 출력하면 문서 내용이 출력되나 그 내용 중 일부만 공개한다.)

    [Document(page_content='위  ‘②  ◯1 에도  불구하고  다음의  손해는  보상하지  않습니다\n\n’\n\n.\n\n1)\n\n2)\n\n3)\n\n4)\n\n양도된  피보험자동차가  양수인  명의로  ...', metadata={}),
     Document(page_content=')\n\n제 조 보상하지  않는  손해\n\n①\n\n8\n\n다음  중  어느  하나에  해당하는  손해는  대인배상 와  대물배상에서  보상하지  않습니다....', metadata={}),
     Document(page_content='도로교통법 에서  정한  사고발생  시의  조치를  하지  않은  경우를  말합니다 다만 주 정차된\n\n차만  손괴한  것이  분명한  경우에  피해자에게  인적사항을  제공하지  아니한  경우는', metadata={}),
     Document(page_content='손해배상보장법령에서  정한  금액을  한도...', metadata={})]

     

    위 처럼 결과가 출력됨을 확인하였으므로 langchain의 load_qa_chain을 통해 문서와 query를 기반으로 GPT로 부터 결과를 얻어온다.

    from langchain.chains.question_answering import load_qa_chain
    from langchain import OpenAI
    chain = load_qa_chain(OpenAI(model_name='gpt-3.5-turbo', temperature=0, max_tokens=100, openai_api_key=openai_api_key), chain_type="map_reduce")
    result_qa_chain = []
    for doc in docs:
        result_qa_chain.append(chain.run(input_documents=[doc], question=query))
    result_qa_chain

    제대로 출력이 안됨을 볼 수 있다. 여기서 기 정의된 프롬프트가 포함되어 이는 load_qa_chain을 써서 그런 것으로 판단된다.

    ["I'm sorry, I cannot provide a final answer as the given content is not in a language that I am programmed to understand.",
     '대인배상 와 대물배상에서는 보상하지 않는 손해로, 보험계약자나 기명피보험자의 고의로 인한 손해와 기명피보험자 이외의 피보험자의 고의로 인한 손해가 있다.',
     'There is no information provided about damages that are not covered by insurance.',
     "I'm sorry, I cannot provide a final answer as the given content is in Korean and I am not programmed to translate languages."]

     

    앞으로 고심할 일은 다음과 같다.

    위 문제를 하나하나 해결해나가며 글을 작성하도록 하겠다.

    특히 인트라넷에서 사용 가능해야하므로 openai의 의존성은 완전히 없애서 완성도를 높여야 할 것이다. 또한, 비상업용 모델은 사용할 수 없으므로 상업적으로 사용가능한 dolly 로 대체할 것이다.

     

     

    참고

    https://www.youtube.com/watch?v=2xxziIWmaSA 

    https://www.youtube.com/watch?v=h0DHDp1FbmQ 

    https://www.youtube.com/watch?v=ih9PBGVVOO4 

    댓글

Designed by Tistory.