Files
2nd/10_Wiki/Topics/Coding/AI_Structured_Output_Zod.md
T
Antigravity Agent f8b21af4be Wiki cleanup: error-doc removal, dedup merge, link normalization
10_Wiki/Topics 대규모 정리:
- 오류 캡처/미완성 stub 문서 227개 제거
- 교차폴더 중복 43클러스터 병합 (63파일 → redirect)
- 링크명 정규화: 깨진 링크 수정·redirect 직결·개념 매핑 ~2,400건
- 카테고리 MOC 6개 신규 생성
- Graph 섹션 미해결 related-keyword 링크 10,058건 제거

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 23:52:15 +09:00

5.1 KiB

id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
id title category status source_trust_level verification_status created_at updated_at tags tech_stack applied_in aliases
ai-structured-output-zod LLM Structured Output — Zod / Function Calling Coding draft B conceptual 2026-05-09 2026-05-09
ai
llm
structured
zod
vibe-coding
language applicable_to
TS / Zod / OpenAI / Anthropic
Backend
structured output
JSON mode
function calling
tool use
response_format

LLM Structured Output

JSON 강제 prompt 만으로는 신뢰 X. OpenAI response_format: { type: 'json_schema' } / Anthropic tool_use 가 schema 보장. Zod → JSON Schema 가 표준 워크플로.

📖 핵심 개념

  • JSON mode: 어떤 JSON 도 통과 (schema 미보장).
  • Structured output (OpenAI): JSON Schema 기반 enforce. parse 실패 0%.
  • Tool use (Anthropic): 함수 호출 형태 — input_schema 강제.
  • Zod → JSON Schema: zod-to-json-schema 라이브러리.

💻 코드 패턴

OpenAI structured output

import OpenAI from 'openai';
import { zodResponseFormat } from 'openai/helpers/zod';
import { z } from 'zod';

const Recipe = z.object({
  title: z.string(),
  servings: z.number().int().positive(),
  ingredients: z.array(z.object({
    name: z.string(),
    qty: z.number(),
    unit: z.enum(['g', 'ml', 'cup', 'tsp']),
  })),
  steps: z.array(z.string()).max(10),
});

const client = new OpenAI();
const r = await client.beta.chat.completions.parse({
  model: 'gpt-4o',
  messages: [{ role: 'user', content: 'Spaghetti carbonara recipe' }],
  response_format: zodResponseFormat(Recipe, 'recipe'),
});

const recipe = r.choices[0].message.parsed!; // 타입 = z.infer<typeof Recipe>

Anthropic tool use (structured)

import Anthropic from '@anthropic-ai/sdk';
import { zodToJsonSchema } from 'zod-to-json-schema';

const client = new Anthropic();
const r = await client.messages.create({
  model: 'claude-opus-4-7',
  max_tokens: 1024,
  tools: [{
    name: 'extract_recipe',
    description: 'Extract recipe data',
    input_schema: zodToJsonSchema(Recipe) as Anthropic.Messages.Tool.InputSchema,
  }],
  tool_choice: { type: 'tool', name: 'extract_recipe' }, // 강제
  messages: [{ role: 'user', content: 'Spaghetti carbonara recipe' }],
});

const block = r.content.find(b => b.type === 'tool_use');
const recipe = Recipe.parse(block!.input);

검증 + 재시도

async function getRecipe(query: string, attempts = 3): Promise<Recipe> {
  let lastErr: unknown;
  for (let i = 0; i < attempts; i++) {
    try {
      const raw = await callLLM(query);
      return Recipe.parse(raw); // throws ZodError on fail
    } catch (e) {
      lastErr = e;
      // 재시도 시 에러 메시지를 LLM 에 피드백
    }
  }
  throw lastErr;
}

점진적 schema (간단 → 복잡)

// V1: 단순
const SimpleRecipe = z.object({ title: z.string(), steps: z.array(z.string()) });
// 동작 확인

// V2: 더 정밀
const Recipe = SimpleRecipe.extend({
  servings: z.number().int().positive(),
  ingredients: z.array(IngredientSchema),
});

Discriminated union (여러 종류)

const Action = z.discriminatedUnion('type', [
  z.object({ type: z.literal('search'), query: z.string() }),
  z.object({ type: z.literal('calc'), expr: z.string() }),
  z.object({ type: z.literal('done'), answer: z.string() }),
]);

Streaming + structured (OpenAI)

const stream = await client.beta.chat.completions.stream({
  model: 'gpt-4o',
  messages: [...],
  response_format: zodResponseFormat(Recipe, 'recipe'),
});

for await (const ev of stream) {
  if (ev.event === 'content.delta') console.log(ev.parsed); // 부분 객체
}

const final = (await stream.finalChatCompletion()).choices[0].message.parsed;

Function calling (legacy)

const r = await client.chat.completions.create({
  model: 'gpt-4o',
  messages: [...],
  tools: [{ type: 'function', function: { name: 'extract', parameters: zodToJsonSchema(Recipe) } }],
  tool_choice: { type: 'function', function: { name: 'extract' } },
});

🤔 의사결정 기준

상황 추천
OpenAI 정확한 schema Structured output
Anthropic Tool use + force tool
간단 JSON JSON mode + Zod parse
여러 종류 액션 Discriminated union
Streaming partial OpenAI stream + structured
Schema 변동 runtime parse + 재시도

안티패턴

  • JSON 그대로 신뢰: 자유 형식이면 누락 / 추가 키.
  • Schema 거대: enum 100개 / 50 필드 — LLM 도 정확히 못 채움.
  • Optional 모두: 강제 없으면 LLM 이 빠뜨림.
  • Description 없음: schema 만 있으면 LLM 이 의미 모름.
  • Re-try infinite: 3번 후 fallback 또는 사용자에게.
  • Tool name 동사 X 명사 O: tool_use 는 동사 권장 (extract_recipe).
  • PII strict 검증 없음: 잘못된 형식 통과.

🤖 LLM 활용 힌트

  • Zod schema → zodResponseFormat (OpenAI) / zodToJsonSchema (Anthropic).
  • 강제 tool_choice 또는 response_format 으로 보장.
  • Description 풍부하게.

🔗 관련 문서