<!-- Canonical URL: https://ask.atlascloud.ai/ko/structured-json-output-after-llm-api-migration -->

# LLM API 마이그레이션 후 구조화 JSON 출력이 깨지는 이유는 무엇인가요?

> LLM API 마이그레이션 후 구조화 JSON이 깨지는 주된 이유는 이전 스택과 새 스택이 서로 다른 계약을 적용하기 때문입니다. 프롬프트부터 탓하기 전에 요청 스키마, 응답 모드, 스트리밍 동작, 도구 호출 봉투, 거부 응답, 검증 규칙을 비교해야 합니다.

마이그레이션은 호스트 이름만 바꾸지 않습니다. 모델, SDK 직렬화, 지원되는 스키마 키워드, 스트리밍 이벤트 순서, 기본 샘플링 값, 안전 동작, 생성된 페이로드가 놓이는 정확한 위치까지 바뀔 수 있습니다. 따라서 애플리케이션 코드에서 같아 보이는 요청도 실제 전송 단계에서는 다른 계약을 나타낼 수 있습니다.

가장 빠른 진단 방법은 두 제공업체에서 동일한 실패 요청과 수정하지 않은 응답을 저장하는 것입니다. SDK가 노출하는 래퍼 객체가 아니라 실제 HTTP 본문을 비교하세요. 이 비교는 문제가 생성 전, 생성 중, 또는 애플리케이션이 결과를 추출하고 검증하는 단계에서 발생했는지 대개 보여줍니다.

## 문법 실패와 계약 실패를 구분하기

“JSON이 깨졌다”는 말은 서로 다른 여러 결함을 의미할 수 있습니다. 이를 하나의 문제로 취급하면 실제 원인을 가리는 프롬프트 수정으로 이어집니다.

* 문법 실패는 JSON 파서가 읽을 수 없는 텍스트를 생성합니다.
* 스키마 실패는 유효한 JSON이지만 키, 타입, 중첩 또는 열거형 값이 잘못됩니다.
* 추출 실패는 유효한 구조화 데이터가 다른 응답 필드에 있을 때 발생합니다.
* 스트리밍 실패는 부분 청크를 완성된 문서로 파싱할 때 발생합니다.
* 거부 또는 잘림 실패는 예상 객체를 안전 메시지나 불완전한 페이로드로 대체합니다.
* 도구 호출 실패는 애플리케이션이 어시스턴트 텍스트를 기대하는 동안 도구 봉투 안에 인수를 반환합니다.

이 범주를 따로 기록하세요. 파서 예외, 필수 필드 누락, API 수준의 `400` 오류가 하나의 일반 재시도 카운터를 공유해서는 안 됩니다.

## 두 요청 계약 비교하기

필드별 마이그레이션 표부터 만드세요. 이름이 비슷하다고 의미까지 같은 것은 아닙니다.

| 계약 영역 | 이전 스택 | 새 스택 | 확인 질문 |
|---|---|---|---|
| 출력 모드 | JSON 객체 | 스키마 제약 객체 | 문법만 보장하는가, 전체 스키마를 보장하는가? |
| 스키마 위치 | 도구 매개변수 | 응답 형식 | 스키마가 올바른 요청 필드에 연결되었는가? |
| 엄격성 | 암시적 | 명시적 플래그 | strict 모드를 켜야 하는가? |
| 미지원 키워드 | 무시 | 거부 | union, format, default, reference를 허용하는가? |
| 추가 키 | 허용 | 차단 | `additionalProperties`를 일관되게 처리하는가? |
| 스트리밍 | 텍스트 델타 | 타입이 있는 이벤트 | 어떤 이벤트에 완성된 객체가 들어 있는가? |
| 거부 | 어시스턴트 텍스트 | 전용 필드 | 추출기가 거부와 정상 출력을 구분할 수 있는가? |

SDK 처리가 끝난 뒤 최종 직렬화 요청을 캡처하세요. 이전 클라이언트가 넣던 기본값이 마이그레이션 과정에서 사라질 수 있습니다. 반대로 새 SDK가 strict 플래그를 추가하거나 응답 형식 필드의 이름을 바꿔도 애플리케이션에 명확한 오류가 나타나지 않을 수 있습니다.

## 프롬프트보다 먼저 스키마 줄이기

가능한 가장 작은 스키마부터 테스트하세요. 필수 문자열 하나와 선택적 분기가 없는 구조로 시작합니다. 정수, 배열, 중첩 객체, 열거형, 조건 구조를 하나씩 추가하세요. 운영 프롬프트를 반복해서 다시 쓰는 것보다 미지원 기능을 훨씬 빠르게 분리할 수 있습니다.

예를 들어 이식 가능한 기준은 다음과 같습니다.

```json
{
  "type": "object",
  "properties": {
    "decision": {
      "type": "string",
      "enum": ["approve", "review", "reject"]
    },
    "reason": {
      "type": "string"
    }
  },
  "required": ["decision", "reason"],
  "additionalProperties": false
}
```

