Cloud/ElasticSearch

[실습] ChatGPT로 Elastic Docs Chatbot 만들기

y-seo 2024. 1. 2. 17:10

ElasticDocs GPT 로직

  1. UI를 통해 사용자가 질의를 입력한다.
  2. Elastic 하이브리드에 검색을 요청한다.
    • 영문 데이터를 수집하여 elasticsearch에 저장하고 vectorization을 하여 학습한다.
  3. 문서 본문과 URL을 반환한다.
  4. Open API Chat Completion에서 API를 호출하낟.
  5. 도메인 지식을 활용한 답변을 반환한다.
  6. Python에서 생성된 응답을 출력한다.

 

준비물

  • ML 노드가 활성화 된 Elastic Cloud 계정
  • Search용 크롤링 인덱스
  • Open API key
  • Elastic Cloud에 준비된 모델

 

1. 수집 준비 하기

데이터 크롤링은 Elastic crawler을 사용하였다.

(1) Integrations > web crawler 에서 index를 생성한다. 

(2) index name과 domain url을 설정한다.

(3) Crawl rule을 설정한다.

이 포스팅에서는 영어만 긁어올 수 있도록 하였다. 설정은 위의 목록이 최우선으로 작동한다.

(4) Mapping 조정을 위해 Dev Tools에서 아래 Query를 실행한다.

POST search-elastic-docs/_mapping
{
  "properties": {
    "title-vector": {
      "type": "dense_vector",
      "dims": 768,
      "index": true,
      "similarity": "dot_product"
    }
  }
}

(5) (3)에 이어서 Pipeline을 아래와 같이 설정한다.

(6) Add inference pipeline에서 아래와 같이 설정하여 Create한다.

 

2. 크롤링 하기

(1) Search > Contecnt > Elasicsearch indices 에 접근하여 Crawl 한다.

(2) Overview에서 Document count가 늘어나는 것을 확인할 수 있다.

(3) DevTool에서 document 모양새를 확인할 수 있다.

GET search-elastic-docs/_mapping

GET search-elastic-docs/_search
{
  "size": 10,
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "title": {
              "query": "how to create index",
              "boost": 1
            }
          }
        }
      ],
      "filter": [
        {
          "exists": {
            "field": "title"
          }
        }
      ]
    }
  },
  "knn": {
    "field": "title-vector",
    "k": 1,
    "num_candidates": 20,
    "query_vector_builder": {
      "text_embedding": {
        "model_id": "sentence-transformers__all-distilroberta-v1",
        "model_text": "how to create index"
      }
    },
    "boost": 24
  }
}

위 재료로 vector search를 진행할 수 있다.

 

3. Python으로 구현하기

Elasticsearch NLP 검색을 이용한 CHatGPT 답변을 구현한다. 이때 UI는 Streamlit을 사용했다.

(1) Open AI, Streamlit, Localtunnel을 설치한다.

!pip install openai==0.28
!pip install streamlit
!npm install localtunnel
!pip install -U typing_extensions
!pip install elasticsearch

(2) 코드 작성을 한다.

%%writefile elastic_gpt_app.py

import os
import streamlit as st
import openai
from elasticsearch import Elasticsearch

# openai apy key 입력
openai_api = ''

openai.api_key = openai_api
model = "gpt-3.5-turbo-0301"

def es_connect(cid, user, passwd):
    es = Elasticsearch(cloud_id=cid, http_auth=(user, passwd))
    return es

# Elasticsearch Search
def search(query_text):
# ES Cloud ID, Username, Password 입력
    cid = ''
    cu = ''
    cp = ''
    es = es_connect(cid, cu, cp)

    # Elasticsearch query (BM25) and kNN configuration for hybrid search
    query = {
        "bool": {
            "must": [{
                "match": {
                    "title": {
                        "query": query_text,
                        "boost": 1
                    }
                }
            }],
            "filter": [{
                "exists": {
                    "field": "title"
                }
            }]
        }
    }

    knn = {
        "field": "title-vector",
        "k": 1,
        "num_candidates": 20,
        "query_vector_builder": {
            "text_embedding": {
                "model_id": "sentence-transformers__all-distilroberta-v1",
                "model_text": query_text
            }
        },
        "boost": 24
    }

    fields = ["title", "body_content", "url"]
    index = 'search-elastic-docs'
    resp = es.search(index=index,
                     query=query,
                     knn=knn,
                     fields=fields,
                     size=1,
                     source=False)

    body = resp['hits']['hits'][0]['fields']['body_content'][0]
    url = resp['hits']['hits'][0]['fields']['url'][0]

    return body, url

def truncate_text(text, max_tokens):
    tokens = text.split()
    if len(tokens) <= max_tokens:
        return text

    return ' '.join(tokens[:max_tokens])

# Chat_gpt 답변 생성
def chat_gpt(prompt, model="gpt-3.5-turbo", max_tokens=1024, max_context_tokens=4000, safety_margin=5):
    # Truncate the prompt content to fit within the model's context length
    truncated_prompt = truncate_text(prompt, max_context_tokens - max_tokens - safety_margin)

    response = openai.ChatCompletion.create(model=model,
                                            messages=[{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": truncated_prompt}])

    return response["choices"][0]["message"]["content"]

st.title("ElasticDocs GPT")

# 입력창
with st.form("chat_form"):
    query = st.text_input("You: ")
    submit_button = st.form_submit_button("Send")

# 답변 출력
negResponse = "I'm unable to answer the question based on the information I have from Elastic Docs."
if submit_button:
    resp, url = search(query)
    prompt = f"Answer this question: {query}\nUsing only the information from this Elastic Doc: {resp}\nIf the answer is not contained in the supplied doc reply '{negResponse}' and nothing else"
    answer = chat_gpt(prompt)

    if negResponse in answer:
        st.write(f"ChatGPT: {answer.strip()}")
    else:
        st.write(f"ChatGPT: {answer.strip()}\n\nDocs: {url}")

 

4. Web UI로 확인하기

(1) Streamlit 실행하기

!streamlit run /content/elastic_gpt_app.py &>/content/logs.txt &

(2) Endpoint IP 출력하기

import urllib
print("IP Endpoint:",urllib.request.urlopen('https://ipv4.icanhazip.com').read().decode('utf8').strip("\n"))

(3) 임시 URL 발급하기

!npx localtunnel --port 8501

(4) (2)에서 나온 IP를 submit한다.