[LLM 개발기초] 7. LangSmith 활용하기


이제껏 우리가 실행한 예제들은 코드를 실행하고 화면에 출력되는 텍스트를 통해 결과를 확인했습니다. 설령 결과에 문제가 있다고 해도 앞선 코드들을 하나씩 확인하면서 문제를 찾을 수 있으만큼 선형적이고 단순한 코드들이었습니다.

하지만 앞으로 배울 내용들을 적용하면서 예제의 동작과 코드가 복잡해지면 해결도 점점 어려워집니다. 실행 속도가 느려진다거나 실행결과가 예상과는 다른 문제들이 발생했을 때 어디서 문제가 발생했는지 찾는 것, 즉 디버깅 방법이 필요합니다. LangSmith 가 바로 이런 역할을 위해 고안된 서비스입니다.



LangSmith

LangSmith는 LangChain 생태계의 일부로, 대규모 언어 모델(LLM) 애플리케이션의 개발, 테스트, 평가, 모니터링을 위한 플랫폼입니다. 주요 역할과 기능은 다음과 같습니다.

  • 개발 및 디버깅
    • LLM 애플리케이션의 실행 과정을 시각화하고 추적합니다.
    • 각 단계의 입출력을 상세히 볼 수 있어 디버깅이 용이합니다.
  • 테스트 및 평가
    • 다양한 입력에 대한 모델의 성능을 테스트할 수 있습니다.
    • 자동화된 테스트 케이스를 생성하고 실행할 수 있습니다.
  • 모니터링 및 로깅
    • 프로덕션 환경에서 애플리케이션의 성능을 실시간으로 모니터링합니다.
    • 다양한 메트릭을 추적하고 로그를 수집합니다.
  • 훈련 및 평가에 사용되는 데이터셋 관리
  • 성능 최적화
  • 협업 툴 제공
  • 버전 관리
  • 비용 추적
  • 보안 및 규정 준수
  • LangChain 및 다른 LLM 도구들과 쉽게 통합

말보다는 직접 LangSmith 와 실습코드를 연동해서 어떻게 사용할 수 있는지 확인하는 것이 훨씬 이해가 빠를 것입니다.



LangSmith 실습 1: LangSmith 프로젝트 생성과 동작 모니터링

먼저 아래 사이트에 접속해서 회원가입 절차를 진행합니다.

LangSmith 는 LangChain 이 동작을 수행할 때마다 보내주는 메시지를 받아서 분석하는 웹 서비스이기 때문에 사용자를 구분하는 고유한 API key를 발급받아 설정해야 합니다.

회원가입 후 메인화면에서 -> 좌측 [1] 설정 클릭


좌측 API Keys 클릭 -> 우측 상단의 Create API Key 선택


API Key 를 생성합니다.


API Key 는 외부에 노출되지 않도록 잘 저장해둬야 합니다. API Key 가 생성된 것을 확인했으면 좌측 상단의 Back 을 눌러 메인 화면으로 돌아옵니다.

다시 좌측 [1] 프로젝트를 클릭 -> [2] New Project 클릭


프로젝트 생성을 위한 안내화면이 나옵니다. 아래 붉은 영역에 Project name 을 입력하세요. 그리고 바로 아래에 있는 4개의 환경변수 코드를 복사합니다.


복사한 환경변수를 파이썬 코드에 넣어주고 실행하면 자동으로 프로젝트가 생성됩니다.

앞선 6-2 실습에서 사용한 코드를 약간만 변형해서 LangSmith 와 연동해 보겠습니다. [07_01_langsmith_monitoring.py]

7-1 소스코드를 저장하고 같은 위치에 .env 파일을 만듭니다. 이미 파일이 있다면 파일 내용을 수정해서 써도 됩니다. 위에서 복사한 환경변수 코드를 .env 파일에 넣어주세요.

  • <your-api-key> 부분은 앞서 발급받은 API Key 값으로 대체해야 합니다!
  • LANGCHAIN_PROJECT 의 값도 앞서 설정한 프로젝트 이름으로 넣어주세요. (다른 이름 사용해도 됩니다. 어차피 여기 설정된 이름으로 프로젝트가 생성됩니다.)
