구조화된 출력
구조화된 출력 (Structured output · LangChain Python)
구조화된 출력(Structured output)은 에이전트가 특정하고 예측 가능한 형식으로 데이터를 돌려주게 해 줘요. 자연어 응답을 파싱하는 대신, 애플리케이션이 바로 쓸 수 있는 형태의 구조화된 데이터를 받게 되죠. 그 형태는 JSON 객체, Pydantic 모델, 또는 dataclass예요.
출처: 공식문서
💡 이 페이지는
create_agent를 쓴 에이전트에서의 구조화된 출력을 다뤄요. 에이전트 없이 모델에 직접 구조화된 출력을 적용하려면 모델 - 구조화된 출력을 보세요.
LangChain의 create_agent는 구조화된 출력을 자동으로 처리해 줘요. 사용자가 원하는 스키마를 정해 두면, 모델이 구조화된 데이터를 만들었을 때 이를 잡아내서 검증하고, 에이전트 상태의 'structured_response' 키에 담아 돌려줘요.
def create_agent(
...
response_format: Union[
ToolStrategy[StructuredResponseT],
ProviderStrategy[StructuredResponseT],
type[StructuredResponseT],
None,
]
)
Response format
response_format으로 에이전트가 구조화된 데이터를 어떻게 돌려줄지 제어할 수 있어요.
ToolStrategy[StructuredResponseT]: 도구 호출(tool calling)을 사용해 구조화된 출력을 만든다ProviderStrategy[StructuredResponseT]: 프로바이더 고유의 구조화된 출력 기능을 사용한다type[StructuredResponseT]: 스키마 타입 — 모델 기능에 따라 최적의 전략을 자동 선택한다None: 구조화된 출력을 명시적으로 요청하지 않음
스키마 타입을 직접 넘기면 LangChain이 자동으로 골라줘요.
- 모델과 프로바이더가 고유(native) 구조화된 출력을 지원하면
ProviderStrategy(예: OpenAI, Anthropic (Claude), xAI (Grok)) - 나머지 모델은
ToolStrategy
⚠️ JSON Schema 딕셔너리는 명시적인 전략(
ProviderStrategy나ToolStrategy)으로 감싸야 해요.response_format에 그냥 딕셔너리를 넘기면 자동으로 감지되지 않아요.
📝 고유 구조화된 출력 기능의 지원 여부는
langchain>=1.1을 쓰면 모델의 프로파일 데이터에서 동적으로 읽어와요. 데이터가 없으면 다른 조건을 쓰거나 직접 지정하면 돼요.custom_profile = { "structured_output": True, # ... } model = init_chat_model("...", profile=custom_profile)도구를 지정한 경우, 모델이 도구와 구조화된 출력을 동시에 지원해야 해요.
구조화된 응답은 에이전트 최종 상태의 structured_response 키에 담겨 돌아와요.
Provider strategy (프로바이더 전략)
일부 모델 프로바이더(OpenAI, xAI (Grok), Gemini, Anthropic (Claude) 등)는 자체 API에서 구조화된 출력을 고유하게 지원해요. 가능할 때 가장 믿을 수 있는 방법이에요.
이 전략을 쓰려면 ProviderStrategy를 구성하면 돼요.
class ProviderStrategy(Generic[SchemaT]):
schema: type[SchemaT]
strict: bool | None = None
ℹ️
strict파라미터는langchain>=1.2가 필요해요.
schema (필수): 구조화된 출력 형식을 정의하는 스키마예요. 지원하는 종류는:
- Pydantic 모델: 필드 검증이 있는
BaseModel하위 클래스. 검증된 Pydantic 인스턴스를 반환해요. - Dataclasses: 타입 어노테이션이 있는 Python dataclass. 딕셔너리를 반환해요.
- TypedDict: 타입이 있는 딕셔너리 클래스. 딕셔너리를 반환해요.
- JSON Schema: JSON 스키마 스펙이 담긴 딕셔너리. 최상위
title과description키가 있어야 해요. 딕셔너리를 반환해요.
strict: 엄격한 스키마 준수를 켜는 선택적 boolean 파라미터예요. 일부 프로바이더(OpenAI, xAI)가 지원해요. 기본값은 None(비활성)이에요.
스키마 타입을 create_agent.response_format에 직접 넘기고 모델이 고유 구조화된 출력을 지원하면, LangChain이 자동으로 ProviderStrategy를 사용해요.
# Pydantic Model
from pydantic import BaseModel, Field
from langchain.agents import create_agent
class ContactInfo(BaseModel):
"""Contact information for a person."""
name: str = Field(description="The name of the person")
email: str = Field(description="The email address of the person")
phone: str = Field(description="The phone number of the person")
agent = create_agent(
model="gpt-5.5",
response_format=ContactInfo # Auto-selects ProviderStrategy
)
result = agent.invoke({
"messages": [{"role": "user", "content": "Extract contact info from: John Doe, [email protected], (555) 123-4567"}]
})
print(result["structured_response"])
# ContactInfo(name='John Doe', email='[email protected]', phone='(555) 123-4567')
# Dataclass
from dataclasses import dataclass
from langchain.agents import create_agent
@dataclass
class ContactInfo:
"""Contact information for a person."""
name: str # The name of the person
email: str # The email address of the person
phone: str # The phone number of the person
agent = create_agent(
model="gpt-5.5",
tools=[],
response_format=ContactInfo # Auto-selects ProviderStrategy
)
result = agent.invoke({
"messages": [{"role": "user", "content": "Extract contact info from: John Doe, [email protected], (555) 123-4567"}]
})
result["structured_response"]
# {'name': 'John Doe', 'email': '[email protected]', 'phone': '(555) 123-4567'}
# TypedDict
from typing_extensions import TypedDict
from langchain.agents import create_agent
class ContactInfo(TypedDict):
"""Contact information for a person."""
name: str # The name of the person
email: str # The email address of the person
phone: str # The phone number of the person
agent = create_agent(
model="gpt-5.5",
tools=[],
response_format=ContactInfo # Auto-selects ProviderStrategy
)
result = agent.invoke({
"messages": [{"role": "user", "content": "Extract contact info from: John Doe, [email protected], (555) 123-4567"}]
})
result["structured_response"]
# {'name': 'John Doe', 'email': '[email protected]', 'phone': '(555) 123-4567'}
# JSON Schema
from langchain.agents import create_agent
from langchain.agents.structured_output import ProviderStrategy
contact_info_schema = {
"title": "ContactInfo",
"type": "object",
"description": "Contact information for a person.",
"properties": {
"name": {"type": "string", "description": "The name of the person"},
"email": {"type": "string", "description": "The email address of the person"},
"phone": {"type": "string", "description": "The phone number of the person"}
},
"required": ["name", "email", "phone"]
}
agent = create_agent(
model="gpt-5.5",
tools=[],
response_format=ProviderStrategy(contact_info_schema)
)
result = agent.invoke({
"messages": [{"role": "user", "content": "Extract contact info from: John Doe, [email protected], (555) 123-4567"}]
})
result["structured_response"]
# {'name': 'John Doe', 'email': '[email protected]', 'phone': '(555) 123-4567'}
프로바이더 고유 구조화된 출력은 모델 프로바이더가 스키마를 직접 강제하기 때문에 신뢰성과 검증이 뛰어나요. 지원한다면 이 방법을 쓰는 게 좋아요.
📝 모델 선택에서 프로바이더가 고유 구조화된 출력을 지원한다면,
response_format=ProviderStrategy(ProductReview)대신response_format=ProductReview라고 쓰는 것과 기능적으로 같아요.어느 쪽이든 구조화된 출력이 지원되지 않을 때는 에이전트가 도구 호출 전략으로 자동 폴백해요.
Tool calling strategy (도구 호출 전략)
고유 구조화된 출력을 지원하지 않는 모델에는, LangChain이 도구 호출로 같은 결과를 만들어 줘요. 도구 호출을 지원하는 모델(대부분의 최신 모델)이면 전부 동작해요.
이 전략을 쓰려면 ToolStrategy를 구성하면 돼요.
class ToolStrategy(Generic[SchemaT]):
schema: type[SchemaT]
tool_message_content: str | None
handle_errors: Union[
bool,
str,
type[Exception],
tuple[type[Exception], ...],
Callable[[Exception], str],
]
schema (필수): 구조화된 출력 형식을 정의하는 스키마예요. 지원하는 종류는:
- Pydantic 모델: 필드 검증이 있는
BaseModel하위 클래스. 검증된 Pydantic 인스턴스를 반환해요. - Dataclasses: 타입 어노테이션이 있는 Python dataclass. 딕셔너리를 반환해요.
- TypedDict: 타입이 있는 딕셔너리 클래스. 딕셔너리를 반환해요.
- JSON Schema: JSON 스키마 스펙이 담긴 딕셔너리. 최상위
title과description키가 있어야 해요. 딕셔너리를 반환해요. - Union 타입: 여러 스키마 옵션. 모델이 문맥에 따라 가장 적절한 스키마를 고를 거예요.
tool_message_content: 구조화된 출력이 만들어졌을 때 반환되는 도구 메시지의 커스텀 내용이에요. 지정하지 않으면 구조화된 응답 데이터를 보여 주는 메시지가 기본으로 사용돼요.
handle_errors: 구조화된 출력 검증 실패 시의 오류 처리 전략이에요. 기본값은 True예요.
True: 기본 오류 템플릿으로 모든 오류를 잡는다str: 이 커스텀 메시지로 모든 오류를 잡는다type[Exception]: 기본 메시지로 이 예외 타입만 잡는다tuple[type[Exception], ...]: 기본 메시지로 이 예외 타입들만 잡는다Callable[[Exception], str]: 오류 메시지를 반환하는 커스텀 함수False: 재시도 없이 예외를 그대로 전파
# Pydantic Model
from pydantic import BaseModel, Field
from typing import Literal
from langchain.agents import create_agent
from langchain.agents.structured_output import ToolStrategy
class ProductReview(BaseModel):
"""Analysis of a product review."""
rating: int | None = Field(description="The rating of the product", ge=1, le=5)
sentiment: Literal["positive", "negative"] = Field(description="The sentiment of the review")
key_points: list[str] = Field(description="The key points of the review. Lowercase, 1-3 words each.")
agent = create_agent(
model="gpt-5.5",
tools=[],
response_format=ToolStrategy(ProductReview)
)
result = agent.invoke({
"messages": [{"role": "user", "content": "Analyze this review: 'Great product: 5 out of 5 stars. Fast shipping, but expensive'"}]
})
result["structured_response"]
# ProductReview(rating=5, sentiment='positive', key_points=['fast shipping', 'expensive'])
# Dataclass
from dataclasses import dataclass
from typing import Literal
from langchain.agents import create_agent
from langchain.agents.structured_output import ToolStrategy
@dataclass
class ProductReview:
"""Analysis of a product review."""
rating: int | None # The rating of the product (1-5)
sentiment: Literal["positive", "negative"] # The sentiment of the review
key_points: list[str] # The key points of the review
agent = create_agent(
model="gpt-5.5",
tools=[],
response_format=ToolStrategy(ProductReview)
)
result = agent.invoke({
"messages": [{"role": "user", "content": "Analyze this review: 'Great product: 5 out of 5 stars. Fast shipping, but expensive'"}]
})
result["structured_response"]
# {'rating': 5, 'sentiment': 'positive', 'key_points': ['fast shipping', 'expensive']}
# TypedDict
from typing import Literal
from typing_extensions import TypedDict
from langchain.agents import create_agent
from langchain.agents.structured_output import ToolStrategy
class ProductReview(TypedDict):
"""Analysis of a product review."""
rating: int | None # The rating of the product (1-5)
sentiment: Literal["positive", "negative"] # The sentiment of the review
key_points: list[str] # The key points of the review
agent = create_agent(
model="gpt-5.5",
tools=[],
response_format=ToolStrategy(ProductReview)
)
result = agent.invoke({
"messages": [{"role": "user", "content": "Analyze this review: 'Great product: 5 out of 5 stars. Fast shipping, but expensive'"}]
})
result["structured_response"]
# {'rating': 5, 'sentiment': 'positive', 'key_points': ['fast shipping', 'expensive']}
# JSON Schema
from langchain.agents import create_agent
from langchain.agents.structured_output import ToolStrategy
product_review_schema = {
"title": "ProductReview",
"type": "object",
"description": "Analysis of a product review.",
"properties": {
"rating": {
"type": ["integer", "null"],
"description": "The rating of the product (1-5)",
"minimum": 1,
"maximum": 5
},
"sentiment": {
"type": "string",
"enum": ["positive", "negative"],
"description": "The sentiment of the review"
},
"key_points": {
"type": "array",
"items": {"type": "string"},
"description": "The key points of the review"
}
},
"required": ["sentiment", "key_points"]
}
agent = create_agent(
model="gpt-5.5",
tools=[],
response_format=ToolStrategy(product_review_schema)
)
result = agent.invoke({
"messages": [{"role": "user", "content": "Analyze this review: 'Great product: 5 out of 5 stars. Fast shipping, but expensive'"}]
})
result["structured_response"]
# {'rating': 5, 'sentiment': 'positive', 'key_points': ['fast shipping', 'expensive']}
# Union Types
from pydantic import BaseModel, Field
from typing import Literal, Union
from langchain.agents import create_agent
from langchain.agents.structured_output import ToolStrategy
class ProductReview(BaseModel):
"""Analysis of a product review."""
rating: int | None = Field(description="The rating of the product", ge=1, le=5)
sentiment: Literal["positive", "negative"] = Field(description="The sentiment of the review")
key_points: list[str] = Field(description="The key points of the review. Lowercase, 1-3 words each.")
class CustomerComplaint(BaseModel):
"""A customer complaint about a product or service."""
issue_type: Literal["product", "service", "shipping", "billing"] = Field(description="The type of issue")
severity: Literal["low", "medium", "high"] = Field(description="The severity of the complaint")
description: str = Field(description="Brief description of the complaint")
agent = create_agent(
model="gpt-5.5",
tools=[],
response_format=ToolStrategy(Union[ProductReview, CustomerComplaint])
)
result = agent.invoke({
"messages": [{"role": "user", "content": "Analyze this review: 'Great product: 5 out of 5 stars. Fast shipping, but expensive'"}]
})
result["structured_response"]
# ProductReview(rating=5, sentiment='positive', key_points=['fast shipping', 'expensive'])
커스텀 도구 메시지 내용 (Custom tool message content)
tool_message_content 파라미터로, 구조화된 출력이 만들어졌을 때 대화 기록에 나타나는 메시지를 원하는 대로 바꿀 수 있어요.
from pydantic import BaseModel, Field
from typing import Literal
from langchain.agents import create_agent
from langchain.agents.structured_output import ToolStrategy
class MeetingAction(BaseModel):
"""Action items extracted from a meeting transcript."""
task: str = Field(description="The specific task to be completed")
assignee: str = Field(description="Person responsible for the task")
priority: Literal["low", "medium", "high"] = Field(description="Priority level")
agent = create_agent(
model="gpt-5.5",
tools=[],
response_format=ToolStrategy(
schema=MeetingAction,
tool_message_content="Action item captured and added to meeting notes!"
)
)
agent.invoke({
"messages": [{"role": "user", "content": "From our meeting: Sarah needs to update the project timeline as soon as possible"}]
})
================================ Human Message =================================
From our meeting: Sarah needs to update the project timeline as soon as possible
================================== Ai Message ==================================
Tool Calls:
MeetingAction (call_1)
Call ID: call_1
Args:
task: Update the project timeline
assignee: Sarah
priority: high
================================= Tool Message =================================
Name: MeetingAction
Action item captured and added to meeting notes!
tool_message_content가 없으면 최종 ToolMessage는 다음과 같을 거예요.
================================= Tool Message =================================
Name: MeetingAction
Returning structured response: {'task': 'update the project timeline', 'assignee': 'Sarah', 'priority': 'high'}
오류 처리 (Error handling)
모델이 도구 호출로 구조화된 출력을 만들 때 실수할 수 있어요. LangChain은 이런 오류를 자동으로 처리하는 지능형 재시도 메커니즘을 제공해요.
여러 구조화된 출력 오류 (Multiple structured outputs error)
모델이 구조화된 출력 도구를 여러 개 잘못 호출하면, 에이전트가 ToolMessage로 오류 피드백을 주고 모델에 재시도를 요청해요.
from pydantic import BaseModel, Field
from typing import Union
from langchain.agents import create_agent
from langchain.agents.structured_output import ToolStrategy
class ContactInfo(BaseModel):
name: str = Field(description="Person's name")
email: str = Field(description="Email address")
class EventDetails(BaseModel):
event_name: str = Field(description="Name of the event")
date: str = Field(description="Event date")
agent = create_agent(
model="gpt-5.5",
tools=[],
response_format=ToolStrategy(Union[ContactInfo, EventDetails]) # Default: handle_errors=True
)
agent.invoke({
"messages": [{"role": "user", "content": "Extract info: John Doe ([email protected]) is organizing Tech Conference on March 15th"}]
})
================================ Human Message =================================
Extract info: John Doe ([email protected]) is organizing Tech Conference on March 15th
None
================================== Ai Message ==================================
Tool Calls:
ContactInfo (call_1)
Call ID: call_1
Args:
name: John Doe
email: [email protected]
EventDetails (call_2)
Call ID: call_2
Args:
event_name: Tech Conference
date: March 15th
================================= Tool Message =================================
Name: ContactInfo
Error: Model incorrectly returned multiple structured responses (ContactInfo, EventDetails) when only one is expected.
Please fix your mistakes.
================================= Tool Message =================================
Name: EventDetails
Error: Model incorrectly returned multiple structured responses (ContactInfo, EventDetails) when only one is expected.
Please fix your mistakes.
================================== Ai Message ==================================
Tool Calls:
ContactInfo (call_3)
Call ID: call_3
Args:
name: John Doe
email: [email protected]
================================= Tool Message =================================
Name: ContactInfo
Returning structured response: {'name': 'John Doe', 'email': '[email protected]'}
스키마 검증 오류 (Schema validation error)
구조화된 출력이 기대한 스키마와 맞지 않으면, 에이전트가 구체적인 오류 피드백을 제공해요.
from pydantic import BaseModel, Field
from langchain.agents import create_agent
from langchain.agents.structured_output import ToolStrategy
class ProductRating(BaseModel):
rating: int | None = Field(description="Rating from 1-5", ge=1, le=5)
comment: str = Field(description="Review comment")
agent = create_agent(
model="gpt-5.5",
tools=[],
response_format=ToolStrategy(ProductRating), # Default: handle_errors=True
system_prompt="You are a helpful assistant that parses product reviews. Do not make any field or value up."
)
agent.invoke({
"messages": [{"role": "user", "content": "Parse this: Amazing product, 10/10!"}]
})
================================ Human Message =================================
Parse this: Amazing product, 10/10!
================================== Ai Message ==================================
Tool Calls:
ProductRating (call_1)
Call ID: call_1
Args:
rating: 10
comment: Amazing product
================================= Tool Message =================================
Name: ProductRating
Error: Failed to parse structured output for tool 'ProductRating': 1 validation error for ProductRating.rating
Input should be less than or equal to 5 [type=less_than_equal, input_value=10, input_type=int].
Please fix your mistakes.
================================== Ai Message ==================================
Tool Calls:
ProductRating (call_2)
Call ID: call_2
Args:
rating: 5
comment: Amazing product
================================= Tool Message =================================
Name: ProductRating
Returning structured response: {'rating': 5, 'comment': 'Amazing product'}
오류 처리 전략 (Error handling strategies)
handle_errors 파라미터로 오류를 처리하는 방식을 바꿀 수 있어요.
커스텀 오류 메시지:
ToolStrategy(
schema=ProductRating,
handle_errors="Please provide a valid rating between 1-5 and include a comment."
)
handle_errors가 문자열이면, 에이전트는 항상 고정된 도구 메시지로 모델에 재시도를 요청해요.
================================= Tool Message =================================
Name: ProductRating
Please provide a valid rating between 1-5 and include a comment.
특정 예외만 처리:
ToolStrategy(
schema=ProductRating,
handle_errors=ValueError # Only retry on ValueError, raise others
)
handle_errors가 예외 타입이면, 발생한 예외가 지정 타입일 때만 (기본 오류 메시지로) 재시도하고, 그 외에는 예외를 그대로 던져요.
여러 예외 타입 처리:
ToolStrategy(
schema=ProductRating,
handle_errors=(ValueError, TypeError) # Retry on ValueError and TypeError
)
handle_errors가 예외 튜플이면, 발생한 예외가 지정 타입 중 하나일 때만 (기본 오류 메시지로) 재시도하고, 그 외에는 예외를 그대로 던져요.
커스텀 오류 처리 함수:
from langchain.agents.structured_output import StructuredOutputValidationError
from langchain.agents.structured_output import MultipleStructuredOutputsError
def custom_error_handler(error: Exception) -> str:
if isinstance(error, StructuredOutputValidationError):
return "There was an issue with the format. Try again."
elif isinstance(error, MultipleStructuredOutputsError):
return "Multiple structured outputs were returned. Pick the most relevant one."
else:
return f"Error: {str(error)}"
agent = create_agent(
model="gpt-5.5",
tools=[],
response_format=ToolStrategy(
schema=Union[ContactInfo, EventDetails],
handle_errors=custom_error_handler
) # Default: handle_errors=True
)
result = agent.invoke({
"messages": [{"role": "user", "content": "Extract info: John Doe ([email protected]) is organizing Tech Conference on March 15th"}]
})
for msg in result['messages']:
# If message is actually a ToolMessage object (not a dict), check its class name
if type(msg).__name__ == "ToolMessage":
print(msg.content)
# If message is a dictionary or you want a fallback
elif isinstance(msg, dict) and msg.get('tool_call_id'):
print(msg['content'])
StructuredOutputValidationError가 발생하면:
================================= Tool Message =================================
Name: ToolStrategy
There was an issue with the format. Try again.
MultipleStructuredOutputsError가 발생하면:
================================= Tool Message =================================
Name: ToolStrategy
Multiple structured outputs were returned. Pick the most relevant one.
그 외 오류일 때:
================================= Tool Message =================================
Name: ToolStrategy
Error: <error message>
오류 처리를 끄기:
response_format = ToolStrategy(
schema=ProductRating,
handle_errors=False # All errors raised
)