데이터사이언스 오리지널
아래 내용은 AWSKRUG (AWS 한국 사용자모임) 데이터 소모임에서 발표한 내용입니다.
https://www.meetup.com/ko-KR/awskrug/events/299642049/

ES의 독주를 추격하는 검색엔진 meilisearch
💡 Meilisearch는 RESTful 검색 API 입니다 . 빠르고 관련성 높은 검색 경험을 원하는 모든 사람을 위한 즉시 사용 가능한 솔루션을 목표로 합니다 ⚡️🔎

https://where2watch.meilisearch.com/


Given a set of words in a dataset:
film cinema movies show harry potter shine musical
query: s: response:
show
shine
but not
movies
musical
query: sho: response:
show





아래광고는
only ES사용해서 검색서비스를 구축하기가 어렵다는 것을 반증합니다.
왜냐하면?
따지자면 알골리아가 비빌만한 경쟁서비스 (하지만 알골리아는 유료)

Syncing databases with PostgreSQL and meilisync — Meilisearch documentation
Postman collection for Meilisearch — Meilisearch documentation
Deploy a Meilisearch instance on AWS — Meilisearch documentation
Integrate Meilisearch Cloud with Vercel — Meilisearch documentation
도커로 합니다.
이미지를 가져옵니다

일단 고~

친절하게 마스터키(api key) 만들어줌
다시고

화면가보자 localhost:7700
아까 만들어준 키를 입력하자

💡 👏👏👏 완료~ 참쉽죠?
샘플데이터 : 나무위키 덤프
heegyu/namuwiki · Datasets at Hugging Face
pip3 install datasets jsonlines meilisearch
# load.py
########### 데이터셋을 파일로 내립니다. ('걸그룹' 이란 단어가 포함된 문서만)
from datasets import load_dataset
dataset = load_dataset("heegyu/namuwiki", split='train[:90%]')
filtered = dataset.filter(lambda x: len(x['text']) < 3000).filter(lambda x: '걸그룹' in x['text'])
print(len(filtered))
filtered.to_json("girlgroup.ndjson")
########### 파일을 읽어서 간단한 전처리 수행합니다.
import jsonlines
def mediaWiki_to_markdown(text):
# 헤더 변환
text = re.sub(r'(={2,})(.*?)\1', lambda match: f"{'#' * len(match.group(1))} {match.group(2)}", text)
# 굵은 글씨 변환
text = re.sub(r"'''(.*?)'''", r"**\1**", text)
# 기울임 변환
text = re.sub(r"''(.*?)''", r"*\1*", text)
# 링크 변환
text = re.sub(r'\[\[(.*?)\]\]', r'[\1](\1)', text)
text = re.sub(r'\[\[(.*?)\|(.*?)\]\]', r'[\2](\1)', text)
# 이미지 변환
text = re.sub(r'\[\[파일:(.*?)\|.*?\]\]', r'', text)
# 목록 변환
text = re.sub(r'^\* (.*)', r'* \1', text, flags=re.MULTILINE)
# 인용 변환
text = re.sub(r'^>(.*)', r'> \1', text, flags=re.MULTILINE)
return text
documents = []
with jsonlines.open("girlgroup.ndjson") as f:
for i, line in enumerate(f.iter()):
# 너무 긴 문서는 제외
if len(line['text']) > 3000:
continue
line["id"] = i
line['text'] = mediaWiki_to_markdown(line['text'])
documents.append(line)
docs = documents
# 검색엔진에 넣습니다. python sdk 사용
import meilisearch
client = meilisearch.Client('http://localhost:7700', '1bc7eb62470c77149919')
# 인덱스가 존재하면 삭제
client.index('girlgroup').delete()
print('index deleted')
client.create_index('girlgroup', {'primaryKey': 'id'})
print(f"girlgroup created successfully")
client.index('girlgroup').add_documents(docs)

localhost:7700
streamlit을 활용해서 간단하게 검색엔진meilisearch+openai chat_completion을 결합하여 RAG 서비스를 모델링 합니다.
LLM을 활용한 생성형 AI(chatGTP)서비스의 최대 단점인 할루시네이션(템플러 생각나면 나랑 같은 세대... 좋아요 눌러주세요)을 보완하기 위한 꼼수?
LLM에 질문과 답변의 맥락 정보를 제공해서 적절한 답변을 생성하게 합니다. (헛소리를 못하게 막음)
💡 검색-증강 생성이란 무엇인가요?
RAG(Retrieval-Augmented Generation)는 대규모 언어 모델의 출력을 최적화하여 응답을 생성하기 전에 학습 데이터 소스 외부의 신뢰할 수 있는 지식 베이스를 참조하도록 하는 프로세스입니다. 대규모 언어 모델(LLM)은 방대한 양의 데이터를 기반으로 학습되며 수십억 개의 매개 변수를 사용하여 질문에 대한 답변, 언어 번역, 문장 완성과 같은 작업에 대한 독창적인 결과를 생성합니다. RAG는 이미 강력한 LLM의 기능을 특정 도메인이나 조직의 내부 지식 기반으로 확장하므로 모델을 다시 교육할 필요가 없습니다. 이는 LLM 결과를 개선하여 다양한 상황에서 관련성, 정확성 및 유용성을 유지하기 위한 비용 효율적인 접근 방식입니다.
출처 - RAG란?
💡 streamlit
데이터 과학자와 개발자가 빠르게 데이터 애플리케이션을 만들 수 있도록 돕는 오픈소스 파이썬 라이브러리입니다. 사용자는 몇 줄의 코드만으로 인터랙티브한 웹 애플리케이션을 구축할 수 있습니다
https://docs.streamlit.io/
시크릿 관리 방법
-> 아래 위치에 secrets.toml을 만드시고 openai_api_key를 넣습니다.

