- 출처 : https://python.langchain.com/docs/modules/model_io/
- 이 블로그 글은 LangChain API document의 글을 기반으로 번역되었으며 이 과정에서 약간의 내용이 추가되었습니다.
- MODEL I/O는 Prompts, Language Model, Output Parser로 이루어져 있습니다.
- Output Parsers에서는 Langchain에서 output을 다루는 여러 가지 방법에 대해 설명합니다.
- 본 글에서는 Auto-fixing parser, Pydantic parser, Retry parser, Structured output parser에 대해 다룹니다.
Auto-fixing parser는 다른 output parser를 wrapping하고 있으며 첫 번째 parser가 실패할 경우 오류를 수정하기 위해 다른 LLM을 호출합니다. 또한 형식이 잘못된 출력과 형식이 잘 된 지침을 모델에 전달하여 수정하도록 요청할 수 있습니다.
이번 세션에서는 Pydantic output parser를 사용할 때 스키마를 준수하지 않는 결과를 전달하면 어떤 일이 일어나는지 살펴보고, OutputFixingParser를 만들어 사용할 것입니다.
from langchain.prompts import PromptTemplate, ChatPromptTemplate, HumanMessagePromptTemplate
from langchain.llms import OpenAI
from langchain.chat_models import ChatOpenAI
from langchain.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field, validator
from typing import List
class Actor(BaseModel):
name: str = Field(description="name of an actor")
film_names: List[str] = Field(description="list of names of films they starred in")
actor_query = "Generate the filmography for a random actor."
parser = PydanticOutputParser(pydantic_object=Actor)
misformatted = "{'name': 'Tom Hanks', 'film_names': ['Forrest Gump']}"
parser.parse(misformatted)
OutputParserException: Failed to parse Actor from completion {'name': 'Tom Hanks', 'film_names': ['Forrest Gump']}. Got: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)
위 예시에서 Pydantic을 사용하여 만든 Actor 객체는 name과 film_names 모두 큰따옴표로 되어있습니다. 하지만 아래 만든 misformatted 문자열은 작은따옴표로 되어있습니다. 이로 인해 parser는 작동하지 않고 에러를 출력합니다. OutputFixingParser를 사용하면 이 에러를 수정하여 출력합니다.
from langchain.output_parsers import OutputFixingParser
new_parser = OutputFixingParser.from_llm(parser=parser, llm=ChatOpenAI())
new_parser.parse(misformatted)
Actor(name='Tom Hanks', film_names=['Forrest Gump'])
output parser는 유저가 임의의 JSON 스키마를 지정하고 해당 스키마에 준수하는 JSON 출력을 LLM에 대해 쿼리할 수 있도록 합니다.
LLM은 약간 추상적이라는 것을 고려해야 합니다. 잘 형식화된 JSON을 생성하기 위해서는 충분히 좋은 LLM을 사용해야 합니다. 예를 들어, OpenAI의 모델 중 Davinci는 신뢰성있게 이를 수행할 수 있지만, Curie는 거의 이 부분을 수행하지 못합니다.
데이터 구조를 선언하기 위해 Pydantic을 사용합니다. Pydantic의 BaseModel은 Python dataclass와 유사하지만 실제 유형확인과 강제변환 기능이 추가됩니다.
from langchain.prompts import PromptTemplate
from langchain.llms import OpenAI
from langchain.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field, validator
from typing import List
model_name = "text-davinci-003"
temperature = 0.0
model = OpenAI(model_name=model_name, temperature=temperature)
# data structure를 선언합니다.
class Joke(BaseModel):
setup: str = Field(description="question to set up a joke")
punchline: str = Field(description="answer to resolve the joke")
@validator("setup")
def question_ends_with_question_mark(cls, field):
if field[-1] != "?":
raise ValueError("Badly formed question!")
return field
joke_query = "Tell me a joke."
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()}
)
_input = prompt.format_prompt(query=joke_query)
output = model(_input.to_string())
print('파싱전 : ',output)
print('파싱후 : ',parser.parse(output))
파싱전 :
{"setup": "Why did the chicken cross the road?", "punchline": "To get to the other side!"}
파싱후 : setup='Why did the chicken cross the road?' punchline='To get to the other side!'
다른 예시로 한번 더 시도해 보겠습니다.
class Actor(BaseModel):
name: str = Field(description="name of an actor")
film_names: List[str] = Field(description="list of names of films they starred in")
actor_query = "Generate the filmography for a random actor."
parser = PydanticOutputParser(pydantic_object=Actor)
prompt = PromptTemplate(
template="Answer the user query.\n{format_instructions}\n{query}\n",
input_variables=["query"],
partial_variables={"format_instructions": parser.get_format_instructions()},
)
_input = prompt.format_prompt(query=actor_query)
output = model(_input.to_string())
print('파싱전 : ',output)
print('파싱후 : ',parser.parse(output))
파싱전 :
{"name": "Tom Hanks", "film_names": ["Forrest Gump", "Saving Private Ryan", "The Green Mile", "Cast Away", "Toy Story"]}
파싱후 : name='Tom Hanks' film_names=['Forrest Gump', 'Saving Private Ryan', 'The Green Mile', 'Cast Away', 'Toy Story']
일부 케이스에서는 오직 output만 사용하여 파싱 실수를 수정했지만, 불가능한 경우도 존재합니다. 예를 들어, 출력이 단순히 잘못된 형식이 아니라 부분적으로 완성되지 않은 경우입니다.
from langchain.prompts import PromptTemplate
from langchain.llms import OpenAI
from langchain.chat_models import ChatOpenAI
from langchain.output_parsers import PydanticOutputParser, OutputFixingParser
from pydantic import BaseModel, Field
class Action(BaseModel):
action: str = Field(description="action to take")
action_input: str = Field(description="input to the action")
parser = PydanticOutputParser(pydantic_object=Action)
prompt = PromptTemplate(
template="Answer the user query.\n{format_instructions}\n{query}\n",
input_variables=["query"],
partial_variables={"format_instructions": parser.get_format_instructions()},
)
prompt_value = prompt.format_prompt(query="who is leo di caprios gf?")
bad_response = '{"action": "search"}'
parser.parse_with_prompt(bad_response, prompt_value)
OutputParserException: Failed to parse Action from completion {"action": "search"}. Got: 1 validation error for Action
action_input
field required (type=value_error.missing)
위 예시를 보면, action과 action_input 변수 중 action 변수만 입력되어 에러가 발생합니다. 이때 OutputFixingParser를 사용하면 어떻게 되는지 확인해 보겠습니다.
fix_parser = OutputFixingParser.from_llm(parser=parser, llm=ChatOpenAI())
fix_parser.parse_with_prompt(bad_response, prompt_value)
Action(action='search', action_input='keyword')
OutputFixingParser를 사용하더라도, model은 action_input에 어떤 입력을 추가해야 할지 모르기 때문에 제대로 된 수정을 할 수 없습니다. 대신 RetryOutputParser를 사용하면 더 나은 response를 얻기 위해 다시 시도합니다.
from langchain.output_parsers import RetryWithErrorOutputParser
retry_parser = RetryWithErrorOutputParser.from_llm(parser=parser, llm=OpenAI(temperature=0))
retry_parser.parse_with_prompt(bad_response, prompt_value)
Action(action='search', action_input='who is leo di caprios gf?')
Structured output parser는 multiple fields를 반환받기 원할 때 사용합니다. Pydantic/JSON parser는 더욱 powerful하지만, 초기에 text field만 가진 데이터 구조에 대해 실험했습니다.
이번 섹션에서는 StructuredOutputParser를 사용하여 질문에 대한 대답과 질문에 대한 대답을 얻기 위해 어떤 사이트를 참조했는지 두 가지 fields를 parse하는 예시를 준비했습니다. davinci와 gpt-3.5-turbo를 사용하여 두 가지 버전을 준비했습니다.
from langchain.output_parsers import StructuredOutputParser, ResponseSchema
from langchain.prompts import PromptTemplate, ChatPromptTemplate, HumanMessagePromptTemplate
from langchain.llms import OpenAI
from langchain.chat_models import ChatOpenAI
response_schemas = [
ResponseSchema(name="answer", description="answer to the user's question"),
ResponseSchema(name="source", description="source used to answer the user's question, should be a website.")
]
output_parser = StructuredOutputParser.from_response_schemas(response_schemas)
prompt = PromptTemplate(
template="answer the users question as best as possible.\n{format_instructions}\n{question}",
input_variables=["question"],
partial_variables={"format_instructions": output_parser.get_format_instructions()}
)
# davinci
model = OpenAI(temperature=0)
_input = prompt.format_prompt(question="what's the capital of korea?")
output = model(_input.to_string())
print(output)
print(output_parser.parse(output))
```json
{
"answer": "Paris",
"source": "https://www.worldatlas.com/articles/what-is-the-capital-of-france.html"
}
```
{'answer': 'Paris', 'source': 'https://www.worldatlas.com/articles/what-is-the-capital-of-france.html'}
# gpt-3.5-turbo
chat_model = ChatOpenAI(temperature=0)
prompt = ChatPromptTemplate(
messages=[HumanMessagePromptTemplate.from_template("answer the users question as best as possible.\n{format_instructions}\n{question}")],
input_variables=["question"],
partial_variables={"format_instructions": output_parser.get_format_instructions()}
)
_input = prompt.format_prompt(question="what's the capital of korea? answer in one word")
output = chat_model(_input.to_messages())
print(output.content)
print(output_parser.parse(output.content))
```json
{
"answer": "Seoul",
"source": "https://en.wikipedia.org/wiki/Seoul"
}
```
{'answer': 'Seoul', 'source': 'https://en.wikipedia.org/wiki/Seoul'}
[Retrieval] Document loaders (0) | 2023.08.24 |
---|---|
[Retrieval] (1) | 2023.08.23 |
[MODEL I/O - Langauge Models] Output Parsers - 1 (0) | 2023.07.14 |
[MODEL I/O - Langauge Models] Chat Model How to (0) | 2023.07.13 |
[MODEL I/O - Langauge Models] Chat Models Base (0) | 2023.07.13 |
댓글 영역