티스토리 뷰

반응형

Python으로 공부하는 OpenAI 3편 — 자유문장 말고 JSON으로 받아야 실무가 시작됩니다

처음 OpenAI를 붙이면 다들 비슷합니다.
질문 보내고, 답변 잘 나오면 신기하고… 그다음엔 금방 이런 생각이 들어요.

“이걸 서비스 코드에 넣으려면 어떻게 하지?”
“응답 형식이 매번 조금씩 다른데 이거 파싱해도 되나?”
“프론트에 넘길 데이터는 문장이 아니라 구조여야 하는데?”

맞아요.
여기서부터가 진짜 시작입니다.

1편에서는 Python으로 OpenAI를 처음 호출했고,
2편에서는 프롬프트를 어떻게 써야 결과가 덜 흔들리는지 봤습니다.
이번 3편에서는 그 다음 단계, 그러니까 모델 응답을 JSON으로 안정적으로 받는 방법을 다룹니다.

이건 생각보다 훨씬 중요합니다.
왜냐하면 실무 서비스는 “그럴듯한 문장”보다 예측 가능한 데이터 구조가 더 중요하기 때문이죠.

OpenAI 공식 문서도 현재 새 프로젝트에는 Responses API 사용을 권장하고 있고, Structured Outputs를 통해 모델 응답이 내가 정의한 JSON Schema를 따르도록 만들 수 있다고 설명합니다. 또 텍스트 생성 가이드에서도 모델 출력은 자연어뿐 아니라 구조화된 JSON 데이터가 가능하다고 안내합니다. (OpenAI Developers)


1. 왜 자유문장 응답으로는 금방 한계가 오나

처음에는 이런 코드로도 충분해 보입니다.

response = client.responses.create(
    model="gpt-5.4",
    input="JWT를 설명해줘"
)

print(response.output_text)

실행은 잘 됩니다.
그런데 이걸 실무에 넣는 순간 문제가 생겨요.

예를 들어 우리는 이런 데이터를 원할 수 있습니다.

  • 제목
  • 한 줄 요약
  • 난이도
  • 예제 코드
  • 주의사항
  • 추천 태그

근데 자유문장으로 받으면 매번 위치가 달라지고, 키 이름도 없고, 어떤 날은 목록으로 나오고 어떤 날은 문단으로 나옵니다.
결국 파싱 로직이 지저분해지고, 프론트엔드나 DB 저장 코드도 불안정해집니다.

그래서 서비스 개발에서는 보통 이런 생각으로 넘어가야 합니다.

“모델에게 글을 잘 쓰게 하는 것”보다 “내가 원하는 구조를 정확히 반환하게 하는 것”이 더 중요하다.

OpenAI의 Structured Outputs 가이드는 JSON이 애플리케이션 간 데이터 교환에 널리 쓰이며, 이 기능은 모델이 개발자가 제공한 JSON Schema를 따르도록 보장해 필수 키 누락이나 잘못된 enum 값 같은 문제를 줄인다고 설명합니다. (OpenAI Developers)


2. JSON mode와 Structured Outputs는 같은 게 아닙니다

이 부분은 초보자가 정말 많이 헷갈립니다.

“JSON으로 받으면 되는 거 아냐?”
반은 맞고, 반은 부족합니다.

OpenAI 문서 기준으로 보면 개념을 이렇게 나누는 게 좋습니다.

JSON mode

모델이 유효한 JSON 형태로 응답하도록 유도합니다.

Structured Outputs

모델이 내가 정의한 JSON Schema를 정확히 따르도록 만듭니다.

차이는 꽤 큽니다.

예를 들어 JSON mode에서는 이런 일이 가능합니다.

{
  "title": "JWT 설명",
  "level": "easyish",
  "extra_note": "이건 내가 덧붙였어"
}

문법적으로는 JSON이라 문제없지만,
내 프로그램 입장에서는 difficulty가 와야 하고 level은 안 되고, easyish 같은 값도 허용되지 않을 수 있죠.

