모두의포트폴리오
PE · FDE 직군을 위한 포트폴리오 밀키트
예전 판입니다 (1판)이 문서는 2026-08-25 에 새 판으로 개정됐습니다. 오너가 산출물 형태 불일치로 반려해 재생성했습니다. 제목이 가리키는 형태와 예측 UI·1주차 산출물이 서로 달랐습니다.최신 판 보기 →
경력인핸스 · Product Engineer (AgentOS)

고객사 데이터를 온톨로지로 정의해두면, 실무자가 자연어로 물었을 때 온톨로지에 정의된 필드로만 읽기 전용 SQL을 만들어 실행하고 어떤 데이터를 근거로 답했는지 함께 보여주되 온톨로지에 없는 걸 물으면 지어내지 않고 되묻는 백엔드입니다.

인핸스 공고에서 뽑은 세 가지 시그널.

이 백엔드가 보여주는 것은 온톨로지(의미 계층)에 근거한 자연어 질의 매핑, 읽기 전용 SQL 가드, 근거 필드·행 추적, 테넌트 격리 검증 기술이며, 특정 산업의 도메인 전문성을 주장하지 않습니다. 다만 이 사슬이 말이 되려면 인핸스 AgentOS 라는 맥락, 즉 기업 데이터를 온톨로지로 구조화하고 그 위에서 자연어로 조회·분석하는 Decision Intelligence 플랫폼이 있어야 의미가 있습니다. ‘정의 안 된 건 지어내지 않고 되묻는다’ 는 각도는 옮길 수 있지만, 엔티티·속성·관계를 명시한 온톨로지 형태가 있어야 질의 매핑과 근거 검증이 구체화됩니다. 실제 대규모 멀티테넌트 격리와 프로덕션 운영은 인핸스가 이미 프로덕션에서 풀고 있는 문제이고, 면접에서 모르는 부분은 모른다고 말하는 편이 낫습니다.

01

온톨로지 기반 자연어 데이터 조회·리포트 멀티 에이전트

‘정규화된 온톨로지 지식을 기반으로 자연어로 데이터를 조회·분석하고, 리포트·뷰·실행 결과물까지 만들어내는 멀티 에이전트 시스템 개발’ 이 주요업무입니다. 이게 이 포트폴리오가 확인할 제품의 뼈대입니다.

온톨로지자연어로 데이터를 조회멀티 에이전트리포트
02

백엔드 5년 이상 · DDD · 분산 정합성

‘Spring Boot 또는 FastAPI 로 프로덕션 서비스를 설계·개발·운영’, ‘Domain Driven Design 과 클린 아키텍처’, ‘분산 환경에서의 트랜잭션 처리’ 를 요구합니다. 도메인 모델(온톨로지)을 코어로 두고 계층을 분리하는 설계가 보여줄 대상입니다.

FastAPIDomain Driven Design분산 환경에서의 트랜잭션PostgreSQL
03

멀티 에이전트 · 멀티테넌트 · 스트리밍

우대사항의 ‘LangChain/LangGraph 와 같은 Multi Agent 개발’, ‘Socket/Message 처리’, 주요업무의 ‘멀티테넌트 기반 플랫폼 운영’, ‘동시 세션에서 에이전트 사고·실행을 지연 없이 전달하는 Socket/Stream’ 이 이 제품의 확장 골조입니다.

LangGraph멀티테넌트Socket/Stream동시 세션

포트폴리오 요약

고객사 업무 데이터를 온톨로지(엔티티·속성·관계와 물리 테이블·컬럼 매핑)로 정의해두면, 실무자가 자연어로 물었을 때 그 온톨로지에 정의된 필드로만 읽기 전용 SQL을 만들어 실행하고, 어떤 온톨로지 필드와 어떤 행을 근거로 답했는지 함께 보여주는 백엔드입니다. 자연어를 SQL로 바꾸는 건 LLM 이 맡지만, 답의 옳고 그름은 규칙으로 결정됩니다. 생성된 SQL 은 읽기 전용(SELECT)만 통과하고, 참조한 필드는 그 테넌트 온톨로지에 실재해야 하며, 근거 행은 모두 같은 테넌트여야 합니다. 온톨로지에 없는 개념이나 모호한 질문은 지어내지 않고 되묻습니다. 1주차만으로 완성품이고, 답 검증(2주차)·멀티테넌트 물리 격리와 스트리밍(3주차)로 키웁니다. 근거는 채용공고 정렬입니다.

고객 정의