LANGCHAIN_TRACING_V2=true
LANGCHAIN_ENDPOINT="https://api.smith.langchain.com"
LANGCHAIN_API_KEY="<your-api-key>"
LANGCHAIN_PROJECT=""

7-1 소스코드는 다음과 같습니다. PDF 파일을 읽어와서 RAG 프로세스를 거쳐 벡터 DB를 만들고, DB에 저장된 내용을 대한 질의-응답을 주고받는 예제입니다.

import os
from dotenv import load_dotenv
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain_community.embeddings import OllamaEmbeddings
from langchain_community.llms import Ollama
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_community.document_loaders import PyPDFLoader

load_dotenv() # .env 파일 로드 <<< 이 부분이 변경됨

# 벡터 DB 파일 경로
VECTOR_DB_PATH = "faiss_index"

# 1. 벡터 DB 파일이 없으면 생성 후 vector_store 리턴
def create_vector_db():
    # 1-1. 문서 로딩 (Document Loading)
    loader = PyPDFLoader("C:\\Workspace\\___Shared\\Llama\\test\\llama_test\\LLM_test\\doc\\news_weather.pdf")
    docs = loader.load()
    print(f"문서의 수: {len(docs)}")

    # 1-2. 문서 분할 (Splitting)
    text_splitter = RecursiveCharacterTextSplitter(chunk_size=700, chunk_overlap=70)
    splits = text_splitter.split_documents(docs)
    print(f"split size: {len(splits)}")

    # 1-3. 임베딩 생성 (Embedding)
    embeddings = OllamaEmbeddings(model="llama3.1")

    # 1-4. 벡터 저장소 구축 (Vector Database)
    vector_store = FAISS.from_documents(
        documents=splits, 
        embedding=embeddings, 
    )
    
    # 1-5. 벡터 DB를 로컬에 저장
    vector_store.save_local(VECTOR_DB_PATH)
    
    return vector_store

# 2. 메인 로직
if os.path.exists(VECTOR_DB_PATH):
    print("기존 벡터 DB를 로드합니다.")
    embeddings = OllamaEmbeddings(model="llama3.1")
    vector_store = FAISS.load_local(
        VECTOR_DB_PATH, 
        embeddings, 
        allow_dangerous_deserialization=True # 믿을 수 있는 소스임을 확인
    )
else:
    print("새로운 벡터 DB를 생성합니다.")
    vector_store = create_vector_db()


# 3. 쿼리 저장소 검색을 위한 retriever 생성
retriever = vector_store.as_retriever()

# 4. PROMPT Template 생성
prompt = PromptTemplate.from_template(
"""당신은 질문-답변(Question-Answering)을 수행하는 AI 어시스턴트입니다. 당신의 임무는 주어진 문맥(context) 에서 주어진 질문(question) 에 답하는 것입니다.
검색된 다음 문맥(context) 을 사용하여 질문(question) 에 답하세요. 만약, 주어진 문맥(context) 에서 답을 찾을 수 없다면, 답을 모른다면 `주어진 정보에서 질문에 대한 정보를 찾을 수 없습니다` 라고 답하세요.
질문과 관련성이 높은 내용만 답변하고 추측된 내용을 생성하지 마세요. 기술적인 용어나 이름은 번역하지 않고 그대로 사용해 주세요.

#Question: 
{question} 

#Context: 
{context} 

#Answer:"""
)

# 5. Ollama 초기화
llm = Ollama(
    model="llama3.1",
    temperature=0
)

# 6. 체인을 생성합니다.
chain = prompt | llm | StrOutputParser()

# 7. chain 실행 및 결과 출력을 반복
while True:
    # 7-1. 사용자의 입력을 기다림
    question = input("\n\n당신: ")
    if question == "끝" or question == "exit":
        break

    # 7-2. 쿼리 처리 (Query-Retriever) : 벡터 DB 에서 참고할 문서 검색
    retrieved_docs = retriever.invoke(question)
    print(f"retrieved size: {len(retrieved_docs)}")
    combined_docs = "\n\n".join(doc.page_content for doc in retrieved_docs)

    # 7-3. 검색된 문서를 첨부해서 PROMPT 생성
    formatted_prompt = {"context": combined_docs, "question": question}

    # 7-4. 체인을 실행하고 결과를 stream 형태로 출력
    result = ""
    for chunk in chain.stream(formatted_prompt):
        print(chunk, end="", flush=True)
        result += chunk