반면 Structured Outputs는 스키마 기준으로 키 이름, 필수 필드, enum, 추가 속성 허용 여부까지 통제할 수 있습니다. OpenAI 문서도 json_schema 형식 설정이 Structured Outputs를 활성화하며, supplied JSON Schema와 맞는 출력을 보장한다고 설명합니다. 반대로 { "type": "json_object" }는 유효한 JSON을 보장하는 JSON mode라고 구분합니다. (OpenAI Developers)


3. 지금 시점 기준으로 Python에서 기본 흐름은 이렇게 이해하면 됩니다

반응형

OpenAI 공식 Python 라이브러리 README에 따르면 Python SDK는 Python 3.9+ 애플리케이션에서 사용할 수 있고, 기본 인터페이스는 Responses API입니다. 예제도 OpenAI() 클라이언트를 만든 다음 client.responses.create(...)를 호출하는 구조로 제시됩니다. 그리고 Chat Completions는 “previous standard”로 안내되고 있습니다. (GitHub)

즉, 지금 기준으로 입문자 흐름은 대략 이렇습니다.

  1. OpenAI() 클라이언트를 만든다.
  2. responses.create()로 요청한다.
  3. 필요하면 text.format에 json_schema를 넣는다.
  4. 응답을 문자열 JSON으로 받아 파싱한다.
  5. 검증 실패나 예외 상황을 처리한다.

그리고 새 프로젝트라면 Responses API 중심으로 익히는 게 맞습니다. OpenAI의 마이그레이션 가이드는 Responses API를 Chat Completions의 진화된 형태로 설명하면서, 새 프로젝트에는 Responses 사용을 권장합니다. (OpenAI Developers)


4. 제일 먼저 해볼 만한 실전 예제: 학습용 JSON 요약기

이번 글에서는 너무 거창한 예제 말고,
주니어 개발자가 바로 이해할 수 있는 “학습용 JSON 요약기”를 만들어보겠습니다.

우리가 원하는 출력 형식은 이렇다고 해볼게요.

  • topic: 주제명
  • difficulty: beginner / intermediate / advanced
  • summary: 한 문단 요약
  • keywords: 핵심 키워드 배열

이렇게 구조를 정해두면 나중에:

  • 블로그 초안 생성기
  • 기술 용어 사전
  • 학습 카드 생성기
  • API 응답 표준화
  • 검색 인덱싱 전처리

같은 곳으로 바로 확장할 수 있습니다.


5. 실행 가능한 Python 코드 예제

아래 코드는 지금 글 기준으로 문법 점검해서 정리한 예제입니다.
핵심은 text.format.type = "json_schema"와 strict: True입니다.

import os
import json
from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()

api_key = os.getenv("OPENAI_API_KEY")
if not api_key:
    raise ValueError("OPENAI_API_KEY가 설정되지 않았습니다. .env 파일을 확인하세요.")

client = OpenAI(api_key=api_key)

SCHEMA = {
    "type": "object",
    "properties": {
        "topic": {"type": "string"},
        "difficulty": {
            "type": "string",
            "enum": ["beginner", "intermediate", "advanced"]
        },
        "summary": {"type": "string"},
        "keywords": {
            "type": "array",
            "items": {"type": "string"}
        }
    },
    "required": ["topic", "difficulty", "summary", "keywords"],
    "additionalProperties": False
}

response = client.responses.create(
    model="gpt-5.4-mini",
    instructions=(
        "당신은 주니어 개발자를 돕는 파이썬 멘토입니다. "
        "반드시 주어진 JSON Schema에 맞는 데이터만 반환하세요."
    ),
    input="파이썬 데코레이터를 설명해줘",
    text={
        "format": {
            "type": "json_schema",
            "name": "python_topic_summary",
            "schema": SCHEMA,
            "strict": True
        }
    }
)

json_text = response.output_text
data = json.loads(json_text)

print("topic:", data["topic"])
print("difficulty:", data["difficulty"])
print("summary:", data["summary"])
print("keywords:", data["keywords"])

OpenAI 문서는 Structured Outputs가 JSON Schema 기반으로 동작한다고 설명하고, Responses API는 새 프로젝트용 기본 인터페이스로 안내합니다. 또 GPT-5.4 가이드는 현재 일반 목적과 코딩 작업에서 gpt-5.4를 기본으로 시작하라고 권장하며, 더 작은 빠른 변형으로 gpt-5.4-mini와 gpt-5.4-nano를 제시합니다. (OpenAI Developers)


