구조화된 출력이 영리한 파싱을 이긴다
아직도 모델 텍스트에서 정규식으로 JSON을 파싱하고 있는가? 이제 그만두자. Bedrock 구조화된 출력은 디코딩 단계에서 JSON Schema를 강제해 응답이 처음부터 유효하다.

애플리케이션이 여전히 모델의 산문에서 정규식과 재시도 루프로 JSON을 뽑아내고 있다면, 이제 Amazon Bedrock이 디코딩 계층에서 해결해 주는 문제를 붙들고 있는 셈이다. 2026년 2월부터 Bedrock에서 정식 제공되는 구조화된 출력(structured outputs)은 모델이 토큰을 생성하는 동안 JSON Schema에 맞도록 제약을 걸어, 응답이 희망이 아니라 구조 자체에 의해 원하는 형태를 갖추게 한다. 정규식은 애초에 해결책이 아니었다. 모델에게 "JSON으로 반환해 달라"고 부탁만 하고, 그렇게 하지 않았을 때 뒤처리를 하던 증상이었을 뿐이다.
파싱 방식은 드물게 발생하기 때문에 더 골치 아픈 방식으로 실패한다. 응답의 90% 남짓은 정상적으로 파싱된다. 나머지는 JSON을 마크다운 펜스로 감싸거나, 앞에 친절한 문장을 붙이거나, 쉼표를 하나 더 붙이거나, 필드를 환각한다. 그러면 파서는 테스트하지 않은 입력에 대해, 가장 나쁜 타이밍에 프로덕션에서 예외를 던진다. 제약된 디코딩은 이 실패 클래스 전체를 없앤다. 애초에 유효하지 않은 토큰이 생성되지 않기 때문이다.
구조를 얻는 세 가지 방법, 강도순
부탁하고 기도하기
시스템 프롬프트에서 JSON을 요청하고, 예시를 하나 줄 수도 있고, 텍스트를 파싱한다. 이 방식은 되는 동안은 된다. 어떤 보장도, 강제성도 없으며, 실패율은 데모에서는 절대 드러나지 않는 바로 그 꼬리 부분이다. 파서를 견고하게 만드는 데 쓰는 매 시간은 플랫폼이 없앨 수 있는 문제에 쓰는 시간이다.
스키마로서의 도구 사용
네이티브 구조화된 출력이 등장하기 훨씬 전부터, 신뢰할 만한 트릭은 원하는 형태를 입력 스키마로 갖는 도구를 정의하고 메시지 텍스트 대신 도구 호출 인자를 읽는 것이었다. 모델이 도구 입력을 채우면 구조화된 객체를 얻는다. 같은 호출에서 실제로 도구를 호출해야 할 때는 지금도 좋은 패턴이다. Bedrock에서는 이제 도구 정의에 strict: true를 추가할 수 있어서, 도구 이름과 입력이 스키마에 의해 단순히 제안되는 것이 아니라 실제로 검증된다.
네이티브 구조화된 출력
가장 직접적인 방법은 응답 형태를 JSON Schema로 선언하고 생성 과정에서 Bedrock이 이를 강제하도록 하는 것이다. JSON Schema Draft 2020-12와 제약된 디코딩을 사용하므로, 모델은 스키마를 깨뜨릴 토큰을 물리적으로 생성할 수 없다. Converse API에서 이 필드는 outputConfig.textFormat이며 스키마에는 name이 필요하다. Converse, ConverseStream, InvokeModel, InvokeModelWithResponseStream 전반에서 Anthropic Claude 4.5와 일부 오픈 웨이트 모델에 대해 사용할 수 있다.
// Converse: enforce the shape instead of parsing for it
outputConfig: {
textFormat: {
jsonSchema: {
name: "extraction",
schema: {
type: "object",
properties: {
invoice_id: { type: "string" },
total_cents: { type: "integer" },
currency: { type: "string", enum: ["USD", "EUR", "GBP"] }
},
required: ["invoice_id", "total_cents", "currency"],
additionalProperties: false
}
}
}
}강제성이 가져다주는 것
명백한 이점은 출력이 파싱된다는 것이다. 더 큰 이점은 무엇을 삭제할 수 있는가에 있다. 유효하지 않을 때 재시도하는 루프, JSON 복구 라이브러리, "유효하지 않은 JSON을 반환했으니 다시 시도해달라"고 말하는 방어적인 재프롬프트, 그리고 그것마저 실패했을 때 울리는 알림까지. 이것들은 모두 확률적인 출력을 보완하기 위한 장치였다. 스키마가 디코딩 단계에서 강제되는 순간, 그 보완 코드는 죽은 코드가 된다. 재시도가 줄면 토큰과 지연 시간도 줄어든다. 첫 번째 호출을 고치기 위한 두 번째 호출을 더는 지불하지 않기 때문이다.
additionalProperties: false를 설정하고 필드를 required로 표시해 스키마를 빡빡하게 유지하라. 추가 키를 허용하는 느슨한 스키마는 방금 사들인 보장의 일부를 다시 내주는 셈이다. 모델이 여전히 코드가 예상하지 못한 필드를 붙일 수 있기 때문이다.
적용되지 않는 곳
구조화된 출력은 기계가 읽는 응답, 즉 추출, 분류, 라우팅, 함수 인자 등 다운스트림 시스템이 소비하는 모든 것을 위한 것이다. 사람이 읽는 산문에는 적합하지 않다. 엄격한 스키마가 오히려 목적을 방해하기 때문이다. 그리고 이것은 형태를 제약할 뿐, 진실을 제약하지 않는다. 스키마는 total_cents가 정수임을 보장하지, 그것이 올바른 정수임을 보장하지 않는다. 값, 범위, 비즈니스 규칙에 대한 검증은 여전히 여러분의 몫이다. 스키마는 파싱 실패 모드를 없애 줄 뿐, 모델이 정답을 맞혔는지 확인할 필요까지 없애 주지는 않는다.
결론
영리한 파싱은 모델이 애초에 준 적 없는 보장을 우회하려고 쏟는 노력이다. Bedrock 구조화된 출력은 그 보장을 디코더 안으로 옮긴다. JSON Schema를 선언하면 응답은 구조 자체에 의해 유효해진다. 순수한 데이터에는 네이티브 구조화된 출력을, 같은 호출이 실제로 행동도 해야 할 때는 도구 사용 스키마를 쓰고, 값 수준 검증은 어느 경우든 유지하라. 그런 다음 정규식과 복구 루프, 재시도를 삭제하라. 그것들은 더 이상 존재하지 않는 문제를 위한 비계였다.
다음으로 읽을 글
- Stop Fine-Tuning. You Need RAG, a Cache, and Better Prompts, 학습 대신 프롬프팅과 플랫폼 기능으로 형식과 동작 문제를 해결하는 것에 관한 글이다.
- Streaming Responses Are a UX Decision, Not a Performance One, 스트리밍과 구조화된 출력이 왜 서로 반대 방향으로 끌어당기는지에 관한 글이다.
이를 안정적으로 배포하는 플랫폼 및 딜리버리 측면은 ercan.cloud의 클라우드 필드 노트에서, 허브는 ercanermis.com에서 확인할 수 있다.
Ercan의 다른 글
같은 저자, 다른 영역의 사이트 두 개.