LLM으로부터 타입이 지정된 JSON 추출하기: generateObject에 대한 현장 노트
요약
Vercel AI SDK의 generateObject를 사용하여 LLM으로부터 타입이 지정되고 스키마 검증을 거친 JSON을 추출하는 방법을 설명합니다. Zod 스키마를 활용해 모델의 출력을 제약하고, 스트리밍 및 다양한 출력 모드를 통해 안정적인 데이터 구조를 확보하는 기술적 노하우를 다룹니다.
핵심 포인트
- generateObject는 Zod 스키마를 통해 검증된 타입 지정 객체를 반환합니다.
- streamObject를 사용하면 모델 생성 중에도 부분적인 객체 스트리밍이 가능합니다.
- object, array, enum, no-schema의 네 가지 출력 모드를 지원합니다.
- NoObjectGeneratedError 처리를 통해 모델의 잘못된 JSON 응답에 대응할 수 있습니다.
- 단순 JSON.parse 대신 스키마 주입 방식을 사용하여 파싱 에러를 방지합니다.
헤드라인: Vercel AI SDK의 generateObject는 언어 모델로부터 타입이 지정되고 스키마 검증(schema-validated)을 거친 JSON을 얻을 수 있는 신뢰할 수 있는 방법입니다. 저는 Zod 스키마를 전달하고, SDK는 모델을 제약하며 결과를 검증합니다. 이를 통해 대부분의 경우 JSON인 문자열을 직접 파싱하는 대신 타입이 지정된 객체를 얻을 수 있습니다. 저에게 큰 비중을 차지했던 네 가지 요소는 다음과 같습니다 — 원샷 추출(one-shot extraction)을 위한 generateObject, 점진적 UI(progressive UI)를 위한 streamObject, object/array/enum/no-schema 출력 모드, 그리고 NoObjectGeneratedError를 일급 코드 경로(first-class code path)로 취급하는 것입니다.
핵심 요약 (Key takeaways)
generateObject({ model, schema, prompt })는 Zod (또는 JSON) 스키마에 따라 검증된 타입이 지정된 객체를 반환합니다. 스키마 불일치 시 잘못된 데이터를 전달하는 대신 에러를 발생시킵니다.streamObject는partialObjectStream을 통해 부분적인 객체를 스트리밍하므로, 모델이 완료되기 전에 양식(form)이나 테이블이 필드별로 채워집니다.- AI SDK에는 네 가지 출력 모드가 있습니다:
object(기본값),array(elementStream을 통해 요소를 스트리밍),enum(단일 레이블 분류), 그리고no-schema(자유 형식 JSON). - 데이터를 추출할 때는
generateObject를 사용하고, 동작을 수행할 때는 도구 호출(tool calling)을 사용하세요.experimental_output은 도구 호출 루프와 최종적인 타입 지정 객체를 결합합니다. - 모델이 유효하지 않은 JSON을 반환하면 SDK는 원시
text와usage를 포함하는NoObjectGeneratedError를 발생시킵니다.experimental_repairText와 더 엄격한 스키마 설명을 통해 대부분의 경우 이를 복구할 수 있습니다.
generateObject는 실제로 무엇을 하나요?
generateObject는 언어 모델이 제가 정의한 스키마와 일치하는 JSON을 반환하도록 강제하고, 제 코드가 확인하기 전에 응답을 검증하는 Vercel AI SDK 함수입니다. 저는 모델, Zod 스키마, 그리고 프롬프트를 전달하며, TypeScript가 이미 형태(shape)를 알고 있는 타입이 지정된 객체를 돌려받습니다.
import { generateObject } from 'ai';
import { z } from 'zod';
...
이 스키마 (schema)는 두 가지 역할을 수행합니다. 모델이 올바른 형태를 갖추도록 유도하고, 출력을 검증 (validate)하는 것입니다. 만약 모델이 파싱 (parse)할 수 없는 필드를 반환하면, generateObject는 불완전한 데이터를 반환하는 대신 에러를 발생 (throw)시킵니다.
왜 generateText에서 JSON을 단순히 파싱하면 안 되나요?
generateText의 문자열에 JSON.parse를 사용하는 것은 바로 generateObject가 제거하고자 하는 실패 모드 (failure mode) 그 자체이기 때문입니다. 가공되지 않은 완성형 텍스트 (raw completion)는 대부분의 경우 잘 형성된 JSON 문자열이지만, 모델이 이를 마크다운 펜스 (markdown fence)로 감싸거나, 끝에 주석을 추가하거나, 필수 필드를 누락하는 순간 프로덕션 환경에서 파싱 에러가 발생하게 됩니다. generateObject는 요청에 스키마를 주입하고, 각 제공자 (provider)의 구조화된 출력 (structured-output) 또는 도구 (tool) 메커니즘을 사용하여 생성을 제한하며, 결과를 반환하기 전에 사용자의 Zod 스키마로 검증합니다.
이점은 코드의 글자 수가 줄어드는 것이 아닙니다. 모델의 출력과 타입이 지정된 값 (typed value) 사이의 경계가 프롬프트 (prompt), 정규 표현식 (regex), 그리고 try/catch에 걸쳐 흩어져 있는 대신, 스키마 (schema)라는 단일 소유자를 갖게 된다는 점입니다.
언제 generateObject 대신 streamObject를 사용해야 하나요?
객체가 너무 커서 전체가 완성될 때까지 기다리는 것이 느리게 느껴질 때는 streamObject를 사용하세요. streamObject는 객체가 구축되는 대로 객체를 생성하는 partialObjectStream을 반환하므로, UI에서 필드가 도착하는 즉시 렌더링할 수 있습니다.
import { streamObject } from 'ai';
const { partialObjectStream } = streamObject({
...
방출 (emit)되는 각 값은 스키마의 딥-파셜 (deep-partial) 형태이므로, 모델이 값을 채우기 전까지 모든 필드는 undefined일 수 있습니다. 크론 잡 (cron job)이나 단발성으로 실행되는 라우트 핸들러 (route handler)와 같은 일회성 서버 작업의 경우에는 generateObject가 더 간단하며 저의 기본 설정 (default)으로 유지됩니다.
AI SDK는 어떤 출력 모드 (output modes)를 지원하나요?
| 모드 (Mode) | 결과물 | 용도 |
|---|---|---|
object (기본값) | 검증된 하나의 객체 (object) | 추출 (extraction), 요약 (summarization), 단일 레코드 |
| ... | ||
enum의 경우 output: 'enum'과 enum: ['spam', 'not_spam'] 리스트를 전달합니다. 모델은 이 정확한 문자열 중 하나만 반환할 수 있으며, 이는 하나의 enum 필드를 가진 객체(object)를 사용하는 것보다 더 엄격하고 비용이 저렴합니다. |
언제 generateObject 대신 도구 호출 (tool calling)을 사용하나요?
값을 추출할 때는 generateObject를 사용하고, 무언가를 실행할 때는 도구 호출 (tool calling)을 사용하세요. generateObject는 부수 효과 (side effects)가 없습니다. 즉, 비정형 입력을 하나의 타입이 지정된 객체 (typed object)로 변환하고 종료됩니다. 반면, tools 맵을 사용하는 generateText 또는 streamText와 같은 도구 호출 (tool calling)은 모델이 답변을 하기 전에 루프 내에서 여러 번 함수를 호출할지 여부를 스스로 결정할 수 있게 합니다.
import { generateText, Output } from 'ai';
const { experimental_output } = await generateText({
...
여전히 실험적 (experimental) 단계로 표시된 experimental_output은 그 사이를 잇는 가교 역할을 합니다. 모델이 도구 호출 (tool-calling) 루프를 실행한 다음, 스키마 (schema)에 따라 검증된 최종 답변을 반환하므로, 단 한 번의 호출로 액션 (actions)과 타입이 지정된 결과 (typed result)를 모두 얻을 수 있습니다.
유효하지 않은 JSON을 반환하는 모델은 어떻게 처리하나요?
생성 과정에서 스키마 검증 (schema validation)에 실패하면, AI SDK는 NoObjectGeneratedError를 발생시킵니다. 이를 명시적으로 포착 (catch)하는 것이 우아한 폴백 (graceful fallback)을 구현하느냐, 아니면 500 에러를 내느냐의 차이를 만듭니다. 이 에러에는 모델이 생성한 원문 text와 더불어 usage 및 response 정보가 포함되어 있습니다.
import { generateObject, NoObjectGeneratedError } from 'ai';
try {
...
catch 블록이 실행되기 전에 실패율을 낮춰준 세 가지 방법이 있습니다. 첫째, 명확하지 않은 모든 필드에 .describe()를 사용하는 것입니다. 설명(description)은 모델로 전송되므로, z.string().describe('ISO 8601 date')와 같이 작성하는 것이 막연히 기대하는 것보다 훨씬 낫습니다. 둘째, 모델이 알지 못할 수도 있는 필드에는 .optional() 대신 .nullable()을 사용하는 것입니다. 많은 모델이 키(key)를 누락하는 것보다 null을 출력하는 것이 더 안정적이기 때문입니다. 셋째, experimental_repairText입니다. 이는 SDK가 다시 파싱하기 전에 코드 펜스(code fence)나 마지막 쉼표(trailing comma)를 제거할 수 있는 콜백 함수입니다. 재시도(retry)는 지연 시간(latency)과 비용을 두 배로 만들기 때문에, 저는 이 세 가지를 적용한 후에만 재시도를 수행합니다.
FAQ
Q: generateObject와 generateText의 차이점은 무엇인가요?
A: generateText는 자유 형식의 문자열(free-form string)을 반환하며, generateObject는 스키마(schema)에 따라 검증된 타입이 지정된 객체(typed object)를 반환하고 출력이 일치하지 않으면 에러를 발생시킵니다. 산문(prose) 형태가 필요할 때는 generateText를 사용하고, 기계가 읽을 수 있는 데이터(machine-readable data)가 필요할 때는 언제나 generateObject를 사용하세요.
Q: generateObject는 모든 모델에서 작동하나요?
A: AI SDK가 지원하는 모든 프로바이더(provider)에서 작동하지만, 메커니즘은 다릅니다. 어떤 모델은 네이티브 구조화된 출력(native structured-output) JSON 모드를 사용하고, 다른 모델은 내부적으로 도구 호출(tool calling)을 사용합니다. 어떤 경우든 동일한 Zod 스키마를 전달하면 되며, 네이티브 구조화된 출력을 지원하는 모델이 가장 신뢰할 수 있습니다.
Q: 구조화된 객체를 브라우저로 스트리밍할 수 있나요?
A: 네. streamObject는 딥 파셜 객체(deep-partial objects)의 partialObjectStream을 반환합니다. 각 필드가 도착하는 대로 렌더링하되, 스트림이 완료될 때까지 모든 필드를 undefined일 수 있다고 가정하고 처리하세요.
Q: 객체 래퍼(object wrapper) 없이 텍스트를 분류하려면 어떻게 하나요?
A: 허용된 레이블(label)의 enum 배열과 함께 output: 'enum'을 사용하세요. 모델은 반드시 문자열 중 정확히 하나를 반환해야 하며, 이는 단일 enum 필드를 가진 객체를 사용하는 것보다 더 엄격하고 비용이 저렴합니다.
Q: 모델 출력이 잘못된 형식(malformed)일 때 어떤 에러가 발생하나요?
A: NoObjectGeneratedError가 발생합니다. 이 에러는 로깅 및 폴백(fallback) 처리를 할 수 있도록 원시 text, usage, response를 노출합니다. 필드 .describe() 힌트, .optional() 대신 .nullable() 사용, 그리고 experimental_repairText 콜백을 통해 이 에러의 발생 빈도를 줄이세요.
devya.dev에 최초 게시되었습니다. 또한 eng-ahmed.com에서도 확인하실 수 있습니다. Devya Solutions에서 제작하였습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기