AI에게 고객 문의를 분석해 달라고 요청하면 “배송이 늦어져 확인이 필요합니다”처럼 자연어로 답할 수 있습니다. 하지만 그 결과를 고객관리 시스템이나 자동화 프로그램에서 바로 사용하려면 문의 유형, 우선순위, 담당 부서처럼 필요한 값을 일정한 위치와 형식으로 받아야 합니다.
이때 활용하는 방법이 구조화 출력(Structured Outputs)입니다. 구조화 출력은 미리 정한 데이터 구조를 기준으로 AI의 응답 형식을 맞추는 기능입니다. 다만 정해진 형식으로 답한다고 해서 내용까지 반드시 사실이 되는 것은 아닙니다.
이미지 안내: 대표 이미지와 본문 그림은 개념을 설명하기 위한 별도 예시이며, 아래 코드의 실행 결과가 아닙니다. 그림의 department·status·action·confidence 등은 본문 스키마에 포함되지 않으며, 예시 주문번호와 신뢰도 기준도 설명용 값입니다. 이 글의 코드에서는 category·priority·order_number·summary·requires_human_review의 다섯 항목만 반환하도록 정하고, 입력에서 주문번호를 찾을 수 없으면 null을 사용합니다.
최초 발행일: 2026년 10월 11일 · 최종 수정일: 2026년 11월 4일

1. 구조화 출력은 왜 필요할까?
고객이 다음과 같이 문의했다고 가정해 보겠습니다.
“어제 주문한 상품이 아직 도착하지 않았어요. 배송 상태를 확인해 주세요.”
AI가 “배송 관련 문의입니다”라고 답하면 사람은 뜻을 이해할 수 있습니다. 하지만 프로그램이 문의 유형, 우선순위, 주문번호를 각각 저장하려면 문장 속에서 필요한 값을 다시 찾아야 합니다.
반면 결과 항목을 미리 정해 두면 프로그램은 정해진 키를 확인해 후속 작업을 처리할 수 있습니다. 예를 들어 category에 따라 담당 부서를 선택하고, priority에 따라 문의 처리 순서를 정할 수 있습니다.
핵심은 AI가 문장을 만드는 데 그치지 않고 다른 프로그램이 읽을 수 있는 약속된 구조로 결과를 반환하도록 하는 것입니다.
2. JSON과 JSON Schema는 무엇이 다를까?
JSON은 프로그램 사이에서 데이터를 주고받을 때 널리 사용하는 데이터 표현 형식입니다. 다음은 앞선 문의에 대한 설명용 예시입니다.
{
"category": "배송",
"priority": "보통",
"order_number": null,
"summary": "배송 상태 확인 요청",
"requires_human_review": false
}
JSON 자체는 값을 담는 형식입니다. 반면 JSON Schema는 그 JSON에 어떤 항목이 있어야 하는지, 각 항목에 어떤 종류의 값이 들어가야 하는지를 설명하는 규칙입니다.
예를 들어 스키마에 priority 값은 “긴급”, “보통”, “낮음” 중 하나라고 정해 두면 다른 표현이 들어오지 않도록 제한할 수 있습니다. order_number는 문자열이거나, 주문번호를 찾을 수 없는 경우에는 null로 반환하도록 정할 수도 있습니다.

