
LLM JSON을 위한 60줄 계약 테스트 (그리고 더 나은 AI 엔지니어링 면접 답변)
요약
LLM의 JSON 응답을 신뢰할 수 없는 입력으로 간주하고, 파싱과 검증을 분리하여 안정성을 확보하는 설계 전략을 다룹니다. Node.js를 활용한 계약 테스트(contract testing) 예시를 통해 AI 엔지니어링 면접에서 답변할 수 있는 실무적인 설계 원칙을 제시합니다.
핵심 포인트
- LLM 응답은 항상 신뢰할 수 없는 입력으로 취급하고 검증해야 함
- JSON 파싱과 데이터 스키마 검증을 명확히 분리할 것
- 잘못된 응답에 임의의 기본값을 넣지 말고 명시적 에러를 반환할 것
- 실패 사례를 포함한 회귀 테스트를 통해 시스템 안정성 확보
LLM JSON을 위한 60줄 계약 테스트 (그리고 더 나은 AI 엔지니어링 면접 답변)
LLM 응답이 UI, 큐(queue), 또는 자동화된 결정에 영향을 미칠 때, “모델이 보통 JSON을 반환한다”는 것은 신뢰성 전략이 아닙니다. 이 응답을 신뢰할 수 없는 입력으로 간주하고 파싱하며, 프로그램이 의존하는 필드를 검증하고, 몇 가지 잘못된 예시를 회귀 테스트(regression suite)에 보관해야 합니다.
이 작은 Node.js 연습은 AI 엔지니어링 면접에서 이러한 설계를 논의할 수 있는 구체적인 방법을 제공합니다. 또한 행복 경로 데모(happy-path demos)가 놓치는 실패 사례, 즉 답변이 사람에게는 그럴듯해 보이지만 소프트웨어로 사용하기에는 여전히 쓸모없을 수 있다는 점도 포착합니다.