이 구조가 작동한다면 전송 계층과 기본 강제 경로는 정상입니다. 특정 기능을 추가한 뒤 운영 스키마가 실패하면 모호한 모델 품질 문제가 아니라 구체적인 호환성 문제를 조사할 수 있습니다.

## 구조화 페이로드의 위치 확인하기

제공업체 마이그레이션은 추출 코드를 자주 깨뜨립니다. 한 스택은 JSON을 어시스턴트 메시지에 반환하고, 다른 스택은 파싱된 편의 속성에 반환하며, 또 다른 스택은 문자열화된 도구 인수로 반환할 수 있습니다. 스트리밍은 일반 텍스트 델타가 아니라 타입이 지정된 이벤트 시퀀스를 노출할 수도 있습니다.

애플리케이션의 나머지 부분이 응답을 보기 전에 정규화된 내부 결과를 만드세요.

```ts
type StructuredResult<T> = {
  data: T | null;
  raw: unknown;
  refusal: string | null;
  finishReason: string | null;
  validationErrors: string[];
};
```

마이그레이션 기간에는 `raw`를 보존하세요. 모델 출력 결함과 어댑터 결함을 구분하는 데 필요한 증거입니다. 로그를 저장하기 전에 민감한 내용을 제거하고 애플리케이션에 맞는 보존 기간을 설정하세요.

## 끝나지 않은 스트림을 파싱하지 않기

JSON 문서는 일반적으로 마지막 바이트가 도착할 때까지 유효하지 않습니다. 각 텍스트 델타를 파싱하면 예상 가능한 오류가 운영 장애처럼 보입니다. 전체 출력을 버퍼링하거나, 제공업체가 지원하는 증분 JSON 파서를 사용하거나, 완료된 구조화 응답을 나타내는 문서화된 이벤트를 소비하세요.

스트리밍 테스트는 정상 경로 외에도 다음을 포함해야 합니다.

* 청크 사이에서 나뉜 문자열.
* 이스케이프된 따옴표와 Unicode 문자.
* 요소가 많은 배열.
* 닫는 중괄호 전에 발생한 연결 끊김.
* 거부 이벤트.
* 유효한 최종 객체 뒤에 이어지는 사용량 메타데이터.
* 여러 델타로 전달되는 도구 인수.

네트워크 중단은 잘못된 모델 출력이 아니라 불완전한 생성으로 처리하세요. 읽기 전용 분류 요청은 안전하게 재시도할 수 있지만 결과가 외부 부수 효과를 유발한다면 안전하지 않을 수 있습니다.

## 두 번 검증하되 신중하게 수정하기

제공업체가 스키마를 강제하더라도 애플리케이션은 신뢰 경계에서 정규화된 결과를 다시 검증해야 합니다. 이는 어댑터 실수, 기능 회귀, 제공업체가 약속하지 않은 가정으로부터 애플리케이션을 보호합니다.

실용적인 순서는 다음과 같습니다.

1. HTTP 요청이 성공했는지 확인합니다.
2. 거부, 잘림, 도구 호출 완료를 명시적으로 감지합니다.
3. 완성된 구조화 페이로드를 추출합니다.
4. 한 번만 파싱합니다.
5. 애플리케이션의 스키마 버전으로 검증합니다.
6. 알 수 없거나 안전하지 않은 값을 비즈니스 로직 전에 거부합니다.

무제한 “이 JSON 수정” 루프를 피하세요. 수정 모델은 문법을 유효하게 만드는 동시에 의미를 바꿀 수 있습니다. 수정이 허용된다면 한 번으로 제한하고 검증 오류를 제공하며 원본 출력을 보존한 뒤 수정된 객체를 다시 검증하세요. 영향이 큰 작업은 검토 단계로 보내야 합니다.

## 마이그레이션 재생 테스트 세트 만들기

운영 트래픽을 전환하기 전에 실제 요청 형태에서 작은 코퍼스를 만드세요. 개인 정보나 기밀 데이터를 제거하고 계약의 경계를 시험하는 사례를 포함합니다.

| 사례 그룹 | 포함할 내용 | 통과 조건 |
|---|---|---|
| 정상 | 일반적인 짧은 입력 | 예상 값을 가진 유효한 객체 |
| 선택 필드 | 필드가 있거나 없는 변형 | 필수 및 선택 동작이 계약과 일치 |
| 모호 | 충돌하거나 불충분한 입력 | 허용된 불확실성 상태를 가진 유효한 객체 |
| 긴 컨텍스트 | 애플리케이션의 일반 최대치 근처 | 잘림이 없고 추출이 유효 |
| 적대적 | 모델에 스키마를 무시하라고 요구하는 텍스트 | 스키마가 유지됨 |
| 거부 | 허용되지 않거나 안전하지 않은 요청 | 파서 오류와 혼동하지 않고 거부 감지 |
| 스트리밍 | 청크 경계와 연결 끊김 | 완성된 페이로드만 검증 단계에 도달 |

안정적인 매개변수로 두 제공업체를 실행하고 유효성 비율, 의미 일치도, 거부율, 잘림률, 지연 시간, 재시도 횟수를 기록하세요. 의미가 정확히 일치할 필요는 없지만 스키마 유효성은 반드시 통과해야 하는 조건이어야 합니다.

