langchain 공부

langchain 기초

필만이 2024. 7. 29. 22:44

출처 : 유튜버 테디 노트의 위키를 보고 공부했으며 아래 링크에 훨씬 더 자세한 설명이 있어.
저 링크를 보고 공부하기를 권장함. 이 포스트는 목적은 개인 공부라 설명이 부족할 수 있고.
붙여넣은 내용이 다수 포함돼, 수익창출 목적이 없음.
https://wikidocs.net/233341

langchain을 쓰는 주된이유

  • 문맥 인식 : 질문할때 문맥도 입력할수 있다. 이 말은 다양한 형태(PDF,링크)의 내용을 근거로 해서 인사이트를 이끌 수 있다는거다.
  • 추론 기능 : 단지 존재하는 자료로부터 정보를 이끌어내는것 말고도, 정보가 없더라도 자료로부터 추론해 답을 낼수 있다. 이거는 모델을 만들다 보면 깨닫았는데, 쓰기에 따라 장점이 되기도 하고, 허위 정보를 이끌어내는 단점이 되기도 한다.
  • 현재 주로 만들고 있는것 : 기업 내 자료를 활용한 llm(기업 내 자료를 활용해 더 빠르게 적합한 대답을 얻을수 있음), 챗봇(기업 응대 메뉴얼, 이전대화 기반 자연스러운 대화 가능)
  • 앞으로 만들수 있는것 : 비~밀 내가 만들꺼얌

프레임워크

  • LangChain 라이브러리: Python 및 JavaScript 라이브러리. 다양한 컴포넌트의 인터페이스와 통합, 이러한 컴포넌트를 체인과 에이전트로 결합하는 기본 런타임, 그리고 즉시 사용 가능한 체인과 에이전트의 구현을 포함
  • LangChain 템플릿: 다양한 작업을 위한 쉽게 배포할 수 있는 참조 아키텍처 모음
  • LangServe: LangChain 체인을 REST API로 배포하기 위한 라이브러리
  • LangSmith: 어떤 LLM 프레임워크에도 구축된 체인을 디버그, 테스트, 평가, 모니터링할 수 있게 해주며 LangChain과 원활하게 통합되는 개발자 플랫폼
  • LangGraph: LLM을 사용한 상태유지가 가능한 다중 액터 애플리케이션을 구축하기 위한 라이브러리로, LangChain 위에 구축되었으며 LangChain과 함께 사용하도록 설계되었습니다. 여러 계산 단계에서 다중 체인(또는 액터)을 순환 방식으로 조정할 수 있는 능력을 LangChain 표현 언어에 추가

프롬프트 템플릿

  • PromptTemplate :사용자의 입력 변수를 사용하여 완전한 프롬프트 문자열을 만드는 데 사용되는 템플릿입니다. 주로 자연어 처리(NLP) 작업에서 모델에 전달할 텍스트를 동적으로 생성하기 위해 사용됩니다.
  • input_variable : 템플릿 안에 들어가는 입력 변수의 이름을 정의한다.
# 필요한 모듈 임포트
from langchain_core.prompts import PromptTemplate
# template 정의
template = "{country}의 수도는 어디인가요?"
# from_template 메소드를 이용하여 PromptTemplate 객체 생성
prompt_template = PromptTemplate.from_template(template)
# 생성된 PromptTemplate 객체 확인
print(prompt_template)
# 출력: PromptTemplate(input_variables=['country'], template='{country}의 수도는 어디인가요?')
# prompt 생성 (대한민국의 경우)
prompt = prompt_template.format(country="대한민국")
print(prompt)
# 출력: '대한민국의 수도는 어디인가요?'
# prompt 생성 (미국의 경우)
prompt = prompt_template.format(country="미국")
print(prompt)
# 출력: '미국의 수도는 어디인가요?'
  • 모델설정
from langchain_openai import ChatOpenAI
# 모델 설정
model = ChatOpenAI(
    model="gpt-3.5-turbo",
    max_tokens=2048,
    temperature=0.1,
)
# 모델에 프롬프트 전달 및 응답 받기
response = model(prompt)
print(response)
  • 예시 : 오늘 날씨 묻기
# 필요한 모듈 임포트
from datetime import datetime

# 오늘 날짜를 반환하는 함수 정의
def get_today():
    return datetime.today().strftime("%Y-%m-%d")

# 프롬프트 템플릿 정의
prompt_template = PromptTemplate(
    template="오늘의 날짜는 {today} 입니다. 오늘 날씨가 뭐야?",
    partial_variables={
        "today": get_today()  # 함수 호출 결과를 partial_variables에 전달
    },
)

