Skip to main content

W&B 문서를 위한 Q&A 봇 만들기

이 글에서�� Weights & Biases 문서를 위한 질문응답(Q&A) 봇을 만드는 방법을 단계별로 설명합니다. 이 기사는 AI 번역본입니다. 오역이 있을 수 있으니 댓글로 알려 주세요.
Created on September 15|Last edited on September 15

소개

이 글은 제가 만든 문서 Q&A 봇에 대한 설명으로, 다음의 일부로 작성되었습니다 Replit x Weights & Biases ML 해커톤. 이 봇은 OpenAI의 GPT-3를 사용하여 자연어 질문과 개발자 관련 질의에 답변합니다 Weights & Biases 문서. 저는 사용합니다 LangChain, OpenAI Embeddings, 그리고 FAISS Q&A 백엔드를 만들고, 봇은 다음과 같이 제공됩니다 그라디오 애플리케이션.
이것은 문서 기반 Q&A가 어떤 모습일지 감을 잡기 위한 매우 단순하고 기초적인 개념 증명입니다. 프로덕션 수준의 애플리케이션으로 만들기 위해 파이프라인 곳곳에서 개선할 여지가 매우 많습니다.
💡
이 글에서 다룰 내용을 간단히 미리 보여드립니다:


시작하기 전에, repl이 어떻게 생겼는지 먼저 보여드릴게요:

replit.com의 repl을 여기에서 확인하세요: 🪄🐝 LangChain과 OpenAI로 만드는 문서 Q&A 봇
출처: 이 봇은 다음 트윗에서 큰 영감을 받았습니다:
💡




문서 데이터세트 만들기

데이터 수집

W&B 문서는 다음에서 확인할 수 있습니다 docs.wandb.ai가이드, API 레퍼런스, 예제를 포함하고 있습니다.
이 작업은 처음 생각했던 것보다 훨씬 까다로웠습니다. 그래서 저는 문서를 수집하는 다른 방식을 택했습니다. W&B/docodile 대신 GitHub 저장소를 사용했습니다. 저장소에서는 각 웹페이지가 사이트의 트리 구조를 반영한 하위 디렉터리에 정리된 마크다운 파일로 표현되어 있습니다. 이는 문서를 파싱하기도 더 수월하게 해주었습니다. 완전성을 위해 상위에서 가져온 데이터도 추가했습니다. 포럼 질문, 지원 로테이션 티켓, 그리고 API 개발자 레퍼런스. 이 추가 데이터는 다음 Google 스프레드시트에 정리되어 있습니다 - wandb_bot 파인튜닝 데이터.

전처리

제가 한 전처리는 연속된 빈 줄을 제거하는 것뿐이었습니다 (예를 들어 \n) 문서 텍스트에서 제거하고 스프레드시트 데이터를 하나의 문서로 병합했습니다. 최종적으로 데이터는 JSONL 파일 형식으로 저장되었으며 source 텍스트의 출처를 나타내는 키입니다. 데이터셋이 담긴 아티팩트는 다음과 같습니다:

docs_dataset
Version overview
Full Name
parambharat/wandb_docs_bot/docs_dataset:v1
Aliases
v1
Tags
Digest
cc8d0cc556fc5f399cda1dca42ce468f
Created By
Created At
February 8th, 2023 05:58:10
Num Consumers
19
Num Files
2
Size
2.7KB
TTL Remaining
Inactive
Upstream Artifacts
Description
다음 코드를 실행하면 데이터셋 아티팩트를 가져올 수 있습니다:
PROJECT = "wandb_docs_bot"
run = wandb.init(project=PROJECT)

def download_raw_dataset():
dataset_artifact_path = 'parambharat/wandb_docs_bot/docs_dataset:latest'
artifact = run.use_artifact(dataset_artifact_path, type='dataset')
artifact_path = artifact.get_path("wandb_docs.json")
file = artifact_path.download()
return file

데이터 수집

문서 생성