코드가 시작되는 부분에 환경변수를 읽어오는 코드가 있습니다. 앞서 우리가 설정한 환경변수가 여기서 로드됩니다.

load_dotenv() # .env 파일 로드 <<< 이 부분이 변경됨

이후에 실행되는 LangChain 코드는 자동으로 환경변수가 있는지 확인해서 환경변수가 설정되어 있으면 LangSmith 서버로 현재 동작 상태를 전송합니다.

위 코드를 한번 실행해서 LangChain 을 동작시킵니다. 질문을 입력해서 응답이 나오는 것을 확인합니다.

> python -u "c:\Workspace\___Shared\Llama\test\llama_test\LLM_test\07_01_langsmith_prompt.py"
기존 벡터 DB를 로드합니다.


당신: 극한 호우의 원인은?

retrieved size: 4
극한 호우의 원인은 뭘까. .......

이제 LangSmith 메인화면으로 돌아가서 [1] 프로젝트를 누릅니다.

환경변수에 넣은 이름대로 프로젝트가 생성되어 있을겁니다. 프로젝트를 클릭해보세요.


상단의 [All Runs] 탭을 눌러보세요.


그럼 5개의 작업이 LangSmith 에 기록으로 남아있을겁니다.

  • StrOutputParser / Ollama / PromptTemplate / RunnableSequence / Retriever



가장 아래 Retriever 작업부터 보겠습니다.


  • Retriever 는 이름처럼 우리가 던진 질문으로 벡터 DB를 검색해서 얻은 결과입니다. 총 4개의 결과가 검색되었고, 이 결과들이 Prompt Template 에 있는 {context} 영역에 들어갔음을 알 수 있습니다.
  • 가장 우측 패널을 보면 실행시간(2.44s)도 알 수 있습니다.



다음 RunnableSequence 는 LLM 에 입력한 prompt 와 응답을 보여줍니다.

  • StrOutputParser / Ollama / PromptTemplate / RunnableSequence / Retriever
  • 좌측 패널(TRACE 섹션)에 어떤 LLM 서비스를 사용했는지 모델명과 함께 표시됩니다.
    • Ollama 로 표시되는 LLM 서비스를 선택하면 LLM 이 어떻게 동작했는지 알려줍니다.
    • 실행한 langchain 동작이 복잡한 단계를 거쳤다면 좌측 패널에 세부 단계까지 표시됩니다
  • 우측 패널에는 동작시간과 성공여부등이 표시됩니다.
  • 우측 상단의 [Compare] 버튼을 누르면 다른 LLM 서비스의 기록과 비교할 수 있습니다.
  • 상단 [Add to Dataset] 버튼을 눌러서 input / output 값을 저장할 수 있습니다.
    • 데이터셋으로 저장하면 추후 코드에서 모델의 응답을 평가하는 기준으로 사용할 수 있습니다.
    • 데이터셋은 좌측 [Datasets and Testing] 메뉴를 통해 관리됩니다.



StrOutputParser / Ollama / PromptTemplate 세 항목은 어딘가 익숙한 단어들일겁니다.

  • StrOutputParser / Ollama / PromptTemplate / RunnableSequence / Retriever

우리가 이제까지 해온 실습들에서 LCEL 이라는 체인 연결 문법을 사용해서 개별적으로 생성하고 설정한 Prompt – LLM – Output parser 를 연결했습니다.

# chain 연결 (LCEL)
chain = prompt | llm | output_parser

이때 chain 을 구성하는 각 항목들에 대한 실행 기록이 LangSmith 에 저장된 것입니다.

StrOutputParser / Ollama / PromptTemplate 세 항목을 선택했을 때 표시되는 내용은 Retriever / RunnableSequence 에서 확인할 수 있는 내용들입니다.