인핸스 AgentOS 백엔드 PE. 고객사 데이터를 연결해두고도 실무자 질문이 올 때마다 SQL을 손으로 짜주던 일을 끝내고, 온톨로지에 정의된 의미·관계로 자연어 질문을 읽기 전용 SQL로 바꿔 근거와 함께 답하되, 정의 안 된 건 지어내지 않고 되묻게 하고 싶은 사람입니다.

고민 지점

자연어 질문을 온톨로지에 정의된 필드로만 SQL 로 매핑하고 규칙으로 검증하면, (a) 정의된 개념 질문은 읽기 전용 SQL 로 실행돼 근거 필드·행과 함께 답이 나오고, (b) 온톨로지에 없는 개념은 지어내지 않고 되묻으며, (c) 근거 행이 모두 같은 테넌트에 묶여 교차 조회가 막혀서, 실무자가 믿고 쓸 수 있는 자연어 데이터 질의를 만든다는 걸 보여줍니다.

주차별 일정

1주차여기까지로 완성

온톨로지 정의 → 자연어 질문 → 근거 답변·되묻기

한 도메인 온톨로지를 정의하고, 자연어 질문을 그 온톨로지에 정의된 필드로만 읽기 전용 SQL로 바꿔 실행해 사용 필드·근거 행과 함께 답하며, 온톨로지에 없는 개념·모호한 질문은 지어내지 않고 되묻는 동작하는 백엔드입니다. 멀티테넌트는 tenant_id 컬럼 필터로 논리 격리를 가정합니다(물리 격리는 3주차).

  1. 1일 · 온톨로지 정의를 한 표로 세우기ontology_field 스키마 설계 + 엔티티·속성·물리 테이블·컬럼·dtype 매핑, 원문 표기(attribute_raw)와 정규화 속성 분리
  2. 2-3일 · 업무 데이터와 읽기 전용 실행 경로order_fact 스키마 설계 + tenant_id 필터를 붙인 읽기 전용 SQL 실행(FastAPI·PostgreSQL)
  3. 4-5일 · 자연어를 온톨로지 필드로 매핑해 SQL 생성그 테넌트 온톨로지 필드 주입 + 출력 스키마 강제, 온톨로지에 없는 필드·테이블 생성 금지
  4. 6일 · 규칙 검증과 되묻기읽기 전용 여부·필드 실재·테넌트 격리 검증 + 정의 밖·모호 질문은 되묻기로 폴백
  5. 7일 · 질문 30건 시연 + 규칙 검증 통과율여기까지가 1주차 완성품
2주차선택 성장

답이 실제로 질문 의도에 맞는지 검토

