Science Score: 31.0%

This score indicates how likely this project is to be science-related based on various indicators:

  • CITATION.cff file
    Found CITATION.cff file
  • codemeta.json file
    Found codemeta.json file
  • .zenodo.json file
  • DOI references
  • Academic publication links
  • Academic email domains
  • Institutional organization owner
  • JOSS paper metadata
  • Scientific vocabulary similarity
    Low similarity (1.5%) to scientific vocabulary
Last synced: 10 months ago · JSON representation ·

Repository

Basic Info
  • Host: GitHub
  • Owner: Habonit
  • Language: Jupyter Notebook
  • Default Branch: main
  • Size: 108 MB
Statistics
  • Stars: 0
  • Watchers: 1
  • Forks: 0
  • Open Issues: 0
  • Releases: 0
Created over 1 year ago · Last pushed over 1 year ago
Metadata Files
Readme Citation

Readme.md

Sparta GPT Portfolio

개요

  • Sparta GPT Portfolio는 스트림릿(Streamlit)을 기반으로 챗봇 서비스를 구현하는 프로젝트입니다. 향후 AWS를 통해 배포까지 진행할 예정입니다.

현재 구현 상황

1. ImageGPT ✅ (완료)

이미지를 업로드하여 이를 기반으로 대화할 수 있는 챗봇 서비스입니다. - 소스 코드 위치 - home.py - pages/ImageGPT.py - 설명 문서: ImageGPT.md

2. CitationLinkerGPT ⚙️ (개발 중)

참고문헌의 내용을 바탕으로 논문을 요약하고 유사도 점수를 산출하며, 이를 기반으로 챗봇 서비스를 구현하는 기능을 개발 중입니다. - 소스 코드 위치 - citationlinker.py - prompt.py - 설명 문서: CitationLinkerGPT.md


프로젝트 목표

  1. 스트림릿을 활용한 직관적인 챗봇 서비스 구축
  2. 다양한 AI 모델을 연동하여 GPT 기반 챗봇 기능 확장
  3. AWS를 이용한 배포 및 서비스 운영 실험
  4. 연구 및 논문 분석을 위한 AI 기반 챗봇 개발 (CitationLinkerGPT)

프로젝트 실행

```bash streamlit run home.py

```

파일 구조

```bash

SpartaGPTPortfolio/ │── home.py # 메인 실행 파일 │── README.md # 프로젝트 설명 문서 │ ├── pages/ │ ├── ImageGPT.py # ImageGPT 관련 코드 │ ├── CitationGPT.py # CitationLinkerGPT 관련 코드 │ ├── citationlinker.py # CitationLinkerGPT의 핵심 로직 ├── prompt.py # GPT 챗봇 프롬프트 관련 코드 │ ├── week6basicImageGpt.mp4 # ImageGPT 구동 영상 │ ├── docs/ │ ├── ImageGPT.md # ImageGPT 설명 문서 │ ├── CitationLinkerGPT.md # CitationLinkerGPT 설명 문서 │ ├── image/ # md에서 사용된 이미지 저장소소 │ ├── citationimage.png │ ├── reference/ # citationlinker.py에서 추출한 논문들이 저장되는 공간 ├── resultl/ │ ├── basicsummary.json # citationlinker.py의 논문에 대한 기본 요약 │ ├── referencecount.json # citationlinker.py의 citationscore를 내기 위한 자료 │ ├── reference_qna.json # citationlinker.py의 참고문헌에 근거한 요약 ```

Owner

  • Login: Habonit
  • Kind: user

Citation (citationlinker.py)

import json
import arxiv
import requests
from pathlib import Path
import os
import re
from dotenv import load_dotenv
from copy import deepcopy
from tqdm import tqdm

from langchain_community.document_loaders import UnstructuredPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.schema import Document

from openai import OpenAI
import openai
from prompt import * 

load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
client = OpenAI()