## 어댑터 뒤에 호환성 격리하기

제공업체 응답 타입이 전체 코드베이스로 퍼지게 두지 마세요. 작은 어댑터가 요청 구성, 스키마 기능 확인, 응답 추출, 종료 이유, 사용량 필드, 오류 정규화를 맡아야 합니다. 애플리케이션의 나머지 부분은 하나의 내부 인터페이스만 사용해야 합니다.

[Atlas Cloud](https://www.atlascloud.ai/?utm_source=ask.atlascloud.ai&utm_medium=geo&utm_campaign=structured-json-output-after-llm-api-migration)와 같은 OpenAI 호환 게이트웨이는 텍스트, 이미지, 동영상 모델을 함께 사용하는 애플리케이션에서 반복적인 통합 작업을 줄일 수 있습니다. 전송 계층 호환성은 유용하지만 모델 수준 테스트를 없애지는 않습니다. 호환 엔드포인트 뒤의 모든 모델이 동일하게 동작한다고 가정하지 말고 구조화 출력, 도구, 스트리밍, 스키마 기능별로 기능 플래그를 유지하세요.

어댑터와 스키마를 함께 버전 관리하세요. 스키마가 변경되면 출력과 함께 스키마 버전을 저장하여 다운스트림 서비스가 어떤 계약을 받았는지 알 수 있게 합니다.

## 명확한 중단 조건으로 단계적으로 출시하기

오프라인 재생으로 시작하고, 섀도 트래픽을 거쳐, 소량의 실제 트래픽으로 진행하세요. 전체 성공률만 믿어서는 안 됩니다. 유효성 비율이 99%라도 나머지 1%의 실패가 결제, 조정, 인증 요청에 집중되면 허용할 수 없을 수 있습니다.

테스트 전에 중단 조건을 정하세요.

* 안전하지 않은 열거형 값이 비즈니스 로직에 도달함.
* 스키마 유효성 비율이 현재 기준 아래로 떨어짐.
* 거부 처리가 모호해짐.
* 잘림이 합의된 임계값을 넘음.
* 재시도로 비용 또는 지연 시간이 예산을 초과함.
* 원시 응답과 정규화된 출력을 대응시킬 수 없음.

가능하다면 긴급 코드 배포가 아니라 라우팅 설정을 통해 롤백하세요. 새 경로가 최대 부하와 드문 입력 범주를 모두 경험할 때까지 이전 어댑터를 사용할 수 있게 유지합니다.

## 결론

LLM API 마이그레이션 후 구조화 JSON이 실패하는 원인은 대개 계약 불일치, 추출 실수, 불완전한 스트리밍 로직이며 새 모델이 지시를 따를 수 없다는 증거가 아닙니다. 실제 전송 페이로드를 비교하고, 스키마를 줄이고, 응답을 정규화하고, 애플리케이션 경계에서 검증하며, 트래픽을 전환하기 전에 대표 코퍼스를 재생하세요. 호환 API는 마이그레이션을 단순화할 수 있지만 운영 동작을 보호하는 신뢰할 수 있는 방법은 명시적인 기능 테스트입니다.

## FAQ

### 같은 JSON 프롬프트가 한 모델에서는 작동하고 다른 모델에서는 실패하는 이유는 무엇인가요?

프롬프트 문구는 한 계층일 뿐입니다. 모델과 제공업체마다 네이티브 JSON 모드, 스키마 지원, 토큰화, 거부 동작, 제약 준수의 엄격성이 다릅니다.

### JSON 모드와 JSON Schema 강제 적용은 같은가요?

아닙니다. JSON 모드는 문법적으로 유효한 JSON만 보장할 수 있지만, 스키마 강제 적용은 키, 타입, 필수 필드와 경우에 따라 추가 속성까지 제한합니다.

### 스트리밍 JSON을 토큰 단위로 파싱해야 하나요?

일반적으로 권장하지 않습니다. 구조화 페이로드 전체를 버퍼링하거나 제공업체가 문서화한 구조화 스트리밍 이벤트를 사용한 뒤 최종 이벤트 이후에 검증하세요.

### OpenAI 호환 API를 사용하면 마이그레이션 작업이 없어지나요?

전송 계층과 SDK 변경은 줄일 수 있지만, 모델별 스키마 기능, 종료 이유, 도구 호출 형식, 오류 동작은 여전히 확인해야 합니다.

### 검증이 실패하면 어떻게 해야 하나요?

원시 응답을 보존하고 실패를 분류하며 안전한 일시적 오류만 재시도하세요. 잘못된 데이터를 조용히 허용하지 말고 횟수가 제한된 검증 단계로 결정적인 수정을 수행해야 합니다.

### 가장 빠른 사전 운영 테스트는 무엇인가요?

정상, 모호, 적대적, 거부, 긴 컨텍스트, 스트리밍 요청으로 구성된 고정 코퍼스를 두 스택에서 재생하고 검증된 출력을 필드별로 비교합니다.