# 모델에 프롬프트 전달 및 응답 받기
response = model(prompt_template.format(query="오늘 날씨가 뭐야?"))
print(response)
  • LCEL(LangChain Expression Language) 를 사용해서 위의 도구들을 하나로 결합 (chain = prompt | model | output_parser)
# prompt 를 PromptTemplate 객체로 생성합니다.
prompt = PromptTemplate.from_template("{topic} 에 대해 쉽게 설명해주세요.")

model = ChatOpenAI()

chain = prompt | model
  • invoke()로 dic 형태로 입력값 전달
# input 딕셔너리에 주제를 '인공지능 모델의 학습 원리'으로 설정합니다.
input = {"topic": "인공지능 모델의 학습 원리"}

# prompt 객체와 model 객체를 파이프(|) 연산자로 연결하고 invoke 메서드를 사용하여 input을 전달합니다.
# 이를 통해 AI 모델이 생성한 메시지를 반환합니다.
chain.invoke(input)

결과 : AIMessage(content='인공지능 모델의 학습 원리는 데이터를 이용하여 패턴을 학습하는 것입니다. 모델은 입력 데이터를 받아들이고 내부적으로 가중치를 조정하여 원하는 결과를 출력합니다. 학습 과정에서 모델은 입력 데이터와 정답 데이터를 이용하여 오차를 계산하고 이 오차를 최소화하는 방향으로 가중치를 업데이트합니다. 이렇게 반복적으로 학습을 진행하면 모델은 입력 데이터로부터 패턴을 학습하여 정확한 결과를 예측하게 됩니다. 이러한 학습 원리를 통해 인공지능 모델은 데이터를 이용하여 스스로 학습하고 문제를 해결할 수 있습니다.', response_metadata={'token_usage': {'completion_tokens': 214, 'prompt_tokens': 33, 'total_tokens': 247}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-7f8a08f4-51ba-4d14-b9d2-2e092be3e7aa-0', usage_metadata={'input_tokens': 33, 'output_tokens': 214, 'total_tokens': 247})
-> 여기서 내용(content)만 추출하려면 StrOutputParser를 사용하면 된다.

# 스트리밍 출력을 위한 요청
answer = chain.stream(input)
# 스트리밍 출력
stream_response(answer)

결과 : (같은 내용이 실시간으로 출력됨)

  • 템플릿에 더 다양한것 추가 변화
template = """
당신은 영어를 가르치는 10년차 영어 선생님입니다. 상황에 [FORMAT]에 영어 회화를 작성해 주세요.

상황:
{question}

FORMAT:
- 영어 회화:
- 한글 해석:
"""

# 프롬프트 템플릿을 이용하여 프롬프트를 생성합니다.
prompt = PromptTemplate.from_template(template)

# ChatOpenAI 챗모델을 초기화합니다.
model = ChatOpenAI(model_name="gpt-4-turbo")

# 문자열 출력 파서를 초기화합니다.
output_parser = StrOutputParser()

# 체인을 구성합니다.
chain = prompt | model | output_parser

# 완성된 Chain을 실행하여 답변을 얻습니다.
# 스트리밍 출력을 위한 요청
answer = chain.stream({"question": "저는 식당에 가서 음식을 주문하고 싶어요"})
# 스트리밍 출력
stream_response(answer)
출력
영어 회화:
- Hello, could I see the menu, please? 
- I'd like to order the grilled salmon and a side of mashed potatoes.
- Could I have a glass of water as well?
- Thank you!

한글 해석:
- 안녕하세요, 메뉴판 좀 볼 수 있을까요?
- 구운 연어와 매시드 포테이토를 주문하고 싶어요.
- 물 한 잔도 주실 수 있나요?
- 감사합니다!
# 이번에는 question 을 '미국에서 피자 주문'으로 설정하여 실행합니다.
# 스트리밍 출력을 위한 요청
answer = chain.stream({"question": "미국에서 피자 주문"})
# 스트리밍 출력
stream_response(answer)
영어 회화:
- Employee: "Hello, Tony's Pizza. How can I help you?"
- Customer: "Hi, I'd like to place an order for delivery, please."
- Employee: "Sure thing! What would you like to order?"
- Customer: "I'll have a large pepperoni pizza with extra cheese and a side of garlic bread."
- Employee: "Anything to drink?"
- Customer: "Yes, a 2-liter bottle of Coke, please."
- Employee: "Alright, your total comes to $22.50. Can I have your delivery address?"
- Customer: "It's 742 Evergreen Terrace."
- Employee: "Thank you. Your order will be there in about 30-45 minutes. Is there anything else I can help you with?"
- Customer: "No, that's everything. Thank you!"
- Employee: "Thank you for choosing Tony's Pizza. Have a great day!"

한글 해석:
- 직원: "안녕하세요, 토니의 피자입니다. 어떻게 도와드릴까요?"
- 고객: "안녕하세요, 배달 주문하고 싶은데요."
- 직원: "네, 무엇을 주문하시겠어요?"
- 고객: "큰 사이즈의 페퍼로니 피자에 치즈 추가하고, 마늘빵 하나 주세요."
- 직원: "음료는 드릴까요?"
- 고객: "네, 콜라 2리터 한 병 주세요."
- 직원: "알겠습니다, 합계는 $22.50입니다. 배달 주소를 알려주시겠어요?"
- 고객: "742 에버그린 테라스입니다."
- 직원: "감사합니다. 주문하신 음식은 대략 30-45분 내에 도착할 예정입니다. 다른 도움이 필요하신가요?"
- 고객: "아니요, 이게 다예요. 감사합니다!"
- 직원: "토니의 피자를 선택해주셔서 감사합니다. 좋은 하루 되세요!"

Runnable

Runnable의 개요

LangChain에서 Runnable 객체는 데이터 처리와 작업 수행을 위한 유연하고 재사용 가능한 구성 요소입니다. 이 객체는 데이터를 입력받아 특정 작업을 수행하고, 그 결과를 출력으로 반환하는 역할을 합니다. Runnable은 다음과 같은 상황에서 특히 유용합니다:

  1. 데이터 흐름 관리: Runnable을 사용하면 데이터 흐름을 체인 형태로 구성하여 복잡한 작업을 순차적으로 수행할 수 있습니다.
  2. 반복 가능한 작업 처리: 동일한 작업을 여러 번 반복적으로 수행해야 할 때, Runnable 객체를 사용하면 코드 재사용성과 가독성이 높아집니다.
  3. 병렬 처리: 여러 작업을 병렬로 처리해야 할 때 RunnableParallel을 사용하면 쉽게 병렬 처리를 구성할 수 있습니다.

주요 역할

  1. 데이터 변환:

    • RunnablePassthrough: 입력 데이터를 그대로 전달하거나 추가 키를 더하여 전달합니다.
    • RunnableLambda: 사용자 정의 함수를 사용하여 입력 데이터를 변환합니다.
  2. 병렬 처리:

    • RunnableParallel: 여러 Runnable 인스턴스를 병렬로 실행하여 동시에 여러 작업을 수행할 수 있습니다.
  3. 체인 구성:

    • 여러 Runnable 객체를 연결하여 복잡한 데이터 처리 파이프라인을 구성할 수 있습니다.

예제 및 설명

1. RunnablePassthrough

RunnablePassthrough는 입력 데이터를 그대로 전달하거나 추가 작업을 수행하지 않는 단순한 Runnable입니다. 입력 데이터를 변경하지 않고 그대로 전달하는 역할을 합니다.

from langchain_core.runnables import RunnablePassthrough

# 입력 데이터를 그대로 전달합니다.
result = RunnablePassthrough().invoke({"num": 10})
print(result)  # 출력: {'num': 10}

2. RunnableLambda

RunnableLambda는 사용자 정의 함수를 사용하여 입력 데이터를 변환하는 Runnable입니다.

from datetime import datetime
from langchain_core.runnables import RunnableLambda

# 사용자 정의 함수
def get_today(_):
    return datetime.today().strftime("%b-%d")

# 사용자 정의 함수를 사용하여 입력 데이터를 변환합니다.
runnable = RunnableLambda(get_today)
result = runnable.invoke(None)
print(result)  # 출력: 현재 날짜

3. RunnableParallel

RunnableParallel은 여러 Runnable 인스턴스를 병렬로 실행하여 동시에 여러 작업을 수행합니다.

from langchain_core.runnables import RunnableParallel

# RunnableParallel 인스턴스를 생성합니다. 이 인스턴스는 여러 Runnable 인스턴스를 병렬로 실행할 수 있습니다.
runnable = RunnableParallel(
    # RunnablePassthrough 인스턴스를 'passed' 키워드 인자로 전달합니다. 이는 입력된 데이터를 그대로 통과시키는 역할을 합니다.
    passed=RunnablePassthrough(),
    # 'extra' 키워드 인자로 RunnablePassthrough.assign을 사용하여, 'mult' 람다 함수를 할당합니다. 이 함수는 입력된 딕셔너리의 'num' 키에 해당하는 값을 3배로 증가시킵니다.
    extra=RunnablePassthrough.assign(mult=lambda x: x["num"] * 3),
    # 'modified' 키워드 인자로 람다 함수를 전달합니다. 이 함수는 입력된 딕셔너리의 'num' 키에 해당하는 값에 1을 더합니다.
    modified=lambda x: x["num"] + 1,
)

# runnable 인스턴스에 {'num': 1} 딕셔너리를 입력으로 전달하여 invoke 메소드를 호출합니다.
runnable.invoke({"num": 1})

print(result)  # 출력: {'passed': {'num': 1}, 'extra': {'num': 1, 'mult': 3}, 'modified': 2}

4. 체인 구성

여러 Runnable 객체를 연결하여 복잡한 데이터 처리 파이프라인을 구성할 수 있습니다.

from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI

# 두 개의 체인을 구성합니다.
chain1 = (
    {"country": RunnablePassthrough()}
    | PromptTemplate.from_template("{country}의 수도는?")
    | ChatOpenAI()
)
chain2 = (
    {"country": RunnablePassthrough()}
    | PromptTemplate.from_template("{country}의 면적은?")
    | ChatOpenAI()
)

# 두 체인을 병렬로 실행하는 체인을 구성합니다.
combined_chain = RunnableParallel(capital=chain1, area=chain2)

# 병렬 체인 실행
result = combined_chain.invoke("대한민국")
print(result)
  1. itemgetter 사용
  • itemgetter는 operator 모듈의 함수로, 딕셔너리나 객체에서 특정 키나 속성의 값을 추출하는 데 사용됩니다. 예를 들어, itemgetter('word1')은 입력된 딕셔너리에서 'word1' 키의 값을 반환
  • RunnableLambda는 length_function과 multiple_length_function을 래핑하여 데이터 변환에 사용
from operator import itemgetter  # itemgetter는 딕셔너리에서 특정 키의 값을 추출하는 데 사용됩니다.

from langchain_core.prompts import ChatPromptTemplate  # 프롬프트 템플릿을 생성하는 모듈
from langchain_core.runnables import RunnableLambda  # 사용자 정의 함수를 실행할 수 있는 Runnable 객체
from langchain_openai import ChatOpenAI  # OpenAI의 챗 모델을 사용하는 모듈


# 문장의 길이를 반환하는 함수입니다.
def length_function(text):
    return len(text)


# 두 문장의 길이를 곱한 값을 반환하는 함수입니다.
def _multiple_length_function(text1, text2):
    return len(text1) * len(text2)


# _multiple_length_function 함수를 사용하여 두 문장의 길이를 곱한 값을 반환하는 함수입니다.
def multiple_length_function(_dict):
    return _multiple_length_function(_dict["text1"], _dict["text2"])


# 프롬프트 템플릿을 생성합니다.
# 여기서 {a}와 {b}는 나중에 실제 값으로 대체될 자리 표시자입니다.
prompt = ChatPromptTemplate.from_template("{a} + {b} 는 무엇인가요?")
model = ChatOpenAI()  # OpenAI의 챗 모델을 초기화합니다.

# 첫 번째 체인을 생성합니다. 여기서는 단순한 프롬프트와 모델의 체인입니다.
chain1 = prompt | model

# 복잡한 체인을 생성합니다.
chain = (
    {
        # "a" 키는 itemgetter("word1")에 의해 "word1" 키의 값을 가져와, RunnableLambda(length_function)을 통해 그 길이를 계산합니다.
        "a": itemgetter("word1") | RunnableLambda(length_function),

        # "b" 키는 두 개의 텍스트를 사용하여 그 길이의 곱을 계산합니다.
        "b": {"text1": itemgetter("word1"), "text2": itemgetter("word2")}
        | RunnableLambda(multiple_length_function),
    }
    | prompt  # 변환된 값들을 프롬프트 템플릿에 적용합니다.
    | model  # 프롬프트에 기반하여 모델이 응답을 생성합니다.
)

# 체인을 실행합니다. 여기서 "word1"과 "word2" 값을 가진 딕셔너리를 입력으로 전달합니다.
result = chain.invoke({"word1": "hello", "word2": "world"})
print(result)  # 모델의 응답을 출력합니다.

출력
AIMessage(content='5 + 25 = 30입니다.', response_metadata={'token_usage': {'completion_tokens': 9, 'prompt_tokens': 22, 'total_tokens': 31}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-5db9b475-09ee-4edb-af9d-b37b320bee1e-0', usage_metadata={'input_tokens': 22, 'output_tokens': 9, 'total_tokens': 31})

결론

Runnable 객체는 LangChain에서 데이터 흐름과 작업 처리를 효율적으로 관리하기 위한 중요한 도구입니다. 데이터 변환, 병렬 처리, 체인 구성을 통해 복잡한 작업을 간단하게 구현할 수 있습니다. 이로 인해 코드의 재사용성과 가독성이 크게 향상됩니다.