이 중 Ollama 로 표시되는 LLM 항목을 클릭하면 아래와 같이 표시됩니다. 여기서 상단의 [Playground] 메뉴를 선택하면 재밌는 기능을 사용할 수 있습니다.


Playground 를 누르면 아래처럼 화면이 전환됩니다.


우측 패널에서 provider를 바꿔서 테스트를 실행하면 같은 프롬프트에 대한 AI 서비스 간의 응답 차이를 비교해볼 수 있습니다.

  • 단, 각 provider 의 LLM 실행을 위해서는 해당 서비스의 API Key 를 입력해야 합니다.
  • 입력한 API Key 는 [Settings > Secrets] 에 저장됩니다.
  • 실행 후에는 [Project] 리스트에 playground 항목이 새로 생성됩니다. 여기에 결과가 저장됩니다.



예제를 통해 살펴본 것처럼 LangSmith 사이트에서 API Key 생성 후 환경변수만 코드에 넣어주면 LangChain 동작의 각 단계를 로그로 남겨줍니다.

지금 당장은 이런게 있구나 정도겠지만, 본격적으로 개발을 시작하고 디버깅-비교-분석-평가 등 고수준의 작업을 수행해야 할 때가 오면 LangSmith 가 무척 유용하게 사용될 것입니다.



LangSmith 실습 2: 프롬프트 허브를 이용하는 방법

LangSmith 의 편리한 기능을 하나 더 살펴보도록 하겠습니다.

LLM 을 가지고 놀 때 성능에 영향을 미치는 가장 중요한 요소 중 하나는 프롬프트입니다. 하지만 다양한 케이스를 테스트 할 때마다 직접 프롬프트를 작성하는 건 너무 비효율적입니다.

이런 경우에 LangChain Hub 서비스를 이용하면 다른 사람들이 심혈을 기울여 작성하고 검증한 프롬프트를 쉽게 가져와 사용할 수 있습니다. LangChain Hub 는 LangSmith 홈페이지의 메뉴 중 하나입니다.


LangChain Hub 에서 원하는 프롬프트를 검색해서 찾기만 하면 단 2줄의 파이썬 코드로 프롬프트를 가져와 사용할 수 있습니다. (Korean 카테고리가 없는 것이 아쉽습니다.)


뿐만 아니라 프롬프트를 직접 생성해서 파이썬 코드에서 프롬프트 호출, 수정, 삭제 등 다양한 작업이 가능합니다.

이번 실습에서는 입력된 뉴스 URL 에 있는 기사 원문을 스크래핑해서 LLM 이 요약하도록 시킬겁니다. 이때 사용할 프롬프트는 LangChain Hub 에서 불러옵니다.

사용할 프롬프트는 아래 링크에 있습니다.

상단에 표시된 hellollama/news_summary 텍스트가 이 프롬프트의 ID 입니다. 아래처럼 두 줄의 코드로 불러와 사용할 수 있지만 주석의 내용처럼 LANGCHAIN_API_KEY 환경변수를 설정해줘야 합니다.

# set the LANGCHAIN_API_KEY environment variable (create key in settings)
from langchain import hub

prompt = hub.pull("hellollama/news_summary")

프롬프트의 본문에는 {news} 텍스트가 파란색으로 표시되어 있습니다.

당신은 뉴스 기사를 요약, 정리하는 AI 어시스턴트입니다.

당신의 임무는 주어진 뉴스(news_text)에서 기사제목(title), 작성자(author), 작성일자(date), 요약문(summary) - 4가지 항목을 정확하게 추출하는 것입니다.

결과는 한국어로 작성해야 하며, 뉴스와 관련성이 높은 내용만 포함하고 추측된 내용을 생성하지 마세요.

특히, 작성자와 작성일자는 반드시 원문에 명시된 정보를 기반으로 하여야 하며, 요약문에는 기사에서 다루고 있는 주요 주제와 관련된 내용, 사건의 원인 및 결과를 포함해야 합니다. 요약문은 사건의 배경과 현재 상황, 그리고 향후 전망을 명확히 전달해야 합니다.

