**1단계:**
(1) AI 뉴스 크롤링 및 DB 구축
- AI기사크롤링_AI타임스_1-850페이지.ipynb
- AI_TIMES_크롤링파일_1-850페이지.egg
(2) AI 뉴스 정보 제공 및 요약 RAG
- 직접 해보기 **(챌린지반 과제 - 다음주 화요일 전까지)**
- 뉴스 **content를 LLM으로 요약해서 임베딩 후 벡터 저장 (1)**
- 뉴스 content자체를 임베딩 후 벡터저장 (2)
- (1), (2) RAG 성능 비교
(3) streamlit 챗봇 UI (특강때 제공, 코드 인터넷)
→ 금요일 이후에 진행 **(가능하신 분만 다음 주 화요일까지 같이)**
→ input()입력, print()출력 → streamlit 연동
AI 뉴스 크롤링은 이미 파일로 주어진 상태
뉴스 content 자체를 임베딩하는 것과 LLM으로 요약해서 임베딩하는 것을 구분해야 함
- 'LLM으로 요약 후 임베딩'의 사용 여부를 사용자가 선택하고, 뉴스 자료를 업로드
- 사용자의 선택에 따라 임베딩 방식을 바꿔서 진행
- 대화 중간에 사용자가 임베딩 방식을 바꿀 경우, 기존 대화 내역을 삭제하고 문서를 다시 임베딩
streamlit으로 UI 제작
with st.sidebar:
# 문서 요약 여부
summarize = st.radio(
"Summarize content for embedding?",
["No", "Yes"]
)
file = st.file_uploader("Upload a .json file", type=["json"])
summarize
값에 따라 다른 임베딩 방식 적용summarize
값이 변경되면 기존 대화 내역 삭제# 파일이 업로드 되었을 때
if file:
# 1. summarize == 'Yes'
# - 업로드한 문서를 LLM을 통해 요약한 후 임베딩
# - summarize 값이 이전 상태와 다른 경우, 기존 대화 내역을 삭제
# - 문서를 요약해서 임베딩한 파일을 retriever로 불러와 사용
# 2. summarize == 'No'
# - 업로드한 문서를 그대로 임베딩
# - summarize 값이 이전 상태와 다른 경우, 기존 대화 내역을 삭제
# - 문서를 그대로 임베딩한 파일을 retriever로 불러와 사용
# 3. 임베딩이 완료되어야 메시지 창 활성화
if summarize == "Yes":
if summarize != st.session_state.previous_radio_value:
if "messages" in st.session_state:
st.session_state["messages"] = []
st.session_state.previous_radio_value = summarize
retriever = embed_file(file, summarize=True)
else:
if summarize != st.session_state.previous_radio_value:
if "messages" in st.session_state:
st.session_state["messages"] = []
st.session_state.previous_radio_value = summarize
retriever = embed_file(file, summarize=False)
summarize
값에 맞게 파일 임베딩# 같은 file에 대해 embed_file()을 실행했었다면 cache에서 결과를 바로 반환하는 decorator
@st.cache_resource(show_spinner="Embedding file...")
def embed_file(file, summarize=False):
file_content = file.read() # 파일의 내용을 읽어오기
file_path = f"./.cache/files/{file.name}" # 저장될 파일의 경로
with open(file_path, "wb") as f:
f.write(file_content) # 선택한 파일의 내용을 .cache/files 디렉토리로 옮김
splitter = CharacterTextSplitter.from_tiktoken_encoder(
separator="\n",
chunk_size=1000,
chunk_overlap=100,
)
loader = JSONLoader(
file_path=file_path,
jq_schema=".[:5] | .[].content", # 5개의 기사만 가져오기
text_content=True,
)
data = loader.load()
# 요약하기 버튼을 선택한 경우
if summarize:
# 요약한 문서의 임베딩 결과를 저장할 디렉토리 만들기
cache_dir_path = f"./.cache/summarized_embeddings/{file.name}"
os.makedirs(cache_dir_path, exist_ok=True) # 디렉토리가 없으면 만들기
cache_dir = LocalFileStore(cache_dir_path)
docs = splitter.split_documents(data) # splitter에 맞게 문서 분할
# 문서 요약
docs = summarize_documents(docs, llm_silent)
embeddings = OpenAIEmbeddings()
# 중복 요청 시 캐시된 결과를 반환
cached_embeddings = CacheBackedEmbeddings.from_bytes_store(
embeddings, cache_dir
)
# 요약하기 버튼을 선택하지 않은 경우
else:
# 문서의 임베딩 결과를 저장할 디렉토리 만들기
cache_dir_path = f"./.cache/embeddings/{file.name}"
os.makedirs(cache_dir_path, exist_ok=True) # 디렉토리가 없으면 만들기
cache_dir = LocalFileStore(cache_dir_path)
docs = splitter.split_documents(data) # splitter에 맞게 문서 분할
embeddings = OpenAIEmbeddings()
# 중복 요청 시 캐시된 결과를 반환
cached_embeddings = CacheBackedEmbeddings.from_bytes_store(
embeddings, cache_dir
)
# FAISS 라이브러리로 캐시에서 임베딩 벡터 검색
vectorstore = FAISS.from_documents(docs, cached_embeddings)
# docs를 불러오는 역할
retriever = vectorstore.as_retriever()
return retriever
# 임베딩 완료 신호
send_message("준비됐습니다! 질문해주세요!","ai", save=False)
paint_history()
message = st.chat_input("Ask anything about your file...")
if message:
send_message(message, "human")
# chain_type = stuff
chain = {
"context": retriever | RunnableLambda(format_docs),
"question": RunnablePassthrough()
} | prompt | llm
with st.chat_message("ai"):
chain.invoke(message)