대규모 언어 모델는 AI 업계에서 가장 뜨거운 논쟁이 되고 있는 주제 중 하나입니다. 우리 모두는 OpenAI의 ChatGPT의 가능성과 기능을 잘 알고 있습니다. 결국 이러한 LLM을 활용하면 데이터를 통해 수많은 새로운 가능성을 발견할 수 있습니다.
하지만 설계상의 한계와 기타 여러 가지 요인으로 인해 LLM에서 모든 것을 기대할 수는 없습니다. 하지만 벡터 검색 는 검색 증강 생성(RAG) 애플리케이션이라는 새로운 유형의 앱을 제공합니다. 이제 RAG 애플리케이션이 무엇인지, 새로운 문제를 해결하는 데 어떻게 사용할 수 있는지, Couchbase를 사용하여 어떻게 개발할 수 있는지, 그리고 이러한 애플리케이션을 만드는 데 벡터 검색이 어떻게 도움이 되는지에 대해 자세히 살펴보겠습니다.
애플리케이션의 배경에 대해 알아보기 전에, 다음은 우리가 구축하는 것과 LangChain이 어떻게 연결되는지에 대한 아키텍처 다이어그램입니다:
검색 증강 생성(RAG)
RAG는 기본 모델 자체를 수정하지 않고 타겟팅 정보로 LLM의 출력을 보강하는 방법을 제공하여 타겟팅 정보가 LLM보다 더 최신일 뿐만 아니라 특정 조직 및 산업에 특화될 수 있도록 합니다. 즉 제너레이티브 AI 시스템은 프롬프트에 보다 상황에 적합한 답변을 제공할 수 있을 뿐만 아니라 최신 데이터를 기반으로 답변을 제공할 수 있습니다. 실제 사례를 통해 이 개념을 이해해 보겠습니다.
데이터베이스에 방대한 데이터를 저장하고 있는 X 조직에 속해 있고 사용자 입력을 요청하고 데이터베이스에 있는 데이터를 기반으로 출력을 제공하는 애플리케이션 개발을 담당하고 있다고 가정해 보겠습니다.
처음에는 쉬워 보인다고 생각할 수도 있겠죠? LLM에 대해 알고 있고 필요에 따라 이를 활용하는 방법을 알고 있다면 간단한 작업입니다. 비용 효율을 높이려면 OpenAI LLM 또는 Llama 및 Mistral 모델을 선택하고, 사용자 질문을 LLM에 전송하여 결과를 얻기만 하면 됩니다.
하지만 여기에는 큰 문제가 있습니다...
예를 들어, 다음과 같이 사용한다고 가정해 보겠습니다. 라마 2 LLM 8B 유형입니다.
이제 이 모델은 공개 인터넷에 존재하는 거의 모든 데이터로 학습되었습니다. 어떤 질문이든, 심지어 조직 X에 대한 질문이라도 물어보면 가장 가까운 정답을 알려줍니다.
이제 문제 설명을 약간 변경해 보겠습니다. 데이터베이스에 있는 수많은 데이터는 더 이상 공개 데이터가 아니라 비공개 데이터입니다. 즉, 라마 2는 사용자의 데이터를 인식하지 못하며 더 이상 정답을 제공하지 않습니다.
위의 시나리오를 염두에 두고 사용자 질문인 "조직 X에서 컴포넌트 C의 업데이트는 무엇인가요?"
그렇다면 이 문제를 어떻게 해결할 수 있을까요?
LLM이 데이터를 컨텍스트로 사용하여 질문에 답할 수 있도록 프롬프트와 함께 데이터베이스에 있는 전체 데이터를 전달하면 어떨까 생각할 수 있습니다. 하지만 여기에 큰 문제가 있습니다. 모든 LLM에는 다음과 같은 제약 조건이 있습니다. 토큰 한도. 토큰이 무엇인지 등에 대해 자세히 설명하지 않고 지금은 1토큰 = 1단어라고 생각하세요.
안타깝게도 라마 2의 토큰 크기 제한은 4096토큰(단어)입니다. 데이터베이스에 존재하는 전체 데이터에 1,000만 개의 단어가 있다고 가정하면, 컨텍스트 목적으로 전체 데이터를 전달하는 것이 불가능해집니다.
위 문제에 대한 해결책을 RAG라고 합니다. RAG에서는 데이터베이스에 존재하는 데이터 중 사용자의 쿼리와 매우 밀접한 관련이 있는 데이터의 비율을 선택합니다. 비율 크기는 다음과 같습니다:
비율 크기 < 토큰 한도
이제 추출된 데이터를 쿼리와 함께 컨텍스트로 전달하고 좋은 결과를 얻습니다. 이것이 바로 RAG입니다. 하지만 사용자 쿼리와 밀접한 관련이 있으면서 동시에 크기가 토큰 크기를 초과하지 않는 데이터의 비율을 어떻게 확보할 수 있을까요? 이것은 다음 개념을 사용하여 해결됩니다. 벡터 검색.
벡터 검색이란 무엇인가요?
벡터 검색은 머신 러닝(ML)을 활용하여 텍스트와 이미지를 포함한 비정형 데이터의 의미와 맥락을 파악하고 이를 수치로 변환합니다. 시맨틱 검색에 자주 사용되는 벡터 검색은 근사 근사 이웃(ANN) 알고리즘을 사용해 유사한 데이터를 찾습니다.
Couchbase 버전 7.6.0 이상에는 다음이 함께 제공됩니다. 벡터 검색 기능. 여기서 중요한 것은 외부 라이브러리, 모듈 및 설정이 필요하지 않다는 것입니다. 최소 1 검색 노드가 작업을 수행합니다.
카우치베이스는 내부적으로 FAISS 프레임워크 를 사용하여 벡터 검색을 수행합니다.
RAG 애플리케이션 구축
이제 RAG 애플리케이션을 엔드투엔드로 개발하는 실제 내용을 살펴보겠습니다. 카우치베이스 벡터 검색 기능.
이 워크스루에서는 다음을 개발할 것입니다. PDF로 채팅하기 애플리케이션입니다.
계속 진행하기 전에 앱을 만드는 방법에는 여러 가지가 있습니다. 그 중 한 가지 방법은 RAG 애플리케이션을 개발하는 데 사용할 LangChain 프레임워크를 사용하는 것입니다.
앱 1: LangChain 프레임워크를 사용한 구축
1단계: Couchbase 데이터베이스 설정하기
EC2, 가상 머신, 로컬 머신 등에서 Couchbase 서버를 설정할 수 있습니다.
이 링크를 따라 카우치베이스 클러스터 설정. 이러한 서비스는 반드시 사용하도록 설정해야 하며, 다른 서비스는 선택 사항입니다:
-
- 데이터
- 검색
참고: 다음을 설치해야 합니다. 카우치베이스 서버 버전 7.6.0 이상으로 벡터 검색을 수행합니다. 또한 Mac OS 환경에서 Python을 사용하여 이 애플리케이션을 개발할 것입니다.
클러스터가 실행되면 새 프로젝트를 만듭니다. <project_name> 을 클릭하고 새 파이썬 파일 호출을 만듭니다. app.py.
이제 프로젝트 터미널에서 아래 명령을 실행합니다:
|
1 |
pip 설치 --업그레이드 --조용한 랭체인 랭체인-openai 랭체인-카우치베이스 문장-트랜스포머 |
이제 UI로 이동하여 다음과 같은 이름의 버킷을 만듭니다. 프로젝트. 이 연습에서는 다음과 같은 기본값 범위 및 수집.
-
- 자세히 알아보기 버킷, 범위 및 컬렉션을 문서에 추가합니다..
이제 벡터 임베딩을 생성하는 방법에는 여러 가지가 있습니다. 가장 널리 사용되는 방법은 OpenAI이며, 벡터 임베딩을 생성하는 데 이를 사용할 것입니다.
아래 코드를 복사하여 app.py:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
가져오기 getpass 가져오기 os 에서 랭체인_카우치베이스.벡터 스토어 가져오기 카우치베이스 벡터 스토어 에서 랭체인_오픈아이 가져오기 OpenAIE임베딩 에서 날짜 시간 가져오기 timedelta 에서 카우치베이스.auth 가져오기 비밀번호 인증기 에서 카우치베이스.클러스터 가져오기 클러스터 에서 카우치베이스.옵션 가져오기 클러스터 옵션 os.환경["OPENAI_API_KEY"] = getpass.getpass("OpenAI API 키:") 카우치베이스_연결_스트링 = "couchbase://localhost" DB_USERNAME = "관리자" DB_PASSWORD = "비밀번호" BUCKET_NAME = "프로젝트" SCOPE_NAME = "_default" 컬렉션_이름 = "_default" 검색_인덱스_이름 = "벡터 인덱스" auth = 비밀번호 인증기(DB_USERNAME, DB_PASSWORD) 옵션 = 클러스터 옵션(auth) 클러스터 = 클러스터(카우치베이스_연결_스트링, 옵션) 클러스터.wait_until_ready(timedelta(초=5)) 임베딩 = OpenAIE임베딩() |
가상 머신에서 Couchbase를 호스팅하는 경우 다음 단어로 대체해야 합니다. localhost 를 공인 IP 로 설정합니다.
2단계: 검색 색인 가져오기
카우치베이스의 벡터 검색 기능을 사용하려면 검색 인덱스가 필요합니다. 인덱스를 만드는 방법은 여러 가지가 있지만, 쉽고 빠르게 만들 수 있도록 아래는 인덱스 JSON입니다. 아래 코드를 복사하여 붙여넣으세요:
- UI > 검색 > 색인 추가 (오른쪽 상단) > 가져오기
Index.json
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
{ "name": "벡터 인덱스", "type": "전체 텍스트 인덱스", "params": { "doc_config": { "docid_prefix_delim": "", "docid_regexp": "", "모드": "type_field", "type_field": "type" }, "매핑": { "기본_분석기": "표준", "기본_날짜_파서": "날짜/시간 옵션", "default_field": "_all", "default_mapping": { "동적": true, "enabled": true, "속성": { "메타데이터": { "동적": true, "enabled": true }, "임베딩": { "enabled": true, "동적": false, "fields": [ { "dims": 1536, "index": true, "name": "임베딩", "유사성": "dot_product", "type": "벡터", "벡터_인덱스_최적화_용": "리콜" } ] }, "text": { "enabled": true, "동적": false, "fields": [ { "index": true, "name": "text", "store": true, "type": "text" } ] } } }, "default_type": "_default", "docvalues_dynamic": false, "index_dynamic": true, "store_dynamic": true, "type_field": "_유형" }, "store": { "indexType": "scorch", "세그먼트 버전": 16 } }, "sourceType": "gocbcore", "sourceName": "프로젝트", "sourceParams": {}, "planParams": { "최대 파티션당 인덱스": 103, "indexPartitions": 10, "numReplicas": 0 } } |
3단계: 데이터 로드
이제 모든 PDF 데이터를 벡터 임베딩과 함께 데이터베이스에 청크로 저장할 차례입니다.
참고: 다음 내용을 읽어보세요. 청킹, 데이터 수집 등에 대한 자세한 블로그. 이후 단계에서 다룰 내용을 명확하게 이해하려면 블로그를 살펴보는 것을 적극 권장합니다.
업로드하려는 다양한 유형의 문서에 대한 라이브러리가 있습니다. 예를 들어 소스 데이터가 .txt 형식에 다음 코드를 추가한 다음 app.py:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
에서 랭체인_커뮤니티.문서 로더 가져오기 텍스트 로더 에서 랭체인_텍스트_스플리터 가져오기 문자 텍스트 스플리터 로더 = 텍스트 로더("텍스트 파일의 경로") 문서 = 로더.load() text_splitter = 문자 텍스트 스플리터(청크 크기=500, 청크_오버랩=0) 문서 = text_splitter.분할_문서(문서) 벡터_스토어 = 카우치베이스 벡터 스토어.from_documents( 문서=문서, 임베딩=임베딩, 클러스터=클러스터, 버킷_이름=BUCKET_NAME, 범위_이름=SCOPE_NAME, 컬렉션_이름=컬렉션_이름, index_name=검색_인덱스_이름, ) |
하지만 소스 유형이 PDF라고 가정해 보겠습니다:
|
1 |
pip 설치 pypdf |
|
1 2 3 4 |
에서 랭체인_커뮤니티.문서 로더 가져오기 PyPDFLoader 로더 = PyPDFLoader("PDF 파일 경로") 페이지 = 로더.load_and_split() |
LangChain은 PDF뿐만 아니라 다음과 같은 여러 유형을 지원합니다:
-
- CSV
- HTML
- JSON
- 마크다운 등
자세히 알아보기 랭체인 문서 로더.
4단계: 결과 추론하기
이제 애플리케이션에 쿼리를 보낼 준비가 되었습니다:
|
1 2 3 |
쿼리 = "" 결과 = 벡터_스토어.유사성 검색(쿼리) 인쇄(결과[0]) |
앱 2: 처음부터 앱 만들기
시작하기 전에 이전 섹션에서 설명한 대로 다음과 같은 이름의 버킷으로 클러스터를 스핀업합니다. 프로젝트. 또한 이전 섹션의 2단계에 따라 검색 인덱스를 가져와야 합니다.
1단계: Couchbase 설정하기
기본값을 사용하는 경우에는 app.py 는 다음과 같이 표시되어야 합니다:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
에서 카우치베이스.auth 가져오기 비밀번호 인증기 에서 카우치베이스.클러스터 가져오기 클러스터 에서 카우치베이스.옵션 가져오기 (클러스터 옵션, 클러스터타임아웃옵션,쿼리 옵션) 에서 날짜 시간 가져오기 timedelta 사용자 이름 = "관리자" 비밀번호 = "비밀번호" 버킷_이름 = "프로젝트" 범위_이름 = "_default" 컬렉션_이름 = "_default" auth = 비밀번호 인증기( 사용자 이름, 비밀번호, ) 클러스터 = 클러스터(f'couchbase://localhost}', 클러스터 옵션(auth)) 클러스터.wait_until_ready(timedelta(초=5)) cb = 클러스터.버킷(버킷_이름) cb_coll = cb.범위(범위_이름).컬렉션(컬렉션_이름) |
이제 Couchbase 컬렉션 링크와 검색 인덱스가 준비되었으므로 데이터 로딩 부분으로 넘어가겠습니다.
2단계: 데이터 로드
모듈화된 상태를 유지하려면 load.py라는 새 Python 파일을 만듭니다.
PDF에서 데이터를 추출하는 방법에는 여러 가지가 있습니다. 쉽게 추출하려면 pypdf 패키지를 제공합니다:
load.py
|
1 2 3 4 |
에서 랭체인_커뮤니티.문서 로더 가져오기 PyPDFLoader 로더 = PyPDFLoader("PDF 경로") 페이지 = 로더.load_and_split() |
이제 이 페이지 변수는 PDF에서 추출한 텍스트 블록의 목록입니다. 모든 콘텐츠를 하나의 변수로 병합해 보겠습니다:
|
1 2 3 |
텍스트 = "" 에 대한 페이지 in 페이지: 텍스트 += 페이지['page_content'] |
청크하기 전에 임베딩 모델을 설정해야 합니다. 이 경우엔 문장-변환기/파라프레이즈-디스틸로베르타-베이스-v1 얼굴을 안는 것을 방지합니다.
이 모델은 768차원의 벡터 임베딩을 제공합니다.
load.py
|
1 2 |
에서 문장_변압기 가져오기 문장 트랜스포머 모델 = 문장 트랜스포머('문장-변환기/파라프레이즈-디스틸로베르타-베이스-v1') |
이제 모델이 준비되었습니다. 문서를 푸시해 보겠습니다. 패키지를 사용할 수 있습니다. 재귀적 문자 텍스트 분할기 에서
이 패키지는 제공된 크기의 청크를 제공한 다음 위의 모델을 사용하여 각 청크에 대한 벡터 임베딩을 찾아 문서를 데이터베이스에 푸시합니다.
따라서 문서에는 두 개의 필드가 있습니다:
|
1 2 3 4 |
{ "데이터" : <청크>, "벡터_데이터" : <벡터 임베딩 의 의 청크> } |
load.py
|
1 2 3 4 5 6 7 8 9 |
에서 랭체인_텍스트_스플리터 가져오기 리커시브 문자 텍스트 스플리터 text_splitter = 리커시브 문자 텍스트 스플리터( 청크 크기=500, 청크_오버랩=20, 길이_함수=len, is_separator_regex=False, ) 청크 = text_splitter.create_documents(텍스트) |
이제 청크가 준비되었으므로 각 청크에 대한 임베딩을 찾아서 데이터베이스에 푸시할 수 있습니다.
load.py
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
에서 json 가져오기 JSONEncoder 에서 플래그 임베딩 가져오기 BGEM3FlagModel 에서 문장_변압기 가져오기 문장 트랜스포머 가져오기 numpy as np 가져오기 json 가져오기 ast 클래스 넘피 인코더(JSONEncoder): def 기본값(self, 객체): 만약 인스턴스(객체, np.ndarray): 반환 객체.토리스트() 반환 JSONEncoder.기본값(self, 객체) docid_카운터 = 1 에 대한 문장 in 청크: emb = 모델.encode(str(문장.페이지_콘텐츠)) 임베딩 = np.배열(emb) np.set_print옵션(suppress=True) json_dump = json.덤프(임베딩, cls=넘피 인코더) 문서 = { "데이터":str(문장.페이지_콘텐츠), "벡터_데이터":ast.literal_eval(json_dump) } docid = 'docid:' + str(이름) + str(docid_counter) docid_counter+=1 시도: 결과 = cb_coll.업서트(docid, 문서) 인쇄(결과.cas) 예외 예외 as e: 인쇄(e) |
어디에 있는지 궁금하십니까? cb_coll 의 출처는 어디일까요? 저희가 만든 컬렉션 커넥터입니다. app.py. 이를 전달하기 위해 이 모든 것을 load.py 를 받아들이는 함수에 cb_coll 를 매개변수로 지정합니다.
따라서 마침내 load.py 는 다음과 같이 표시되어야 합니다:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
에서 랭체인_커뮤니티.문서 로더 가져오기 PyPDFLoader 에서 랭체인_텍스트_스플리터 가져오기 리커시브 문자 텍스트 스플리터 에서 json 가져오기 JSONEncoder 에서 플래그 임베딩 가져오기 BGEM3FlagModel 에서 문장_변압기 가져오기 문장 트랜스포머 가져오기 numpy as np 가져오기 json 가져오기 ast 클래스 넘피 인코더(JSONEncoder): def 기본값(self, 객체): 만약 인스턴스(객체, np.ndarray): 반환 객체.토리스트() 반환 JSONEncoder.기본값(self, 객체) def Load_data(cb_coll): 로더 = PyPDFLoader("PDF 경로") 페이지 = 로더.load_and_split() 텍스트 = "" 에 대한 페이지 in 페이지: 텍스트 += 페이지['page_content'] text_splitter = 리커시브 문자 텍스트 스플리터( 청크 크기=500, 청크_오버랩=20, 길이_함수=len, is_separator_regex=False, ) 청크 = text_splitter.create_documents(텍스트) docid_카운터 = 1 에 대한 문장 in 청크: emb = 모델.encode(str(문장.페이지_콘텐츠)) 임베딩 = np.배열(emb) np.set_print옵션(suppress=True) json_dump = json.덤프(임베딩, cls=넘피 인코더) 문서 = { "데이터":str(문장.페이지_콘텐츠), "벡터_데이터":ast.literal_eval(json_dump) } docid = 'docid:' + str(이름) + str(docid_counter) docid_counter+=1 시도: 결과 = cb_coll.업서트(docid, 문서) 인쇄(결과.cas) 예외 예외 as e: 인쇄(e) |
이제 다음으로 이동해 보겠습니다. app.py를 가져와서 load_data 함수입니다.
app.py
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
에서 카우치베이스.auth 가져오기 비밀번호 인증기 에서 카우치베이스.클러스터 가져오기 클러스터 에서 카우치베이스.옵션 가져오기 (클러스터 옵션, 클러스터타임아웃옵션,쿼리 옵션) 에서 날짜 시간 가져오기 timedelta 에서 ./load 가져오기 Load_data 사용자 이름 = "관리자" 비밀번호 = "비밀번호" 버킷_이름 = "프로젝트" 범위_이름 = "_default" 컬렉션_이름 = "_default" auth = 비밀번호 인증기( 사용자 이름, 비밀번호, ) 클러스터 = 클러스터(f'couchbase://localhost}', 클러스터 옵션(auth)) 클러스터.wait_until_ready(timedelta(초=5)) cb = 클러스터.버킷(버킷_이름) cb_coll = cb.범위(범위_이름).컬렉션(컬렉션_이름) Load_data(cb_coll) |
이제 필요한 형식으로 문서가 푸시되고 검색 색인도 변경됩니다. 이제 벡터 검색을 할 차례입니다.
3단계: 벡터 검색
Couchbase에서는 여러 가지 방법이 있으며, 그 중 하나가 컬 방법입니다.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
가져오기 하위 프로세스 가져오기 json 에서 구성 가져오기 COUCHBASE_URL def 벡터_검색_채팅1(index_name, 검색_벡터, k, cb_coll,쿼리): 인쇄("query :",쿼리) args = {"index_name":index_name, "search_vector":검색_벡터,"k":k,"query":쿼리,"couchbase_url":COUCHBASE_URL} curl_command = """ curl -XPOST -H "Content-Type: application/json" -u Administrator:password \ http://{couchbase_url}:8094/api/버킷/프로젝트/범위/_기본/인덱스/{인덱스_이름}/쿼리 \. -d '{{ "쿼리": {{ "match_none":{} }}, "size": 6, "knn": [ {{ "field": "벡터_데이터", "k": {k}, "벡터":{검색_벡터} }} ], "에서": 0 }}' """.형식(**args) 시도: 결과 = 하위 프로세스.실행(curl_command, shell=True, 확인=True,stdout=하위 프로세스.PIPE) 반환 결과.stdout |
그리고 result.stdout 에는 k 가장 가까운 문서 ID. 스크립트를 확장하여 반환된 모든 ID에 대해 get 요청을 수행하고 결과를 결합하여 최종 컨텍스트를 얻을 수 있습니다. 그런 다음 이 컨텍스트를 프롬프트와 함께 LLM에 전달하여 원하는 결과를 얻습니다.
잘했어!