6. 코드에서 꼭 이해해야 하는 부분

이 코드는 길어 보여도 실제 핵심은 몇 개 없습니다.

SCHEMA

여기가 제일 중요합니다.
모델이 만들어야 할 응답의 “모양”을 정의합니다.

  • 어떤 키가 필요한지
  • 값의 타입이 뭔지
  • 허용 가능한 enum이 뭔지
  • 추가 필드를 허용할지

이걸 코드로 먼저 명확하게 적는 순간, 프롬프트 감이 아니라 계약(contract) 에 가까운 개발 방식으로 바뀝니다.

strict: True

이 옵션은 “대충 비슷한 JSON 말고, 내가 준 스키마에 맞춰”라는 느낌에 가깝습니다.
Structured Outputs 소개 문서도 이 기능이 supplied JSON Schema를 따르도록 보장한다고 설명합니다. (OpenAI Developers)

json.loads(json_text)

응답을 문자열로 받아 Python dict로 파싱합니다.
이제부터는 일반 백엔드 데이터처럼 다루면 됩니다.

이 지점이 좋습니다.
AI 결과물이 갑자기 마법의 문장이 아니라,
내 코드 안에서 검증 가능한 구조체 비슷한 존재가 되는 거죠.


7. 자유문장 응답과 JSON 응답의 차이, 체감으로 보면 이렇습니다

자유문장 응답은 읽기는 좋습니다.
하지만 코드에서 쓰기엔 불안합니다.

예를 들어 아래 두 경우를 비교해보세요.

자유문장

  • 사람이 읽기엔 자연스럽다
  • 하지만 키가 없다
  • 순서가 바뀔 수 있다
  • 일부 정보가 빠질 수 있다
  • 프론트/DB/검색 인덱싱에 바로 넣기 어렵다

JSON 응답

  • 사람이 읽기엔 덜 감성적일 수 있다
  • 하지만 키가 고정돼 있다
  • 파싱하기 쉽다
  • 누락 여부를 검증할 수 있다
  • 후처리 자동화가 쉽다

그래서 실무에서는 보통 이렇게 씁니다.

  • 사용자에게 보여줄 최종 문장: 별도 생성
  • 시스템 내부 처리 데이터: JSON 구조화 출력

즉, 보여주는 글과 저장하는 데이터는 분리하는 쪽이 훨씬 건강합니다.


8. 실무에서 JSON 구조화 출력이 특히 빛나는 순간들

이건 꼭 감이 와야 합니다.
JSON이 필요한 순간은 생각보다 많아요.

8-1. 블로그 초안 생성기

예를 들어 AI가 글을 바로 길게 쓰는 대신 먼저 이렇게 반환하게 할 수 있습니다.

  • 제목
  • 소제목 배열
  • 핵심 요약
  • 예상 독자
  • 추천 태그

그다음 템플릿 엔진이나 후속 요청으로 본문을 생성하면 됩니다.

8-2. 문서 분류기

공지문, 계약서, 회의록, 이력서, 문의글 등을 받아서:

  • 문서 유형
  • 중요도
  • 핵심 키워드
  • 후속 액션

형태로 반환하면, 워크플로 자동화에 바로 연결할 수 있습니다.

8-3. 개발 보조 도구

코드 리뷰 결과를:

  • severity
  • file
  • line
  • issue_type
  • suggestion

형태로 반환하면 IDE 확장이나 대시보드에 붙이기 좋습니다.

8-4. 고객 문의 분류

  • category
  • sentiment
  • urgency
  • reply_draft
  • escalation_needed

같이 뽑아두면 상담 시스템에 연결하기 훨씬 쉽습니다.

Structured Outputs 가이드가 강조하는 장점도 결국 비슷합니다. 애플리케이션이 다루기 쉬운 구조, 누락 없는 필수 필드, 잘못된 enum 값 방지 같은 것들이 전부 서비스 자동화와 연결되기 때문입니다. (OpenAI Developers)


9. 초보자가 제일 많이 하는 실수

이 파트는 진짜 중요합니다.

실수 1. “JSON으로 주세요”만 적고 끝낸다