3. JSON 모드와 구조화 출력은 무엇이 다를까?
JSON 모드는 모델이 문법적으로 올바른 JSON을 반환하도록 돕습니다. 하지만 JSON 문법이 맞더라도 필요한 항목이 빠지거나, 프로그램이 허용하지 않는 값이 들어갈 수 있습니다.
예를 들어 다음 JSON은 문법상 유효하지만 priority가 빠져 있습니다.
{
"category": "배송"
}
프로그램이 category와 priority를 모두 필요로 한다면 이 결과는 업무에 바로 쓸 수 없습니다. 구조화 출력은 지원되는 JSON Schema를 사용해 필요한 키와 값의 유형, 허용 가능한 값 등을 제한합니다.
아래 표는 두 방식의 차이를 간단히 정리한 것입니다.
| 비교 항목 | JSON 모드 | 구조화 출력 |
|---|---|---|
| 목표 | 문법상 유효한 JSON을 반환하도록 지원 | 지원되는 JSON Schema의 구조와 값 규칙을 따르도록 지원 |
| 필수 키·허용 값 | 별도 보장이 없어 애플리케이션에서 확인해야 함 | 스키마로 필수 키와 열거형 값 등을 지정 |
| Responses API 설정 | text.format.type: "json_object" | text.format.type: "json_schema"와 스키마 설정 |
| 주의점 | 응답 중단 등 예외를 처리해야 함 | 거부·불완전 응답과 의미상 오류를 별도로 처리해야 함 |
단, “구조화 출력이면 어떤 JSON Schema 규칙이든 쓸 수 있다”는 뜻은 아닙니다. 지원되는 스키마 문법과 모델을 확인해야 합니다.
4. JSON Schema 예제로 보는 필수 항목과 허용 값
다음 스키마는 고객 문의 분류 결과에 필요한 항목을 정의한 예입니다.
{
"type": "object",
"properties": {
"category": {
"type": "string",
"enum": [
"배송",
"환불",
"상품 문의",
"기타"
]
},
"priority": {
"type": "string",
"enum": [
"긴급",
"보통",
"낮음"
]
},
"order_number": {
"type": [
"string",
"null"
]
},
"summary": {
"type": "string"
},
"requires_human_review": {
"type": "boolean"
}
},
"required": [
"category",
"priority",
"order_number",
"summary",
"requires_human_review"
],
"additionalProperties": false
}
각 규칙은 다음과 같은 뜻입니다.
type: "object": 결과를 객체 형태로 반환합니다.properties: 결과에 들어갈 키와 각 값의 유형을 정합니다.enum: 지정한 목록 안의 값만 허용합니다.required: 반드시 반환해야 하는 키를 나열합니다.additionalProperties: false: 스키마에 정의하지 않은 키를 추가하지 못하게 합니다.["string", "null"]: 값이 문자열이거나null일 수 있다는 뜻입니다.
Strict 모드에서는 모든 필드를 required에 포함해야 합니다. 값이 없는 항목을 선택적으로 생략하는 대신, 위 예시의 order_number처럼 null을 허용하는 방식으로 표현할 수 있습니다.
다음은 이 스키마에 맞는 설명용 JSON 예시입니다. 실제 API 실행 결과가 아닙니다.
{
"category": "배송",
"priority": "보통",
"order_number": null,
"summary": "배송 상태 확인 요청",
"requires_human_review": false
}
반대로 order_number나 requires_human_review를 누락하면 필수 키가 빠진 결과가 됩니다. priority에 “즉시”처럼 허용 목록에 없는 값을 넣어도 스키마에 맞지 않습니다.
OpenAI의 Structured Outputs는 JSON Schema 전체가 아니라 지원되는 일부 규칙을 사용합니다. 예를 들어 객체 스키마에서는 additionalProperties: false가 필요하며, Strict 모드에서는 모든 필드를 필수로 지정해야 합니다. 지원되지 않는 규칙을 넣으면 요청이 오류로 끝날 수 있습니다.
5. Responses API에서 구조화 출력 사용하기
다음은 Python SDK와 Responses API로 문의를 분류하는 예시입니다. API 키를 환경 변수 OPENAI_API_KEY로 설정하고 OpenAI Python SDK를 설치한 환경을 가정합니다.
import json
from openai import OpenAI
client = OpenAI()
schema = {
"type": "object",
"properties": {
"category": {"type": "string", "enum": ["배송", "환불", "상품 문의", "기타"]},
"priority": {"type": "string", "enum": ["긴급", "보통", "낮음"]},
"order_number": {"type": ["string", "null"]},
"summary": {"type": "string"},
"requires_human_review": {"type": "boolean"},
},
"required": ["category", "priority", "order_number", "summary", "requires_human_review"],
"additionalProperties": False,
}
response = client.responses.create(
model="gpt-6-astra",
input=[
{"role": "system", "content": "고객 문의를 분석해 지정된 JSON 구조로 분류하세요."},
{"role": "user", "content": "어제 주문한 상품이 아직 도착하지 않았어요. 배송 상태를 확인해 주세요."},
],
text={"format": {"type": "json_schema", "name": "support_ticket", "strict": True, "schema": schema}},
)
if response.status == "incomplete":
reason = response.incomplete_details.reason if response.incomplete_details else "unknown"
raise RuntimeError(f"응답이 완성되지 않았습니다: {reason}")
if response.status != "completed":
raise RuntimeError(f"응답 상태를 확인해야 합니다: {response.status}")
message = next((item for item in response.output if item.type == "message"), None)
if message is None:
raise RuntimeError("응답 메시지가 없습니다.")
content = next((item for item in message.content if item.type in ("refusal", "output_text")), None)
if content is None:
raise RuntimeError("처리할 수 있는 응답 내용이 없습니다.")
if content.type == "refusal":
raise RuntimeError(f"모델이 요청을 거부했습니다: {content.refusal}")
result = json.loads(content.text)
print(result)
이 예시는 API 요청의 형태를 보여 주기 위한 것입니다. 이 글의 문법 검수는 Python 컴파일 검사로 수행하며, 실제 API 요청을 보내 실행한 것은 아닙니다. 사용 중인 모델이 Structured Outputs를 지원하는지, SDK 버전과 API 설정이 맞는지 확인한 뒤 테스트해야 합니다.
gpt-6-astra는 Structured Outputs를 지원하지만 API 호출은 토큰 사용량에 따라 비용이 발생합니다. 확인 시점의 공식 요금표에서 표준·짧은 문맥 가격은 입력 100만 토큰당 $10, 출력 100만 토큰당 $50입니다. 긴 문맥·캐시·Batch 등은 요금이 다를 수 있고 가격과 지원 모델도 변경될 수 있으므로 예제를 실행하기 전에 공식 모델 페이지와 최신 API 요금표를 확인하세요.
response.status가 incomplete이면 JSON이 완성되지 않았을 수 있습니다. incomplete_details.reason을 확인해 원인을 처리해야 합니다. 거부 응답은 요청한 JSON 스키마를 따르지 않을 수 있으므로 일반 결과와 별도로 다뤄야 합니다.

