2026년에 OpenAI Agent를 운영하는 팀이 가장 잘 빠지는 함정은 「모델이 말을 하느냐」가 아니다. 옛 코드가 여전히 json_object를 구조화 출력으로 쓰거나, Chat Completions에서 비엄격 Function Calling을 그대로 두는 쪽이다. 신규 프로젝트는 공식 권장대로 gpt-5.6부터 시작하는 편이 맞다. Function Calling과 Structured Outputs는 밑단이 같은 제약 디코딩이지만, 진입점·기본 strict·JSON Schema 부분집합은 서로 다르다.
대조 기준일은 2026년 8월 18일이다. 필드와 동작은 OpenAI Function Calling 가이드, Structured Outputs 가이드를 따른다. 지연·가격·성공률은 지어내지 않는다. 공개 문서가 고정하지 않은 지점은 「실서비스 재생으로 확인해야 함」이라고 밝힌다.
OpenAI 호환 백엔드를 다른 모델로 옮길 때는 구조화 출력과 도구 루프를 따로 검수해야 한다. Base URL만 바꾸면 안 된다. 사이트 안의 OpenAI API를 Kimi K3로 옮길 때 검수표 2026과 맞춰 보면 된다.
결론부터: 세 층이 한꺼번에 바뀌었다
많은 저장소는 아직 2024년 머리로 돌아간다. 프롬프트에 「JSON으로 답하라」고 쓰고, 코드 펜스를 정규식으로 긁는다. 2026년 운영 경로에서는 스키마 위반, 필드 누락, enum 환각이 파서를 그대로 뚫는다.
바꿔야 하는 것은 세 층이지, 더 큰 모델 이름으로 갈아끼우는 일이 아니다.
- 납품 계약
- 사용자나 하류 서비스에 넘기는 최종 객체. Structured Outputs를 쓴다(Responses에서는
text.format, Chat Completions에서는response_format.json_schema). - 실행 계약
- 모델이 도구를 호출할 때 인자는 그 도구 자체의 JSON Schema에 맞아야 한다. 이것이 Function Calling이며, 밑단은 Structured Outputs와 같은 제약 디코딩이다.
- 호환 계약
- 옛 JSON Mode(
json_object)는 「JSON처럼 보인다」만 보장하고, 필드·타입·enum이 Schema와 같다는 것은 보장하지 않는다. 공식은 Structured Outputs의 전신으로 두었고, 신규 프로젝트의 주경로로 쓰지 말라고 본다.
놓치기 쉬운 제품 쪽 변화도 있다. 새 코드는 Responses API로 간다. Chat Completions는 여전히 쓸 수 있지만 strict 기본값이 다르다. Responses는 Schema를 가능한 한 엄격 모드로 정규화하고, 실패할 때만 되돌린다. Chat Completions 기본값은 여전히 비엄격 최선을 다하기다.
모델과 API 주경로: gpt-5.6 + Responses
Structured Outputs는 GPT-4o 세대부터 쓸 수 있다. 공식이 신규 프로젝트에 권하는 것은 gpt-5.6이다. 더 오래된 gpt-4-turbo와 그 이전 스냅샷에서 문서는 여전히 JSON Mode를 가리키며, 완전한 json_schema 엄격 출력이 아니다.
선정할 때 진입점부터 나눈다
| 원하는 결과 | 쓸 진입점 | 2026년 주의점 |
|---|---|---|
| 사용자/하류에 고정 객체 | Responses: text.format, 또는 Chat Completions: response_format: json_schema |
strict: true를 켠다. SDK는 Pydantic / Zod + parse() |
| 함수 호출, DB 조회, 상태 변경 | tools의 function tool |
인자 Schema도 strict. 병렬 호출과 다중 도구 루프는 실행기를 직접 짠다 |
| 도구 면이 넓어 한 번에 컨텍스트에 넣기 싫다 | tool_search 지연 로드 |
gpt-5.4 이상만. 도구 정의는 입력 토큰에 잡힌다 |
| 인자가 JSON이 아니라 자유 텍스트나 특정 문법 | custom tools + 선택 CFG | DSL·질의 언어에 맞다. function JSON Schema에 억지로 넣지 않는다 |
SDK 쪽에서 들일 습관은, additionalProperties를 빼먹기 쉬운 Schema를 손으로 쓰지 않는 것이다. 공식 helper로 타입에서 생성한다. Python은 client.responses.parse(..., text_format=YourModel), JavaScript는 zodTextFormat. 손으로 쓴 Schema가 strict: true 제약을 못 지키면 요청 자체가 거절된다. 「모델이 아무거나 내고 재시도」가 아니다.
Gemini 노선과 비교할 때 「OpenAI SDK 호환」을 Schema 동작까지 같다고 읽지 않는다. Google 쪽 능력 경로는 Gemini 3.5 Pro 신기능: 알아야 할 AI 능력 업그레이드 10가지를 본다. 같은 JSON Schema를 벤더 간에 복사하면 중첩 객체의 additionalProperties가 보통 첫 폭발 지점이다.
JSON Mode, Structured Outputs, Function Calling
운영 사고에서 가장 흔한 혼동은, 로그가 JSON이니 Structured Outputs가 이미 켜졌다고 믿는 것이다. 공식 의미로 나누면 아래와 같다.
| 능력 | 적법 JSON 보장 | Schema 준수 보장 | 전형적인 활성화 | 해당 모델 |
|---|---|---|---|---|
| JSON Mode | 예 | 아니오 | text.format.type = json_object |
일부 GPT-5 호환 구간 포함. 옛 스냅샷에서 흔함 |
| Structured Outputs | 예 | 예(지원되는 Schema 부분집합) | json_schema + strict: true |
gpt-4o-2024-08-06 / gpt-4o-mini 이후. 신규는 gpt-5.6 |
| Function Calling + strict | 도구 인자는 적법 JSON | 인자가 parameters Schema에 맞음 | tools의 strict: true |
tools를 지원하는 모델. 항상 strict 권장 |
Function Calling을 쓰지 말아야 할 때
모델이 시스템에 손댈 필요가 없으면—재고 조회도, 티켓 수정도, 스크립트 실행도 없이 답을 카드·단계·점수로만 쪼개면—Structured Outputs가 맞다. 반대로 출력이 「이 부작용을 실행해 달라」면 반드시 tools다. 함수 인자를 최종 답 Schema로 위장하지 않는다.
거절은 「깨진 JSON」이 아니다
안전 거절 때 모델은 Schema에 억지로 끼워 넣지 않는다. Responses / Chat Completions는 별도의 refusal 필드를 준다. 파싱 층은 거절을 일급으로 다룬다. 먼저 refusal, 그다음 output_parsed. 빈 객체를 성공으로 치지 않는다.
Strict JSON Schema의 하드 규칙
strict를 켜면 OpenAI가 받는 것은 JSON Schema의 부분집합이지, 임의의 Draft 2020-12 문서가 아니다. 요청 단위에서 가장 자주 벽에 부딪히는 세 가지는 다음과 같다.
properties에 나온 필드는 모두required배열에 있어야 한다.- 중첩을 포함한 모든
object에additionalProperties: false가 필요하다. - 루트 객체는
anyOf가 될 수 없다. 선택은 「필수 + null 허용」으로 쓴다. 예:["string", "null"].
즉 required에서 빼서 선택인 척하기는 strict에서 바로 400이다. 올바른 쓰기는 필드를 required로 두고 타입을 null 허용 유니온으로 하는 것이다. 애플리케이션 층에서 null을 「미제공」으로 본다.
아래는 운영에서 흔한 「티켓 추출」 객체다. 중첩 object에도 additionalProperties를 쓴 점에 주목한다.
from openai import OpenAI
from pydantic import BaseModel
client = OpenAI()
class Ticket(BaseModel):
title: str
priority: str
assignee: str | None
tags: list[str]
response = client.responses.parse(
model="gpt-5.6",
input=[
{"role": "system", "content": "사용자 설명에서 티켓 필드를 추출한다."},
{"role": "user", "content": "로그인 페이지 500, Noah에게 배정, 우선순위 높음, 태그 auth와 api."},
],
text_format=Ticket,
)
ticket = response.output_parsed
print(ticket.title, ticket.priority, ticket.assignee)
디버깅 중 요청이 거절되면 먼저 오류 메시지에 빠진 제약을 본다. 모델을 내리는 것은 그다음이다. Playground가 만든 Schema는 기본이 이미 strict이므로, 저장소에 그대로 복사하는 편이 「옛 json_object 프롬프트를 고치는」 것보다 보통 빠르다.
벤더를 넘길 때 한 번 더 맞춘다. 「모든 object에 false」인 같은 Schema가 일부 호환 게이트웨이·다른 모델에서는 HTTP 400이 될 수 있다. 그때는 업무 Schema를 세 벌 두지 말고 공급자별 변환을 둔다.
Function Calling 2026: strict, tool_search, custom tools
공식은 이제 Function Calling과 tool calling을 같은 일로 본다. JSON Schema로 호출 가능한 함수를 적고, 앱 쪽 실행기로 부작용을 돌린다. 2026년 문서에서 추가되거나 강조된 항목은 Agent 루프를 바로 바꾼다.
strict 기본값은 추측하지 않는다
- 항상
strict: true를 명시한다. - Responses: strict를 생략하면 서버가 Schema 정규화를 시도한다. 실패하면 비엄격으로 돌아가고, 응답의 tool은
strict: false로 보인다. - Chat Completions: 생략 시 기본은 비엄격이다.
- 미세조정 모델이 한 턴에 여러 함수를 부르면, 문서는 그 턴에서 strict가 꺼질 수 있다고 적는다.
도구 정의는 컨텍스트에 들어가고 입력 토큰으로 과금된다. 설명이 너무 길거나 한 번에 40개를 걸면 청구와 도구 선택 정확도가 같이 나빠진다. 도구가 많으면 tool_search로 저빈도 도구를 지연 로드한다. gpt-5.4 이상만. 루프에서는 tool_search_call / tool_search_output이 먼저 나온 뒤 실제 function_call로 들어갈 수 있다.
custom tools: DSL을 JSON 객체에 구겨 넣지 않는다
function tools는 구조화 인자에 맞다. custom tools는 자유 텍스트 입출력에 맞고, 문맥 자유 문법(CFG)을 붙일 수 있다. SQL 조각, 내부 질의 언어, 종결 기호가 서로 배타여야 하는 형식은 「string 필드에 프롬프트를 쌓는」 것보다 CFG가 안정적이다. CFG가 unexpected tokens를 내면 먼저 종결자의 겹침을 보고, 모델을 탓하지 않는다.
tools = [{
"type": "function",
"name": "get_order",
"description": "주문 번호로 상태를 조회한다. 사용자가 명확한 주문 번호를 준 경우에만 호출한다.",
"strict": True,
"parameters": {
"type": "object",
"properties": {
"order_id": {"type": "string"},
"locale": {"type": ["string", "null"]},
},
"required": ["order_id", "locale"],
"additionalProperties": False,
},
}]
실행 루프 자체는 같다. finish_reason / item type이 도구 호출이면 로컬 함수를 돌리고, 결과를 tool 역할로 돌려 다시 요청한다. 바뀐 점은 인자를 json.loads로 운에 맡기지 않아도 된다는 것이다. assistant 메시지 전체(tool_calls 포함)는 반드시 저장한다. 빼면 다음 턴에서 호출 ID가 사라진다.
병렬 도구 호출에서는 순서가 아니라 호출 ID로 맞춘다
한 응답에 tool call이 여러 개일 수 있다. 반환은 call_id로 맞추고, 배열 인덱스로 순서를 가정하지 않는다. 카나리 로그에는 최소한 도구 이름, 인자 해시, 소요 시간, strict 여부, schema 폴백 여부를 남긴다.
기존 프로젝트 이관 검수표
「돌아간다」와 「납품할 수 있다」를 나눠 검수한다. 옛 백엔드 스위치는 남겨 두고, 독립 환경에서 실제 트래픽을 재생한다.
- 모델: 새 경로는
gpt-5.6(또는 계정에서 열린 동급)을 지정한다. 게이트웨이가 조용히 옛 스냅샷으로 매핑하지 못하게 한다. - 출력:
json_object를json_schema+strict: true, 또는 Responses의text.format으로 바꾼다. - 도구: 각 function에
required와 중첩additionalProperties: false를 채운다. 선택 필드는 null 허용 유니온으로 바꾼다. - 파싱:
parse()와refusal분기를 넣는다. 스트리밍에서는 증분 JSON과 최종 parsed 객체가 같은지 확인한다. - 도구 면: 열몇 개를 넘으면
tool_search를 검토한다. 먼저 description을 줄이고, 그다음 지연 로드를 본다. - 대조: 같은 고정 작업으로 옛 JSON Mode와 새 Schema 경로의 재시도율, 필드 누락률, 수작업 재작업을 비교한다.
통과 기준은 「200이 돌아온다」가 아니다. 파서에 정규식 우회가 없고, 도구 인자 타입이 안정적이고, 거절이 관측 가능하고, 롤백 스위치를 연습했다는 것이다. SDK·재생 스크립트·브라우저 세션을 오래 켜 둘 때 노트북 절전은 대조 실험을 끊는다. 바로 뒤에서 클라우드 Mac mini가 들어가는 지점이다.
FAQ
JSON Mode와 Structured Outputs를 섞을 수 있나?
같은 경로에서는 섞지 않는다. JSON Mode는 적법 JSON만, Structured Outputs가 Schema를 보장한다. 섞으면 모니터링이 「파싱 실패」를 모델 문제인지 계약 문제인지 가리지 못한다. 새 코드는 json_schema / text.format만 쓴다.
신규 프로젝트에서도 Chat Completions를 써야 하나?
Responses를 쓸 수 있으면 Responses다. 공식 예제, parse helper, strict 정규화가 이쪽을 우선한다. 기존 Chat Completions는 이어갈 수 있지만 strict를 명시하고, 기본이 비엄격이라는 차이를 받아들인다.
strict만 켜면 Schema가 400인 이유
가장 흔한 것은 required 누락, 중첩 object에 additionalProperties:false 없음, 루트를 anyOf로 씀, 선택을 「required에 안 넣기」로 표현함이다. 오류 메시지의 제약을 채운 뒤 다시 보낸다. Schema 오류를 strict 끄기로 가리지 않는다. 비엄격 폴백을 의도한 경우만 예외다.
Function Calling은 반드시 strict인가?
공식은 항상 켜라고 한다. 끄면 인자는 최선을 다하기라서, 실행기가 누락과 타입 드리프트를 막아야 한다. Responses에서 생략하면 서버가 값을 바꿀 수도 있다. 로그에 최종 strict 값을 남긴다.
tool_search는 언제 올릴 가치가 있나?
도구 정의가 컨텍스트를 분명히 밀어내거나, 대부분 도구가 한 작업에서 쓰이지 않을 때다. gpt-5.4 이상이 필요하다. 배포 전에 「먼저 도구 검색, 그다음 호출」 두 구간 궤적을 재생한다. 옛 실행기가 function_call만 보면 거기서 끊긴다.
Schema가 보장되면 업무 값 검증은 생략하나?
아니다. 제약 디코딩은 외래 키, 권한, 멱등을 보지 않는다. enum이 적법해도 재고에 의미 있는 값은 아닐 수 있다. Schema 검증과 업무 검증을 로그 두 층으로 나눠야 장애 때 가른다.
gpt-5.6과 이전 GPT-5.x의 구조화 출력 차이는?
문서는 gpt-5.6을 신규 프로젝트 기본으로 적는다. 계정·리전·배치·미세조정 경로에서 능력이 맞는지는 현재 모델 목록과 최소 parse 요청으로 확인한다. 블로그 별칭으로 게이트웨이 매핑을 추측하지 않는다.
Claude / Grok과 JSON Schema를 한 장으로 공유할 수 있나?
계약 방언은 Draft 2020-12에 가깝지만 부분집합이 다르다. OpenAI strict는 모든 object에 additionalProperties:false를 요구한다. 중첩 층의 그 필드를 거절하는 공급자도 있다. 업무 Schema는 한 장, 공급자 변환 층을 따로 둔다.
추가 읽기
클라우드 Mac mini에서는 Schema 검수를 24/7 켜 둘 수 있다
Function Calling과 Structured Outputs 회귀는 본질이 장시간 온라인 대조 실험이다. SDK 두 세트, 고정 재생 집합, 도구 샌드박스, 스트리밍 프론트. 노트북 덮개 절전도 피해야 한다. Apple Silicon 통합 메모리는 로컬 프록시와 브라우저 디버그를 같이 돌리기에 맞다. macOS에서는 Homebrew, Docker, SSH가 바로 된다. M4 Mac mini 대기 전력은 대략 4W라 검수 환경을 밤새 켜 두기 좋다.
가정 대역폭을 빼앗지 않고 SSH로 상주하는 Mac에서 Agent 재생을 돌리고 싶다면, Nuvcloud 클라우드 Mac mini M4는 「개발기」와 「대조 실험기」를 나누는 마찰이 적은 선택이다——요금제를 확인하기. strict Schema 카나리를 자기 노트북에 묶지 않아도 된다.