class CitationLinker():
    def __init__ (self, config):
        self.target_id = config['arxiv_id']
        self.preprocess_threhsold = config['preprocess_threhsold']
        self.reference_ratio = config['reference_ratio']
        self.reference_condition = config['reference_condition']
        self.model = config['model']

        self.essay_dir = Path(config['essay_dir'])
        self.result_dir = Path(config['result_dir'])

        self.title = None
        self.authors = None
        self.submitted = None
        self.abstract = None
        self.pdf_url = None

        self.basic_keys = ["Title", "Authors", "Submitted" ,"Abstract"]
        self.references = ['References']
        
        self.content_config = config['content_keys']
        self.content_keys = [value_dict["name"] for _, value_dict in self.content_config.items()]
        
    @staticmethod
    def _create_directory_if_not_exists(directory_path):
        if directory_path.split("/")[-1] == "target-id":
            pass

        elif not os.path.exists(directory_path):
            os.makedirs(directory_path)
            print(f"디렉토리 '{directory_path}'가 생성되었습니다.")
        else:
            raise FileExistsError(f"에러: '{directory_path}' 디렉토리가 이미 존재합니다.")

    def _search_arxiv_pdf(self, arxiv_id):
        search = arxiv.Search(id_list=[arxiv_id])
        for result in search.results():
            break
        print(f"📌 Title: {result.title}")
        print(f"📝 Authors: {', '.join([author.name for author in result.authors])}")
        print(f"📅 Submitted: {result.published}")
        print(f"🔗 PDF Link: {result.pdf_url}")
        print(f"📝 Abstract:\n{result.summary}")
        self.title = result.title
        self.authors = ', '.join([author.name for author in result.authors])
        self.submitted = str(result.published)
        self.abstract = result.summary
        self.pdf_url = result.pdf_url

    def _download_arxiv_pdf(self, pdf_url, save_path):
        response = requests.get(pdf_url, stream=True)
        if response.status_code == 200:
            with open(save_path, "wb") as file:
                for chunk in response.iter_content(chunk_size=1024):
                    file.write(chunk)
        print("논문 저장 완료!")

    def _fetch_arxiv_paper(self, title, max_results=30):
        
        search = arxiv.Search(
            query=title,
            max_results=max_results, 
            sort_by=arxiv.SortCriterion.Relevance
        )

        for result in search.results():
            if title[10:-10].lower().replace(" ", "") in result.title.lower().replace(" ", ""):
                return ( {
                    "title": result.title,
                    "abstract": result.summary,
                    "pdf_url": result.pdf_url
                })
            
        return None 
    
    @staticmethod
    def _message_to_openai(message, model):
        response = client.chat.completions.create(
            model=model,
            store=True,
            messages=[{"role": "user", "content": message}],
            temperature=0.5
        )
        return response
    
    def _search_and_download_essay(self, arxiv_id):
        self._search_arxiv_pdf(arxiv_id=arxiv_id)
        self._download_arxiv_pdf(
            pdf_url=self.pdf_url, 
            save_path=self.essay_dir / f"0-{self.title}.pdf"
        )
    def _preprocess(self, save_path):
        # 데이터를 불러와 섹션 별로 나눕니다.
        loader = UnstructuredPDFLoader(save_path)
        documents = loader.load()
        processed_output = {}
        for key, value_dict in self.content_config.items():
            if value_dict['name'] == "Title":
                processed_output[value_dict['name']]=self.title
            elif value_dict['name'] == "Authors":
                processed_output[value_dict['name']]=self.authors
            elif value_dict['name'] == "Submitted":
                processed_output[value_dict['name']]=self.submitted
            elif value_dict['name'] == "Abstract":    
                processed_output[value_dict['name']]=self.abstract
            else:
                processed_output[value_dict['name']]=documents[0].page_content.split(value_dict['deliminators']['forward'])[-1].split(value_dict['deliminators']['backward'])[0]

        # basic_key가 아닌 섹션 중 threshold 미만으로 잘리면 모두 없앱니다.
        threshold = self.preprocess_threhsold
        for key in self.content_keys:
            if key not in self.basic_keys:
                result = []
                for text in processed_output[key].split("\n"):
                    if len(text) >= threshold:
                        result.append(text)
                processed_output[key] = "\n".join(result)
        
        # 2000자 단위로 모두 자릅니다.
        text_splitter = RecursiveCharacterTextSplitter(
                chunk_size=2000, 
                chunk_overlap=0  
            )
        
        for key in self.content_keys:
            documents = text_splitter.create_documents([processed_output[key]])
            for doc in documents:
                doc.metadata = {"Title": self.title, "Key": key} 
            processed_output[key] = documents

        return processed_output

    def _basic_summarize(self, basic_summarize_template, processed_output):
        # 기본 요약
        essay = ""
        for key in self.content_keys:
            if key not in self.references:
                for doc in processed_output[key]:
                    essay += doc.page_content + "\n\n"
        basic_summarize_message = basic_summarize_template.format(essay=essay)
        response = CitationLinker._message_to_openai(message=basic_summarize_message, model=self.model)
        return response.choices[0].message.content
    
    def _reference_preprocess(self, reference_extraction_template, processed_output):
        reference_extraction_message = reference_extraction_template.format(references=processed_output['References'])
        flag = True
        while flag:
            response = CitationLinker._message_to_openai(message=reference_extraction_message, model=self.model)
            text = response.choices[0].message.content
            text = re.sub("```json","",text)
            text = re.sub("```","",text)
            json_data = json.loads(text)
            flag = False
            
        reference_dict = {}
        for key, dict_data in json_data.items():
            dict_data['Counter'] = 0
            dict_data['Context'] = []
            reference_dict[key] = dict_data
        processed_output['References'] = deepcopy(reference_dict)
        return processed_output, reference_dict
    
    def _reference_counting(self, reference_count_template_dict, processed_output, reference_dict):
        for index in range(len(reference_count_template_dict)):
            result = []
            for key in self.content_keys:
                if key not in self.basic_keys + self.references:
                    for essay in processed_output[key]:
                        reference_count_message = reference_count_template_dict[str(index)].format(references=reference_dict, essay=essay, condition=self.reference_condition)
                        response = CitationLinker._message_to_openai(reference_count_message, model=self.model)
                        try:
                            text = response.choices[0].message.content
                            text = re.sub("```json","",text)
                            text = re.sub("```","",text)
                            text_data = json.loads(text)
                            result.append(text_data)
                        except: 
                            text_data = None
                        # items['References'] = text_data

            for data in result:
                for key, value_dict in data.items():
                    try:
                        processed_output["References"][key]['Counter'] += value_dict['Counter']
                        processed_output["References"][key]['Context'].extend(value_dict['Context'])
                    except:
                        pass
        return processed_output
    
    def _download_reference(self, processed_output, nums = 5):
        # counter 순으로 정렬을 한다
        # title을 던진다
        # 개수가 맞으면 멈춘다
        related_reference = processed_output['References']
        related_reference = dict(sorted(related_reference.items(), key=lambda item: item[1]["Counter"], reverse=True))
        processed_output['References'] = related_reference
        ordered_titles = list(processed_output['References'].items())
        downloads_lst = []

        for index, valud_dict in ordered_titles:
            title = valud_dict['Title']
            try :
                paper_info = self._fetch_arxiv_paper(title)
                if paper_info is None:
                    paper_info = self._fetch_arxiv_paper(title, 150)
            except Exception as e:
                print(index,"번째 논문 예외 발생: ", e)
                try :
                    paper_info = self._fetch_arxiv_paper(title, None)
                except:
                    paper_info = None

            if paper_info is not None:
                pdf_url = paper_info['pdf_url']
                abstract = paper_info['abstract']
                processed_output['References'][index]['abstract'] = abstract
                processed_output['References'][index]['pdf_url'] = pdf_url
                save_path = self.essay_dir / (index+ "-" + paper_info['title']+".pdf")
                self._download_arxiv_pdf(pdf_url, save_path)
                print(index,"번째 논문 다운로드 완료")

                downloads_lst.append((index, valud_dict))
            
            else:
                pdf_url = None
                abstract = None
                processed_output['References'][index]['abstract'] = abstract
                processed_output['References'][index]['pdf_url'] = pdf_url
                print(index,"번째 논문 다운로드 실패")
                print(f"    {processed_output['References'][index]['Title']}")
            
            if len(downloads_lst) == nums:
                break
            
        # 논문 다운로드 후, 질문 축소
        # 이렇게 하는 이유는 동일 질문을 여러개 갖고 있을 수 있기 때문입니다.
        
        related_reference = dict(downloads_lst)
        total_related_reference = processed_output['References']
        return related_reference, total_related_reference, processed_output
    
    def _reduce_questions(self, question_reduction_template, related_reference):
        for index in related_reference.keys():
            query_list = related_reference[index]['Context']
            user_message = question_reduction_template.format(text_list=query_list)
            response = CitationLinker._message_to_openai(user_message, model=self.model)
            related_reference[index]['Questions'] = response.choices[0].message.content
        return related_reference
    
    def _find_connection_from_reference(self, reference_qna_template, related_reference):

        for index in tqdm(related_reference.keys(), desc="인용 논문과의 관련 지점 정리..."):
            title = related_reference[index]['Title']
            questions = related_reference[index]['Questions']
        
            for path in self.essay_dir.rglob("*.pdf"):
                if path.name.split("-")[0] == index:
                    break
        
            loader = UnstructuredPDFLoader(path)
            documents = loader.load()
            essay = documents[0].page_content
            essay = "\n".join([text for text in essay.split("\n") if len(text) >= self.preprocess_threhsold])
            reference_qna_message = reference_qna_template.format(essay = essay, questions=questions, title=title)
            response = CitationLinker._message_to_openai(reference_qna_message, model=self.model)
            summary = response.choices[0].message.content
            related_reference[index]['Summary'] = summary
            
        return related_reference
    
    def forward(self):

        # 논문 id를 받아서 논문을 다운 받습니다.
        # 추후에 논문 pdf를 drag and drop 방식으로 바꿀 수도 있습니다
        self._search_and_download_essay(
            arxiv_id=self.target_id,
        )

        # 텍스트 전처리
        processed_output = self._preprocess(
            save_path=self.essay_dir / f"0-{self.title}.pdf"
        )
        print("step 1: ", "\n",
            processed_output)

        # 기본 요약
        response=self._basic_summarize(
            basic_summarize_template=basic_summarize_template,
            processed_output=processed_output
        )
        print("step 2: ", "\n",
            response, "\n")

        # 기본 요약된 정보 저장
        with open(self.result_dir/"basic_summary.json", 'w', encoding="utf-8") as f:
            json.dump(response, f, ensure_ascii=False, indent=4)

        # 참고문헌 목록화
        processed_output, reference_dict = self._reference_preprocess(
            reference_extraction_template=reference_extraction_template,
            processed_output=processed_output
        )
        print("step 3: ", "\n", 
            processed_output, "\n",
            reference_dict, "\n" 
        )
        # 인용횟수 counting
        # reference_count_template_dict / processed_output / reference_dict
        processed_output = self._reference_counting(
            reference_count_template_dict=reference_count_template_dict, 
            processed_output=processed_output, 
            reference_dict=reference_dict
            )
        print("step 4: ", "\n",
            processed_output, "\n")
        # reference 논문 다운 받아오기
        # 전략 처음엔 30개 중에 다운을 받습니다.
        # 그 다음 150개를 받습니다. 
        # 그 다음 default 값으로 받습니다.
        # 그럼에도 없으면 None으로 채워넣습니다. 
        related_reference, total_related_reference, processed_output = self._download_reference(processed_output=processed_output)
        print("step 5: ", "\n",
            related_reference, "\n", 
            total_related_reference, "\n",
            processed_output, "\n",
        )

        with open(self.result_dir/"reference_count.json", 'w', encoding="utf-8") as f:
            json.dump(total_related_reference, f, ensure_ascii=False, indent=4)

        # # 논문 다운로드 후, 질문 축소
        # # 이렇게 하는 이유는 동일 질문을 여러개 갖고 있을 수 있기 때문입니다.
        related_reference = self._reduce_questions(
            question_reduction_template=question_reduction_template,
            related_reference=related_reference
        )
        print("step 6: ", "\n",
            related_reference, "\n", 
        )
        # reference와의 접점을 찾기 위한 요약
        # 인용 논문과 원래 논문의 접점을 찾고 정리합니다.
        # 원래 논문이 어떻게 연구를 발전시키는지까지 정리합니다. 
        related_reference=self._find_connection_from_reference(
            reference_qna_template=reference_qna_template,
            # research_progress_template=research_progress_template,
            # processed_output=processed_output,
            related_reference=related_reference
        )
        print("step 7: ", "\n",
            related_reference, "\n", 
        )
        with open(self.result_dir/"reference_qna.json", 'w', encoding="utf-8") as f:
            json.dump(related_reference, f, ensure_ascii=False, indent=4)

if __name__ == "__main__":

    with open("archive/config.json",'r') as f:
        config = json.load(f)

    citation_linker = CitationLinker(config)
    citation_linker.forward()

GitHub Events

Total
  • Push event: 13
  • Create event: 2
Last Year
  • Push event: 13
  • Create event: 2