실제로 보호해야 할 계약은 무엇인가?
사건 트리아지(incident-triage) 어시스턴트를 상상해 보세요. 귀하의 애플리케이션은 각 응답에서 세 가지가 필요합니다:
| 필드 | 애플리케이션이 필요한 이유 |
|---|---|
summary | 사람이 빠르게 스캔할 수 있는 내용 |
| ... | |
| 그것이 계약(contract)이지, 프롬프트 선호도(prompt preference)가 아닙니다. ` |
여기에는 두 가지 의도적인 선택이 있습니다.
첫째, 파싱(parsing)과 검증(validation)을 분리했습니다. JSON.parse는 텍스트가 JSON 구문을 갖추고 있는지만 알려줄 뿐입니다. 해당 JSON이 우리 프로그램이 사용할 수 있는 형태(shape)인지 여부는 알려주지 않습니다.
둘째, 검증기(validator)는 대체 값(fallback values)을 임의로 만들어내는 대신 에러를 반환합니다. 기본값으로 risk: "low"를 설정한다면, 망가진 모델 응답이 마치 안전한 것처럼 보일 것입니다. 이것이 바로 면접관이 파고들 만한 버그의 유형입니다: “출력이 잘못된 형식(malformed)일 때 어떤 일이 발생하나요?” 정직한 답변은, 추측된 데이터를 바탕으로 조용히 동작을 수행하는 대신, 애플리케이션이 실패를 기록하고 명시적인 복구 경로(recovery path)를 사용한다는 것입니다.
프로덕션(production) 환경이라면, 저는 동일한 계약(contract)을 공유 스키마 패키지에 넣고 코드베이스에서 이미 채택하고 있는 스키마 도구를 사용할 것입니다. 중요한 설계 결정은 이 특정 수동 작성 검증기가 아니라, 경계(boundary)에서 실행 가능한 단일 진실 공급원(source of truth)을 갖는 것입니다.
어떤 실패가 회귀 테스트(regression test)를 받을 가치가 있는가?
해피 패스(happy path)는 거의 아무것도 증명하지 못합니다. 하위 코드(downstream code)가 성공으로 오해할 수 있는 대표적인 실패 사례부터 시작하십시오:
const cases = [
[
"accepts a usable result",
...
다음 명령어로 실행하십시오:
node contract-test.mjs
예상 출력:
✓ accepts a usable result
✓ rejects prose around JSON
✓ rejects an unsupported enum
...
두 번째 사례는 사용 중인 제공자(provider)가 구조화된 출력(structured-output) 모드를 제공하더라도 유지할 가치가 있습니다. 전송(transport), 미들웨어(middleware), 모델 변경, 그리고 수동으로 편집된 피스처(fixtures)는 여전히 경계에 예상치 못한 것을 전달할 수 있습니다. 제공자의 제약 사항은 잘못된 출력의 빈도를 줄여줄 수는 있지만, 로컬 검증(local validation)은 잘못된 출력이 도착했을 때 시스템이 어떻게 동작할지를 정의합니다.
실제 서비스는 어떻게 복구해야 하는가?
유효하지 않은 결과를 거부하는 것은 설계의 절반에 불과합니다. 복구 정책(recovery policy)은 해당 동작의 비용과 일치해야 합니다:
| 출력 용도 | 합리적인 복구 방식 |
|---|---|
| 사람을 위한 초안 텍스트 | 유효성 검사 오류를 표시하고 재시도 제안 |
| ... |
여기서 빠진 점에 주목하세요: 무제한 재시도 루프(unlimited retry loop)입니다. 잘못된 형식의 출력(malformed-output)에 대한 재시도는 제한된 시도 횟수, 구조화된 로그(structured logs), 그리고 민감한 프롬프트나 고객 데이터를 기본적으로 저장하지 않으면서 실패를 재현할 수 있는 충분한 컨텍스트가 필요합니다.
이를 통해 깔끔한 면접 답변을 얻을 수 있습니다: “저는 모델 출력을 통합 경계(integration boundary)로 취급합니다. 로컬에서 이를 검증하고, 파싱(parse) 실패와 의미론적(semantic) 실패를 모두 테스트하며, 후속 작업의 영향 범위(blast radius)에 따라 복구 방식을 결정합니다.”
이 연습 후에 무엇을 추가하겠습니까?
이 작은 예제는 의도적으로 몇 가지 운영 환경(production)에서의 고려 사항을 생략했습니다. 다음 단계의 개선은 데모를 기업용(enterprise-ready)처럼 보이게 하려는 욕구가 아니라, 실제 위험(risk)에 의해 주도되어야 합니다:
- 두 소비자(consumer)가 서로 다른 속도로 진화할 수 있는 경우 버전(version) 필드를 추가합니다.
- 유효성 검사에 실패했을 때, 비식별화된 샘플(redacted sample)과 요청 식별자(request identifier)를 캡처합니다.
- 프롬프트, 제공자(provider), 또는 스키마(schema)가 변경될 때마다 계약 픽스처(contract fixtures)를 추가합니다.
- 유효성 검사 실패율을 일반적인 모델 지연 시간(latency)과 분리하여 측정합니다.
- 검증기(validator)뿐만 아니라 호출자(caller)의 폴백(fallback) 경로를 테스트합니다.
이러한 단계들은 '검증되지 않은 모델 텍스트는 비즈니스 로직으로 넘어가지 않는다'라는 단순한 규칙을 유지함으로써 시스템을 더 이해하기 쉽게 만듭니다.
연습한 것처럼 들리지 않으면서 이를 설명하는 방법은 무엇입니까?
코드를 보기 전에 네 가지 사례를 말로 설명해 보세요. 왜 JSON 주변의 산문(prose)이 실패하는지, 왜 열거형(enum)이 파싱 규칙이 아닌 비즈니스 규칙인지, 그리고 왜 초안(draft)을 위한 폴백과 결제 작업(payment action)을 위한 폴백이 다른지 설명해 보세요.
시간 제한이 있는 모의 연습을 위해서는, aceround.app — AI 면접 어시스턴트를 중립적인 면접관으로 활용하는 것이 유용할 수 있습니다. AI에게 복구 정책(recovery policy)에 대해 이의를 제기하도록 요청한 다음, 코드를 실제 실패 모드(failure mode)와 2분 안에 연결할 수 있을 때까지 설명을 수정해 보세요.
핵심은 "정답"인 AI 답변을 암기하는 것이 아닙니다. 구체적인 엔지니어링 판단(engineering judgment)을 가시화하는 것입니다. 즉, 모델은 유용한 텍스트를 생성할 수 있지만, 프로그램에는 여전히 명시적인 계약(explicit contracts)이 필요하다는 점입니다.
AI 공개: AI가 개요 작성 및 카피 에디팅(copy editing)을 보조했습니다. 코드, 실패 사례(failure cases) 및 기술적 주장(technical claims)은 발행 전 검토 및 검증되었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기