<news_text>

  {news}

<news_text>

요약된 결과는 아래 형식에 맞춰야 합니다.

<news>

  <title></title>

  <author></author>

  <date></date>

  <summary></summary>

</news>

이 텍스트(파라미터)는 코드상에서 실제 사용될 데이터로 대체되어야 하는 부분입니다. 따라서 {} 로 표시된 파라미터들은 빠짐없이 chain 호출할 때 대체될 문자열을 넣어줘야 합니다. 그렇지 않으면 실행시 에러가 발생합니다.

res = chain.invoke({"news": "기사원문이 여기 들어가야 합니다..."})


예제 코드를 보겠습니다. [07_02_langsmith_prompt_hub.py]

import bs4
from dotenv import load_dotenv
from langchain import hub
from langchain_community.llms import Ollama
from langchain_community.document_loaders import WebBaseLoader
from langchain_core.output_parsers import StrOutputParser

# 프로세스 실행을 위한 환경설정 및 파라미터 준비
# LangSmith 사이트에서 아래 내용을 복사해서 .env 파일에 입력
# LANGCHAIN_TRACING_V2=true
# LANGCHAIN_ENDPOINT=https://api.smith.langchain.com
# LANGCHAIN_API_KEY=<your_api_key>
# LANGCHAIN_PROJECT="<your_project_name>"

load_dotenv()  # .env 파일 로드

# 1. 뉴스 URL
news_url = """https://www.bbc.com/korean/articles/c166p510n79o"""

# 2. 뉴스 스크래핑
loader = WebBaseLoader(
    web_paths=([news_url]),
    bs_kwargs=dict(
        parse_only=bs4.SoupStrainer(
            "div",
            attrs={"class": ["bbc-1cvxiy9", "bbc-fa0wmp"]},
        )
    ),
)
news_array = loader.load()
news = news_array[0]

# 3. 요약에 사용할 프롬프트 불러오기
prompt = hub.pull("hellollama/news_summary")

# 4. Ollama 초기화
llm = Ollama(model="llama3.1", temperature=0)

# 5. 프롬프트를 실행할 체인생성
summary_chain = prompt | llm | StrOutputParser()

# 6. 프롬프트에 스크래핑한 기사 원문을 넣어줘야한다
formatted_prompt = {"news": news}

# 7. LLM에 질문
for chunk in summary_chain.stream(formatted_prompt):
    print(chunk, end="", flush=True)
  1. 요약할 뉴스 URL 입니다.
  2. 뉴스를 스크래핑해서 기사의 원문을 텍스트로 추출합니다.
  3. 요약에 사용할 프롬프트를 LangChain Hub 에서 불러옵니다.
  4. LLM 모델을 초기화합니다.
  5. 프롬프트를 실행할 체인을 생성합니다.
  6. 프롬프트에 포함된 {news} 영역이 실제 기사 원문으로 대체되도록 dictionary 를 만듭니다.
  7. 프롬프트를 포함한 체인을 실행해서 결과를 출력합니다.

예제를 실행해보면 아래와 같이 뉴스를 프롬프트가 정한 형식으로 요약해줍니다.

$ python -u "c:\Workspace\___Shared\Llama\test\llama_test\LLM_test\07_02_langsmith_prompt_hub.py"

<news>
  <title>구글, AI 오버뷰의 부정확한 정보 제공에 대해 반론</title>
  <author> </author>
  <date>2024년 5월 18일</date>
  <summary>구글은 최근 AI 오버뷰가 부정확한 정보를 제공할 수 있다는 점을 인정하면서도, 결과 개선을 위해 끊임없이 노력하고 있다고 설명했다. 구글 대변인은 'AI 오버뷰'는 일반적으로 단일 웹사이트가 아닌 여러 
웹사이트를 기반으로 답변을 생성하며, 관련 링크를 강조하는 형태로 설계됐다고 주장했다.</summary>
</news>

$ 



참고자료


You may also like...

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

이 사이트는 스팸을 줄이는 아키스밋을 사용합니다. 댓글이 어떻게 처리되는지 알아보십시오.