๐RAG์ LangChain์ ํ์ฉํ Chatbot ๊ตฌํ(2) ๊ฒ์๋ฌผ ๋ณด๋ฌ๊ฐ๊ธฐ
์ง๋ ๊ฒ์๋ฌผ์์ chat history์ few shot์ ํตํด
1. ๋ชจ๋ธ์ด ๋ํ์ ๋งฅ๋ฝ์ ํ์ ํ์ฌ ๋๋ตํ๊ณ
2. ๋ต๋ณ์ ์ผ๊ด๋ ์์์ผ๋ก ์ ๊ณตํ๊ณ
3. hallucination ๊ฐ๋ฅ์ฑ์ ์ค์ด๊ณ ๋ต๋ณ์ ์ ํ๋๋ฅผ ํฅ์์์ผ
RAG์ ์ ์ฒด์ ์ธ ํ์ง์ ๋์๋ค.
๋ง์ง๋ง์ผ๋ก ์ฐ๋ฆฌ๊ฐ ์์ฑํ ๋ชจ๋ธ์ evaluation(ํ๊ฐ)๋ฅผ ์ถ๊ฐํด์ RAG์ ์ ๋ขฐ๋์ ํ์ง์ ๋ ๋์ด์.
RAG Evaluation ์ถ๊ฐํ๊ธฐ
์ง๋์น๊ฒ ๊ฐ์กฐํ๋ ๊ฒ ๊ฐ์ง๋ง LLM์ ํฐ ์์ ์ค ํ๋๋ ๊ณ์ ํด์ ์ธ๊ธํ๋ hallucination(ํ๊ฐ) ํ์์ด๋ค. ํ๊ฐ์ผ๋ก LLM์ด ์๋ชป๋ ์ ๋ณด๋ฅผ ์ ๊ณตํ๋๊ฒ์ ์ต์ํํ๊ธฐ ์ํ ๊ธฐ๋ฒ์ด evaluation์ด๋ค. evaluation์ ํตํด ๋ต๋ณ์ด ์ผ๋ง๋ ๋์์ด ๋๋์ง, ๋ ํผ๋ฐ์ค ๋ต๋ณ๊ณผ ๋น๊ตํ์ ๋ ์ผ๋ง๋ ์ ํํ์ง ์์น์์ผ๋ก ํ์ธํ ์ ์๋ค. evaluation tool๋ก๋ LangChain์ LangSmith๋ฅผ ์ฌ์ฉํ๋ค.
RAG Evaluation ๊ฐ๋
RAG๋ฅผ ํ๊ฐ๋ ํฌ๊ฒ ๋ ๊ฐ์ง๋ก ๋๋๋ค.
1. Reference output(์ฐธ์กฐ ๋ต๋ณ)์ด ์๊ตฌ๋๋ ๋ฐฉ์:
RAG ์ฒด์ธ์ ํตํด ์์ฑ๋ ๋ต๋ณ์ ๋ชจ๋ฒ ๋ต์์ด๋ผ๊ณ ํ ์ ์๋ ๋ ํผ๋ฐ์ค ๋ต๋ณ๊ณผ ๋น๊ตํ์ฌ ์ ์ฌํ ์ ๋๋ฅผ ํ๊ฐํ๋ ๋ฐฉ์์ด๋ค.
2. Reference output(์ฐธ์กฐ ๋ต๋ณ)์ด ์๊ตฌ๋์ง ์๋ ๋ฐฉ์:
์์ ๊ทธ๋ฆผ์ ๋ ธ๋์, ์ด๋ก์, ๋นจ๊ฐ์๊ณผ ๊ฐ์ด ๋ต๋ณ์ด ํน์ ๊ธฐ์ค์ ์ผ๋ง๋ ์ถฉ์กฑํ๋์ง๋ฅผ ํ ๋๋ก ํ๊ฐํ๋ ๋ฐฉ์์ด๋ค. ํ๋กฌํํธ๋ฅผ ํ์ฉํด์ ๋์จ ๋ต๋ณ์ด ์ผ๋ง๋ ํ๋นํ์ง ์์ฒด์ ์ผ๋ก ํ๊ฐํ๋ ๋ฐฉ์์ด๋ค.
RAG evaluation์ ์ ์ฉํ๋ ๋ฐฉ์์๋ 3๊ฐ์ง๊ฐ ์๋ค.
1๏ธโฃ Offline evaluation:
โ dataset:
๋ฏธ๋ฆฌ ์์ฑํ๋ ๋ต์ง์ ๊ฐ๋ค. ์ด๋ฌํ ์ง๋ฌธ์ด ๋ค์ด์ค๋ฉด ์ด๋ ๊ฒ ๋ต๋ณํ๋ฉด ๋๋ค๋ฅผ ์์๋ก ์ ์ด๋๋ ๊ฒ์ด๋ค.
๊ฐ๋จํ๊ฒ ์๋ฅผ ๋ค์๋ฉด ์๋๊ฐ ํ๋์ dataset์ด ๋ ์ ์๊ฒ ๋ค.
์ง๋ฌธ: 1+1์ ๊ณ์ฐํ๋ฉด ๋ญ๊ฐ ๋์ค๋์?
๋ต๋ณ: 1+1์ ๊ณ์ฐํ๋ฉด 2๊ฐ ๋์ต๋๋ค!
few shot๊ณผ ๋น์ทํ๋ค๊ณ ์๊ฐํ ์ ์์ผ๋, ์ฌ์ฉ ๋ชฉ์ ์ ์์ด ์กฐ๊ธ ๋ค๋ฅด๋ค. few shot์ ๋ชจ๋ธ์ด ๋ต๋ณ์ ์ฐธ๊ณ ํ์ฌ ๋ต๋ณ ์คํ์ผ์ด๋ ํ์์ ๋ง์ถฐ ๋ต๋ณํ ์ ์๋๋ก ํ๋๊ฒ ๋ชฉ์ ์ด๊ณ , rag evaluation์์์ dataset์ ๋ชจ๋ธ์ด ์์ฑํ ๋ต๋ณ์ด ๋ฏธ๋ฆฌ ์์ฑํ dataset๊ณผ ์ผ๋ง๋ ๊ฐ๊น์ด์ง ์ ์๋ฅผ ๋งค๊ฒจ ํ๊ฐํ๋ ๊ฒ์ด ๋ชฉ์ ์ด๋ค!
๋ฏธ๋ฆฌ ์์งํ ์ง๋ฌธ๊ณผ ๋ต๋ณ์ผ๋ก ์ด๋ฃจ์ด์ง dataset๋ค์ ๊ธฐ๋ฐ์ผ๋ก ์์ฉ ํ๋ก๊ทธ๋จ์ ์คํํ์ ๋ ๋์ค๋ ์ถ๋ ฅ ๊ฐ์ dataset์ ๋ ํผ๋ฐ์ค ๋ต๋ณ๊ณผ ๋น๊ตํด์ ํ๊ฐํ๋ ๋ฐฉ์์ด๋ค. ์ค์๊ฐ์ผ๋ก ์ธก์ ํ๋ ๊ฒ์ด ์๋, dataset์ ๋ฏธ๋ฆฌ ์์งํด์ ํ๊ฐ ๊ฐ์ ๋ด๋ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ ์คํ๋ผ์ธ ํ๊ฐ๋ผ๊ณ ๋ถ๋ฆฐ๋ค.
2๏ธโฃ Online evaluation:
Offline evaluation๊ณผ ๋ฐ๋๋๋ ๊ฐ๋ ์ผ๋ก ๋ฐฐํฌ๋ ํ๋ก๊ทธ๋จ์ ๊ฒฐ๊ณผ๋ฅผ ์ค์๊ฐ์ผ๋ก ํ๊ฐํ๋ ๋ฐฉ์์ด๋ค. ์ค์ ๋ก ์ ์ ๊ฐ ํ๋ก๊ทธ๋จ์ ์ฌ์ฉํ๋ฉด์ input ์ ๋ ฅ ์์ ํ๊ฐ๋ฅผ ์ค์๊ฐ์ผ๋ก ์ํํ๋ค. ์ฆ ์ ์ ์ ํ๋ก๊ทธ๋จ ๊ฐ์ ์ํธ์์ฉ์ ํตํด ํ๊ฐ๋ฅผ ์ํํ๊ธฐ ๋๋ฌธ์ ๋ฏธ๋ฆฌ ์์ฑํ dataset๊ณผ ๋ ํผ๋ฐ์ค ๋ต๋ณ์ด ํ์ ์๋ค. ์ ์ ์ ์ค์ฌ์ฉ์ ํตํด ์์ฑ๋๋ ์ง๋ฌธ๊ณผ ์ถ๋ ฅ๋๋ ๋ต๋ณ์ ํ๊ฐํ๊ธฐ ๋๋ฌธ์ด๋ค.
3๏ธโฃ Pairwise evaluation:
์๋ก ๋ค๋ฅธ ์ฒด์ธ์์ ์์ฑ๋ ๋ต๋ณ์ ๋ํ ํ๊ฐ๋ฅผ ํ ๋ ์ฌ์ฉ๋๋ค. ๋ ๋ต๋ณ์ ๋น๊ตํด์ ์ด๋ค ๋ต๋ณ์ด ๋ ์์ฐ์ค๋ฝ๊ณ ์ ํํ๊ฐ ๊ฐ์ ์ ํ์ฑ ํ๊ฐ์๋ ์ฌ์ฉ๋๋ฉฐ ๋ต๋ณ ํฌ๋งท์ด๋ ์คํ์ผ์ด ์ผ๋ง๋ ๋ชจ๋ฒ๋ต์๊ณผ ๋น์ทํ๊ฐ, ๋ต๋ณ์ด ์ผ๋ง๋ ์ผ๊ด์ฑ์๊ฒ ์์ฑ๋๋๊ฐ์ ๊ฐ์ ์คํ์ผ๊ณผ ์ผ๊ด์ฑ ํ๊ฐ์๋ ์ฌ์ฉ๋๋ค.
RAG Evaluation ์ ์ฉ
๋จผ์ dataset์ ๋ง๋ค์ด๋ณด์. GPTํํ ์ฐ๋ฆฌ๊ฐ ์ด์ ์ ๋ง๋ค์๋ KBO ์ผ๊ตฌ ๊ท์ ์ง ๋งํฌ๋ค์ด ํ์ผ ์ฃผ๋ฉด์ ๋ฐ์ดํฐ์ ํ 20๊ฐ ์ ๋ ๋ง๋ค์ด์ฃผ๋ฉด ์ ๋ง๋ค์ด์ค๋ค. ๋ง๋ ํ์ผ์ ๋งํฌ๋ค์ด ํ์ผ์ LangSmith์ ๋ฐ์ดํฐ์ ์ ์ถ๊ฐํ๋ฉด ๋๋ค.
RAG evaluation์ ํตํด ํ๊ฐํ ํญ๋ชฉ์ ์ด 4๊ฐ์ง์ด๋ค. ํ๊ฐํ๋ ๊ธฐ์ค์ด ๋๋ ์ด ํญ๋ชฉ๋ค์ evaluator๋ผ๊ณ ๋ถ๋ฅธ๋ค.
์๋์ evaluator๋ค์ ์ฝ๊ฒ ์ดํดํ๊ธฐ ์ํด์ (ํ๊ฐํ๋ ค๋ ๋์ ↔๏ธ ํ๊ฐ์ ๊ธฐ์ค์ด ๋๋ ๋์)์ ๋ฌถ์ด์ ์๊ฐํ๋ฉด ํธํ๋ค. ๊ทธ๋ ๊ฒ ์๊ฐํ๋๊ฒ ํธํ๊ธฐ๋ ํ๊ณ ์ค์ ๋ก ๋ชจ๋ฒ ๋์ถ ๊ฐ(ํ๊ฐ์ ๊ธฐ์ค์ด ๋๋)๊ณผ ๋น๊ตํ์ฌ ์ ์๋ฅผ ์ธก์ ํ๋ ๊ฒ์ด ์ฌ์ค์ด๊ธฐ๋ ํ๋ค.
์ค๋ช ๋ ์ด์ฌํ ์ ์์ง๋ง ๊ทธ๋ฆผ์ผ๋ก ์ดํดํ๋๊ฒ ํจ์ฌ ํธํ๊ณ ๋น ๋ฅด๋ค.
โ LLM-as-judge:
LLM์ ํ๊ฐ์๋ก ํ์ฉํ์ฌ ๋ชจ๋ธ์ ์ถ๋ ฅ ๋ต๋ณ์ ์๋์ผ๋ก ํ๊ฐํ๋ ๋ฐฉ์. ์ฌ๋์ด ์ง์ ํ๊ฐํ๋ ๋์ LLM์ด ์์ฑ๋ ๋ต๋ณ์ ๋ถ์ํ๊ณ ์ ์๋ฅผ ๋งค๊ธฐ๊ณ ํผ๋๋ฐฑ์ ์ ๊ณตํ๋ค.
1๏ธโฃ Correctness:
RAG๋ฅผ ํตํด ์ต์ข ๋์ถ๋ ๋ต๋ณ(response)์ด ์ค์ ๋ชจ๋ฒ ๋ต๋ณ(reference answer)๊ณผ ์ผ๋ง๋ ์ ์ฌํ๊ฐ?๋ฅผ ์ธก์ ํ๋ค. ์ด ๋ ์ ์ฌ๋๋ฅผ ์ธก์ ํ๊ธฐ ์ํด ๋ชจ๋ฒ ๋ต์์ผ๋ก ์ฌ์ฉํ๋ ์ ๋ณด๊ฐ ์ฐ๋ฆฌ๊ฐ ์์ ์์ฑํ ๋ฐ์ดํฐ์ (dataset)์ด๋ค. RAG ๋ต๋ณ๊ณผ ๋ฐ์ดํฐ์ ์ ๋ต๋ณ์ LLM-as-judge(LLM์ ํ์ฉํ์ฌ ๋ ๋ต๋ณ ๊ฐ์ ์ ์ฌํ ์ ๋๋ฅผ ์ธก์ ) ๋ฐฉ์์ ํ์ฉํ์ฌ ์ต์ข correctness๋ฅผ ์ฐ์ ํ๋ค.
2๏ธโฃ Relevance:
RAG๋ฅผ ํตํด ์์ฑ๋ ๋ต๋ณ(response)์ด ์ฒ์์ ์ ์ ๊ฐ ์ ๋ ฅํ ์ ๋ ฅ ๊ฐ(input)์ ์ผ๋ง๋ ์ ์ฒ๋ฆฌํ ์ ์๋์ง๋ฅผ ์ธก์ ํ๋ค. ์์ฑ๋ ๋ต๋ณ๊ณผ ์ ๋ ฅ๊ฐ ๊ณผ์ ์ฐ๊ด์ฑ, ํจ์ฉ์ฑ์ ํ ๋๋ก LLM-as-judge ๋ฐฉ์์ผ๋ก ํ๊ฐํ๊ธฐ ๋๋ฌธ์ ๋ฐ์ดํฐ์ ์ด ํ์ ์๋ค๋ ํน์ง์ด ์๋ค.
3๏ธโฃ Groundness:
RAG๋ฅผ ํตํด ์์ฑ๋ ๋ต๋ณ(response)์ด ์ฐ๋ฆฌ๊ฐ retriever๋ฅผ ํตํด ๊ฒ์ํ ๋ฌธ์(relevant documents)์ ๊ฐ์ ๋ฌธ๋งฅ์ ๊ณต์ ํ๋์ง ์ธก์ ํ๋ค. ์ฝ๊ฒ ๋งํด์ ๋ง๋ ๋ต๋ณ์ด ๊ฒ์ํ ๋ฌธ์๋ ๊ฐ์ ์ฃผ์ ๋ฅผ ๊ณต์ ํ๋์ง, ์๋ฑ๋ง์ ์๋ฆฌ๋ฅผ ํ์ง๋ ์๋์ง๋ฅผ ํ๊ฐํ๋ค. LLM-as-judge ๋ฐฉ์์ผ๋ก ๊ฒ์ ๋ฌธ์์ ๋ต๋ณ์ ๋น๊ตํ์ฌ LLM ์ถฉ์ค๋, ํ๊ฐ(hallucination)์ ์ ๋๋ฅผ ์ธก์ ๋ฐ ํ๊ฐํ๋ค.
4๏ธโฃ Retrieval relevance:
์ ์ ๊ฐ ์ ๋ ฅํ ์ฟผ๋ฆฌ์ retriever๋ฅผ ํตํด ๋์ถ๋ ๊ฒ์๋ ๋ฌธ์ ๊ฐ์ ์ฐ๊ด์ฑ์ ์ธก์ ํ๋ค. ๋ง์ฐฌ๊ฐ์ง๋ก LLM-as-judge ๋ฐฉ์์ผ๋ก ํ๊ฐํ๋ค.
๊ณต์๋ฌธ์๋ฅผ ์ฐธ๊ณ ํด์ ๊ฐ Correctness, Relevance, Groundness, Retrieval relevance๋ฅผ ํ๊ฐํด๋ณด์๋ค.
์... ๋ชจ๋ evaluator์์ ๋ง์ ์ ๋ฐ๊ธฐ๋ ํ๋๋ฐ ์ผ๊ด์ฑ ์๋ ํฌ๋งท์ผ๋ก ๋ต๋ณ์ ํด์ฃผ์ง ์๋๋ค. evaluation์ few-shot์ด ๋น ์ ธ์ ๊ทธ๋ฐ ๋ฏํ๋ few-shot ์ถ๊ฐ ํ์ ๋ค์ ๋๋ ค๋ณด์๋ค.
๋ ํผ๋ฐ์ค output์ด๋ ๋ค๋ฅด๊ฒ ์ถ์ฒ ์์ ํ์ค ๊ณต๋ฐฑ์ด ์๋๊ฒ ์ข ๋ถํธํ๊ธด ํ๋ฐ ๊ทธ๋๋ ์ด๋ ์ ๋ ํฌ๋งท์ ์ง์ผ์ ๋ต๋ณ์ ์ค๋ค. ์ง๊ธ๊น์ง ์์ฑํ evaluation ์ฝ๋๋ ์๋์ ๊ฐ๋ค. ํ๊ตญ์ด ์๋ฒ ๋ฉ ์ฒ๋ฆฌ ๋ฐ ๊ฒ์ ์ฑ๋ฅ์ Upstage๊ฐ ๋ ์ข์์ Upstage๋ฅผ ํ์ฉํ๋ค. ๊ทธ ์ธ์ evaluation ๋ถ๋ถ์์์ LLM-as-judge๋ ๊ณต์๋ฌธ์์์ ํ์ฉํ OpenAI๋ฅผ ํ์ฉํ๋ค.
from langchain_core.prompts import ChatPromptTemplate, FewShotChatMessagePromptTemplate
from langchain_openai import ChatOpenAI
from langchain_upstage import ChatUpstage, UpstageEmbeddings
from langchain_pinecone import PineconeVectorStore
from langsmith import Client, traceable
from typing_extensions import Annotated, TypedDict
from fewshot_doc import answer_examples
# ๋ฌธ์๋ฅผ ๋ถ๋ฌ์ค๊ธฐ ์ํ retriever ์ค์
def get_retriever():
index_name = "baseball-rules-index"
# ๋ฐ์ดํฐ๋ฅผ ๋ฒกํฐํํ ์๋ฒ ๋ฉ ๋ชจ๋ธ ์ค์
embedding = UpstageEmbeddings(model="solar-embedding-1-large")
database = PineconeVectorStore.from_existing_index(index_name=index_name, embedding=embedding)
retriever = database.as_retriever(search_kwargs={"k": 4})
return retriever
# With langchain we can easily turn any vector store into a retrieval component:
retriever = get_retriever()
llm = ChatUpstage()
# Add decorator so this function is traced in LangSmith
@traceable()
def rag_bot(question: str) -> dict:
# langchain Retriever will be automatically traced
docs = retriever.invoke(question)
docs_string = "".join(doc.page_content for doc in docs)
instructions = f"""
๋น์ ์ ์ต๊ณ ์ KBO ๋ฆฌ๊ทธ ์ผ๊ตฌ ๊ท์น ์ ๋ฌธ๊ฐ์
๋๋ค.
์๋์ ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํด์ ์ฌ์ฉ์์ ์ง๋ฌธ์ ๋ํ ๋ต๋ณ์ ํด์ฃผ์ธ์.
๋จ์ฝ์ ์ผ๊ตฌ ๊ท์น๊ณผ ๊ด๋ จ์ด ์๋ค๋ฉด "๐ซ์ผ๊ตฌ ๊ท์น์ ๋ํ ์ง๋ฌธ๋ง ๋ต๋ณ์ด ๊ฐ๋ฅํฉ๋๋ค."๋ผ๊ณ ๋ต๋ณํด์ฃผ์ธ์.
๋ง์ฝ์ ๋ต๋ณํ ์ ์๋ค๋ฉด ๋ชจ๋ฅธ๋ค๊ณ ๋ต๋ณํด์ฃผ์ธ์.
๋ต๋ณ ์์ ์ถ์ฒ์ ๋ํด์๋ ๋ช
ํํ๊ฒ ๋ฐํ์ฃผ์๊ณ , ์ฌ์ฉ์๊ฐ ์ดํดํ๊ธฐ ์ฝ๊ฒ ์ค๋ช
ํด์ฃผ์ธ์.
๋ต๋ณ์ ๊ธธ์ด๋ 2-3์ค ์ ๋๋ก ์ ํํด์ฃผ์ธ์.
Documents:
{docs_string}
"""
example_prompt = ChatPromptTemplate.from_messages(
[
("human", "{input}"),
("ai", "{answer}"),
]
)
few_shot_prompt = FewShotChatMessagePromptTemplate(
example_prompt=example_prompt,
examples=answer_examples,
)
few_shot_messages = few_shot_prompt.format(input=question)
# langchain ChatModel will be automatically traced
ai_msg = llm.invoke(
[
{"role": "system", "content": instructions},
few_shot_messages,
{"role": "user", "content": question},
],
)
return {"answer": ai_msg.content, "documents": docs}
client = Client()
# LangSmith์ ์ถ๊ฐํ ๋ฐ์ดํฐ์
์ด๋ฆ
dataset_name = "baseball_rules_dataset"
# โ
Correctness
# Grade output schema
class CorrectnessGrade(TypedDict):
# Note that the order in the fields are defined is the order in which the model will generate them.
# It is useful to put explanations before responses because it forces the model to think through
# its final response before generating it:
explanation: Annotated[str, ..., "Explain your reasoning for the score"]
correct: Annotated[bool, ..., "True if the answer is correct, False otherwise."]
# Grade prompt
correctness_instructions = """You are a teacher grading a quiz.
You will be given a QUESTION, the GROUND TRUTH (correct) ANSWER, and the STUDENT ANSWER.
Here is the grade criteria to follow:
(1) Grade the student answers based ONLY on their factual accuracy relative to the ground truth answer.
(2) Ensure that the student answer does not contain any conflicting statements.
(3) It is OK if the student answer contains more information than the ground truth answer, as long as it is factually accurate relative to the ground truth answer.
Correctness:
A correctness value of True means that the student's answer meets all of the criteria.
A correctness value of False means that the student's answer does not meet all of the criteria.
Explain your reasoning in a step-by-step manner to ensure your reasoning and conclusion are correct.
Avoid simply stating the correct answer at the outset."""
# Grader LLM
grader_llm = ChatOpenAI(model="gpt-4o-mini", temperature=0).with_structured_output(
CorrectnessGrade, method="json_schema", strict=True
)
def correctness(inputs: dict, outputs: dict, reference_outputs: dict) -> bool:
"""An evaluator for RAG answer accuracy"""
answers = f""" QUESTION: {inputs['question']}
GROUND TRUTH ANSWER: {reference_outputs['answer']}
RAG ANSWER: {outputs['answer']}"""
# Run evaluator
grade = grader_llm.invoke(
[
{"role": "system", "content": correctness_instructions},
{"role": "user", "content": answers},
]
)
return grade["correct"]
# โ
Relevance
# Grade output schema
class RelevanceGrade(TypedDict):
explanation: Annotated[str, ..., "Explain your reasoning for the score"]
relevant: Annotated[
bool, ..., "Provide the score on whether the answer addresses the question"
]
# Grade prompt
relevance_instructions = """You are a teacher grading a quiz.
You will be given a QUESTION and a STUDENT ANSWER.
Here is the grade criteria to follow:
(1) Ensure the STUDENT ANSWER is concise and relevant to the QUESTION
(2) Ensure the STUDENT ANSWER helps to answer the QUESTION
Relevance:
A relevance value of True means that the student's answer meets all of the criteria.
A relevance value of False means that the student's answer does not meet all of the criteria.
Explain your reasoning in a step-by-step manner to ensure your reasoning and conclusion are correct.
Avoid simply stating the correct answer at the outset."""
# Grader LLM
relevance_llm = ChatOpenAI(model="gpt-4o-mini", temperature=0).with_structured_output(
RelevanceGrade, method="json_schema", strict=True
)
# Evaluator
def relevance(inputs: dict, outputs: dict) -> bool:
"""A simple evaluator for RAG answer helpfulness."""
answer = f""" QUESTION: {inputs['question']}
STUDENT ANSWER: {outputs['answer']}"""
grade = relevance_llm.invoke(
[
{"role": "system", "content": relevance_instructions},
{"role": "user", "content": answer},
]
)
return grade["relevant"]
# โ
Groundness
# Grade output schema
class GroundedGrade(TypedDict):
explanation: Annotated[str, ..., "Explain your reasoning for the score"]
grounded: Annotated[
bool, ..., "Provide the score on if the answer hallucinates from the documents"
]
# Grade prompt
grounded_instructions = """You are a teacher grading a quiz.
You will be given FACTS and a STUDENT ANSWER.
Here is the grade criteria to follow:
(1) Ensure the STUDENT ANSWER is grounded in the FACTS.
(2) Ensure the STUDENT ANSWER does not contain "hallucinated" information outside the scope of the FACTS.
Grounded:
A grounded value of True means that the student's answer meets all of the criteria.
A grounded value of False means that the student's answer does not meet all of the criteria.
Explain your reasoning in a step-by-step manner to ensure your reasoning and conclusion are correct.
Avoid simply stating the correct answer at the outset."""
# Grader LLM
grounded_llm = ChatOpenAI(model="gpt-4o-mini", temperature=0).with_structured_output(
GroundedGrade, method="json_schema", strict=True
)
# Evaluator
def groundedness(inputs: dict, outputs: dict) -> bool:
"""A simple evaluator for RAG answer groundedness."""
doc_string = "".join(doc.page_content for doc in outputs["documents"])
answer = f""" FACTS: {doc_string}
STUDENT ANSWER: {outputs['answer']}"""
grade = grounded_llm.invoke(
[
{"role": "system", "content": grounded_instructions},
{"role": "user", "content": answer},
]
)
return grade["grounded"]
# โ
Retrieval Relevance
# Grade output schema
class RetrievalRelevanceGrade(TypedDict):
explanation: Annotated[str, ..., "Explain your reasoning for the score"]
relevant: Annotated[
bool,
...,
"True if the retrieved documents are relevant to the question, False otherwise",
]
# Grade prompt
retrieval_relevance_instructions = """You are a teacher grading a quiz.
You will be given a QUESTION and a set of FACTS provided by the student.
Here is the grade criteria to follow:
(1) You goal is to identify FACTS that are completely unrelated to the QUESTION
(2) If the facts contain ANY keywords or semantic meaning related to the question, consider them relevant
(3) It is OK if the facts have SOME information that is unrelated to the question as long as (2) is met
Relevance:
A relevance value of True means that the FACTS contain ANY keywords or semantic meaning related to the QUESTION and are therefore relevant.
A relevance value of False means that the FACTS are completely unrelated to the QUESTION.
Explain your reasoning in a step-by-step manner to ensure your reasoning and conclusion are correct.
Avoid simply stating the correct answer at the outset."""
# Grader LLM
retrieval_relevance_llm = ChatOpenAI(model="gpt-4o-mini", temperature=0).with_structured_output(
RetrievalRelevanceGrade, method="json_schema", strict=True
)
def retrieval_relevance(inputs: dict, outputs: dict) -> bool:
"""An evaluator for document relevance"""
doc_string = "".join(doc.page_content for doc in outputs["documents"])
answer = f""" FACTS: {doc_string}
QUESTION: {inputs['question']}"""
# Run evaluator
grade = retrieval_relevance_llm.invoke(
[
{"role": "system", "content": retrieval_relevance_instructions},
{"role": "user", "content": answer},
]
)
return grade["relevant"]
def target(inputs: dict) -> dict:
return rag_bot(inputs["question"])
experiment_results = client.evaluate(
target,
data=dataset_name,
evaluators=[correctness, groundedness, relevance, retrieval_relevance],
experiment_prefix="baseball-rules-evaluator",
metadata={"version": "baseball rules v1, gpt-4o-mini"},
)
langsmith evaluation ๋์๋ณด๋์์ ๊ฐ ์คํ ํ์ฐจ ๋ง๋ค์ evaluation ๊ฒฐ๊ณผ๋ฅผ ๊ทธ๋ํ๋ก ๋์ํ ํด์ ํ์ธ ๊ฐ๋ฅํ๋ฉฐ ํ์ฐจ๋ณ evaluator ์ ์ ๋ํ ํ์ธ ๊ฐ๋ฅํ๋ค.
Streamlit์ ํตํด ๋ฐฐํฌ
์ง๊ธ๊น์ง ๋ง๋ ์ฑ๋ด ํ๋ก๊ทธ๋จ์ streamlit์ ํตํด ๋ฐฐํฌํด๋ณด์. ๋จผ์ streamlit์ ์ค์นํ๊ณ streamlit์ ์คํ์ํค๊ธฐ ์ํ ์ฝ๋๋ฅผ ๋ฐ๋ก ์์ฑํ์๋ค.
โ Streamlit:
ํ์ด์ฌ ๊ธฐ๋ฐ์ ์คํ์์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ๋ฐ์ดํฐ ์ ํ๋ฆฌ์ผ์ด์ ์ด๋ ๋์๋ณด๋๋ฅผ ๋น ๋ฅด๊ณ ์ฝ๊ฒ ๋ง๋ค ์ ์๋๋ก ํด์ฃผ๋ ํ๋ ์์ํฌ์ด๋ค. ๋ณ๋์ UI๋ ํ๋ก ํธ ์ฝ๋ ์์ ์์ด ํ์ด์ฌ์ผ๋ก ์์ฑํ ์ฝ๋๋ฅผ ์คํํ๊ธฐ๋ง ํ๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค์ด ์ฃผ๋ ๋ฐฉ์์ด๋ค. ์ฃผ๋ก ๋ฐ์ดํฐ ๋ถ์, ์ธ๊ณต์ง๋ฅ ๋ชจ๋ธ ์๊ฐํ ์์ ์ ๋ง์ด ์ฌ์ฉ๋๋ค.
pip install streamlit
import streamlit as st
from llm import get_ai_response
st.set_page_config(page_title="์ผ๊ตฌ ๊ท์น ์ฑ๋ด", layout="wide")
st.title("โพ๏ธ์ผ๊ตฌ ๊ท์น ์ฑ๋ด")
st.caption("KBO ์ผ๊ตฌ ๊ท์น์ ๋ํ ๋ชจ๋ ๊ฒ์ ๋ฌผ์ด๋ณด์ธ์!")
# session_state์ ์ฑ๋ด๊ณผ ๋๋ ์ฑํ
๊ธฐ๋ก์ด ์กด์ฌํ์ง ์์ผ๋ฉด ์ ์ฅํ๋ ๋น ๋ฐฐ์ด ์์ฑ
if "message_list" not in st.session_state:
st.session_state.message_list = []
# ์ ์ ์ ์ฑ๋ด์ ์ฑํ
๊ธฐ๋ก์ ๋ชจ๋ ์ถ๋ ฅ
for chat in st.session_state.message_list:
with st.chat_message(chat["role"]):
st.write(chat["content"])
# ์ฌ์ฉ์์ ์
๋ ฅ ๋ฐ๊ธฐ
if user_question := st.chat_input(placeholder="์ง๋ฌธ์ ์
๋ ฅํด ์ฃผ์ธ์!"):
# ์ฑํ
์ด "user"๋ฉด ์ง๋ฌธ์ ์ถ๋ ฅ
with st.chat_message("user"):
st.write(user_question)
# ์ ์ ์ ์
๋ ฅ message_list์ ์ถ๊ฐ
st.session_state.message_list.append({"role": "user", "content": user_question})
# ์ฑ๋ด์ ๋ต๋ณ ์์ฑ
with st.spinner("์ ์๋ง ๊ธฐ๋ค๋ ค ์ฃผ์ธ์... ๋ต๋ณ์ ์์ฑํ๋ ์ค์
๋๋ค..."):
ai_response = get_ai_response(user_question)
# ์ฑํ
์ด "ai"๋ฉด ๋ต๋ณ์ ์ถ๋ ฅ
with st.chat_message("ai"):
ai_message = st.write_stream(ai_response)
# ์ฑ๋ด์ ๋ต๋ณ message_list์ ์ถ๊ฐ
st.session_state.message_list.append({"role": "ai", "content": ai_message})
์ฐ๋ฆฌ๊ฐ ์์ฑํ chatbot.py๋ฅผ ํตํด ์ ์ ์ ์ ๋ ฅ์ ๋ฐ์์ rag ์ฒด์ธ ๋จ์ผ๋ก ๋๊ฒจ์ ๋ต๋ณ์ ๋ฐ์๋ด๊ณ ์ด๋ฅผ ๋ค์ streamlit์ ํ๋ฉด์ ์ถ๋ ฅํ๋ค. streamlit์ ํตํด ํ๋ก ํธ์๋ ๋จ์ ๊ฐ๋จํ๊ฒ ๊ตฌ์ถํ๋ ๊ฒ์ด๋ค.
์์ฑํ chatbot.py ์ฝ๋๋ฅผ ๋ก์ปฌ์์ ์คํ์ํค๋ฉด ์๋์ ๊ฐ์ ๊ฒฐ๊ณผ๊ฐ ๋์จ๋ค!
์ ๋์๊ฐ๋ ๊ฒ์ ํ์ธํ์ผ๋ ๋ณธ๊ฒฉ์ ์ผ๋ก ์ด๋ฅผ ๋ฐฐํฌํด๋ณด์. ๋ฐฐํฌ๋ฅผ ํ๊ธฐ ์ํด์๋ ์์ฑํ ํ์ผ์ ๊นํ๋ธ repository์ ์ ๋ก๋ ํ๊ณ streamlit์์ app์ ์์ฑ ํ ๊นํ๋ธ repo ์ฃผ์๋ฅผ ์ถ๊ฐํ์ฌ ์งํํ๋ค.
๐จ ๊ฐ์ํ๊ฒฝ์์ ์ค์นํ ํ์ด์ฌ ํจํค์ง๋ค์ ๋ชจ๋ pip freeze๋ฅผ ํตํด requirements.txt ํ์ผ์ ๋ง๋ค์ด์ ํจ๊ป ๋ ํฌ์ ํธ์ํด์ฃผ์.
Advanced settings์์ ํ๋ก๊ทธ๋จ์ ๋๋ฆฌ๋ ๋ฐ์ ํ์ํ secret key๋ค์ ์ ๋ ฅํด์ฃผ๋ฉด ๋๋ค. ๋ก์ปฌ์์ ํ ์คํธ ํ ๋ .envํ์ผ์ ์ถ๊ฐํ๋ secret key๋ค์ ๋ชจ๋ ์ฌ๊ธฐ์ ์ถ๊ฐํด์ฃผ์(OpenAI API ํค, Langchain API ํค ๋ฑ).
์ด๋ก์จ chatbot ์ ๋ก๋ ๊น์ง ๋๋ฌ๋ค. app ์์ฑ ์ ์ง์ ํ url๋ก ๋ค์ด๊ฐ๋ฉด ์ฑ๋ด์ ํ์ธํ ์ ์๋ค. ์ ํ๋ฆฌ์ผ์ด์ ๊ด๋ จ ์ค์ ์ My apps ํญ์์ ๊ฐ๋ฅํ๋ค.
๐โ๏ธ์ฝ๋ ๋ฐ ํ์ผ์ ๋ชจ๋ ๊นํ๋ธ ๋ ํฌ์ ์ฌ๋ ค๋์์ต๋๋ค. ์์ฑํ ์ฝ๋ ๋ฐ ํ์ผ์ ์ฌ๊ธฐ์ ํ์ธ ๊ฐ๋ฅํฉ๋๋ค!
References
https://docs.smith.langchain.com/evaluation/concepts#retrieval-augmented-generation-rag
'AI' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[AI] RAG์ LangChain์ ํ์ฉํ Chatbot ๊ตฌํ(2) (0) | 2025.02.14 |
---|---|
[AI] RAG์ LangChain์ ํ์ฉํ Chatbot ๊ตฌํ(1) (0) | 2025.02.04 |