도메인 검토자 페르소나 2명이 생성된 SQL·답이 질문 의도에 맞는지, 되묻기가 적절했는지 라벨링하고, 미리 만든 정답 SQL 대비 정확도와 잘못 답한 비율, 온톨로지 밖 질문을 걸러낸 재현율을 대조하는 대시보드입니다.

  1. 8-9일 · 검토자 페르소나 2명이 SQL·답 타당성 라벨링검토 가이드 + 라벨러 간 일치도 측정(Cohen's κ: 두 사람 답이 우연 이상으로 일치하는 정도, 0~1)
  2. 10-11일 · 정답 SQL 대비 정확도·거짓 응답 측정미리 만든 정답 SQL 과 결과를 대조해 정확도와 잘못 답한 비율 측정
  3. 12일 · 온톨로지 밖·모호 질문 판별 보강일부러 심은 정의 밖·모호 질문을 걸러낸 재현율 측정하고 되묻기 임계 조정
  4. 13일 · 근거 자동 대조 + 5% 사람 점검답의 사용 필드가 온톨로지에 실재하고 근거 행이 같은 테넌트인지 검사 통과율 측정
3주차선택 성장

멀티테넌트 물리 격리 + 실행 과정 스트리밍

tenant_id 필터를 테넌트별 스키마·커넥션 분리로 바꿔 교차 조회를 원천 차단하고, 에이전트의 질의 해석·SQL 생성·실행 과정을 WebSocket 으로 흘리며, 대용량 업무 데이터는 SparkSQL 경로로 질의하는 단계입니다.

  1. 15-16일 · 멀티테넌트 물리 격리tenant_id 필터를 테넌트별 스키마·커넥션 분리로 전환해 교차 조회 원천 차단
  2. 17-18일 · 실행 과정 스트리밍에이전트의 질의 해석·SQL 생성·실행 과정을 WebSocket 으로 실무자 화면에 흘림
  3. 19일 · SparkSQL 대용량 경로대용량 업무 데이터 질의를 SparkSQL 로 분산 실행하는 경로 추가
  4. 20일 · 답 신뢰도 표기질문 유형별 정확도·되묻기 비율을 답에 함께 표기해 실무자 과신 방지
  5. 21일 · 3주 통합 시연 + 실제 도입 계획(실 고객사 온톨로지·데이터 레이크 연결, 대규모 멀티테넌트 격리 결합)

데모 · 측정 지표

01

자연어 질문 30건을 넣으면, 온톨로지에 정의된 필드로만 읽기 전용 SQL을 만들어 실행하고 사용 필드·근거 행과 함께 답하는 걸 시연합니다

02

생성된 SQL 이 전부 읽기 전용(SELECT)이고, 참조한 필드가 전부 그 테넌트 온톨로지에 실재하며, 근거 행이 전부 같은 테넌트인지 자동 대조합니다. 온톨로지에 없는 개념을 물으면 지어내지 않고 되묻는 걸 보여줍니다

03

일부러 만든 온톨로지 밖 질문·모호 질문·타 테넌트 침범 유도 30종을 넣어도 출력 형식이 안 깨지고 되묻기나 차단으로 빠지는 비율을 보여줍니다

04

내가 만든 정답 SQL 30건 중 결과가 일치한 게 25건(83%). 단, 정답과 데이터를 모두 내가 만든 합성 기준이라 ‘성능 검증’ 이 아니라 ‘데모 조건 동작’ 임을 밝힙니다

정직 경계선

진짜 성능 검증은 여기서 멈춥니다

위 수치는 전부 작성자가 만든 더미데이터 자가채점이며, 실제 자연어 질의 정확도와 대규모 멀티테넌트 격리 안정성은 인핸스 실 고객사 온톨로지·데이터 레이크와 실 검토자가 있어야 측정 가능합니다. 이 백엔드는 그 검증을 바로 돌릴 수 있는 상태까지 만듭니다.

미리 알아둬야 할 것들

이건 고객사·실데이터 없이 혼자 만든 포트폴리오라, 수치는 합성 데이터 자가채점이라 성능 증명이 아닙니다. 면접에서 ‘데모 동작 + 실검증 준비 상태’ 로 솔직히 설명합니다

정답 SQL 을 내가 만들었으니 잘 맞는 게 당연합니다. 온톨로지 밖 개념·모호한 지시·타 테넌트 침범 유도 질문을 더미에 일부러 섞어, 깔끔한 질문만 다루는 게 아님을 보여줍니다

이 포트폴리오가 보여주는 것은 온톨로지 근거 질의 매핑·읽기 전용 가드·테넌트 격리 검증 기술이지 특정 산업 도메인 전문성이 아닙니다. 답 품질은 온톨로지 정의 품질에 종속되므로, 정의가 부실하면 되묻기가 늘어난다는 한계를 함께 설명합니다

자연어는 같은 개념을 다르게 표현하고(매출·판매액) 애매한 질문이 많습니다. 원문 표기와 정규화 속성을 분리하고 모호하면 단정 대신 되묻어, 잘못된 SQL 을 실행하지 않게 막습니다

실제 도입 시 달라지는 것 명시합니다. 실 고객사 온톨로지 규모·데이터 분포는 더미와 다르므로 매핑 규칙과 되묻기 임계를 다시 맞춰야 하고, 논리 격리(tenant_id 필터)를 실제 스키마·커넥션 물리 격리로 바꿔야 하며, 검토자도 도메인 실무자로 교체해야 합니다(도입 단계 과제로 분리)

더미 데이터

① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.

ontology_fieldTABLE· 11 rows

테넌트별 온톨로지 정의. 엔티티·속성이 어떤 물리 테이블·컬럼으로 매핑되는지 담아, 자연어 질의가 이 정의된 필드로만 SQL 을 만들 수 있게 하는 근거입니다. 물리 매핑 결측(엔티티만 있고 컬럼 미매핑)·설명 결측·원문 표기 흔들림(매출/판매액)을 일부러 섞어, 정의 밖 개념은 되묻고 표기가 흔들려도 정규화로 잡는지 보여줍니다.

② ontology_field.seed.json 다운로드
① 인라인 샘플 (프리뷰 5 / 11)
tenant_identityattributeattribute_rawtable_namecolumn_namedtypedescriptionpii
t_Aorderamount매출order_factamountnumber주문 결제 금액(원)false
t_Aorderamount판매액order_factamountnumber—false
t_Aorderregion지역order_factregionstring주문 지역false
t_Aorderstatus상태order_factstatusenum주문 상태(paid·cancelled·refunded)false
t_Aorderorder_date주문일order_factorder_datedate주문 발생일false
③ 증강 프롬프트 (본인 LLM 에 붙여서 N 배 늘리기)
아래 JSON Schema 에 맞는 테넌트별 온톨로지 정의 30행 JSON 배열로 생성합니다. 테넌트 2~3개, 엔티티 3~4종.

{"type":"object","properties":{"tenant_id":{"type":"string"},"entity":{"type":"string"},"attribute":{"type":"string"},"attribute_raw":{"type":["string","null"]},"table_name":{"type":"string"},"column_name":{"type":["string","null"]},"dtype":{"type":"string","enum":["string","number","date","enum","id"]},"description":{"type":["string","null"]},"pii":{"type":"boolean"}},"required":["tenant_id","entity","attribute","table_name","dtype","pii"]}

쉬운 데이터만 만들지 말 것. 하드 케이스 의도적 혼입: (1) column_name 이 null 인 미매핑 필드 1~2건(엔티티는 있으나 물리 매핑 미완 → 질의 시 되묻기), (2) description 이 null 인 결측 1~2건, (3) 같은 attribute 를 attribute_raw 별칭(매출/판매액/거래액)으로 다르게 표기한 표기 흔들림 2건, (4) 테넌트마다 같은 엔티티를 두어 교차 테넌트 침범 검증이 가능하게, (5) pii=true 인 필드 1~2건. JSON 배열만.
schema (3 층의 단일 출처)
{"type":"object","properties":{"tenant_id":{"type":"string"},"entity":{"type":"string"},"attribute":{"type":"string"},"attribute_raw":{"type":["string","null"]},"table_name":{"type":"string"},"column_name":{"type":["string","null"]},"dtype":{"type":"string","enum":["string","number","date","enum","id"]},"description":{"type":["string","null"]},"pii":{"type":"boolean"}},"required":["tenant_id","entity","attribute","table_name","dtype","pii"]}
order_factTABLE· 11 rows

온톨로지가 가리키는 실제 업무 데이터. 자연어 질의가 실행돼 답이 나오는 대상입니다. 금액 결측·0원 경계·마스터에 없는 고객 orphan·상태 원문 표기 흔들림(취소/cancelled)·서로 다른 테넌트 행을 섞어, 규칙이 결측·경계에 안 깨지고 교차 테넌트 조회를 막는지 보여줍니다.

② order_fact.seed.json 다운로드
① 인라인 샘플 (프리뷰 5 / 11)
tenant_idorder_idcustomer_idregionamountstatusstatus_raworder_date
t_Ao_001c_A1서울52,000paid결제완료2026-06-03
t_Ao_002c_A2부산0cancelled취소2026-06-05
t_Ao_003c_A1서울—paid—2026-06-07
t_Ao_004c_ZZ대구31,000paid결제완료2026-06-08
t_Ao_005——18,000refunded환불2026-06-09
③ 증강 프롬프트 (본인 LLM 에 붙여서 N 배 늘리기)
아래 JSON Schema 에 맞는 주문 업무 데이터 40행 JSON 배열로 생성합니다. 테넌트 2~3개, 며칠치.

{"type":"object","properties":{"tenant_id":{"type":"string"},"order_id":{"type":"string"},"customer_id":{"type":["string","null"]},"region":{"type":["string","null"]},"amount":{"type":["number","null"]},"status":{"type":"string","enum":["paid","cancelled","refunded"]},"status_raw":{"type":["string","null"]},"order_date":{"type":"string"}},"required":["tenant_id","order_id","status","order_date"]}

쉬운 데이터만 만들지 말 것. 하드 케이스 의도적 혼입: (1) amount 가 null 인 결측 2~3건, (2) amount 가 0 인 경계 1~2건, (3) customer_id 가 마스터에 없는 orphan 이거나 null 인 행 2건(집계 시 미매핑으로 알려야 함), (4) status_raw 를 취소/환불/cancelled 처럼 원문 표기로 흔들어 status enum 과 분리, (5) 서로 다른 tenant_id 행을 섞어 교차 테넌트 조회 차단을 검증 가능하게. JSON 배열만.
schema (3 층의 단일 출처)
{"type":"object","properties":{"tenant_id":{"type":"string"},"order_id":{"type":"string"},"customer_id":{"type":["string","null"]},"region":{"type":["string","null"]},"amount":{"type":["number","null"]},"status":{"type":"string","enum":["paid","cancelled","refunded"]},"status_raw":{"type":["string","null"]},"order_date":{"type":"string"}},"required":["tenant_id","order_id","status","order_date"]}

기술 상세 · 아키텍처·운영·출력 스키마

기술 스택 매핑
Python (FastAPI)온톨로지 해석·자연어에서 SQL 변환·읽기 전용 실행을 맡는 API 서버(자격요건 FastAPI 스택 반영)
PostgreSQL고객사 업무 데이터와 온톨로지 정의를 담는 관계형 저장소, 테넌트별 격리·인덱싱·읽기 전용 롤 분리(자격요건 반영)
LangGraph질문 해석에서 온톨로지 매핑, SQL 생성, 규칙 검증, 답변 생성까지 노드로 잇는 멀티 에이전트 그래프(우대 반영)
Domain Driven Design온톨로지(도메인 모델)를 코어로 두고 질의·실행 계층을 인프라 관심사와 분리하는 설계(자격요건 반영)
WebSocket에이전트의 질의 해석·실행 과정을 지연 없이 실무자 화면으로 흘리는 스트리밍(주요업무 Socket/Stream 반영)
SparkSQL대용량 업무 데이터 질의를 분산 실행하는 경로로 확장(우대 반영)
LLM 선택
model
Claude Sonnet (자연어 질문을 온톨로지 필드에 매핑해 SQL 초안 생성) + 규칙 엔진(읽기 전용 여부·필드 실재·테넌트 격리 검증은 결정론)
prompt
그 테넌트의 온톨로지 필드 목록을 주입 + 출력 스키마 강제(SQL·사용 필드·근거 행·되묻기 사유) + few-shot 3 예시(정의된 질문/온톨로지 밖 질문/모호한 질문) + 온톨로지에 없는 필드·테이블 생성 금지, 쓰기 SQL 금지, 근거 약하면 되묻기입니다.
rationale
자연어를 SQL 로 바꾸는 매핑은 문맥 이해가 필요해 Sonnet 을 쓰되, 답의 옳고 그름은 온톨로지 실재·읽기 전용·테넌트 규칙으로 결정론적으로 검증합니다. 온톨로지 필드를 프롬프트에 직접 주입하므로 별도 RAG 검색 없이도 근거가 고정됩니다.
결정 게이트
1주차 끝자연어 질문이 온톨로지에 정의된 필드로만 읽기 전용 SQL로 바뀌어 실행되고, 답에 사용 필드·근거 행이 붙으며, 온톨로지에 없거나 모호한 질문은 지어내지 않고 되묻습니까? 여기까지가 그 자체로 제출 가능한 완성품입니다.

✓ pass → 그대로 멈춰도 완결입니다. 더 키우려면 2주차(답이 질문 의도에 맞는지 검토)로 진행합니다.

2주차 끝정답 SQL 대비 정확도와 잘못 답한 비율이 측정되고, 검토자 신뢰도가 붙으며, 온톨로지 밖 질문을 걸러낸 재현율이 잡힙니까?

✓ pass → 여기서 멈춰도 완결입니다. 더 키우면 3주차(멀티테넌트 물리 격리와 스트리밍)로 진행합니다.

재사용 레이어 · 코어 70% / 도메인 30%
core (재사용 가능)
  • 자연어 질문을 온톨로지 필드에 매핑해 SQL 초안을 만드는 변환 계층
  • 생성 SQL 이 읽기 전용(SELECT)인지, 쓰기·DDL 이 섞이지 않았는지 거르는 가드
  • SQL 이 참조한 필드가 그 테넌트 온톨로지에 실재하는지 검증하는 그라운딩 검사
  • 근거 행이 모두 같은 테넌트에 묶였는지 확인하는 테넌트 격리 검증
  • 온톨로지에 없거나 모호한 질문을 지어내지 않고 되묻는 폴백
  • 정답 SQL 대비 정확도·거짓 응답·온톨로지 밖 질문 재현율을 재는 검토 대시보드
  • 일부러 만든 온톨로지 밖·모호·교차 테넌트 유도 질문 강건성 테스트 세트
domain (교체 대상)
  • order_fact 같은 도메인 업무 테이블 세트
  • 엔티티·속성·관계를 채운 온톨로지 사전과 dtype enum(string·number·date·enum·id)은 도메인별로 갈아끼웁니다
  • 도메인 검토자 페르소나와 정답 SQL 라벨 가이드
출력 스키마 · nl_query_result

자연어 질문 하나에 대한 질의 결과. 생성 SQL·사용한 온톨로지 필드(used_fields)·근거 행(evidence_rows)·해결 여부(resolved)·되묻기 사유(refusal_reason)를 한 스키마에 담아, 온톨로지 밖 필드를 지어내지 않았는지, SQL 이 읽기 전용인지, 근거 행이 같은 테넌트인지 객관 검증합니다.

schema (JSON Schema)
{"type":"object","properties":{"tenant_id":{"type":"string"},"question":{"type":"string"},"resolved":{"type":"boolean"},"sql":{"type":["string","null"]},"used_fields":{"type":"array","items":{"type":"string"}},"evidence_rows":{"type":"array","items":{"type":"string"}},"answer":{"type":"string"},"refusal_reason":{"type":"string","enum":["not_in_ontology","ambiguous","unmapped_field","none"]}},"required":["tenant_id","question","resolved","used_fields","evidence_rows","answer","refusal_reason"]}
모범 출력 (3)
[
  {
    "input_ref": "t_A: ‘지난달 지역별 매출 합계 알려줘’ (정의된 질문)",
    "output": {
      "tenant_id": "t_A",
      "question": "지난달 지역별 매출 합계 알려줘",
      "resolved": true,
      "sql": "SELECT region, SUM(amount) AS total FROM order_fact WHERE tenant_id='t_A' AND status='paid' AND order_date >= '2026-06-01' AND order_date < '2026-07-01' GROUP BY region",
      "used_fields": [
        "order.region",
        "order.amount",
        "order.status",
        "order.order_date"
      ],
      "evidence_rows": [
        "o_001",
        "o_006",
        "o_007"
      ],
      "answer": "지난달 결제 완료 기준 지역별 매출은 서울 126,000원, 부산 26,000원입니다. 금액이 비어 있는 주문(o_003)은 합계에서 제외했습니다.",
      "refusal_reason": "none"
    }
  },
  {
    "input_ref": "t_A: ‘재고 얼마나 남았어?’ (엔티티는 있으나 컬럼 미매핑)",
    "output": {
      "tenant_id": "t_A",
      "question": "재고 얼마나 남았어?",
      "resolved": false,
      "sql": null,
      "used_fields": [],
      "evidence_rows": [],
      "answer": "‘재고(inventory)’ 엔티티는 온톨로지에 정의됐지만 물리 컬럼 매핑이 아직 없어 조회할 수 없습니다. 데이터 연결을 담당하는 분께 inventory_fact 매핑을 요청해 주세요.",
      "refusal_reason": "unmapped_field"
    }
  },
  {
    "input_ref": "t_A: ‘요즘 잘 나가?’ (모호한 질문)",
    "output": {
      "tenant_id": "t_A",
      "question": "요즘 잘 나가?",
      "resolved": false,
      "sql": null,
      "used_fields": [],
      "evidence_rows": [],
      "answer": "‘잘 나가’ 의 기준이 모호합니다. 매출 합계인지, 주문 건수인지, 어느 기간인지 알려주시면 정확히 조회하겠습니다.",
      "refusal_reason": "ambiguous"
    }
  }
]
검증 노트 (자동 vs 수동)

[자동 검증] 1. 출력이 스키마를 통과하는가(형식·필수 필드). 2. sql 이 있으면 읽기 전용인가(SELECT 로 시작하고 INSERT·UPDATE·DELETE·DROP 등 쓰기·DDL 이 섞이지 않았는지 정적 검사). 3. used_fields 의 각 필드가 그 tenant_id 온톨로지에 실재하는가(지어낸 필드 차단), sql 의 WHERE 에 tenant_id 조건이 있는가. 4. evidence_rows 가 전부 같은 tenant_id 행인가(교차 테넌트 근거 차단), 온톨로지 밖·모호·미매핑이면 resolved=false 이고 refusal_reason 이 그 사유로 채워지는가. [수동 검증] 1. 도메인 검토자 페르소나 2명이 SQL·답이 질문 의도에 맞는지 라벨링 → bootstrap Cohen's κ(두 사람 답이 우연 이상으로 일치하는 정도, 0~1) 하한 ≥ 0.6. 2. 정답 SQL 대비 결과 일치·거짓 응답 5% 사람 점검.