다음 단계는 검색과 검색 결과 반환을 위해 문서, 메타데이터, 그리고 해당하는 OpenAI 임베딩을 저장하는 것이었습니다. LangChain은 기본적으로 text-embedding-ada-002 문서를 임베딩하기 위한 모델입니다. 이 모델은 최대 8,191 토큰 길이의 문서에 대해 1,536차원 임베딩을 생성합니다.
그러나 질의 시점에는 검색된 문서들도 함께 전달하여 text-davinci-003 모델입니다. 이 모델의 컨텍스트 길이는 4,096입니다. 따라서 텍스트를 1,024자 단위로 분할하고 각 청크를 LangChain의 문서 해당 소스 파일도 메타데이터로 함께 저장했습니다.
참고: 저는 the CharacterTextSplitter 청크 분할에는 LangChain의 class를 사용했습니다. 원래는 사용했어야 했습니다 TokenTextSplitter.from_tiktoken_encoder 대신
💡
이 단계에서 사용한 코드는 다음과 같습니다:
import json
from langchain.docstore.document import Document
from langchain.text_splitter import CharacterTextSplitter

def load_documents(fname):
source_chunks = []
splitter = CharacterTextSplitter(separator=" ", chunk_size=1024, chunk_overlap=0)
for line in open(fname, "r"):
line = json.loads(line)
for chunk in splitter.split_text(line["reference"]):
source_chunks.append(Document(page_content=chunk, metadata={"source": line["source"]}))
return source_chunks

FAISS 인덱스 생성

마침내 호출할 준비가 되었습니다 OpenAI Embeddings API 엔드포인트 임베딩에 인덱싱된 문서로 저장하여 밀집 벡터 검색과 검색을 수행할 수 있게 합니다. 벡터를 저장하는 방법은 여러 가지가 있지만, 저는 사용했습니다 faiss-gpu pip으로 쉽게 설치해 Replit에서 실행할 수 있기 때문입니다. 프로덕션용이라면 더 탄탄한 벡터 스토어 데이터베이스나 다음과 같은 서비스를 사용하는 것이 좋습니다 qdrant 또는 weaviate.
LangChain을 사용해 문서 임베딩을 생성하고 저장하는 코드입니다.
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.vectorstores.faiss import FAISS

def create_and_save_index(documents):
store = FAISS.from_documents(documents,OpenAIEmbeddings())
artifact = wandb.Artifact("faiss_store", type="search_index")
faiss.write_index(store.index, "docs.index")
artifact.add_file("docs.index")
store.index = None
with artifact.new_file("faiss_store.pkl", "wb") as f:
pickle.dump(store, f)
wandb.log_artifact(artifact, "docs_index", type="embeddings_index")
return store
위 코드에서는 문서 인덱스와 임베딩을 하나의 아티팩트 안에서 각각 별도의 파일로 저장합니다. 아래에서 해당 아티팩트를 확인하세요.
💡

faiss_store
root
docs.index
7.4MB
7.4MB
faiss_store.pkl
1.3MB
1.3MB

다음을 확인하세요 ingest.py 데이터 수집 파이프라인에서 위 코드들이 어떻게 조합되었는지 확인하려면 repl의 파일을 참고하세요.
💡

Q&A 봇 만들기

데이터가 준비되고 올바른 형식으로 정리되었으니, 이제 문서화 봇을 만들 준비가 거의 끝났습니다.
유의할 점: 우리가 만들고자 하는 봇은 대화형 에이전트여야 합니다. GPT-3는 컨텍스트 기반 질의응답에서 제로샷 성능이 꽤 준수함을 보여줬지만, 환각을 최소화할 수 있도록 견고한 프롬프트를 설계해야 합니다. 이제 프롬프트 설계로 넘어가겠습니다.

LLM을 위한 견고한 프롬프트 설계

최근 프롬프트 설계가 예술에서 과학으로 발전해 왔지만, 나는 여전히 그것을 예술에 가깝게 대하는 편입니다. 예를 들어 말하자면, 저는… 영감을 얻었다 에서 그리고 모방했다 해당 사용 사례에 잘 맞는 프롬프트를 설계하기 위해 현업의 다른 프롬프트 엔지니어들에게서 영감을 얻어 참고했습니다. 아래는 제가 봇을 위해 최종적으로 만든 프롬프트입니다:

You are an AI assistant for the open source library wandb. The documentation is located at https://docs.wandb.ai.
You are given the following extracted parts of a long document and a question. Provide a conversational answer with a hyperlink to the documentation.
You should only use hyperlinks that are explicitly listed as a source in the context. Do NOT make up a hyperlink that is not listed.
If the question includes a request for code, provide a code block directly from the documentation.
If you don't know the answer, just say "Hmm, I'm not sure." Don't try to make up an answer.
If the question is not about wandb, politely inform them that you are tuned to only answer questions about wandb.

QUESTION: How to log audio with wandb?
=========
Content: Weights & Biases supports logging audio data arrays or file that can be played back in W&B. You can log audio with `wandb.Audio()`
Source: 28-pl
Content: # Log an audio array or file
wandb.log({{"my whale song": wandb.Audio(
    array_or_path, caption="montery whale 0034", sample_rate=32)}})

# OR  

# Log your audio as part of a W&B Table
my_table = wandb.Table(columns=["audio", "spectrogram", "bird_class", "prediction"])
for (audio_arr, spec, label) in my_data:
       pred = model(audio)
       
       # Add the data to a W&B Table
       audio = wandb.Audio(audio_arr, sample_rate=32)
       img = wandb.Image(spec)
       my_table.add_data(audio, img, label, pred) 

# Log the Table to wandb
 wandb.log({{"validation_samples" : my_table}})'
Source: 30-pl
=========
FINAL ANSWER: Here is an example of how to log audio with wandb:

```
import wandb

# Create an instance of the wandb.data_types.Audio class
audio = wandb.data_types.Audio(data_or_path="path/to/audio.wav", sample_rate=44100, caption="My audio clip")

# Get information about the audio clip
durations = audio.durations()
sample_rates = audio.sample_rates()

# Log the audio clip
wandb.log({{"audio": audio}})
```
SOURCES: 28-pl 30-pl

QUESTION: How to eat vegetables using pandas?
=========
Content: ExtensionArray.repeat(repeats, axis=None) Returns a new ExtensionArray where each element of the current ExtensionArray is repeated consecutively a given number of times. 

Parameters: repeats int or array of ints. The number of repetitions for each element. This should be a positive integer. Repeating 0 times will return an empty array. axis (0 or ‘index’, 1 or ‘columns’), default 0 The axis along which to repeat values. Currently only axis=0 is supported.
Source: 0-pl
=========
FINAL ANSWER: You can't eat vegetables using pandas. You can only eat them using your mouth.
SOURCES:

Question: {question}
=========
{summaries}
=========
Answer in Markdown:
프롬프트는 꽤 길지만, 언어 모델이 어떻게 동작하길 원하는지 정확히 설명하고, 원하는 방식으로 응답을 생성하도록 보장하기 위해 몇-shot 예시도 함께 제공합니다.
참고: LangChain 프롬프트 사용법 진자 템플릿. 여기서 a {xxx} 는 자리표시자 텍스트에 사용됩니다. 프롬프트에 코드 블록이 있다면 이스케이프할 수 있습니다. { 이중으로 {{ .
💡
위의 프롬프트 템플릿은 다음 코드 스니펫을 사용해 쉽게 다운로드할 수 있습니다.
def load_prompt():
dataset_artifact_path = 'parambharat/wandb_docs_bot/docs_dataset:latest'
artifact = run.use_artifact(dataset_artifact_path, type='dataset')
artifact_path = artifact.get_path("combine_prompt.txt")
file = artifact_path.download()
prompt_template = (open(file, "r").read())
prompt = PromptTemplate(input_variables=["question", "summaries"],
template=prompt_template)
return prompt

Q&A 파이프라인 구축

질의 시점에 문서를 참조하는 Q&A 파이프라인을 만들기 위해, 나는 다음을 활용했다 VectorDBQAWithSourcesChain LangChain에서입니다. 이름에서 알 수 있듯이, 이 체인은 벡터 스토어를 사용해 주어진 질의와 가장 가까운 임베딩을 먼저 조회합니다. 이렇게 검색된 문서들은 질의와 함께 프롬프트에 삽입된 뒤 LLM으로 전달되어 응답을 생성합니다. 이를 구현하는 코드 스니펫은 다음과 같습니다.
def load_chain(openai_api_key):
if validate_openai_key(openai_api_key):
vectorstore = load_vectostore()
prompt = load_prompt()
chain = VectorDBQAWithSourcesChain.from_chain_type(
llm=OpenAI(temperature=0, openai_api_key=openai_api_key),
chain_type="map_reduce",
vectorstore=vectorstore,
combine_prompt=prompt,)
return chain

def get_answer(question, chain):
if chain is not None:
result = chain(
{
"question": question,
},
return_only_outputs=True,
)
response = f"Answer:\t{result['answer']}\n\nSources:\t{result['sources']}\n"
return response

채팅 인터페이스 만들기

채팅 인터페이스를 만들 때는 사용자 입력, 데이터, 모델 응답이 상태를 유지하도록 저장되는지가 중요합니다. 이렇게 해야 후속 질의가 채팅의 상태를 활용할 수 있고, UI에서 사용자 질의와 모델 응답을 포함한 전체 대화를 완전하게 렌더링할 수 있습니다. 이를 위해 채팅 시작 시 위의 Q&A 체인이 초기화되도록 보장하는 래퍼 클래스를 만듭니다. 코드는 다음과 같습니다.
class Chat:
def __init__(self):
self.chain = None

def __call__(self, message, history, openai_api_key):
if self.chain is None:
self.chain = load_chain(openai_api_key)

history = history or []
message = message.lower()
response = get_answer(message, self.chain)
if response is None:
response = "Please enter a valid Openai API Key and try again. "
history.append((message, response))
return history, history
첫 호출 시 초기화된 체인을 저장하기 위해 클래스를 사용하고, 대화 상태는 history 변수에 유지한다는 점에 유의하세요.
💡

사용자 인터페이스

사용하기 그라디오 애플리케이션을 위한 간단한 UI를 만드는 것은 믿을 수 없을 만큼 쉬웠습니다. 이 라이브러리는 심지어 Chatbot 텍스트 기반 챗봇 인터페이스를 구현하는 클래스입니다. 사용자의 질문과 OpenAI API 키를 텍스트 입력으로 받아, LLM의 응답을 출력하는 매우 최소한의 간단한 인터페이스를 만들었습니다. 이를 위한 코드는 다음과 같습니다.
with gr.Blocks() as demo:
with gr.Row():
question = gr.Textbox(
label='Type in your questions about wandb here and press Enter!',
placeholder='How do i log images with wandb ?')
openai_api_key = gr.Textbox(type='password',
label="Enter your OpenAI API key here",)
state = gr.State()
chatbot = gr.Chatbot()
question.submit(Chat(), [question, state, openai_api_key], [chatbot, state])

위 코드에는 봇 소개와 사용법을 안내하는 간단한 HTML 블록도 추가했습니다. 전체 코드는 다음에서 확인할 수 있습니다. main.py repl의 파일
💡

최종 애플리케이션은 아래에서 확인할 수 있습니다.


마무리 및 향후 과제

이번 해커톤은 정말 멋지고 재미있는 기회였고, 마음껏 즐길 수 있었습니다. 기존 데이터와 리소스 위에 LLM을 활용해 흥미로운 애플리케이션을 많이 만들 수 있다는 것을 배웠습니다. 또한 임베딩과 시맨틱 검색을 사용해 LLM의 프롬프트 길이 제한을 극복할 수 있는 방법도 이해하게 되었습니다. 이번에 만든 챗봇은 꽤 단순하고 개선 여지가 많지만, 여전히 LLM을 활용해 반복적 업무를 자동화하고 풍부한 사용자 경험을 만드는 강력한 방식이라고 생각합니다.
이 프로젝트는 LLM의 더 많은 활용 사례를 만들어 보고 싶다는 동기도 주었습니다. 현재 사이드 프로젝트로 탐색 중인 아이디어 중 하나는 장과 요약을 생성하는 것입니다. 그래디언트 디센트 LLM 임베딩과 LangChain을 사용해 에피소드를 생성하고 있습니다. 곧 보고서를 게시하고 소식 전해 드릴게요!

이 글은 AI 번역본입니다. 오역이 있을 수 있으니 댓글로 알려 주세요. 원문 보고서는 아래 링크에서 확인하실 수 있습니다: 원문 보고서 보기
artifact
artifact
File<{extension: txt}>