머신러닝 & 딥러닝/딥러닝

Langchain 문서(Simple Start)

Haru_29 2024. 11. 19. 20:39

1. langchain 구성 요소 및 사전 세팅

langchain에서는 전체적으로 Schema, Model, Prompt, Index, Memory, Chain, Agent 총 7가지의 컴포넌트를 제공합니다.

환경 설정

  1. 환경 세팅을 실시하기 위해 langchain과 openai의 SDK를 설치
pip install langchain
pip install openai
  1. 환경 변수 설정
    export OPENAI_API_KEY = "SK-..."
    
    2.2) 변수로 api 세팅
    2.3) Jupyter notebook 활용 시, 환경 변수 설정
  2. import os os.environ["OPENAI_API_KEY"] = "..."
  3. api_key = "sk-..." chat_model = ChatOpenAI(openai_api_key=api_key)
  4. 2.1) export를 활용한 api 세팅

2. Simple start

LangSmith

들어가기에 앞서 많은 어플리케이션은 Langchain을 여러번 호출 할 수 있기 때문에 상대적으로 복잡해 질 수 있습니다. 때문에 안의 chain과 agent의 상황을 관리 감독하는 것이 중요한데 이를 LangSmith로 가능하게 되었습니다.

export LANGCHAIN_TRACING_V2="true"
export LANGCHAIN_API_KEY=...

LangServe

추가적으로 Langchain을 REST API로 배포를 쉽게 하기 위해서 LangServe를 사용하면 가능합니다.

pip install "Langserve[all]"

LangChain 구축

LangChain은 LLM을 구축하는데 많은 모듈을 제공하는데 이는 독립 실행형으로 사용할 수 있으며 복잡한 사용 사례를 위해 구성이 될 수 있습니다. 이러한 다양한 모듈은 LangChain Expreesion Language Runnable(LCEL)에 의해 구동되므로 구성 요소를 원활하게 연결이 가능합니다.

여기서 가장 단순하고 가장 일반적인 체인에는 세 가지가 포함됩니다.

  • LLM/Chat Model: 여기서 언어 모델은 핵심 추론 엔진입니다. LangChain을 사용하려면 다양한 유형의 언어 모델과 이를 사용하는 방법을 이해해야 합니다.
  • Prompt Template: 언어 모델에 대한 지침을 제공합니다. 이는 언어 모델의 출력을 제어하므로 프롬프트와 다양한 프롬프트 전략을 구성하는 방법을 이해하는 것이 중요합니다.
  • Output Parser: 언어 모델의 원시 응답을 보다 실행 가능한 형식으로 변환하여 출력 다운스트림을 쉽게 사용할 수 있도록 합니다.

LLM / Chat Model

여기에는 LLM과 ChatModel이라는 두 가지 언어 모델을 제공해줍니다.

  • LLM : 문자열을 입력하고 문자열을 반환
  • ChatModel: 메세지의 리스트를 입력하고 메세지로 반환

기본 메시지 인터페이스는 BaseMessage로 다음 두 가지 필수 속성에 의해 정의됩니다.

  • content : 문자열로 구성된 메시지 내용
  • role : BaseMessage가 나오는 entity

LangChain은 서로 다른 역할을 쉽게 구별할 수 있는 여러 개체를 제공합니다.

  • HumanMessage : human/user에게 오는 BaseMessage
  • AIMessage : AI/assiatant에게 오는 BaseMessage
  • SystemMessage : system에게 오는 BaseMessage
  • FunctionMessage / ToolMessage : 함수나 tool 호출을 출력을 포함하는 BaseMessage

이러한 역할 중 어느 것도 적절하지 않은 경우 ChatMessage역할을 수동으로 지정할 수 있는 클래스도 있습니다.

Langchain은 LLM과 ChatModel을 같이 제공하는 것이 가능하지만 효과적인 프롬프트 작성을 위해서는 두 가지의 차이점을 인지하는 것이 좋습니다.

LLM과 ChatModel을 호출할 때 가장 간단한 방법은 LangChain Expression Language (LCEL)의 동시성 호출 방법인 .invoke()를 호출하는 것입니다.

  • LLM.invoke : 문자열로 입력 받아 문자열로 반환합니다.
  • ChatModel.invoke : BaseMessage의 리스트를 입력하고 BaseMessage로 반환을 해줍니다.
from langchain.llms import OpenAI
from langchain.chat_models import ChatOpenAI

llm = OpenAI()
chat_model = ChatOpenAI()
from langchain.schema import HumanMessage

text = "What would be a good company name for a company that makes colorful socks?"
messages = [HumanMessage(content=text)]

llm.invoke(text)
# >> Feetful of Fun

chat_model.invoke(messages)
# >> AIMessage(content="Socks O'Color")

더 알아보면 LLM.invoke와 ChatModel.invoke는 둘 다 입력으로 Union[str, List[BaseMessage], PromptValue] 제공합니다. 따라서 LLM동일한 입력을 수용 한다는 사실은 ChatModel 대부분의 chain에서 아무것도 손상 하지 않고 서로 직접 교환할 수 있다는 것을 의미합니다. 다만, 입력이 강제되는 방식과 이것이 모델 성능에 어떤 영향을 미칠 수 있는지 생각하는 것이 중요합니다.

Prompt templates

대부분의 LLM 응용 프로그램은 사용자 입력을 LLM에 직접 전달하지 않습니다. 일반적으로 현재 특정 작업에 대한 추가 Context를 제공하는 Prompt templates이라는 더 큰 텍스트에 사용자 입력을 추가합니다.

