CLOVA Studio 운영자 Posted May 10 공유하기 Posted May 10 이 cookbook은 네이버 클라우드 플랫폼에서 CLOVA Studio의 기능을 활용하여 RAG(Retrieval Augmented Generation)를 구현하는 방법을 설명합니다. RAG에 사용된 데이터는 HTML 형식이며, CLOVA Studio의 문단 나누기 API, 임베딩 API, Chat Completion API를 활용했습니다. HyperCLOVA X를 연계하여 주어진 데이터를 기반으로 대화를 진행하는 서비스 구현이 가능합니다. RAG 3부작 시리즈 ✔︎ (1부) RAG란 무엇인가 링크 ✔︎ (2부) RAG 구현 단계 알아보기 링크 ✔︎ (3부) CLOVA Studio를 이용해 RAG 구현하기 Cookbook Quote Python 3.12.2 requirements.txt # Vector DB인 Milvus와 관련된 모듈들은 모듈의 용도를 명확히 구분하기 위해 Vector DB 구축 단계에서 불러왔으며 해당 부분에서 코드를 확인하실 수 있습니다. import json import os import subprocess from langchain_community.document_loaders import UnstructuredHTMLLoader from pathlib import Path import base64 import http.client from tqdm import tqdm import requests 1. Raw Data → Connecting 데이터를 추후 작업을 위해 가져오는 단계입니다. 직접 크롤링을 하지 않고, txt 파일(clovastudiourl.txt)에 사용하고 싶은 사이트 URL을 작성한 후, wget 라이브러리와 LangChain의 로더를 통해 작업할 수 있는 형태로 HTML 데이터를 변환했습니다. 또한, 로딩한 데이터 안에 사이트 URL을 넣어 추후 답변과 함께 사이트 주소가 출력되게 하여 질문한 사용자가 답변의 내용이 있는 실제 URL을 함께 참조할 수 있게 로딩된 데이터를 수정했습니다. txt → html 변환 및 원본 사이트 주소 mapping url_to_filename_map = {} with open("clovastudiourl.txt", "r") as file: urls = [url.strip() for url in file.readlines()] folder_path = "clovastudioguide" if not os.path.exists(folder_path): os.makedirs(folder_path) for url in urls: filename = url.split("/")[-1] + ".html" file_path = os.path.join(folder_path, filename) subprocess.run(["wget", "-O", file_path, url], check=True) url_to_filename_map[url] = filename with open("url_to_filename_map.json", "w") as map_file: json.dump(url_to_filename_map, map_file) Output --2024-04-11 17:51:59-- https://guide.ncloud-docs.com/docs/clovastudio-overview guide.ncloud-docs.com (guide.ncloud-docs.com) 해석 중... 104.18.7.159, 104.18.6.159 다음으로 연결 중: guide.ncloud-docs.com (guide.ncloud-docs.com)|104.18.7.159|:443... 연결했습니다. HTTP 요청을 보냈습니다. 응답 기다리는 중... 200 OK 길이: 지정하지 않음 [text/html] 저장 위치: `clovastudioguide/clovastudio-overview.html' 0K .......... .......... .......... .......... .......... 15.5M 50K .......... .......... .......... ... 158M=0.003s 2024-04-11 17:52:00 (24.5 MB/s) - `clovastudioguide/clovastudio-overview.html' 저장함 [85968] --2024-04-11 17:52:00-- https://guide.ncloud-docs.com/docs/clovastudio-spec guide.ncloud-docs.com (guide.ncloud-docs.com) 해석 중... 104.18.6.159, 104.18.7.159 다음으로 연결 중: guide.ncloud-docs.com (guide.ncloud-docs.com)|104.18.6.159|:443... 연결했습니다. HTTP 요청을 보냈습니다. 응답 기다리는 중... 200 OK 길이: 지정하지 않음 [text/html] 저장 위치: `clovastudioguide/clovastudio-spec.html' 0K .......... .......... .......... .......... .......... 42.0M 50K .......... .......... ...... 83.4M=0.001s 2024-04-11 17:52:01 (50.8 MB/s) - `clovastudioguide/clovastudio-spec.html' 저장함 [78387] --2024-04-11 17:52:01-- https://guide.ncloud-docs.com/docs/clovastudio-info ... 50K .......... .......... .......... . 14.0M=0.7s 2024-04-11 17:52:03 (110 KB/s) - `clovastudioguide/clovastudio-procedure.html' 저장함 [83208] Output is truncated. View as a scrollable element or open in a text editor. Adjust cell output settings... Quote txt 파일을 작성할 때는 한 줄에 하나의 URL을 작성해야 합니다. 이때, 각 URL은 줄바꿈으로 구분되어야 합니다. 그리고 html 데이터를 로딩하기 전, robots.txt를 통해 데이터로 활용할 사이트의 접근 허용 여부를 확인해야합니다. NCP의 클로바스튜디오 사용 가이드의 경우 User-agent: * Allow:/ 로 접근이 가능합니다. 예제에서 활용한 HTML은, 네이버클라우드 플랫폼(NCP)의 클로바스튜디오 사용 가이드중 CLOVA Studio("AI Services" → "CLOVA Studio")와 관련된 모든 안내 페이지를 데이터로 활용했습니다. LangChain을 활용해 로딩한 html은, 파일을 저장한 디렉토리의 주소를 metadata의 'source'로 가져오게 됩니다. 추후 답변 제공시, 디렉토리 주소가 아닌 실제 URL을 제공하기 위해, LangChain이 로딩한 데이터를 수정해야합니다. html을 로딩할 때 파일명을 URL로 할 경우, /와 : 기호로 인해 파일명이 깨지게 됩니다. 따라서 원본 URL과 로딩한 HTML 파일을 쌍으로 mapping하고, 이 정보를 json 형식으로 "url_to_filename_map"에 저장합니다. 이후, html 파일의 저장 경로가 담긴 'source'를, 이 json 파일과 연결해 실제 URL로 변환해줍니다. 위 코드는, json 파일을 저장하는 단계까지입니다. LangChain 활용 HTML 로딩 이전 단계에서 txt 파일을 HTML로 변환한 후, Langchain의 UnstructuredHTMLLoader를 사용하여 이후 문단 나누기와 임베딩을 위해 machine readable한 형태로 변환해 줍니다. # 폴더 이름에 맞게 수정 html_files_dir = Path('/Users/user/Desktop/raghigh/rag_html/forwiki/clovastudioguide') html_files = list(html_files_dir.glob("*.html")) clovastudiodatas = [] for html_file in html_files: loader = UnstructuredHTMLLoader(str(html_file)) document_data = loader.load() clovastudiodatas.append(document_data) print(f"Processed {html_file}") Output Processed /Users/user/Desktop/raghigh/rag_html/forwiki/clovastudioguide/clovastudio-info.html Processed /Users/user/Desktop/raghigh/rag_html/forwiki/clovastudioguide/clovastudio-overview.html Processed /Users/user/Desktop/raghigh/rag_html/forwiki/clovastudioguide/clovastudio-procedure.html Processed /Users/user/Desktop/raghigh/rag_html/forwiki/clovastudioguide/clovastudio-spec.html ... Processed /Users/user/Desktop/raghigh/rag_html/forwiki/clovastudioguide/clovastudio-explorer03.html Processed /Users/user/Desktop/raghigh/rag_html/forwiki/clovastudioguide/clovastudio-dataset.html Processed /Users/user/Desktop/raghigh/rag_html/forwiki/clovastudioguide/clovastudio-procedure.html Processed /Users/user/Desktop/raghigh/rag_html/forwiki/clovastudioguide/clovastudio-spec.html # page_content는 길이가 긴 관계로 중략합니다. [Document(page_content="Login\n\nrelease/20240321 release/20240321 release/20240125 release/20231123 release/20230525\n\nKorean English Ja - 日本語 Korean\n\nAPI 가이드\n\nCLI 가이드\n\n내용 \n\nHOME\n\n포털 및 콘솔\n\n네이버 클라우드 플랫폼 사용 환경\n\nCompute\n\nContainers\n\nStorage\n\nNetworking\n\nDatabase\n\nSecurity\n\nAI Services\n\nAI·NAVER API\n\nApplication Services ... Enter a valid password\n\nYour profile has been successfully updated.\n\nLogout", metadata={'source': '/Users/user/Desktop/raghigh/rag_html/forwiki/clovastudioguide/clovastudio-info.html'})] Quote 각 HTML에는 page_content에 모든 텍스트가, metadata의 'source'에는 저장 경로가 기록되어 있습니다. 위는, 로딩된 데이터 중 하나인 clovastudio-info.html의 형태입니다. metadata의 'source'에 실제 URL이 아닌 html 파일이 저장된 디렉토리 경로가 담긴 것을 확인할 수 있습니다. Mapping을 통해 이 'source'를 실제 URL로 바꾸는 작업을 다음 단계에 진행하게 됩니다. Mapping 정보를 활용해 'source'를 실제 URL로 대체 아래의 output은 앞선 예시인 clovastudio-info.html의 형태입니다. Mapping 정보를 활용하여 'source'의 값을 실제 URL로 변환한 것을 확인할 수 있습니다. with open("url_to_filename_map.json", "r") as map_file: url_to_filename_map = json.load(map_file) filename_to_url_map = {v: k for k, v in url_to_filename_map.items()} # clovastudiodatas 리스트의 각 Document 객체의 'source' 수정 for doc_list in clovastudiodatas: for doc in doc_list: extracted_filename = doc.metadata["source"].split("/")[-1] if extracted_filename in filename_to_url_map: doc.metadata["source"] = filename_to_url_map[extracted_filename] else: print(f"Warning: {extracted_filename}에 해당하는 URL을 찾을 수 없습니다.") # page_content는 길이가 긴 관계로 중략합니다. [Document(page_content="Login\n\nrelease/20240321 release/20240321 release/20240125 release/20231123 release/20230525\n\nKorean English Ja - 日本語 Korean\n\nAPI 가이드\n\nCLI 가이드\n\n내용 \n\nHOME\n\n포털 및 콘솔\n\n네이버 클라우드 플랫폼 사용 환경\n\nCompute\n\nContainers\n\nStorage\n\nNetworking\n\nDatabase\n\nSecurity\n\nAI Services\n\nAI·NAVER API\n\nApplication Services ... Enter a valid password\n\nYour profile has been successfully updated.\n\nLogout", metadata={'source': 'https://guide.ncloud-docs.com/docs/clovastudio-info'})] # 이중 리스트를 풀어서 하나의 리스트로 만드는 작업 clovastudiodatas_flattened = [item for sublist in clovastudiodatas for item in sublist] 2. Chunking 임베딩 모델이 처리할 수 있는 적당한 크기로 raw data를 나누는 것은 매우 중요합니다. 이는 임베딩 모델마다 한 번에 처리할 수 있는 토큰 수의 한계가 있기 때문입니다. CLOVA Studio의 문단 나누기 API는 모델이 직접 문장들간의 의미 유사도를 찾아 최적의 chunk 개수와 사용자가 원하는 1개 chunk의 크기(글자 수)를 직접 설정하여 문단을 나눌 수도 있습니다. 추가로, 후처리(postProcess = True)를 통해 chunk당 글자 수의 상한선과 하한선을 postProcessMaxSize와 postProcessMinSize로 조절할 수도 있습니다. class SegmentationExecutor: def __init__(self, host, api_key, api_key_primary_val, request_id): self._host = host self._api_key = api_key self._api_key_primary_val = api_key_primary_val self._request_id = request_id def _send_request(self, completion_request): headers = { "Content-Type": "application/json; charset=utf-8", "X-NCP-CLOVASTUDIO-API-KEY": self._api_key, "X-NCP-APIGW-API-KEY": self._api_key_primary_val, "X-NCP-CLOVASTUDIO-REQUEST-ID": self._request_id } conn = http.client.HTTPSConnection(self._host) conn.request( "POST", "/testapp/v1/api-tools/segmentation/{app-id}", # If using Service App, change 'testapp' to 'serviceapp', and corresponding app id. json.dumps(completion_request), headers ) response = conn.getresponse() result = json.loads(response.read().decode(encoding="utf-8")) conn.close() return result def execute(self, completion_request): res = self._send_request(completion_request) if res["status"]["code"] == "20000": return res["result"]["topicSeg"] else: raise ValueError(f"{res}") if __name__ == "__main__": segmentation_executor = SegmentationExecutor( host="clovastudio.apigw.ntruss.com", api_key='<api_key>', api_key_primary_val='<api_key_primary_val>', request_id='<request_id>' ) chunked_html = [] for htmldata in tqdm(clovastudiodatas_flattened): try: request_data = { "postProcessMaxSize": 100, "alpha": -100, "segCnt": -1, "postProcessMinSize": -1, "text": htmldata.page_content, "postProcess": True } request_json_string = json.dumps(request_data) request_data = json.loads(request_json_string, strict=False) response_data = segmentation_executor.execute(request_data) result_data = [' '.join(segment) for segment in response_data] except json.JSONDecodeError as e: print(f"JSON decoding failed: {e}") except Exception as e: print(f"An error occurred: {e}") for paragraph in result_data: chunked_document = { "source": htmldata.metadata["source"], "text": paragraph } chunked_html.append(chunked_document) print(len(chunked_html)) Output 100%|██████████| 4/4 [00:08<00:00, 2.02s/it] 525 Quote 파이썬 스펙을 확인하기 위해서는 테스트 앱을 발급받아야 합니다. 테스트 앱에는 고유한 식별자(코드)인 **{테스트앱 식별자}**가 부여됩니다. 테스트 앱이 요청한 작업에는 <api_key>, <api_key_primary_val>, **<request_id>**와 같은 3가지 정보가 사용되며, 이는 사용자 계정마다 고유한 값입니다. 파이썬 스펙에서는 필요한 모듈을 불러오는 부분이 상단에 위치하지만, 이 파이프라인에서는 초반에 한 번에 로딩하였습니다. 또한, 파이썬 스펙에서는 클래스 이름이 "CompletionExecutor"로 명시되어 있으나, 다른 API와의 이름 중복을 피하기 위해 "SegmentationExecutor"로 임의 변경하였습니다. 3. Embedding 문단 나누기 API를 통해 나누어진 525개의 chunk(chunked_html)을 CLOVA Studio의 임베딩 API를 사용해 1024차원의 벡터로 변환하는 과정입니다. clir-emb-dolphin의 경우, 임베딩 과정에서 유사도 판단을 위해 벡터의 내적(Inner Product, IP)을 거리 단위로 사용합니다. 반면, clir-sts-dolphin은 코사인 거리(Cosine)를 거리 단위로 사용합니다. 이후, 벡터의 인덱싱과 검색 과정에서 사용자는 어떤 거리 단위를 사용하여 데이터와 사용자의 쿼리 간 유사도를 판단할 것인지 선택할 수 있습니다. 리 단위를 임베딩부터 인덱싱, 검색까지 일치시켜야 데이터의 품질이 향상되므로, 임베딩 과정에서 사용한 모델(emb / sts)을 기억하는 것이 중요합니다. 임베딩 클래스에 오류가 발생시 멈추게끔 하는 로직을 일부 추가해, 다량의 데이터를 처리할 때 오류 발생시 재실행의 부담을 줄입니다. class EmbeddingExecutor: def __init__(self, host, api_key, api_key_primary_val, request_id): self._host = host self._api_key = api_key self._api_key_primary_val = api_key_primary_val self._request_id = request_id def _send_request(self, completion_request): headers = { "Content-Type": "application/json; charset=utf-8", "X-NCP-CLOVASTUDIO-API-KEY": self._api_key, "X-NCP-APIGW-API-KEY": self._api_key_primary_val, "X-NCP-CLOVASTUDIO-REQUEST-ID": self._request_id } conn = http.client.HTTPSConnection(self._host) conn.request( "POST", "/testapp/v1/api-tools/embedding/clir-emb-dolphin/{app-id (앱 식별자)}", # If using Service App, change 'testapp' to 'serviceapp', and corresponding app id. json.dumps(completion_request), headers ) response = conn.getresponse() result = json.loads(response.read().decode(encoding="utf-8")) conn.close() return result def execute(self, completion_request): res = self._send_request(completion_request) if res["status"]["code"] == "20000": return res["result"]["embedding"] else: error_code = res["status"]["code"] error_message = res.get("status", {}).get("message", "Unknown error") raise ValueError(f"오류 발생: {error_code}: {error_message}") if __name__ == "__main__": embedding_executor = EmbeddingExecutor( host="clovastudio.apigw.ntruss.com", api_key='<api_key>', api_key_primary_val='<api_key_primary_val>', request_id='<request_id>' ) for i, chunked_document in enumerate(tqdm(chunked_html)): try: request_json = { "text": chunked_document['text'] } request_json_string = json.dumps(request_json) request_data = json.loads(request_json_string, strict=False) response_data = embedding_executor.execute(request_data) except ValueError as e: print(f"Embedding API Error. {e}") except Exception as e: print(f"Unexpected error: {e}") chunked_document["embedding"] = response_data Output 100%|██████████| 525/525 [01:04<00:00, 8.08it/s] Quote 이 예제에서는 clir-emb-dolphin을 임베딩 모델로 사용했으며, 이후 인덱싱과 검색에서도 거리 단위를 IP로 활용할 것입니다. clir-sts-dolphin, v2 (bge-m3) 를 임베딩 모델로 사용하고자 할 경우 해당 테스트 앱 또는 서비스 앱을 생성하여 이용하면 되며, 이 때는 인덱싱 및 검색 시 거리 단위를 Cosine Similarity (코사인 유사도) 로 활용하는 것을 권장합니다. dimension_set = set() for item in chunked_html: if "embedding" in item: dimension = len(item["embedding"]) dimension_set.add(dimension) print("임베딩된 벡터들의 차원:", dimension_set) Output 임베딩된 벡터들의 차원: {1024} chunked_html[400] Output {'source': 'https://guide.ncloud-docs.com/docs/clovastudio-procedure', 'text': '4. 테스트 앱 생성', 'embedding': [-0.4846521, -0.47464514, -0.47014025, 1.5649279, -0.28591785, 1.0830964, ... -0.09297561, -0.48607916, -0.4411355, -0.86348283, ...]} Output is truncated. View as a scrollable element or open in a text editor. Adjust cell output settings... 4. Vector DB CLOVA Studio의 임베딩 API를 활용하여 모든 chunked_의 text를 1024차원의 벡터로 변환한 후, 'embedding'이라는 이름의 객체로 chunked_html에 추가해 주었습니다. 이제 이 데이터를 Vector DB에 저장하고, 인덱싱을 하는 단계입니다. Vector DB로는 Milvus를 사용하였으며, 대시보드는 Docker를 사용하여 DB를 켜고(Run) 끄는(Stop) 작업을 수행하였습니다. Milvus Set Up from pymilvus import connections, FieldSchema, CollectionSchema, DataType, Collection, utility MacOS 환경에서 Docker를 설치한 뒤, 터미널을 사용하여 git clone 명령어로 Milvus Standalone을 설치했습니다. Milvus Standalone 모드는 단일 노드 또는 서버에서 모든 기능을 제공하며, 필요한 모든 컴포넌트가 하나의 인스턴스나 컨테이너 안에서 작동합니다. 이는 주로 소규모 프로젝트나 개발 단계에 이상적입니다. 프로젝트에 파이썬을 사용함에 따라, Milvus를 효과적으로 활용하기 위해 파이썬용 SDK인 Pymilvus를 설치하고 필요한 라이브러리를 가져왔습니다. Collections & ID 생성 connections.connect() fields = [ FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True), FieldSchema(name="source", dtype=DataType.VARCHAR, max_length=3000), FieldSchema(name="text", dtype=DataType.VARCHAR, max_length=9000), FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=1024) ] schema = CollectionSchema(fields, description="컬렉션 설명 적어넣기") collection_name = "컬렉션 이름" collection = Collection(name=collection_name, schema=schema, using='default', shards_num=2) for item in chunked_html: source_list = [item['source']] text_list = [item['text']] embedding_list = [item['embedding']] entities = [ source_list, text_list, embedding_list ] insert_result = collection.insert(entities) print("데이터 Insertion이 완료된 ID:", insert_result.primary_keys) print("데이터 Insertion이 전부 완료되었습니다") Output Data insertion complete. IDs: [448298454101600167, 448298454101600168, 448298454101600173, ..., 448298454101600687, 448298454101600688, 448298454101600689, 448298454101600690, 448298454101600691] Quote 상단 Output의 경우, 길이가 긴 관계로 중략(...) 처리한 것이며, 실제로는 모든 ID가 출력됩니다. Quote 활용 SDK가 업데이트 될 경우 코드에 변화가 발생할 수 있습니다. 데이터 chunk가 에러가 발생하여 임베딩 값을 가지고 있지 않을 경우 오류가 발생할 수 있으니, 임베딩이 정상적으로 완료 되었는지 확인해야 합니다. Indexing 앞서 임베딩 시 거리 단위를 IP로 설정했기 때문에, 인덱싱에서도 "metric_type"을 "IP"로 설정합니다. 인덱싱 유형은 다양한 알고리즘이 존재하며, 최근에는 HNSW 방식이 많이 활용됩니다. 하지만 선택한 Vector DB가 어떤 인덱싱 유형을 지원하는지 확인해야 합니다. "M"은 각 노드(데이터 포인트)가 유지할 수 있는 최대 엣지(연결)의 수로, 값이 높을수록 검색 정확도는 높아지지만 인덱스 생성 시간과 메모리 사용량도 증가하는 trade-off 관계를 가지고 있습니다. "efConstruction"은 인덱스 구축 시 만들 그래프 구조의 깊이와 너비로, 값이 높을수록 정확도는 올라가지만 인덱스 생성 시간과 메모리 사용량도 증가하는 trade-off 관계를 가지고 있습니다. "M" : 8, "efConstruction" : 200의 설정은 Milvus가 공개한 Milvus 2.2.0의 성능 벤치마크에서 실험 시 설정한 파라미터 값을 가져온 것으로, Cookbook도 Milvus 2.2.x 환경에서 진행되었습니다. 데이터의 양이 많을수록 인덱싱에 시간이 오래 소요될 수 있습니다. 데이터가 많은 경우, 인덱싱 생성 후 바로 결과를 확인하면 결과를 확인할 수 없으며, 인덱싱은 하나의 객체와 값으로 저장되는 것이 아니기 때문에 아래와 같은 실행 결과로 인덱싱이 완료되었는지 확인할 수 있습니다. index_params = { "metric_type": "IP" "index_type": "HNSW" "params": { "M": 8, "efConstruction": 200 } } collection = Collection("htmlrag_forncp") collection.create_index(field_name="embedding", index_params=index_params) utility.index_building_progress("htmlrag_forncp") print([index.params for index in collection.indexes]) Output [{'metric_type': 'IP', 'index_type': 'HNSW', 'params': {'M': 8, 'efConstruction': 200}}] Quote 위와 같은 output이 나왔다는 것은 인덱싱 작업이 성공적으로 완료됐다는 의미입니다. 재실행시 Milvus 로딩 방법 작업 환경 종료 이후, RAG를 이용해 다시 질의응답을 하려면, Vector DB인 Milvus와 연결 상태여야 합니다. Docker를 Milvus의 대시보드로 사용할 때의 연결 방법은 다음과 같습니다. 질의응답은 html_chat() 함수를 통해 이전과 동일하게 진행할 수 있습니다. 1. Docker를 실행한 후, collections가 저장되어 있는 컨테이너(milvus-standalone)를 실행시켜줍니다. 2. 아래의 코드를 통해 connection을 실행하고, 만든 collection의 이름을 넣어 연결합니다. Milvus 및 Milvus-Standalone의 기본 환경은 host = "localhost", port="19530"입니다. 필요한 모듈과 라이브러리는 Milvus에 데이터를 저장할 때와 동일합니다. connections.connect("default", host="localhost", port="19530") # 불러올 collection 이름을 넣는 곳 collection = Collection("htmlrag_forncp") utility.load_state("htmlrag_forncp") Output <LoadState: Loaded> 5. Retrieval → HyperCLOVA X 사용자의 질문을 임베딩하는 함수와, 임베딩한 사용자의 질문에 대응하는 Vector DB에 저장된 데이터를 검색하는 로직을 구성하는 단계입니다. 검색을 통해 사용자의 질문과 가장 유사한 데이터를 모은 "reference"를 HyperCLOVA X가 참조해 질문에 대한 답변을 생성합니다. Chat Completion API를 생성하여 답변 생성을 위한 LLM을 호출하며, 플레이그라운드와 동일하게 파라미터(Top P, Top K 등)와 시스템 프롬프트를 작성할 수 있습니다. Chat Completion API Chat Completion의 파이썬 스펙은 플레이그라운드의 테스트앱 발급을 통해 확인할 수 있습니다. 기본적으로 Chat Completion API의 실행 결과는, 아래와 같은 stream 형태로 출력됩니다. Chat Completion API의 파이썬 스펙을 약간 변형하여 full-length 최종 답변만을 추출할 수 있습니다. 이를 위해 두 가지 로직을 사용할 수 있는데, 첫째는 Stream 답변 중 가장 긴 길이의 답변을 선택하는 방법이고, 둘째는 제일 마지막 출력 결과인 "[DONE]" 바로 직전의 출력 결과를 선택하는 방법입니다. 각각의 방법에 대한 코드는 아래에서 확인하실 수 있습니다. 1. 가장 긴 길이의 답변을 선택하는 경우 class CompletionExecutor: def __init__(self, host, api_key, api_key_primary_val, request_id): self._host = host self._api_key = api_key self._api_key_primary_val = api_key_primary_val self._request_id = request_id def execute(self, completion_request, response_type="stream"): headers = { "X-NCP-CLOVASTUDIO-API-KEY": self._api_key, "X-NCP-APIGW-API-KEY": self._api_key_primary_val, "X-NCP-CLOVASTUDIO-REQUEST-ID": self._request_id, "Content-Type": "application/json; charset=utf-8", "Accept": "text/event-stream" } final_answer = "" with requests.post( self._host + "/testapp/v1/chat-completions/HCX-003", headers=headers, json=completion_request, stream=True ) as r: if response_type == "stream": longest_line = "" for line in r.iter_lines(): if line: decoded_line = line.decode("utf-8") if decoded_line.startswith("data:"): event_data = json.loads(decoded_line[len("data:"):]) message_content = event_data.get("message", {}).get("content", "") if len(message_content) > len(longest_line): longest_line = message_content final_answer = longest_line elif response_type == "single": final_answer = r.json() # 가정: 단일 응답이 JSON 형태로 반환됨 2. "[DONE]" 바로 직전의 출력 결과를 선택하는 경우 class CompletionExecutor: def __init__(self, host, api_key, api_key_primary_val, request_id): self._host = host self._api_key = api_key self._api_key_primary_val = api_key_primary_val self._request_id = request_id def execute(self, completion_request): headers = { "X-NCP-CLOVASTUDIO-API-KEY": self._api_key, "X-NCP-APIGW-API-KEY": self._api_key_primary_val, "X-NCP-CLOVASTUDIO-REQUEST-ID": self._request_id, "Content-Type": "application/json; charset=utf-8", "Accept": "text/event-stream" } response = requests.post( self._host + "/testapp/v1/chat-completions/HCX-003", headers=headers, json=completion_request, stream=True ) # 스트림에서 마지막 'data:' 라인을 찾기 위한 로직 last_data_content = "" for line in response.iter_lines(): if line: decoded_line = line.decode("utf-8") if '"data":"[DONE]"' in decoded_line: break if decoded_line.startswith("data:"): last_data_content = json.loads(decoded_line[5:])["message"]["content"] return last_data_content Quote Stream 형태의 답변 구조에서는 다음과 같은 두 가지 로직을 사용하여 최종 full-length 답변을 추출할 수 있습니다. 최종 full-length 답변은 출력 결과들 중 가장 긴 답변이므로, 모든 출력 결과의 길이를 비교하여 가장 긴 답변을 선택합니다. Stream 답변 구조상 "[DONE]" 표시 직전의 출력 결과가 최종 full-length 답변이므로, "[DONE]" 표시 이전의 마지막 출력 결과를 선택합니다. Cookbook에서는 1번 로직을 사용하여 클래스를 정의했습니다. 답변 생성 함수 정의 # 사용자의 쿼리를 임베딩하는 함수를 먼저 정의 def query_embed(text: str): request_data = {"text": text} response_data = embedding_executor.execute(request_data) return response_data def html_chat(realquery: str) -> str: # 사용자 쿼리 벡터화 query_vector = query_embed(realquery) collection.load() search_params = {"metric_type": "IP", "params": {"ef": 64}} results = collection.search( data=[query_vector], # 검색할 벡터 데이터 anns_field="embedding", # 검색을 수행할 벡터 필드 지정 param=search_params, limit=10, output_fields=["source", "text"] ) reference = [] for hit in results[0]: distance = hit.distance source = hit.entity.get("source") text = hit.entity.get("text") reference.append({"distance": distance, "source": source, "text": text}) completion_executor = CompletionExecutor( host="https://clovastudio.stream.ntruss.com", api_key='<api_key>', api_key_primary_val='<api_key_primary_val>', request_id='<request_id>' ) preset_texts = [ { "role": "system", "content": "- 너의 역할은 사용자의 질문에 reference를 바탕으로 답변하는거야. \n- 너가 가지고있는 지식은 모두 배제하고, 주어진 reference의 내용만을 바탕으로 답변해야해. \n- 답변의 출처가 되는 html의 내용인 'source'도 답변과 함께 {url:}의 형태로 제공해야해. \n- 만약 사용자의 질문이 reference와 관련이 없다면, {제가 가지고 있는 정보로는 답변할 수 없습니다.}라고만 반드시 말해야해." } ] for ref in reference: preset_texts.append( { "role": "system", "content": f"reference: {ref['text']}, url: {ref['source']}" } ) preset_texts.append({"role": "user", "content": realquery}) request_data = { "messages": preset_texts, "topP": 0.6, "topK": 0, "maxTokens": 1024, "temperature": 0.5, "repeatPenalty": 1.2, "stopBefore": [], "includeAiFilters": False } # LLM 생성 답변 반환 response_data = completion_executor.execute(request_data) return response_data Quote - Search 역시 "metric_type" = "IP"로 설정해 임베딩과 인덱싱의 거리 단위와 일치시켜줍니다. 이때, "ef" 값을 64로 설정한 것은 Milvus 2.2.0의 성능 벤치마크에서 사용된 설정을 참고한 것입니다. - results 내 limit = 10의 경우 상위 10개의 문서를 가져온다는 의미인데, Milvus가 우수한 성능을 보여준 2023 ANN Benchmarks에서의 실험 세팅인 k = 10을 그대로 가져온 것입니다. - 이렇게 선정된 상위 10개의 문서를 'reference'라는 리스트로 만들고, 이를 바탕으로 HyperCLOVA X가 오로지 이 리스트를 참조하여 답변하도록 프롬프팅을 설정했습니다. 또한, 추가적인 프롬프팅을 통해 데이터를 기반으로 한 질문에만 답변하도록 하며, 정보가 없을 경우 이를 인정할 수 있는 지침을 부여했습니다. 실행 결과 모음 별도의 패키징 작업 없이, RAG 파이프라인을 사용하여 질문을 하고 HyperCLOVA X로부터 출력된 실행 결과입니다. 정확성을 위해 코드 실행 화면을 그대로 캡쳐해 이미지로 첨부했습니다. 실제 원본 데이터인 NCP의 CLOVA Studio 사용 가이드에 포함된 내용에 대해 정확한 답변을 제공하는 것을 확인할 수 있습니다. 추가로, mapping을 통해 답변의 출처가 되는 데이터의 로컬 디렉토리 경로가 아닌, 접속 가능한 실제 URL로 답변하는 것을 확인할 수 있습니다. 시스템 프롬프트에 입력된 지침에 따라, 참조 자료에서 답변을 찾을 수 없는 질문에 대해서는 시스템이 아는 듯이 답변하지 않는 것을 발견할 수 있습니다. 만약, 이 예제에서 사용하지 않은 다른 CLOVA Studio 사용 가이드 문서가 데이터 소스로 사용되었다면 "CLOVA X에서 스킬을 만들 수 있어?"라는 질문에 답변할 수 있었을 것입니다. 하지만, 데이터 소스로 사용된 NCP의 CLOVA Studio 사용 가이드에는 해당 내용이 포함되어 있지 않아, HyperCLOVA X가 답변을 회피하는 안정적인 결과를 보여주었습니다. 맺음말 이 가이드는 CLOVA Studio의 기능을 이용하여 간략하게 구현한 RAG 파이프라인입니다. 문단 나누기 API, 임베딩 API, Chat Completion API를 통해 HyperCLOVA X를 RAG에서 활용할 수 있습니다. RAG의 전체적인 구조와 흐름에 대해서만 다루었으며, Vector DB에 따라 적용할 수 있는 다양한 post-retrieval 방법론이나 retrieval의 평가에 대해서는 다루지 않았습니다. CLOVA Studio의 다양한 기능이 추가되고 업데이트될 예정이므로, 이를 활용하여 RAG를 더욱 발전시킬 수 있을 것입니다. RAG 3부작 시리즈 ✔︎ (1부) RAG란 무엇인가 링크 ✔︎ (2부) RAG 구현 단계 알아보기 링크 ✔︎ (3부) CLOVA Studio를 이용해 RAG 구현하기 Cookbook 1 1 링크 복사 다른 사이트에 공유하기 More sharing options...
hatiolab Posted May 20 공유하기 Posted May 20 자세한 가이드 감사 드립니다. RAG 파이프라인 구성에 대한 좋은 레퍼런스가 될 것 같습니다. 링크 복사 다른 사이트에 공유하기 More sharing options...
ak68 Posted May 25 공유하기 Posted May 25 재료가 될 Html 가져오는거 막힌듯 링크 복사 다른 사이트에 공유하기 More sharing options...
hatiolab Posted May 27 공유하기 Posted May 27 On 2024. 5. 25. at 오후 4시 18분, ak68 said: 재료가 될 Html 가져오는거 막힌듯 다음과 같이 임의의 --user-agent를 추가하면 가져올 수 있습니다. subprocess.run( [ "wget", "--user-agent='MyCrawler/1.0 (+http://mycrawler.com/info)'", "-O", file_path, url, ], check=True, ) 2 링크 복사 다른 사이트에 공유하기 More sharing options...
CLOVA Studio 운영자 Posted May 27 Author 공유하기 Posted May 27 안녕하세요, @ak68님, 본 Cookbook에서 활용된 html을 로딩하기 위해선 ‘—user-agent=Mozilla/5.0’ 을 추가하면 정상적으로 불러올 수 있습니다. 대상 사이트에서 사용하는 User-Agent 정보와 동일한 형태로 --user-agent 옵션을 설정하면, 요청 차단으로 인해 발생할 수 있는 에러를 방지할 수 있습니다. @hatiolab님, 공유 감사합니다. 1 링크 복사 다른 사이트에 공유하기 More sharing options...
ak68 Posted May 27 공유하기 Posted May 27 알려주신대로 해보고 있습니다. 임베딩 API 호출 시 분당 60회 제한이 걸려있는거 같은데 맞나요? 위의 코드대로 하면 임베딩 처리해야할 분량이 총 4992개 인데. 83.2분 동안 기다려가면서 돌려야 되는 방법 말곤 없나요? {'code': '42901', 'message': 'Too many requests - rate exceeded'} 링크 복사 다른 사이트에 공유하기 More sharing options...
CLOVA Studio 운영자9 Posted May 27 공유하기 Posted May 27 안녕하세요 @ak68님, 임베딩 API를 테스트 앱으로 이용하고 계신 경우 클로바 스튜디오 이용량 제어 정책에 따라 분당 요청수에 제한이 있습니다. 향후 임베딩 API를 서비스에 적용할 계획이 있으신 경우 서비스 앱 신청을 통해 이용량을 늘릴 수 있는 점 참고 부탁드립니다. 감사합니다. 링크 복사 다른 사이트에 공유하기 More sharing options...
포비즈코리아 Posted May 27 공유하기 Posted May 27 도움 많이 되었습니다. 감사합니다. :) 1 링크 복사 다른 사이트에 공유하기 More sharing options...
Roadpia Posted June 4 공유하기 Posted June 4 from pymilvus import connections, FieldSchema, CollectionSchema, DataType, Collection, utility connections.connect() fields = [ FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True), FieldSchema(name="source", dtype=DataType.VARCHAR, max_length=3000), FieldSchema(name="text", dtype=DataType.VARCHAR, max_length=3000), FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=1024) ] schema = CollectionSchema(fields, description="html chunk collection for rag inner product") collection_name = "htmlrag_forncp" collection = Collection(name=collection_name, schema=schema) entities = [] for item in chunked_html: entities.append( { "source": item["source"], "text": item["text"], "embedding": item["embedding"] } ) print(fields) insert_result = collection.insert(entities) print("Data insertion complete. IDs:", insert_result.primary_keys) insert_result = collection.insert(entities) 이 부분에서 DataNotMatchException: <DataNotMatchException: (code=1, message=The Input data type is inconsistent with defined schema, please check it.)> 오류가 납니다. 해당 오류를 해결하기 위해 구글링을 하였으나 명확한 답변을 찾지 못한 상태입니다. 해결방법을 아신다면 도움 부탁드립니다. 링크 복사 다른 사이트에 공유하기 More sharing options...
CLOVA Studio 운영자 Posted June 4 Author 공유하기 Posted June 4 안녕하세요, @Roadpia 님, Milvus가 2.4.x 버전으로 업데이트되면서 코드에 변화가 생겼습니다. 이로 인해 collections를 만드는 형식도 변경되었습니다. 저희가 확인해보니, Requirements.txt에 기재된 2.4.x 버전은 실제로는 2.2.x 버전이었습니다. Milvus 2.4x 버전에서는 아래 코드로 정상 동작하는 것을 확인하였습니다. 따라서 이 방법으로 진행해 주시기 바랍니다. 본문의 코드 또한 수정하도록 하겠습니다. from pymilvus import connections, FieldSchema, CollectionSchema, DataType, Collection, utility fields = [ FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True), FieldSchema(name="source", dtype=DataType.VARCHAR, max_length=3000), FieldSchema(name="text", dtype=DataType.VARCHAR, max_length=9000), FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=1024) ] schema = CollectionSchema(fields, description="컬렉션 설명 적어넣기") collection_name = "컬렉션 이름" collection = Collection(name=collection_name, schema=schema, using='default', shards_num=2) for item in chunked_html: source_list = [item['source']] text_list = [item['text']] embedding_list = [item['embedding']] entities = [ source_list, text_list, embedding_list ] insert_result = collection.insert(entities) print("데이터 Insertion이 완료된 ID:", insert_result.primary_keys) print("데이터 Insertion이 전부 완료되었습니다") 1 링크 복사 다른 사이트에 공유하기 More sharing options...
Roadpia Posted June 4 공유하기 Posted June 4 1 hour ago, Roadpia said: 자세한 설명 감사합니다 혹시 RPC error: [batch_insert], <ParamError: (code=1, message=expect string input, got: <class 'list'>)> 라는 에러가 동일한 위치에서 발생하는데, 이중 리스트 구조로 된 배열이 맞나요?? 확인 부탁드립니다. 매번 감사합니다. 링크 복사 다른 사이트에 공유하기 More sharing options...
CLOVA Studio 운영자 Posted June 4 Author 공유하기 Posted June 4 @Roadpia 님, 각 chunk의 'text'열이 이중 리스트여서 발생한 이슈 같습니다. for item in chunked_html: item['text'] = ", ".join(item['text']) 실행을 통해 이중 리스트를 풀어야할 것으로 보입니다. 1 링크 복사 다른 사이트에 공유하기 More sharing options...
wji6205 Posted July 3 공유하기 Posted July 3 class SegmentationExecutor: def __init__(self, host, api_key, api_key_primary_val, request_id): self._host = host self._api_key = api_key self._api_key_primary_val = api_key_primary_val self._request_id = request_id def _send_request(self, completion_request): headers = { "Content-Type": "application/json; charset=utf-8", "X-NCP-CLOVASTUDIO-API-KEY": self._api_key, "X-NCP-APIGW-API-KEY": self._api_key_primary_val, "X-NCP-CLOVASTUDIO-REQUEST-ID": self._request_id } conn = http.client.HTTPSConnection(self._host) conn.request( "POST", "/testapp/v1/api-tools/segmentation/-", json.dumps(completion_request), headers ) response = conn.getresponse() result = json.loads(response.read().decode(encoding="utf-8")) conn.close() return result def execute(self, completion_request): res = self._send_request(completion_request) if res["status"]["code"] == "20000": return res["result"]["topicSeg"] else: return "Error" if __name__ == "__main__": segmentation_executor = SegmentationExecutor( host="clovastudio.apigw.ntruss.com", api_key='-', api_key_primary_val='-', request_id='-' ) chunked_html = [] for htmldata in tqdm(clovastudiodatas_flattened): request_data = { "postProcessMaxSize": 100, "alpha": 1.5, "segCnt": -1, "postProcessMinSize": 0, "text": htmldata.page_content, "postProcess": False } request_json_string = json.dumps(request_data) request_data = json.loads(request_json_string, strict=False) print(request_data) response_data = segmentation_executor.execute(request_data) # 반환된 각 문단에 대해 source(주소) 포함하여 chunked_documents에 추가 for paragraph in response_data: chunked_document = { "source": htmldata.metadata["source"], "text": paragraph } chunked_html.append(chunked_document) 문단 나누기 api를 게시물과 똑같이 호출했음에도 응답값이 제대로 돌아오지 않습니다. code는 40000으로 나오고, messegae는 ' '라고만 출력되네요. 혹시 문단 나누기 api에 변경이 생겼을까요? 링크 복사 다른 사이트에 공유하기 More sharing options...
CLOVA Studio 운영자9 Posted July 3 공유하기 Posted July 3 안녕하세요 @wji6205님, 문단나누기 API에는 변경사항이 없는데요. 40000 에러는 입력값과 관련한 오류로 보여 혹시 인용해주신 코드 스니펫 상의 '-'로 표기된 부분들을 이용중이신 App ID, API Key, API Gateway Key 등으로 알맞게 수정하여 요청을 하신 것인지 확인 부탁드립니다. (request id의 경우 optional한 값으로 입력되지 않아도 무방합니다.) 위 사항이 제대로 입력될 경우 문제가 재현되지 않는데 혹시 시도해보시고 여전히 문제가 있는 경우 말씀주세요. 감사합니다. 링크 복사 다른 사이트에 공유하기 More sharing options...
wji6205 Posted July 3 공유하기 Posted July 3 안녕하세요 @CLOVA Studio 운영자9 님. 빠른 답변 감사합니다! api key, api gateway key, request_id는 반복적으로 확인해 봤으나 오타가 없습니다. (실제로는 -이 아니라 발급받은 API key값을 입력했습니다.) 입력을 넣는 자료형에도 문제가 없는 듯 한데, 원인을 찾기가 어렵네요.. 링크 복사 다른 사이트에 공유하기 More sharing options...
CLOVA Studio 운영자9 Posted July 3 공유하기 Posted July 3 @wji6205님, _send_request 함수의 'conn.request(' 로 시작하는 부분의 API URL도 생성하신 테스트앱 (또는 서비스앱, 이 경우 앞의 'testapp' 을 'serviceapp'으로 변경해야 합니다) App ID로 추가하여 호출하셨을까요? api key, gw key, req id 말고 위 사항도 확인을 부탁드립니다. 링크 복사 다른 사이트에 공유하기 More sharing options...
wji6205 Posted July 4 공유하기 Posted July 4 @CLOVA Studio 운영자9 답변 감사합니다. app id 쪽에 문제가 있었네요. app id를 수정하여 해결했습니다! 1 링크 복사 다른 사이트에 공유하기 More sharing options...
swift Posted July 17 공유하기 Posted July 17 안녕하세요? 잘 진행하다가 몇가지 문제가 있어 문의드립니다. 1. 404 에러 페이지인 사이트가 일부 있습니다. 예) https://guide.ncloud-docs.com/docs/clovastudio-classification 2. 1의 404 사이트를 하나씩 찾아서 제거하고 진행하다가, SegmentationExecutor 클래스 실행시, JSONDecodeError 에러가 뜹니다. SegmentationExecutor { "name": "JSONDecodeError", "message": "Expecting ',' delimiter: line 116 column 252 (char 1581)", "stack": "\u001b[0;31m------------ } 감사합니다. 링크 복사 다른 사이트에 공유하기 More sharing options...
CLOVA Studio 운영자 Posted July 17 Author 공유하기 Posted July 17 안녕하세요, @swift님, 6월20일에 가이드 문서를 개편 하면서 일부 삭제된 URL이 있었습니다. clovastudiourl.txt 에 수정 반영하였습니다. 다시 시도를 부탁드립니다. 감사합니다. 링크 복사 다른 사이트에 공유하기 More sharing options...
swift Posted July 17 공유하기 Posted July 17 @CLOVA Studio 운영자 아쉽게도 여전히 아래 클래스를 실행하면, 15% 정도 진행되다가, JSON Decode 에러가 뜹니다. class SegmentationExecutor: def __init__(self, host, api_key, api_key_primary_val, request_id): self._host = host self._api_key = api_key self._api_key_primary_val = api_key_primary_val self._request_id = request_id 문단나누기 API를 신청해서, 주어진 API키, 테스트앱 id, key primary val, request id를 넣었고, 이 앞에서, pip install unstructured 를 수행했습니다. 혹시나 몰라서 ubuntu22.04에서 실행해보고, colab으로도 다시 실행해봤는데 여전히 한 15%정도 진행되다가 동일하게 JSON Decode error가 발생합니다. 방법이 없을까요? 링크 복사 다른 사이트에 공유하기 More sharing options...
CLOVA Studio 운영자 Posted July 17 Author 공유하기 Posted July 17 @swift님, html 상의 데이터에 특정 기호에 대한 전처리가 제대로 반영되지 않는 이슈가 있는 것으로 보입니다. 2. Chunking 항목의 코드에서 if __name __ 이후의 내용을 아래 코드로 변경 후 시도 부탁드립니다. 문서의 본문 코드에도 수정해둔 상태입니다. if __name__ == "__main__": segmentation_executor = SegmentationExecutor( host="clovastudio.apigw.ntruss.com", api_key='<api_key>', api_key_primary_val='<api_key_primary_val>', request_id='<request_id>' ) chunked_html = [] for htmldata in tqdm(clovastudiodatas_flattened): try: request_data = { "postProcessMaxSize": 100, "alpha": 1.5, "segCnt": -1, "postProcessMinSize": 0, "text": htmldata.page_content, "postProcess": False } request_json_string = json.dumps(request_data) request_data = json.loads(request_json_string, strict=False) response_data = segmentation_executor.execute(request_data) except Exception as e: print(f"Error occurred. Message: {e}") for paragraph in response_data: chunked_document = { "source": htmldata.metadata["source"], "text": paragraph } chunked_html.append(chunked_document) print(len(chunked_html)) 1 링크 복사 다른 사이트에 공유하기 More sharing options...
swift Posted July 17 공유하기 Posted July 17 감사합니다. 위 클래스를 잘 수행했습니다. 그런데 그 밑의 EmbeddingExecutor 클래스도, 실행 도중 이런 에러가 뜹니다. 따로 예외처리를 추가하면 해결이 될까요? 궁금합니다. # embedding class EmbeddingExecutor: def __init__(self, host, api_key, api_key_primary_val, request_id😞 self._host = host self._api_key = api_key self._api_key_primary_val = api_key_primary_val self._request_id = request_id 링크 복사 다른 사이트에 공유하기 More sharing options...
CLOVA Studio 운영자 Posted July 18 Author 공유하기 Posted July 18 @swift님, 안녕하세요, 가이드 페이지는 지속적으로 업데이트중이며, 이에 따라 clovastudiourl.txt를 수정 업로드하였습니다. 기존 참조 페이지가 다소 많다보니 cookbook 취지에 비해 임베딩 및 벡터DB 인덱싱에 시간이 오래 걸리는 것으로 확인되어 수를 줄였습니다. 기존 cookbook에서 설명하던 2.Chunking 항목의 내용중 문단을 보다 최적화된 방식으로 나누기 위해 segmentation API의 postProcess 파라미터를 활용하였고, 그 외 파라미터도 일부 조정하였습니다. 더불어 임베딩이 보다 수월하게 이뤄질 수 있도록 후처리하는 부분을 추가하였습니다. 3.Embedding의 코드 실행 시 에러 처리되는 부분 역시 일부 개선하였습니다. 감사합니다. 링크 복사 다른 사이트에 공유하기 More sharing options...
memoria Posted July 18 공유하기 Posted July 18 (edited) 안녕하세요. 좋은 글 잘 읽었습니다. 다름아니라 해당 Rag 모델 자체를 flask로 api 개발하여 유니티에서 활용하고 싶은데 참고할만한 레퍼런스 코드가 있을까요? 혹은 불가능하련지요.. Edited July 18 by memoria 링크 복사 다른 사이트에 공유하기 More sharing options...
swift Posted July 19 공유하기 Posted July 19 @CLOVA Studio 운영자 안녕하세요? 잘 실행되어 이용 중입니다. 그런데 답변에 url(출처 등)이 제시되는데요, 위의 보여주신 예시와는 다르게 일부가 마스킹되어 출력됩니다. 혹 방법이 없을까요? 링크 복사 다른 사이트에 공유하기 More sharing options...
CLOVA Studio 운영자 Posted July 19 Author 공유하기 Posted July 19 @swift님, 현재 서비스 정책상 본문의 캡처와는 다르게 URL에 대해서 마스킹 처리가 될 수 있습니다. 이 점 양해 부탁드립니다. 관련 상세 문의는 고객센터를 이용해 주십시오. 링크 복사 다른 사이트에 공유하기 More sharing options...
CLOVA Studio 운영자9 Posted July 22 공유하기 Posted July 22 안녕하세요 @memoria님, 우선 저희가 내부적으로 flask를 통해 Unity에서 활용하기 위해 확보한 레퍼런스 코드는 없는 상황입니다. 불가능할 것으로 보이진 않으나, RAG 구현을 위해서는 게시글에서 언급하듯 CLOVA Studio 상의 API 뿐만 아니라 Milvus 등의 벡터DB도 통합이 필요하기 때문에 해당 부분 구현 및 연동 가능 여부에 대해서도 검토를 하셔야 할 것으로 보입니다. 앞으로 CLOVA Studio를 보다 손쉽게 활용하실 수 있도록 요청이 많은 활용 사례에 대해 쿡북 및 튜토리얼의 형태로 제공하도록 하겠습니다. 감사합니다. 링크 복사 다른 사이트에 공유하기 More sharing options...
hammubara Posted July 29 공유하기 Posted July 29 On 2024. 5. 27. at 오전 10시 52분, CLOVA Studio 운영자 said: 안녕하세요, @ak68님, 본 Cookbook에서 활용된 html을 로딩하기 위해선 ‘—user-agent=Mozilla/5.0’ 을 추가하면 정상적으로 불러올 수 있습니다. 대상 사이트에서 사용하는 User-Agent 정보와 동일한 형태로 --user-agent 옵션을 설정하면, 요청 차단으로 인해 발생할 수 있는 에러를 방지할 수 있습니다. @hatiolab님, 공유 감사합니다. "--user-agent=Mozilla/5.0"를 추가해도 FileNotFoundError가 발생합니다. 현재도 "--user-agent=Mozilla/5.0"를 추가하면 정상적으로 불러와지나요? 링크 복사 다른 사이트에 공유하기 More sharing options...
hammubara Posted July 29 공유하기 Posted July 29 27 minutes ago, hammubara said: "--user-agent=Mozilla/5.0"를 추가해도 FileNotFoundError가 발생합니다. 현재도 "--user-agent=Mozilla/5.0"를 추가하면 정상적으로 불러와지나요? 해결했습니다. 윈도우환경인데...ㅎㅎ... 제가 착각했네요. 1 링크 복사 다른 사이트에 공유하기 More sharing options...
hammubara Posted July 30 공유하기 Posted July 30 Quote class CompletionExecutor: def __init__(self, host, api_key, api_key_primary_val, request_id): self._host = host self._api_key = api_key self._api_key_primary_val = api_key_primary_val self._request_id = request_id def execute(self, completion_request, response_type="stream"): headers = { "X-NCP-CLOVASTUDIO-API-KEY": self._api_key, "X-NCP-APIGW-API-KEY": self._api_key_primary_val, "X-NCP-CLOVASTUDIO-REQUEST-ID": self._request_id, "Content-Type": "application/json; charset=utf-8", "Accept": "text/event-stream" } final_answer = "" with requests.post( self._host + "/testapp/v1/chat-completions/HCX-003", headers=headers, json=completion_request, stream=True ) as r: if response_type == "stream": longest_line = "" for line in r.iter_lines(): if line: decoded_line = line.decode("utf-8") if decoded_line.startswith("data:"): event_data = json.loads(decoded_line[len("data:"):]) message_content = event_data.get("message", {}).get("content", "") if len(message_content) > len(longest_line): longest_line = message_content final_answer = longest_line elif response_type == "single": final_answer = r.json() # 가정: 단일 응답이 JSON 형태로 반환됨 Quote # 사용자의 쿼리를 임베딩하는 함수를 먼저 정의 def query_embed(text: str): request_data = {"text": text} response_data = embedding_executor.execute(request_data) return response_data Quote def html_chat(realquery: str) -> str: # 사용자 쿼리 벡터화 query_vector = query_embed(realquery) collection.load() search_params = {"metric_type": "IP", "params": {"ef": 64}} results = collection.search( data=[query_vector], # 검색할 벡터 데이터 anns_field="embedding", # 검색을 수행할 벡터 필드 지정 param=search_params, limit=10, output_fields=["source", "text"] ) reference = [] for hit in results[0]: distance = hit.distance source = hit.entity.get("source") text = hit.entity.get("text") reference.append({"distance": distance, "source": source, "text": text}) completion_executor = CompletionExecutor( host="https://clovastudio.stream.ntruss.com", api_key='~~~', api_key_primary_val='~~~', request_id='~~~' ) preset_texts = [ { "role": "system", "content": "- 너의 역할은 사용자의 질문에 reference를 바탕으로 답변하는거야. \n- 너가 가지고있는 지식은 모두 배제하고, 주어진 reference의 내용만을 바탕으로 답변해야해. \n- 답변의 출처가 되는 html의 내용인 'source'도 답변과 함께 {url:}의 형태로 제공해야해. \n- 만약 사용자의 질문이 reference와 관련이 없다면, {제가 가지고 있는 정보로는 답변할 수 없습니다.}라고만 반드시 말해야해." } ] for ref in reference: preset_texts.append( { "role": "system", "content": f"reference: {ref['text']}, url: {ref['source']}" } ) preset_texts.append({"role": "user", "content": realquery}) request_data = { "messages": preset_texts, "topP": 0.6, "topK": 0, "maxTokens": 1024, "temperature": 0.5, "repeatPenalty": 1.2, "stopBefore": [], "includeAiFilters": False } # LLM 생성 답변 반환 response_data = completion_executor.execute(request_data) return response_data 위의 코드들을 차례대로 실행했는데 사진과 같이 response가 None으로만 나옵니다. 혹시 api_key, api_key_primary_val, request_id 이외에도 수정해주어야 하는 부분이 있을까요? 링크 복사 다른 사이트에 공유하기 More sharing options...
hammubara Posted July 30 공유하기 Posted July 30 3 hours ago, hammubara said: 위의 코드들을 차례대로 실행했는데 사진과 같이 response가 None으로만 나옵니다. 혹시 api_key, api_key_primary_val, request_id 이외에도 수정해주어야 하는 부분이 있을까요? 자문자답합니다. CompletionExecutor.execute 에 return final_answer가 없네요. 1 링크 복사 다른 사이트에 공유하기 More sharing options...
Recommended Posts
게시글 및 댓글을 작성하려면 로그인 해주세요.
로그인