티스토리 뷰
7차 미니프로젝트의 첫번째 과목
아나콘다 가상 환경을 설정하고, 해당 가상 환경에서 실행한다.
각 프로젝트에서 ipnyb 파일을 커널 선택하여 해당 가상 환경을 실행한다.
OpenAI API 키를 환경 변수에 저장해서 사용한다.
이래야 env에서 오기 때문에 키가 노출되지 않는다.
OpenAI API
손님이 점원에게 주문을 하면, 점원은 요리사에게 주문을 전달하고, 요리사는 요리를 만들어서 점원에게 전달해준다.
점원이 바로 API이다.
API는 클라이언트에게 요청을 받아서 서버에서의 요청을 처리한 결과 데이터를 클라이언트로 전달해준다.
Request = API 주소 + API Key / 형식은 해당하는 요청 양식
Response : 결과에 대한 양식값
LangChain
LangChain은 대규모 언어 모델 LLMs를 활용하여 체인을 구성한다.
이 체인을 통해, 복잡한 작업을 자동화하고 쉽게 수행할 수 있도록 돕는 라이브러리이다.
미니프로젝트 6과의 구현 비교를 하면,
미니프로젝트 6 | LangChain | |
위키 데이터 준비 | 위키 데이터 다운로드 chunk로 쪼개기 |
위키 라이브러리 로딩 Retrever 설정 |
모델 준비 | Search model 다운로드 SLLM 모델, 토크나이저 다운로드 |
OpenAI API로 모델 연결 |
Pipeline 구성 | SLLM 모델과 토크나이저 연결 | |
프롬프트 생성 함수 | 질문과 검색으로 찾아진 chunk들을 포함하는 프롬프트 생성 함수 | 연결하기(Chain) 제공된 RetrevalQA 함수로 모델과 retriever 연결 |
문장 검색함수 | 목록 중에서 질문과 유사도가 높은 chunk들을 검색하는 함수 | |
QA Chatting | 질문 > 문장검색 > 프롬프트 생성 > 파이프라인에 입력 > 답변 받기 | 질문 > 답변 받기 |
LangChain에서는 hugging Face 모델 연결도 지원한다.
하지만 본 프로젝트 에서는 OpenAI API를 사용한다.
Embedding만 test-embedding-ada-002를 사용
Message 타입
SystemMessage : 시스템 역할 부여
HumanMessage : 질문
AIMessage : 답변
RAG (Retrieval Augmented Generation)
나에게 필요한 답변을 해주는 LLM을 만들려면..
맨 땅에서, 나의 데이터를 가지고 직접 학습(모델링)
사전학습 된 LLM에 나의 데이터를 가지고 추가학습(파인 튜닝)
사전학습 된 LLM에, 나의 데이터를 가지고 답변 시킨다(RAG)
LLM모델은 학습하지 않은 내용은 모르지만,
LLM이 RAG와 합쳐지면, 준비된 정보 DB에서 답변에 필요한 문서를 검색하여 프롬프트를 생성해 답변을 생성한다.
Vector DB
대규모 텍스트 데이터 및 임베딩 벡터를 저장하고 검색하는 데 사용한다.
LLM with RAG 절차
1. 사용자 질문을 받고
2. 해당 질문을 임베딩하여 벡터로 변환한다.
3. 준비된 정보 DB에서 답변에 필요한 문서를 검색한다.
4. 질문 벡터와 DB의 문서 벡터와의 유사도 계산 및 가장 유사도가 높은 문서 n개 찾기
5. 필요한 문서를 포함한 프롬프트 생성
6. LLM이 답변 생성
해당 프로젝트에서는 Vector DB로 Chroma DB를 사용한다.
Chroma DB에서는 Cosine Distance = 1 - Cosine Similarity를 사용한다.
0에 가까울수록 유사도가 높다.
-1~1을, 0~2로 바꿔줘서 더 좋은 효율을 만든다.
Vector DB 구축 절차(Chroma DB)
텍스트 추출 : Documnet Loader
다양한 문서로부터 텍스트 추출하기
- 데이터를 Load
텍스트 분할 : Text Splitter
chunk 단위로 분할
Document 객체로 만들기
-데이터를 Split한다.
텍스트 벡터화 : Text Embedding
Vector DB로 저장 : Vector Sotre
- Embed 한다.
Chroma DB는 SQLite3 기반 Vector DB이며, DB Broser for SQLite3로 접속이 가능하다.
- Store한다.
이번 프로젝트에서는 load와 split은 생략하고, csv를 임베딩 벡터화 한다.
따로 공부해야 한다.
Vector DB 생성
임베딩 모델을 지정
Chroma DB 선언
경로 지정(persist_directory)
- 해당 경로에 DB가 있으면 연결
- 없다면 새로 DB 생성
임베딩 모델 지정(embedding_function)
embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")
database = Chroma(persist_directory="./db",
embedding_function = embeddings
알아서 벡터화하고, 알아서 저장해준다.
from langchain.vectorstores import Chroma
.add_texts()로 텍스트를 리스트 형태로 입력하고,
텍스트와 메타정보를 함께하려면 .add_documents()를 활용한다.
langchain의 document를 이용하여 documnet 형태로 집어넣는다.
ducumnets는 dict 형태로만 넣으면 된다.
데이터를 조회하려면 .get을 쓰면 된다.
데이터 베이스 조건 조회는 지원하지 않아서,
일단 pd.DataFrame으로 바꿔서 조회한다.
data.loc[data['metadata']=={'category':'test'}]와 같이 조회한다.
유사도 검색 조회
.similarity_search로 하면 된다.
질문에 해당하는 상위 n개를 출력해준다.
.similarity_search_with_score = 이것도 똑같은데, 값을 표시해준다.
코사인 거리로 출력된다.
삭제
Delete는 .delete 하고 ids에 리스트만 주면 된다.
실전에서는 데이터프레임을 읽어서 통째로 넣는다.
데이터화
데이터는 DB 내용으로 사용할 것들을 모아둔다. 종류별 구분은 메타데이터로 한다.
내용을 리스트로 바꿔서 집어넣고, Document 오브젝트로 만들어서 만들어낸다.
Memory
대화의 맥락을 이어가려면
사람 : 이전 대화를 기억하면서 현재 대화를 진행한다.
챗봇 : 이전 대화를 기억 - 이전 질문 답변을 Memory에 저장하고, 이를 Prompt에 포함시킨다.
이제 Chain하기
ConverstionalRetievalChain 함수
qa = ConversationalRetrievalChain.from_llm(llm=chat, reriever=retriever, memory=memory,
return_source_documents= , output_key="answer")
return_source_documents=True
True로 설정되어 있으면, 답변과 함께 출처 문서도 반환된다. - 참조 문서들까지 반환
Output_key
Output_key 파라미터는 모델의 출력이 저장될 키를 지정해준다.
memory는 Chain 함수를 위한 메모리 설정
ConversationBufferMemory로 만든다.
유사도 점수는 반환하지 않는다.
그렇기에 위에서 썼던 함수를 쓴다.
또한 유사도 높은 프롬프트를 구성하려면 retriever에 지정해주면 된다.
page_content는 chunk 단위이다.
질문 답변 하나 세트로 page_content에 집어넣으면 된다.
해당 단위를 어떻게 적절하게 나눠서 vectorDB를 만들것인가가 QA 챗봇의 핵심이다.
유사도가 매우 낮다면 아예 검색 결과가 없다는 메세지를 띄우는 게 나을 수 있다.
SQL on Python
import sqlite3
가벼운 db를 만들 때 사용한다.
무거운 건 mysql과 오라클 등을 사용
path = './db_chatlog/db_chatlog.db'
conn = sqlite3.connect(path)
폴더는 미리 생성하고, db는 만들어야 한다.
그 후에는 해당하는 db에 연결하고, cursor를 이용해서 객체를 생성한다.
# 커서 객체 생성
cursor = conn.cursor()
# test 테이블 생성
cursor.execute('''
CREATE TABLE IF NOT EXISTS test (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
age INTEGER NOT NULL
)
''')
# 변경사항 커밋 (저장)
conn.commit()
# 연결 종료
conn.close()
커서는 명령문을 실행하기 위한 준비작업이다.
커서 안에 명령문을 넣는다.
test라는 Table이 없다면, id,name,age를 만들어서 커밋한다.
그리고 연결을 종료한다.
Insert
데이터프레임을 sql에 넣기 위해서는 2가지 방법이 있다.
하나는 위에서처럼 연결-커서 선언-작업-커밋-연결 종료를 하거나,
아니면 pandas를 이용하는 방법이 있다.
# ① 연결
conn = sqlite3.connect(path)
# ② 작업 : to_sql
data = pd.DataFrame({'name': ['Alice', 'Bob', 'Charlie', 'David'], 'age': [30, 25, 35, 40]})
data.to_sql('test', conn, if_exists='append', index=False) # test 테이블이 있으면 insert, 없으면 생성
# ③ 연결 종료
conn.close()
판다스에서 데이터 프레임을 create table 하는 방법도 있다.
연결하고, 해당 데이터 프레임을 가지고 test가 있다면, conn으로 연결을 하고(커서를 선언하지 않는다)
만약 존재한다면 append 하고, index는 들어가지 않는다.
없으면 테이블을 만들면서 집어넣는다.
그리고 연결을 종료한다.
Select
# ① 연결
conn = sqlite3.connect(path)
# ② 작업 : select
df = pd.read_sql('SELECT * FROM test', conn)
display(df)
# ③ 연결 종료
conn.close()
연결을 하고, read_sql로 sql을 쓰고 연결을 지정한다.
그러면 전체가 다 출력된다.
만약 mysql로 데이터를 받으려면, df.to_csv로 저장할 수 있다.
데이터베이스에 직접 접속하는 게 허용되지 않는 경우에는 CSV 자체로 데이터베이스에서 꺼내달라고 부탁한다.
Drop
드롭은 커서로만 작업가능하다.
# ① 연결
conn = sqlite3.connect(path)
# ② 커서 선언
cursor = conn.cursor()
# ③ 작업 : 테이블 삭제
cursor.execute('DROP TABLE IF EXISTS test')
# ④ 커밋(수정작업 저장)
conn.commit()
# ⑤ 연결종료
conn.close()