Model I/O
LangChain 패키지는 NLP 어플리케이션을 원활하게 만들기 위해서 필요한 여러 모듈을 제공합니다. 오늘은 그 중 Model I/O와 관련된 내용을 다룹니다. 이 모듈을 통해 LangChain은 모든 언어 모델과 상호 작용하고, 모델에 대한 입력을 관리하고 출력에서 정보를 추출하는 등의 작업을 수행할 수 있습니다.
Prompts
언어 모델에 입력되는 것은 보통 프롬프트라 불립니다. 종종 앱에서 사용자 입력은 모델에 직접적으로 입력되는 것이 아닙니다. 그 대신, 사용자의 입력은 어떤 방식으로든 변형되어 최종적으로 모델에 들어가는 문자열 또는 메시지의 형태로 생성됩니다. 사용자 입력을 받아 최종 문자열 또는 메시지로 변환하는 객체를 PromptTemplate이라고 합니다. LangChain은 프롬프트 작업을 더 쉽게 처리하기 위해 여러 추상화를 제공합니다.
PromptValue
ChatModels과 LLMs은 서로 다른 입력 유형을 사용합니다. PromptValue는 두 모델에서 모두 적용 가능하도록 설계된 클래스입니다. 이 클래스는 LLMs에서는 문자열로 캐스팅되는 메서드가 노출되고, ChatModels에서는 Message 리스트로 캐스팅되어 두 모델에 모두 적용이 가능합니다.
PromptTemplate
PromptTemplate은 언어 모델에 대한 프롬프트를 생성하기 위한 미리 정의된 레시피입니다. 템플릿에는 instructions, few-shot examples, 특정 context 및 특정 작업에 적합한 질문과 같은 내용이 포함될 수 있습니다. LangChain은 프롬프트 템플릿을 생성하고 활용하기 위한 도구를 제공합니다. 일반적으로 언어 모델은 프롬프트가 문자열이거나 채팅 메시지 목록일 것으로 예상합니다.
from langchain.prompts import PromptTemplate
prompt_template = PromptTemplate.from_template(
"Tell me a {adjective} joke about {content}."
)
prompt_template.format(adjective="funny", content="chickens")
"""
'Tell me a funny joke about chickens.'
"""
MessagePromptTemplate
LangChain은 다양한 종류의 MessagePromptTemplate을 제공합니다. 가장 일반적으로 사용되는 것은 AIMessagePromptTemplate, SystemMessagePromptTemplate, 그리고 HumanMessagePromptTemplate입니다. 각각은 AI 메시지, 시스템 메시지, 그리고 인간 메시지를 생성합니다.
그러나 ChatModels가 임의의 역할을 가진 채팅 메시지를 받아들일 수 있는 경우 ChatMessagePromptTemplate을 사용할 수 있습니다. 이를 통해 사용자는 역할 이름을 지정할 수 있습니다.
from langchain.prompts import ChatMessagePromptTemplate
prompt = "May the {subject} be with you"
chat_message_prompt = ChatMessagePromptTemplate.from_template(
role="Jedi", template=prompt
)
chat_message_prompt.format(subject="force")
"""
ChatMessage(content='May the force be with you', role='Jedi')
"""
LangChain은 또한 MessagesPlaceholder를 제공하는데, 이를 사용하면 포맷 중에 렌더링될 메시지를 완전히 제어할 수 있습니다. 이것은 메시지 프롬프트 템플릿에 어떤 역할을 사용해야 할지 확실하지 않을 때나 포맷 중에 메시지 목록을 삽입하고 싶을 때 유용할 수 있습니다.
from langchain.prompts import (
ChatPromptTemplate,
HumanMessagePromptTemplate,
MessagesPlaceholder,
)
human_prompt = "Summarize our conversation so far in {word_count} words."
human_message_template = HumanMessagePromptTemplate.from_template(human_prompt)
chat_prompt = ChatPromptTemplate.from_messages(
[MessagesPlaceholder(variable_name="conversation"), human_message_template]
)
from langchain_core.messages import AIMessage, HumanMessage
human_message = HumanMessage(content="What is the best way to learn programming?")
ai_message = AIMessage(
content="""\
1. Choose a programming language: Decide on a programming language that you want to learn.
2. Start with the basics: Familiarize yourself with the basic programming concepts such as variables, data types and control structures.
3. Practice, practice, practice: The best way to learn programming is through hands-on experience\
"""
)
chat_prompt.format_prompt(
conversation=[human_message, ai_message], word_count="10"
).to_messages()
[HumanMessage(content='What is the best way to learn programming?'),
AIMessage(content='1. Choose a programming language: Decide on a programming language that you want to learn.\n\n2. Start with the basics: Familiarize yourself with the basic programming concepts such as variables, data types and control structures.\n\n3. Practice, practice, practice: The best way to learn programming is through hands-on experience'),
HumanMessage(content='Summarize our conversation so far in 10 words.')]
Models
LangChain의 모델은 LLMs와 ChatModels의 두 가지 유형이 있습니다. 이것들은 각각의 입력 및 출력 유형에 따라 정의됩니다.
이 두 가지 API 유형은 입력 및 출력 스키마가 꽤 다릅니다. 이는 이것들과 상호 작용하는 가장 좋은 방법이 상당히 다를 수 있다는 것을 의미합니다. LangChain은 두 가지 모두 호환 가능하게 만들긴 하지만, 그렇다고 해서 항상 그렇게 사용해야 하는 것은 아닙니다. 특히 LLMs와 ChatModels에 대한 프롬프팅 전략은 꽤 다를 수 있습니다. 따라서 사용 중인 모델 유형에 맞게 설계된 프롬프트를 사용하는지 확인하려고 할 것입니다.
또한, 모든 모델이 동일하지 않습니다. 각 모델은 자체에 가장 적합한 프롬프팅 전략을 가지고 있습니다. 예를 들어, Anthropic의 모델은 XML과 가장 잘 작동하지만 OpenAI의 모델은 JSON과 가장 잘 작동합니다. 이는 하나의 모델에 사용하는 프롬프트가 다른 모델로 전환되지 않을 수 있다는 것을 의미합니다. LangChain은 많은 기본 프롬프트를 제공하지만, 이것들이 사용 중인 모델과 잘 작동할 것을 보장하지는 않습니다.
LLMs
LangChain의 LLM은 순수 텍스트 완성 모델을 참조합니다. 이들이 래핑하는 API는 문자열 프롬프트를 입력으로 사용하고 문자열 completion을 출력합니다.
from langchain_openai import OpenAI
llm = OpenAI(openai_api_key="...")
llm.invoke(
"What are some theories about the relationship between unemployment and inflation?"
)
"""
'\n\n1. The Phillips Curve Theory: This suggests that there is an inverse relationship between unemployment and inflation, meaning that when unemployment is low, inflation will be higher, and when unemployment is high, inflation will be lower.\n\n2. The Monetarist Theory: This theory suggests that the relationship between unemployment and inflation is weak, and that changes in the money supply are more important in determining inflation.\n\n3. The Resource Utilization Theory: This suggests that when unemployment is low, firms are able to raise wages and prices in order to take advantage of the increased demand for their products and services. This leads to higher inflation.'
"""
Custom LLM
Custom LLM을 구현하기 위해서 구현해야 하는 필수 사항은 두 가지뿐입니다.
- 문자열과 일부 선택적인 중지 단어를 가져와서 문자열을 반환하는 _call 메서드
- 문자열을 반환하는 _llm_type 속성입니다. 로깅 목적으로만 사용
class CustomLLM(LLM):
n: int
@property
def _llm_type(self) -> str:
return "custom"
def _call(
self,
prompt: str,
stop: Optional[List[str]] = None,
run_manager: Optional[CallbackManagerForLLMRun] = None,
**kwargs: Any,
) -> str:
if stop is not None:
raise ValueError("stop kwargs are not permitted.")
return prompt[: self.n]
@property
def _identifying_params(self) -> Mapping[str, Any]:
"""Get the identifying parameters."""
return {"n": self.n}
llm = CustomLLM(n=10)
llm.invoke("This is a foobar thing")
"""
'This is a '
"""
print(llm)
"""
CustomLLM
Params: {'n': 10}
"""
Caching
LangChain은 LLM을 위한 선택적 캐싱 계층을 제공합니다. 이는 다음 두 가지 이유로 유용합니다.
- 동일한 completion을 여러 번 요청하는 경우가 많을 경우 LLM에 대한 API 호출 횟수를 줄여 비용을 절약
- LLM에 대한 API 호출 수를 줄여 애플리케이션 속도 향상
from langchain.globals import set_llm_cache
from langchain_openai import OpenAI
# To make the caching really obvious, lets use a slower model.
llm = OpenAI(model_name="gpt-3.5-turbo-instruct", n=2, best_of=2)
%%time
from langchain.cache import InMemoryCache
set_llm_cache(InMemoryCache())
# The first time, it is not yet in cache, so it should take longer
llm.predict("Tell me a joke")
"""
CPU times: user 13.7 ms, sys: 6.54 ms, total: 20.2 ms
Wall time: 330 ms
"\n\nWhy couldn't the bicycle stand up by itself? Because it was two-tired!"
"""
%%time
# The second time it is, so it goes faster
llm.predict("Tell me a joke")
"""
CPU times: user 436 µs, sys: 921 µs, total: 1.36 ms
Wall time: 1.36 ms
"\n\nWhy couldn't the bicycle stand up by itself? Because it was two-tired!"
"""
Streaming
모든 LLM은 ainvoke, batch, abatch, stream, astream와 같은 Runnable 인터페이스의 기본 메소드를 구현합니다. 따라서 모든 LLM은 streaming 지원이 가능합니다.
스트리밍 지원은 기본적으로 하위 LLM 프로바이더에서 반환된 최종 결과의 Iterator(또는 비동기 스트리밍의 경우 AsyncIterator)를 반환합니다. 이는 물론 토큰별 스트리밍을 제공하지 않습니다. 토큰별 스트리밍은 하위 LLM 프로바이더로부터 네이티브 지원이 필요하지만, 토큰의 iterator를 기대하는 코드가 모든 LLM 통합에 대해 작동할 수 있도록 보장합니다.
from langchain_openai import OpenAI
llm = OpenAI(model="gpt-3.5-turbo-instruct", temperature=0, max_tokens=512)
for chunk in llm.stream("Write me a song about sparkling water."):
print(chunk, end="", flush=True)
ChatModels
채팅 모델은 종종 대화를 수행하기 위해 특별히 조정된 LLMs를 기반으로 합니다. 중요한 점은 이들의 제공 엔터페이스(API)가 순수 텍스트 완성 모델과는 다른 인터페이스를 사용합니다. 단일 문자열 대신에 채팅 메시지 목록을 입력으로 받고, AI 메시지를 출력으로 반환합니다.
from langchain_openai import ChatOpenAI
chat = ChatOpenAI()
Messages
ChatModels은 Message 리스트을 입력으로 받아들이고 메시지를 반환합니다. LangChain에는 여러가지 서로 다른 유형의 메시지가 있고, 모든 메시지는 role과 content를 가지고 있습니다. role은 누가 메시지를 전하는지를 나타냅니다.
- HumanMessage: 사용자로부터의 메시지를 나타냅니다. 일반적으로 content만으로 구성됩니다.
- AIMessage: 모델로부터의 메시지를 나타냅니다. 이 메시지에는 추가적인 인수들이 들어갈 수 있습니다 - 예를 들어 OpenAI 함수 호출 시 functional_call과 같은 것이 있을 수 있습니다.
- SystemMessage: 시스템 메시지를 나타냅니다. 일부 모델만 지원합니다. 이는 모델에게 어떻게 동작해야 하는지 알려줍니다. 일반적으로 content만으로 구성됩니다.
- FunctionMessage: 함수 호출의 결과를 나타냅니다. role과 content에 추가로, 이 메시지에는 이 결과를 생성하기 위해 호출된 함수의 이름을 전달하는 name 매개변수가 있습니다.
- ToolMessage: 도구 호출의 결과를 나타냅니다. 이는 OpenAI의 함수 및 도구 메시지 유형과 일치시키기 위해 FunctionMessage와 구분됩니다. role과 content에 추가로, 이 메시지에는 이 결과를 생성하기 위해 호출된 도구에 대한 호출의 id를 전달하는 tool_call_id 매개변수가 있습니다.
from langchain_core.messages import HumanMessage, SystemMessage
messages = [
SystemMessage(content="You're a helpful assistant"),
HumanMessage(content="What is the purpose of model regularization?"),
]
chat.invoke(messages)
"""
AIMessage(content="The purpose of model regularization is to prevent overfitting in machine learning models. Overfitting occurs when a model becomes too complex and starts to fit the noise in the training data, leading to poor generalization on unseen data. Regularization techniques introduce additional constraints or penalties to the model's objective function, discouraging it from becoming overly complex and promoting simpler and more generalizable models. Regularization helps to strike a balance between fitting the training data well and avoiding overfitting, leading to better performance on new, unseen data.")
"""
Output Parsers
모델의 출력은 문자열 또는 메시지 중 하나입니다. 종종, 문자열이나 메시지에는 downstream에서 사용하기 위해 특정 형식으로 서식이 지정된 정보가 포함되어 있습니다(예: 쉼표로 구분된 목록 또는 JSON 블롭). OutputParser는 모델의 출력을 가져와 더 유용한 형태로 변환하는 역할을 담당합니다. 이러한 파서는 일반적으로 출력 메시지의 콘텐츠에서 작동하지만 때로는 additional_kwargs 필드의 값을 처리하기도 합니다.
from langchain.output_parsers import PydanticOutputParser
from langchain.prompts import PromptTemplate
from langchain_core.pydantic_v1 import BaseModel, Field, validator
from langchain_openai import OpenAI
model = OpenAI(model_name="gpt-3.5-turbo-instruct", temperature=0.0)
# Define your desired data structure.
class Joke(BaseModel):
setup: str = Field(description="question to set up a joke")
punchline: str = Field(description="answer to resolve the joke")
# You can add custom validation logic easily with Pydantic.
@validator("setup")
def question_ends_with_question_mark(cls, field):
if field[-1] != "?":
raise ValueError("Badly formed question!")
return field
# Set up a parser + inject instructions into the prompt template.
parser = PydanticOutputParser(pydantic_object=Joke)
prompt = PromptTemplate(
template="Answer the user query.\n{format_instructions}\n{query}\n",
input_variables=["query"],
partial_variables={"format_instructions": parser.get_format_instructions()},
)
# And a query intended to prompt a language model to populate the data structure.
prompt_and_model = prompt | model
output = prompt_and_model.invoke({"query": "Tell me a joke."})
parser.invoke(output)
"""
Joke(setup='Why did the chicken cross the road?', punchline='To get to the other side!')
"""
LangChain OutputParsers의 특별한 이점은 다수의 구현된 OutputParser가 스트리밍을 지원한다는 것입니다.
from langchain.output_parsers.json import SimpleJsonOutputParser
json_prompt = PromptTemplate.from_template(
"Return a JSON object with an `answer` key that answers the following question: {question}"
)
json_parser = SimpleJsonOutputParser()
json_chain = json_prompt | model | json_parser
list(json_chain.stream({"question": "Who invented the microscope?"}))
"""
[{},
{'answer': ''},
{'answer': 'Ant'},
{'answer': 'Anton'},
{'answer': 'Antonie'},
{'answer': 'Antonie van'},
{'answer': 'Antonie van Lee'},
{'answer': 'Antonie van Leeu'},
{'answer': 'Antonie van Leeuwen'},
{'answer': 'Antonie van Leeuwenho'},
{'answer': 'Antonie van Leeuwenhoek'}]
"""
'AI > 어플리케이션 개발' 카테고리의 다른 글
LangChain을 활용한 Tool Calling # 2 (3) | 2024.11.29 |
---|---|
LangChain을 활용한 Tool Calling # 1 (2) | 2024.11.29 |
LLM 어플리케이션에서의 Tool Calling: AI가 더 똑똑해지는 방법 (0) | 2024.11.29 |
LLM 애플리케이션 개발 훑어보기 - LangChain #2 LangChain Expression Language (LCEL) (0) | 2024.01.22 |
LLM 애플리케이션 개발 훑어보기 - LangChain #1 Intro 및 QuickStart (0) | 2024.01.20 |