6. AI 에이전트는 이 결과를 어떻게 활용할까?
고객 문의 처리 시스템의 흐름을 단순화하면 다음과 같습니다.
- 고객이 문의를 제출합니다.
- 프로그램이 문의 내용을 모델에 전달합니다.
- 모델이 스키마에 맞는 결과를 반환합니다.
- 프로그램이 응답 상태와 결과를 확인합니다.
- 업무 규칙에 따라 담당 부서에 전달하거나 저장합니다.
- 중요한 업무라면 담당자가 확인한 뒤 처리합니다.
구조화 출력은 결과의 형식을 일정하게 만드는 역할을 합니다. 실제로 데이터를 저장하거나 환불을 승인하는 것은 별도의 프로그램이나 업무 절차가 담당합니다.
7. 구조화 출력이 오류를 없애 주지는 않는다
스키마를 지키는 결과라도 내용이 사실과 다를 수 있습니다. 모델이 주문번호를 잘못 읽거나 문의 유형을 잘못 분류했더라도, 반환된 값의 유형과 형식은 스키마에 맞을 수 있습니다.
따라서 다음 검사를 구분해야 합니다.
- 형식 확인: 필수 항목이 있고, 값이 정한 자료형과 허용 목록을 따르는지 확인합니다.
- 업무 내용 확인: 주문번호가 실제 존재하는지, 금액이 원본 문서와 맞는지 확인합니다.
- 응답 상태 확인: 모델이 거부했는지, 응답이 중간에 끝났는지 확인합니다.
결제·금융·의료 정보처럼 오류의 영향이 큰 업무에서는 구조화 출력만으로 판단을 확정하지 않는 편이 안전합니다. 검증에 실패한 결과는 자동 처리하지 않고 보류하거나 담당자 검토로 넘기는 절차를 둘 수 있습니다.

8. 구조화 출력과 도구 사용·웹훅은 어떻게 다를까?
구조화 출력은 AI가 어떤 형식으로 답할지 정하는 기능입니다. 도구 사용은 AI가 프로그램의 기능을 요청하고, 그 결과를 바탕으로 작업을 이어가는 과정입니다.
AI가 달력에 일정을 추가하거나 데이터베이스를 조회해야 한다면 도구 사용 또는 함수 호출이 필요할 수 있습니다. 반대로 AI의 답변을 화면에 표시하거나 데이터베이스에 저장할 일정한 결과가 필요하다면 text.format의 JSON Schema 응답 형식을 사용할 수 있습니다. OpenAI API에서는 구조화 출력을 일반 응답 형식과 함수 호출 방식으로 사용할 수 있으므로, 필요한 작업이 “정해진 형식으로 답하기”인지 “도구를 호출하기”인지 구분해야 합니다.
도구 실행 과정은 AI 에이전트 도구 사용 설명에서, 외부 사건을 전달하는 웹훅의 역할은 AI 에이전트 웹훅 설명에서 더 자세히 볼 수 있습니다.
웹훅은 주문 완료 같은 사건이 발생했을 때 다른 시스템에 정보를 전달하는 통로입니다. 구조화 출력은 그 사건을 전달하는 기술이 아니라, AI가 문의를 분석한 결과를 정해진 데이터 형태로 만드는 기술입니다. 두 기능은 자동화 흐름에서 함께 쓰일 수 있지만 서로 같은 기능은 아닙니다.
9. 정리: 형식과 내용의 정확성을 함께 확인해야 한다
구조화 출력은 AI의 결과를 프로그램이 읽기 쉬운 데이터 구조로 만드는 방법입니다. JSON Schema로 필수 항목, 자료형, 허용 값을 정하면 후속 시스템에서 결과를 다루기 쉬워집니다.
하지만 스키마에 맞는다는 사실이 내용의 사실성이나 업무상 정답성을 보장하지는 않습니다. API 응답이 완료되었는지, 거부되거나 중단된 응답은 아닌지 확인하고, 중요한 값은 별도의 업무 규칙이나 사람의 검토로 검증해야 합니다.
기존에 제공하던 형식을 유지하면서 더 안정적으로 연결하려면 응답 형식, 응답 상태, 내용의 정확성을 각각 확인하는 것이 중요합니다.
핑백: AI 에이전트 웹훅이란? 새로운 정보가 들어오면 자동으로 작업하는 원리 – nextsignal24