이렇게 하면 유효한 JSON 비슷한 건 받을 수 있어도,
스키마가 흔들리기 쉽습니다.

input="JWT를 JSON으로 설명해줘"

이건 부족합니다.
가능하면 스키마를 명시하세요.

실수 2. 스키마는 만들었는데 additionalProperties를 열어둔다

그러면 모델이 필요 없는 필드를 덧붙일 수 있습니다.
실무에서 예측 가능성을 높이려면 additionalProperties: False가 꽤 유용합니다.

실수 3. enum을 안 쓴다

난이도처럼 값 후보가 정해진 필드는 enum으로 제한하는 게 좋습니다.

예:

  • beginner
  • intermediate
  • advanced

이렇게요.

실수 4. 파싱 실패 처리를 안 한다

AI 응답을 너무 믿고 곧바로 사용하는 코드는 금방 사고 납니다.
최소한 try/except와 로깅은 있어야 합니다.

실수 5. 사용자에게 보여줄 문장과 내부 처리 JSON을 섞는다

하나의 응답 안에 설명문도 길게 받고, JSON도 받고, 마크다운도 받고…
이렇게 섞기 시작하면 후처리가 지옥이 됩니다.


10. 예외 처리까지 넣은 조금 더 실무적인 버전

아래처럼 감싸두면 훨씬 낫습니다.

import os
import json
from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()

client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

SCHEMA = {
    "type": "object",
    "properties": {
        "topic": {"type": "string"},
        "difficulty": {
            "type": "string",
            "enum": ["beginner", "intermediate", "advanced"]
        },
        "summary": {"type": "string"},
        "keywords": {
            "type": "array",
            "items": {"type": "string"}
        }
    },
    "required": ["topic", "difficulty", "summary", "keywords"],
    "additionalProperties": False
}

def generate_python_topic_summary(user_input: str) -> dict:
    try:
        response = client.responses.create(
            model="gpt-5.4-mini",
            instructions=(
                "당신은 주니어 개발자를 위한 파이썬 멘토입니다. "
                "반드시 JSON Schema에 맞는 결과만 반환하세요."
            ),
            input=user_input,
            text={
                "format": {
                    "type": "json_schema",
                    "name": "python_topic_summary",
                    "schema": SCHEMA,
                    "strict": True
                }
            }
        )

        return json.loads(response.output_text)

    except json.JSONDecodeError as e:
        raise ValueError(f"응답 JSON 파싱 실패: {e}") from e
    except Exception as e:
        raise RuntimeError(f"OpenAI 호출 실패: {e}") from e

if __name__ == "__main__":
    result = generate_python_topic_summary("파이썬 제너레이터를 설명해줘")
    print(json.dumps(result, ensure_ascii=False, indent=2))

이 코드는 아직도 입문 단계지만,
그래도 벌써 서비스 코드 냄새가 좀 납니다.
함수로 감쌌고, 실패를 분리했고, JSON을 최종 표준 응답처럼 다루기 시작했으니까요.


11. 언제 gpt-5.4, 언제 gpt-5.4-mini를 쓰면 좋나

모델 선택은 늘 고민됩니다.
지금 공식 가이드 기준으로는 gpt-5.4가 일반 목적과 대부분의 코딩 작업의 기본 출발점이고, 더 작고 빠른 변형으로 gpt-5.4-mini, gpt-5.4-nano가 제시됩니다. (OpenAI Developers)

실무 감각으로 단순화하면 이렇습니다.

  • 품질이 가장 중요하고 결과 정확도가 우선이면 gpt-5.4
  • 반복 호출이 많고 비용/속도가 중요하면 gpt-5.4-mini
  • 대량 분류나 가벼운 후처리엔 더 작은 모델 검토

입문자라면 처음엔 gpt-5.4-mini로 실험하고,
정말 중요한 작업에서 gpt-5.4와 비교해보는 방식이 괜찮습니다.


12. 앞으로는 Pydantic까지 연결하면 훨씬 강해집니다

이번 글에서는 일부러 표준 json.loads()까지만 썼습니다.
왜냐하면 주니어 입장에서 먼저 이해해야 할 건 “스키마 기반 구조화 응답” 그 자체이기 때문입니다.