LangChain에서는 Prompt template이 바로 이 작업에 도움이 되며 사용자 입력을 완전히 형식화된 Prompt로 전환하기 위한 모든 논리를 묶습니다.

from langchain.propts import PromptTemplate

prompt = PromptTemplate.from_template("What is a good name for a company that makes {product}?")
prompt.format(product="colorful socks")
What is a good name for a company that makes colorful socks?

위와 같이 raw string format으로 질문을 하면 장점은 부분적으로 출력이 가능합니다. 예를 들어 한 번만에 일부 변수를 형식화가 가능한데 이는 다른 프롬프트와 결합이 가능하게 됩니다.

PromptTemplate도 리스트의 메세지를 생성 할 수 있습니다. 이 경우 프롬프트에는 콘텐츠에 대한 정보뿐만 아니라 각 메시지(해당 역할, 목록에서의 위치 등)도 포함됩니다. 여기서 가장 자주 일어나는 일은 ChatMessageTemplates에서의 ChatPromptTemplate입니다. 각각의 ChatMessageTemplate에서는 어떻게 ChatMessageTemplate을 구성 할 지가 나와있습니다.

from langchain.prompts.chat import ChatPromptTemplate

template = "You are a helpful assistant that translate {input_language} to {output_language}."
human_template = "{text}"

chat_prompt = ChatPromptTemplate.from_messages([
			("system", template),
			("human",human_template),
])

chat_prompt.format_messages(input_language="English", output_language="Korean", text="I love programming.")
[SystemMessage(content='You are a helpful assistant that translate English to Korean.'),
 HumanMessage(content='I love programming.')]

Output parsers

OutputParser는 원시적인 언어 출력을 downstream에 사용이 가능하도록 변환해 줍니다. 여기에 OutputParser의 몇 가지 중요한 타입이 있습니다.

  • text를 JSON과 같은 구조화된 정보에서 온 LLM으로 변환
  • ChatMessage를 단지 문자열로 변환
  • 메시지(예: OpenAI 함수 호출) 외에 호출에서 반환된 추가 정보를 문자열로 변환
from langchain.schema import BaseOutputParser

class CommaSeparatedListOutputParser(BaseOutputParser):
    """Parse the output of an LLM call to a comma-separated list."""

    def parse(self, text: str):
        """Parse the output of an LLM call."""
        return text.strip().split(", ")

CommaSeparatedListOutputParser().parse("hi, bye")
# >> ['hi', 'bye']

Composing with LCEL

앞에 나온 모든 것들을 하나의 Chain으로 결합할 수 있습니다. 이 Chain은 입력 변수를 가져와 Prompt Template에 전달하여 Prompt 를 생성하고, Prompt 를 언어 모델에 전달한 다음, (선택 사항) output parser를 통해 출력을 전달합니다.

from typing import list

from langchain.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.schema import BaseOutputParser

class CommaSeparatedListOutputParser(BaseOutputParser[List[str]]):
		"""Parse the output of an LLM call to a comma-separated list."""

		def parse(self, text:str) -> List[str]:
			"""Parse the output of an LLM call."""
			return text.strip().split(",")

template = """You are a helpful assistant who generates comma separated lists.
A user will pass in a category, and you should generate 5 objects in that category in a comma separated list.
ONLY return a comma separated list, and nothing more."""
human_template = "{text}"

chat_prompt = ChatPromptTemplate.from_messages([
    ("system", template),
    ("human", human_template),
])
chain = chat_prompt | ChatOpenAI() | CommaSeparatedListOutputParser()
chain.invoke({"text": "colors"})
['red', 'blue', 'green', 'yellow', 'orange']

Serving with LangServe

이제 애플리케이션을 구축했으므로 이를 제공해야 합니다. 이것이 바로 LangServe가 등장하는 이유입니다. LangServe는 개발자가 LCEL chain을 REST API로 배포하는 데 도움을 줍니다. 라이브러리는 FastAPI와 통합되어 있으며 데이터 검증을 위해 pydantic을 사용합니다.

Server

어플리케이션용 서버를 생성하기 위해 3개의 파일로 구성된 serve.py를 생성해 줍니다.

  1. 위와 동일한 우리의 chain의 정의
  2. FastAPI app
  3. langserve.add_routes이라는 chain에 대한 경로를 정의
#!/usr/bin/env python
from typing import List

from fastapi import FastAPI
from langchain.prompts import ChatPromptTemplate
from langchain.chat_models import ChatOpenAI
from langchain.schema import BaseOutputParser
from langserve import add_routes

# 1. Chain definition

class CommaSeparatedListOutputParser(BaseOutputParser[List[str]]):
    """Parse the output of an LLM call to a comma-separated list."""

    def parse(self, text: str) -> List[str]:
        """Parse the output of an LLM call."""
        return text.strip().split(", ")

template = """You are a helpful assistant who generates comma separated lists.
A user will pass in a category, and you should generate 5 objects in that category in a comma separated list.
ONLY return a comma separated list, and nothing more."""
human_template = "{text}"

chat_prompt = ChatPromptTemplate.from_messages([
    ("system", template),
    ("human", human_template),
])
category_chain = chat_prompt | ChatOpenAI() | CommaSeparatedListOutputParser()

# 2. App definition
app = FastAPI(
  title="LangChain Server",
  version="1.0",
  description="A simple API server using LangChain's Runnable interfaces",
)

# 3. Adding chain route
add_routes(
    app,
    category_chain,
    path="/category_chain",
)

if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="localhost", port=8000)
python serve.py