[9기 문과생도 AI 캠프]에서 네이버 블로그 포스팅 자동화 코드를 작성하면서 겪었던 경험을 바탕으로 Langchain의 output parsing과 관련된 몇 가지 라이브러리에 대해 정리해 보도록 하겠습니다.
당시 프로젝트에서 ChatGPT를 이용해 “제목, 본문, 해시태그”라는 3개의 구성 요소로 글을 생성하도록 프롬프트를 작성하였습니다. 그러나 때때로 ChatGPT가 이 구조와는 다른 결과를 만들어 주어 네이버 자동 포스팅 과정에서 에러가 발생하곤 했었습니다.
이 문제를 해결하기 위해 try-except문과 Json 변환 함수 등을 활용했는데, 이는 에러가 발생하지 않을 때까지 글 생성을 반복하는 방식이었습니다.
이런 경험을 통해 ChatGPT가 생성한 결과물의 구조적 일관성 확보라는 것에 대해 생각을 하게 되었는데, 이는 Langchain의 LCEL(LangChain Expression Language)에서 제공하는 “Output Parser”의 개념과 밀접하게 연관되어 있는 듯 합니다.
여기에서는 Output Parser의 여러 라이브러리 중에서 특히 “PydanticOutputParser”와 “JsonOutputParser”에 대한 내용과 이를 활용한 간단한 예(뉴스 기사 3줄 요약)를 함께 정리해 보았습니다.
PydanticOutputParser
JsonOutputParser
JsonOutputParser 활용 사례
PydanticOutputParser
이 output parser를 사용하여 임의의 pydangtic model 형식을 지정하고, 이 스키마에 맞춰 LLM이 출력하도록 제어할 수 있습니다.
먼저, 여기서 “pydactic”에 대해 claude 3.5 sonnet에 물어 봤습니다.
네, Pydantic에 대해 설명해 드리겠습니다.
Pydantic은 Python에서 데이터 유효성 검사와 설정 관리를 위한 라이브러리입니다. 주요 특징과 용도는 다음과 같습니다:
1. 데이터 검증: Pydantic은 데이터 모델을 정의하고 입력된 데이터가 해당 모델과 일치하는지 확인합니다.
2. 타입 힌트 사용: Python의 타입 힌트를 활용하여 데이터 구조를 명확하게 정의합니다.
3. 자동 형변환: 가능한 경우 입력 데이터를 올바른 타입으로 자동 변환합니다.
4. 에러 처리: 데이터가 유효하지 않을 때 명확한 에러 메시지를 제공합니다.
5. JSON 호환: JSON으로의 직렬화와 역직렬화를 지원합니다.
6. 설정 관리: 애플리케이션의 설정을 관리하는 데 유용합니다.
추가적으로 Pydantic의 예시 코드는 다음과 같습니다.
from pydantic import BaseModel, Field, ValidationError
from typing import List
class User(BaseModel):
username: str
email: str
age: int = Field(gt=0, lt=120)
hobbies: List[str] = []
# 유효한 데이터로 User 인스턴스 생성
try:
user1 = User(username="john_doe", email="[email protected]", age=30, hobbies=["reading", "swimming"])
print(user1)
# JSON으로 변환
print(user1.json())
# 유효하지 않은 데이터로 User 인스턴스 생성 시도
user2 = User(username="jane_doe", email="invalid_email", age=150)
except ValidationError as e:
print(f"Validation error: {e}")
다음은 Langchain 공식 문서에 나온 “PydanticOutputParser”의 코드입니다.
from typing import List
from langchain.output_parsers import PydanticOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_core.pydantic_v1 import BaseModel, Field, validator
from langchain_openai import ChatOpenAI
model = ChatOpenAI(temperature=0)
# Define your desired data structure.
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")
# And a query intented to prompt a language model to populate the data structure.
actor_query = "Generate the filmography for a random actor."
# Set up a parser + inject instructions into the prompt template.
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()},
)
chain = prompt | model | parser
result = chain.invoke({"query": actor_query})
print(result)
# 요소별 분리 출력
print("요소별로 분리 출력")
print(f"[name] : {result.name}")
print(f"[file_names] : {result.film_names}")
name='Tom Hanks' film_names=['Forrest Gump', 'Cast Away', 'Saving Private Ryan', 'Toy Story', 'The Green Mile']
요소별로 분리 출력
[name] : Tom Hanks
[file_names] : ['Forrest Gump', 'Cast Away', 'Saving Private Ryan', 'Toy Story', 'The Green Mile']
기본적인 LCEL 구조에 다음의 3가지 포인트를 신경써서 보면 될 듯 합니다.
출력 형식에 대한 정의
parser 객체 생성
prompt 적용
2. JsonOutputParser
이 parser를 사용하여 임의의 json 스키마를 지정하고, 이 스키마에 맞춰 LLM의 생성 결과의 출력을 제어할 수 있습니다.
Langchain 공식 문서에 나온 기본 코드는 다음과 같습니다.
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_openai import ChatOpenAI
model = ChatOpenAI(temperature=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")
# And a query intented to prompt a language model to populate the data structure.
joke_query = "Tell me a joke."
# Set up a parser + inject instructions into the prompt template.
parser = JsonOutputParser(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()},
)
chain = prompt | model | parser
chain.invoke({"query": joke_query})
{'setup': "Why don't scientists trust atoms?",
'punchline': 'Because they make up everything!'}
3. 실제 활용 사례 : 뉴스 기사 3줄 요약
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_openai import ChatOpenAI
from typing import List
from trafilatura import fetch_url, extract
llm = ChatOpenAI(temperature=0)
# trafilatura library 활용 뉴스 내용 크롤링
def content_trafilatura(link):
downloaded = fetch_url(link)
return extract(downloaded, favor_precision=True, deduplicate=True)
link = "https://n.news.naver.com/mnews/article/031/0000846788?sid=104"
context = content_trafilatura(link)
# Define your desired data structure.
class Summary(BaseModel):
summary: List[str] = Field(description="3 bullet style list of summary in given context")
# And a query intented to prompt a language model to populate the data structure.
joke_query = "Tell me a joke."
# Set up a parser + inject instructions into the prompt template.
parser = JsonOutputParser(pydantic_object=Summary)
template = """당신은 뉴스 요약 Assistant입니다. 주어진 <context>를 3개의 bullet style list로 요약해 주세요.
<Important>각각의 bullet list는 50자를 넘지 않아야 하며, 개조식으로 작성할 것.</Important>
\n{format_instructions}\n<context>{context}</context>\n"
"""
prompt = PromptTemplate(
template=template,
input_variables=["context"],
partial_variables={"format_instructions": parser.get_format_instructions()},
)
chain = prompt | llm | parser
responses = chain.invoke({"context": context})
formatted_string = '\n'.join(f'👌 {item}' for item in responses['summary'])
print(formatted_string)
👌 애플과 메타 플랫폼이 '애플 인텔리전스'에 메타의 생성형 AI 모델을 통합하는 방안 논의 중
👌 애플은 독자적인 생성형 AI 시스템 '애플 인텔리전스'를 개발, 파트너사와 협력
👌 애플 인텔리전스를 통해 AI 스타트업들이 자사 서비스에 대한 프리미엄 구독을 판매할 수 있음
#11기 랭체인 #11기랭체인