Skip to content

출력 파서와 구조화 응답

LLM의 응답을 문자열이나 JSON 등 원하는 형식으로 변환하는 방법을 배웁니다

학습 목표

  1. StrOutputParser로 AIMessage를 문자열로 변환한다
  2. JSON OutputParser의 한계를 이해한다
  3. Pydantic 모델과 with_structured_output으로 안정적인 구조화 응답을 받는다
  4. 프롬프트 설계로 출력을 최소화하는 방법을 익힌다

1. 왜 출력 파서가 필요한가?

LLM을 invoke하면 AIMessage 객체가 반환됩니다. 하지만 실제로 필요한 것은 그 안의 텍스트입니다.

python
response = llm.invoke("What is the capital of France?")
print(type(response))  # <class 'langchain_core.messages.ai.AIMessage'>
print(response.content)  # "The capital of France is Paris."

예를 들어, Python 서버에서 이 결과를 JavaScript 프론트엔드로 전달해야 한다면 AIMessage 클래스는 쓸 수 없습니다. 문자열로 변환해야 합니다.


2. StrOutputParser

2.1 AIMessage → string 변환

python
from langchain_core.output_parsers import StrOutputParser

parser = StrOutputParser()

# LLM 호출
ai_message = llm.invoke("What is the capital of France?")

# 파서로 변환
answer = parser.invoke(ai_message)
print(type(answer))  # <class 'str'>
print(answer)         # "The capital of France is Paris."

2.2 핵심 값만 추출하기

"Paris"만 필요한데 장황한 답변이 오면? 프롬프트를 수정합니다:

python
from langchain_core.prompts import PromptTemplate

prompt = PromptTemplate(
    template="What is the capital of {country}? Return the name of the city only.",
    input_variables=["country"]
)

prompt_value = prompt.invoke({"country": "France"})
ai_message = llm.invoke(prompt_value)
answer = parser.invoke(ai_message)
print(answer)  # "Paris"

: 많은 상용 모델은 출력 토큰이 입력 토큰보다 더 비싼 편이다. 정확한 가격은 자주 바뀌므로, 필요한 정보만 받도록 프롬프트를 설계하고 최신 가격표는 제공사 문서를 확인하자.


3. JSON OutputParser의 한계

3.1 시도해보기

나라에 대한 정보를 JSON으로 받고 싶다면?

python
from langchain_core.output_parsers import JsonOutputParser

prompt = PromptTemplate(
    template="""Tell me about {country}.
Include capital, population, language, and currency.
Return as a JSON dictionary only.""",
    input_variables=["country"]
)

parser = JsonOutputParser()

prompt_value = prompt.invoke({"country": "France"})
ai_message = llm.invoke(prompt_value)
result = parser.invoke(ai_message)  # 에러 발생!

3.2 왜 에러가 나는가?

LLM의 응답은 항상 string입니다. JSON처럼 보여도 실제로는 문자열이라서:

  • \``json` 마크다운 코드블록이 붙기도 하고 안 붙기도 함
  • replace로 제거하면 본문의 "json" 단어까지 영향받음
  • 매번 형식이 달라서 안정적인 파싱이 불가능

즉, 자유 생성된 텍스트를 JSON OutputParser에 바로 기대는 방식은 실전에서 불안정하다. 가능하면 모델이 구조화 출력을 직접 지원하는 경로를 우선 고려하는 편이 안전하다.


4. Pydantic + with_structured_output

4.1 Pydantic 모델 정의

Pydantic의 BaseModel로 원하는 출력 구조를 스키마로 정의합니다:

python
from pydantic import BaseModel, Field

class CountryDetail(BaseModel):
    capital: str = Field(description="수도 이름")
    population: int = Field(description="인구 수")
    language: str = Field(description="공용 언어")
    currency: str = Field(description="화폐 단위")

이 모델은 LLM에게 **"이 4개의 필드만 정확하게 반환해라"**고 지시하는 역할을 합니다.

4.2 with_structured_output 사용

python
from langchain_ollama import ChatOllama
from pydantic import BaseModel, Field

class CountryDetail(BaseModel):
    capital: str = Field(description="수도 이름")
    population: int = Field(description="인구 수")
    language: str = Field(description="공용 언어")
    currency: str = Field(description="화폐 단위")

llm = ChatOllama(model="llama3.2:1b")

# LLM에 Pydantic 모델을 연결
structured_llm = llm.with_structured_output(CountryDetail)

# 호출하면 Pydantic 모델 인스턴스가 반환됨
result = structured_llm.invoke("Tell me about France")

with_structured_output()은 특히 구조화 출력 또는 tool calling을 지원하는 모델에서 안정적으로 동작한다. ChatOllama도 지원하지만, 실제 성공 여부는 선택한 로컬 모델의 기능에 따라 달라질 수 있다.

4.3 결과 활용

python
# 필드 직접 접근
print(result.capital)      # "Paris"
print(result.population)   # 67390000
print(result.language)     # "French"
print(result.currency)     # "Euro"

# JSON(dict)으로 변환
json_data = result.model_dump()
print(json_data)
# {"capital": "Paris", "population": 67390000,
#  "language": "French", "currency": "Euro"}

# dict 접근
print(json_data["capital"])  # "Paris"

4.4 JSON OutputParser vs Pydantic 비교

항목JSON OutputParserPydantic + with_structured_output
안정성불안정 (형식이 오락가락)안정적 (스키마 강제)
타입 검증없음자동 (int, str 등)
필드 접근dict["key"]result.field 또는 dict["key"]
JSON 변환수동 파싱 필요model_dump() 한 줄
실전 사용제한적으로 가능하지만 취약권장

5. 전체 흐름 비교


핵심 정리

  • StrOutputParser: AIMessage → 문자열 변환. 프론트엔드 연동에 유용
  • JSON OutputParser: LLM 응답의 형식이 불안정하여 실전에서 비권장
  • Pydantic + with_structured_output: 스키마를 정의하여 안정적으로 구조화 응답을 받는 권장 방법
  • model_dump()로 Pydantic 인스턴스를 dict/JSON으로 변환 가능
  • 프롬프트에 "Return only..."를 추가하여 불필요한 출력을 줄이면 비용 절감