그런데 Python 실무에서는 여기서 한 단계 더 나아가서 보통 이런 흐름으로 갑니다.

  • JSON Schema 정의
  • 응답 받기
  • Pydantic 모델로 검증
  • 유효하지 않으면 예외 처리
  • 서비스 계층으로 전달

이렇게 되면 거의 일반 API 응답 검증과 비슷한 감각으로 다룰 수 있습니다.
OpenAI Python 라이브러리 자체도 타입 정의를 포함한다고 README에서 설명하고 있고, SDK는 sync/async 클라이언트를 모두 제공합니다. (GitHub)

이번 편에서는 여기까지만 하고,
다음 편에서 이걸 더 파이썬스럽게 가져가 보겠습니다.


13. 오늘 꼭 해보면 좋은 실습

이번 편은 직접 손으로 돌려봐야 확실히 느껴집니다.

실습 1

SCHEMA에 example_code 필드를 추가해보세요.

"example_code": {"type": "string"}

그리고 required에도 넣어보세요.

실습 2

difficulty enum 값을 일부러 잘못 설계해보세요.

예:

  • ["low", "middle", "high"]

그리고 모델이 그 틀에 맞춰 어떻게 응답하는지 보세요.

실습 3

additionalProperties를 True처럼 열어두거나 제거해보세요.
응답이 얼마나 덜 통제되는지 체감이 납니다.

실습 4

같은 스키마로 질문만 바꿔보세요.

  • “파이썬 데코레이터를 설명해줘”
  • “리스트 컴프리헨션을 설명해줘”
  • “비동기 프로그래밍을 설명해줘”

이러면 하나의 스키마가 여러 주제에 재사용된다는 감각이 옵니다.


14. 오늘 글의 핵심 요약

오늘 글에서 꼭 가져가야 할 건 딱 이것입니다.

실무에서 중요한 건 AI가 말을 잘하는 게 아니라, 내 프로그램이 안정적으로 다룰 수 있는 구조로 응답하는 것이다.

그리고 그 첫걸음이 바로:

  • Responses API 사용
  • JSON Schema 정의
  • Structured Outputs 적용
  • json.loads()로 파싱
  • 예외 처리 넣기

입니다. OpenAI는 새 프로젝트에 Responses API를 권장하고 있고, Structured Outputs는 모델이 supplied JSON Schema를 따르도록 설계된 기능입니다. 이 둘을 같이 쓰는 게 현재 Python 실무 입문에서 가장 좋은 출발점 중 하나입니다. (OpenAI Developers)


다음 편 예고

다음 글에서는 여기서 한 단계 더 실무적으로 들어가겠습니다.

Python + Pydantic으로 OpenAI 구조화 응답 검증하기

이 주제로,

  • Pydantic 모델 정의
  • JSON 응답을 Python 객체처럼 다루는 방법
  • 검증 실패 처리
  • FastAPI와 붙일 때 왜 이 조합이 좋은지
  • 블로그 생성기/문서 분류기/코드 리뷰 봇 구조 예시

까지 이어가보겠습니다.


출처

  • OpenAI 공식 Python SDK README — Python 3.9+ 지원, OpenAI() 클라이언트, responses.create() 사용, Chat Completions는 이전 표준으로 안내. (GitHub)
  • OpenAI Structured Outputs 가이드 — Structured Outputs는 supplied JSON Schema를 따르는 응답을 보장하도록 설계됨. (OpenAI Developers)
  • OpenAI Responses API 마이그레이션 가이드 — 새 프로젝트에는 Responses API 권장. (OpenAI Developers)
  • OpenAI Text generation / API docs — 텍스트뿐 아니라 구조화된 JSON 데이터 출력 가능. (OpenAI Developers)
  • OpenAI GPT-5.4 가이드 — gpt-5.4를 일반 목적과 코딩의 기본 모델로 제안, gpt-5.4-mini/nano 안내. (OpenAI Developers)

 

Python, OpenAI, Structured Outputs, JSON Schema, OpenAI Python SDK, Responses API, GPT-5.4, Pydantic, AI 백엔드, 생성형 AI, LLM, Python 실무, API 응답 검증, JSON 파싱, 주니어 개발자

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/03   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함
반응형