# This is a TOML document.
# secrets.toml
OPENAI_API_KEY = "sk-XXXXXXXXXXXXXXXXXXXX"
pip3 install streamlit openai
# app.py
import streamlit as st
from openai import OpenAI
import meilisearch
# 검색엔진 API 키 설정
meili = meilisearch.Client('http://localhost:7700', 'meilisearch 서버 띄울때 넣은 MEILI_MASTER_KEY')
# OPENAI 서비스 키 설정
gpt = OpenAI(api_key=st.secrets["OPENAI_API_KEY"])
st.set_page_config(layout="wide")
col1, col2 = st.columns(2, gap="small")
with col1:
st.title('걸그룹 검색기 :tulip::cherry_blossom::rose:')
search = st.form('search')
sentence = search.text_input('검색어 입력')
submit = search.form_submit_button(f'검색!')
if submit:
#search(query: str, opt_params: Mapping[str, Any] | None = None)→ Dict[str, Any]
res = meili.index('girlgroup').search(
'"'+sentence+'"',
# {
# 'hybrid': {
# 'semanticRatio': 0.5,
# 'embedder': 'default'
# }
# }
)
l = res['hits'][:10]
for i in l:
st.title(i['title'])
st.subheader(i['id'])
st.markdown(i['text'].replace(sentence, f":red[{sentence}]") )
st.write('---')
with col2:
st.title("챗봇 🤖")
on = st.toggle('RAG 활성화')
if on:
st.write(':rocket: :red[RAG 작동!]')
def getMessage():
messages = []
template = """Answer the question based only on the following context:
{context}
Question: {question}
"""
if( on ) :
#챗봇에 물어보는 문장을 직접 넣으면 검색엔진이 못찾아서 그냥 첫단어만 짤라서 넣음
#나중에 벡터검색을 활용해서 hybrid_search 로 변경하면 전체 넣어도 됨
qq = prompt.split(' ', 1)[0]
res = meili.index('girlgroup').search(qq)
context = res['hits'][0]['text']
q = template.format(context=context, question=prompt)
messages.append({"role": "user", "content": q})
else:
for message in st.session_state.messages:
messages.append({"role": message["role"], "content": message["content"]})
return messages
if "openai_model" not in st.session_state:
st.session_state["openai_model"] = "gpt-3.5-turbo"
if "messages" not in st.session_state:
st.session_state.messages = []
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
if prompt := st.chat_input("What is up?"):
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.markdown(prompt)
with st.chat_message("assistant"):
print(st.session_state.messages)
stream = gpt.chat.completions.create(
model=st.session_state["openai_model"],
messages=getMessage(),
stream=True,
)
response = st.write_stream(stream)
st.session_state.messages.append({"role": "assistant", "content": response})
실행~
$streamlit run app.py
필요한 정보를 주지 않았기 때문에 의미없는 답변이 돌아 옵니다.
잘 요약해서 답변해 줍니다. 👏👏👏
<참고> 걸그룹 딜라잇

Vector search — Meilisearch documentation
텍스트 임베딩을 통해 의미검색(semantic search)이 가능합니다.
다음 기회에 관련 꿀팁을 정리 해보겠습니다.
meilisearch는 LMDB를 내부 저장소로 활용하고 있기 때문에, LMDB를 알면 특성을 이해하는데 큰 도움이 됩니다. LMDB는 아래와 같은 특징이 있습니다.
- 디스크 IO작업을 최소화 하여 빠른 성능
- 트랜잭션 (ACID) 지원
- 자유로운 스키마 (장점이자 단점)
- 읽기 동시성(Read Concurrency) 지원
- 여러 프로세스에서 동시에 데이터를 읽을 수 있습니다.
- 쓰기 단일 프로세스 제한(Single Writer Process)
- 한 번에 하나의 프로세스만 데이터를 수정할 수 있습니다.
- 다른 프로세스에서 쓰기 작업 중일 때 또 다른 프로세스가 쓰기를 시도하면 실패합니다.
GitHub - meilisearch/charabia: Library used by Meilisearch to tokenize queries and documents