Context Ontology Accelerator 기술 가이드

대상 버전: public mirror main 2026-08-18 동기화 기준 (저장소 VERSION 파일은 0.1.0이나 v0.2.x 변경 일부가 선반영된 시점이다. v0.2.1 릴리즈와의 차이는 해당 릴리즈 노트 참조)
근거 자료: 소스코드(github.com/aws/context-ontology-accelerator), 공식 문서(aws.github.io/context-ontology-accelerator), Smithy API 정의(models/src/main/smithy)
작성: 이지후(Jihoo Lee), Solutions Architect, AWS Korea
본 문서의 동작 서술은 소스코드에서 직접 확인한 내용을 기준으로 하며, 해당 파일 경로를 각주로 표기했다. 문서와 코드가 다른 지점은 코드를 따랐다.
목차
용어 사전
  1. 개요와 아키텍처 특징
  2. 설계 분석: 왜 온톨로지인가
  3. SCAN: 데이터 소스 연결과 메타데이터 구축
  4. MODEL: 온톨로지 유도와 발행
  5. SERVE: 질의 계층
  6. Context Manager 상세와 질의 처리 예시 4종
  7. 제약사항

용어 사전

본문에 앞서 semantic web 계열 용어와 COA 고유 용어의 최소 정의를 정리했다. 상세한 맥락은 각 절에서 다룬다.

용어정의
RDF모든 지식을 주어-술어-목적어 3요소(트리플)로 표현하는 W3C 데이터 모델. "클레임#123, 제기자, 딜러A"가 트리플 하나다.
Turtle (.ttl)RDF를 사람이 읽기 쉬운 텍스트로 쓰는 직렬화 포맷. COA가 발행하는 ontology.ttl이 이 형식이다.
OWLRDF 위에서 class, property, 계층, 제약을 정의하는 온톨로지 언어. "모든 Order는 정확히 하나의 Customer를 가진다" 같은 규칙을 기계가 추론할 수 있는 형태로 표현한다.
온톨로지도메인의 개념(class), 속성(property), 관계, 제약을 담은 공식 지식 모델. ERD와 달리 표준 포맷(OWL)으로 존재해 기계가 직접 읽고 추론한다.
T-Box / A-Box온톨로지 지식의 두 층. T-Box는 개념 정의("Customer라는 class가 있다"), A-Box는 인스턴스 사실("김철수는 Customer다"). COA는 정형 데이터에 대해 T-Box만 만들고 A-Box는 만들지 않는다.
SPARQLRDF 그래프를 조회하는 W3C 표준 질의 언어. SQL의 그래프 대응물이다.
R2RML관계형 테이블과 RDF 개념의 대응 규칙을 기술하는 W3C 표준 언어. "이 class의 인스턴스는 이 테이블의 행, 이 property는 이 컬럼"이라는 대응표다(예시는 2.3절).
OBDA / VKGOntology-Based Data Access / Virtual Knowledge Graph. 데이터를 그래프로 복제하지 않고 관계형 DB 위에 온톨로지 뷰를 씌운 뒤, 질의 시점에 SPARQL을 SQL로 번역하는 접근.
OntopOBDA/VKG를 구현한 오픈소스 번역 엔진. COA의 VKG 컨테이너가 Ontop 5.5.0이다.
Reasoner (HermiT)온톨로지의 논리적 결함(모순, 만족 불가능한 class, 순환 상속)을 수학적으로 검사하는 추론기. COA의 proposal 검증(온디맨드 실행)에 쓰인다.
SHACLRDF 데이터가 지켜야 할 형태 규칙("모든 class에는 label이 있어야 한다")을 선언하고 검사하는 W3C 표준.
GraphRAG문서에서 entity와 fact를 추출해 지식 그래프를 구축하고, 벡터 검색과 그래프 탐색으로 맥락을 모아 LLM이 답을 서술하는 기법. COA에서는 Tier 3와 문서 파이프라인이 이 계열이다.
Induction (COA)승인된 테이블 스키마로부터 온톨로지 초안과 R2RML mapping을 자동 유도하는 COA의 파이프라인.
Grounding (COA)유도된 class를 표준 온톨로지(Schema.org, FIBO 등)와 embedding 매칭으로 정렬하는 induction의 후속 단계.
Governed metric (COA)이름, 설명, SQL expression으로 등록되며 등록 시 검증을 거치는 공식 비즈니스 계산 정의. Tier 1의 답변 재료다.

1. 개요와 아키텍처 특징

1.1 해결하는 문제

LLM에 테이블 스키마만 제공하고 자연어 질의를 SQL로 변환하게 하면 세 가지 문제가 반복된다. 첫째, 스키마에는 비즈니스 의미가 없어 LLM이 컬럼의 의미를 추측하다 틀린다. 둘째, "총 매출"과 같은 비즈니스 계산을 질의할 때마다 LLM이 새로운 SQL을 생성하므로 답이 재현되지 않는다. 셋째, AI agent에 DB 접근을 부여할 때 테이블·컬럼 수준의 통제 수단이 없다.

Context Ontology Accelerator(이하 COA)는 데이터와 AI 소비자 사이에 semantic context layer를 두어 이 문제에 대응한다. 승인된 온톨로지가 의미 해석을 고정하고, governed metric이 비즈니스 계산을 고정하며, Cedar 정책과 SQL Firewall이 실행을 통제한다. Apache 2.0 라이선스의 CDK 기반 self-hosted 에셋이며, CloudFormation 스택 16개로 배포된다.

1.2 세 단계 워크플로우

운영은 Scan, Model, Serve 세 단계로 진행된다.

단계목적산출물사람의 개입
Scan소스 연결, 스키마 발견, AI 메타데이터 생성검증된 테이블 명세(DataZone asset), Athena 쿼리 경로steward가 테이블·컬럼 승인, 메타데이터 교정, PK/FK 지정
Model온톨로지 유도, 검증, 발행. metric 등록T-Box(OWL) + R2RML mapping + schema.sql, governed metricinduction 트리거, proposal 검토 후 accept
Serve자연어 질의 응답표, 숫자, 서술형 답변, 실행 trace없음(질의만)
COA 전체 아키텍처
그림 1. 전체 아키텍처. RDB(PostgreSQL) 직결과 SAP·SFDC 추출 경유를 병용하는 구성 예시이며, Scan·Model·Serve 세 plane으로 구성된다. 각 plane의 상세는 해당 절의 그림을 참조한다.

1.3 아키텍처 특징 요약

특징내용
Virtual Knowledge Graph정형 데이터의 인스턴스(A-Box)를 그래프로 복제하지 않는다. 온톨로지 정의(T-Box)와 R2RML mapping(개념과 물리 테이블의 대응 규칙, 2.3절 참조)만 유지하고, 질의 시점에 Ontop 엔진이 SPARQL을 SQL로 번역한다. 데이터는 원본 DB에 남는다.
3-tier fall-through질의를 신뢰도 내림차순으로 처리한다. Tier 1은 등록·검증된 metric SQL을 그대로 실행하고, Tier 2는 SQL을 생성하며, Tier 3는 검색된 맥락으로 서술형 답변을 합성한다. 상위 tier가 확신하지 못하면 다음 tier로 넘긴다.
생성·검증·승인의 분리LLM이 생성하고(메타데이터, 온톨로지, 질의문), 발행 게이트의 검증은 결정적 도구가 수행하며(HermiT reasoner, SHACL, SQL Firewall), 최종 승인은 사람이 담당한다(scan 리뷰, proposal accept). 생성 단계 내부에 품질 보강용 LLM 상호 평가(grounding reranker, RIGOR 전략의 심판 LLM)가 있지만, 게이트 통과 여부는 결정적 도구와 사람만 결정한다.
Provenance 우선순위모든 메타데이터에 출처가 기록된다. 사람이 지정한 값(STEWARD_*)은 DB constraint(DETERMINISTIC)보다, DB constraint는 AI 산출물보다 우선한다. AI 산출물은 서술 메타데이터가 AI_GENERATED, 추론된 PK/FK가 AI_INFERRED로 구분 마킹되며 둘은 같은 최하위 등급이다. 재스캔이 사람의 편집을 덮어쓰지 못한다.
2-layer 권한Layer 1(Cedar)은 API 행위를 role 기반으로 허용하고, Layer 2(SQL Firewall)는 실행 시점에 테이블 allowlist와 컬럼 denylist를 강제한다. Layer 2는 platform-admin도 우회할 수 없다.
Agent 위임 모델MCP agent는 별도 자격증명 없이 사용자의 OIDC 토큰으로만 동작한다. agent가 조회할 수 있는 범위는 위임한 사용자의 grant와 동일하며, 감사 로그에는 사용자 ID가 남는다.
실행 경로 자동 선택단일 소스 질의는 native driver로 직접 실행하고(약 20~50ms), 크로스 소스 질의와 Glue 소스는 Athena federation으로 실행한다(약 500~800ms). 설정이 필요 없다.
카탈로그 이원화Glue Data Catalog는 물리 카탈로그(실행용)로 다룬다. 스캔은 읽기만 하고 federation은 쿼리 통로(연결, federated catalog) 등록만 하며, 비즈니스 메타데이터와 리뷰 이력은 DataZone에 별도 기록한다. 고객의 운영 카탈로그를 AI 생성물로 오염시키지 않는다.

2. 설계 분석: 왜 온톨로지인가

2.1 문제의 본질: 의미 간극

사용자의 질문은 비즈니스 개념으로 구성된다. "우수 딜러", "클레임", "지난달 매출"이 그 어휘다. 데이터는 다른 공간에 있다. 물리 테이블과 컬럼(clm_mst, dlr_cd), 그리고 문서 더미다. 자연어 질의 시스템의 일은 이 두 공간의 간극을 메우는 것이고, 접근 방식들의 차이는 결국 하나의 질문으로 수렴한다. 개념과 데이터의 대응, 곧 "의미"를 무엇으로 표현해서 어디에 보관하는가.

2.2 기존 접근이 의미를 두는 곳

접근의미가 존재하는 곳귀결
Text-to-SQLLLM 내부. 질의 순간의 확률적 출력으로만 존재검토·버전·감사 불가, 재현성 없음
Text-to-SPARQL
(트리플 스토어 실행)
온톨로지로 명시화실행을 위해 데이터까지 트리플로 복제해야 함
GraphRAG문서에서 통계적으로 추출한 그래프추출물이지 승인된 모델이 아님. 집계 부적합

Text-to-SQL은 간극을 질의마다 LLM이 즉석에서 메운다. "우수 딜러"가 어느 테이블의 어느 조건에 해당하는지에 대한 판단이 그 순간의 생성 결과로만 존재하고, 질의가 끝나면 사라진다. 이 판단은 자산이 아니므로 검토할 수 없고, 버전이 없으며, 어제와 오늘이 다를 수 있다. JOIN 경로 추론은 hop이 늘수록 오류가 누적된다. 조직 관점에서 보면 수백 번의 질의는 수백 번의 서로 다른 의미 해석이다.

Text-to-SPARQL은 의미를 온톨로지로 명시화한다는 점에서 방향이 같다. 문제는 실행이다. SPARQL을 실행하려면 인스턴스 데이터가 트리플 스토어에 있어야 하므로 원본 DB를 트리플로 변환해 적재해야 한다. 이중 저장과 동기화 파이프라인, 신선도 문제가 따라오고, 트리플 저장 구조는 대규모 집계에 불리하다. 의미의 명시화라는 이득을 데이터 복제라는 비용으로 지불하는 셈이다.

GraphRAG는 문서 지식에 대한 답이다. entity와 fact를 추출해 그래프를 만들고, 벡터 검색과 그래프 탐색으로 맥락을 모아 LLM이 서술한다. 그러나 이 그래프는 LLM이 추출한 진술의 집합이지 사람이 검토하고 승인한 모델이 아니다. 검색이 조각을 놓치면 답이 틀리고, 틀렸는지 확인할 방법도 없다. "딜러별 클레임 건수" 같은 집계 질문에는 구조적으로 부적합하다. 정형 데이터를 GraphRAG 방식으로 그래프에 물질화하는 선택지도 같은 이유로 배제된다. 동기화 비용에 더해, LLM이 대량 변환에 개입하는 순간 오류 검출이 불가능해진다.

2.3 COA의 선택: 의미를 거버넌스 가능한 자산으로

COA의 답은 의미를 일급 자산으로 승격시키는 것이다. 이 선택은 세 가지 판단으로 나뉜다.

첫째, 의미가 자산이어야 거버넌스가 성립한다. "WarrantyClaim은 Dealer가 제기하며 Part를 참조한다"는 해석을 OWL 문서로 명시하면, LLM이 제안하되 reasoner가 검증하고(HermiT, SHACL) 사람이 승인하는 파이프라인을 만들 수 있다. LLM 내부의 잠재적 해석은 리뷰할 수 없지만 온톨로지는 리뷰, 검증, 버전 관리, 감사가 가능하다. Text-to-SQL이 원리적으로 제공할 수 없는 성질이다.

둘째, 의미만 자산화하고 데이터는 복제하지 않는다. COA는 T-Box(개념 정의)만 생성하고 A-Box(인스턴스)는 만들지 않으며, 둘 사이를 R2RML mapping이 잇는다. R2RML은 관계형 데이터와 RDF 개념의 대응 규칙을 기술하는 W3C 표준 언어로, "이 class의 인스턴스는 이 테이블의 행이고, 이 property는 이 컬럼이다"라는 대응표를 기계가 읽을 수 있는 형태로 담는다. 다음 mapping은 claims 테이블의 각 행을 dd:WarrantyClaim의 인스턴스로, created_at 컬럼을 dd:claimDate property로 선언한다.

<#ClaimMap>  rr:logicalTable  [ rr:tableName "claims" ] ;
  rr:subjectMap  [ rr:class dd:WarrantyClaim ; rr:template "urn:claim/{id}" ] ;
  rr:predicateObjectMap [
    rr:predicate  dd:claimDate ;
    rr:objectMap  [ rr:column "created_at" ]
  ] .

COA에서 이 mapping은 사람이 손으로 쓰지 않는다. induction 단계에서 코드가 스키마로부터 기계적으로 생성하고(4.2절), proposal의 일부로 사람이 승인한다. VKG는 이 원칙의 실행 메커니즘이다. 질의 시점에 Ontop이 SPARQL을 SQL로 번역해 원본에서 조회하므로(packages/vkg), 트리플 스토어 방식의 이중 저장 없이 온톨로지 기반 질의가 성립한다. LLM의 오류 가능 범위도 함께 줄어든다. 수백만 행의 변환이 아니라 사람이 검토 가능한 크기의 T-Box(수십~수백 class)만 LLM이 다루기 때문이다.

셋째, LLM의 역할을 재배치한다. 온톨로지가 있으면 LLM은 닫힌 어휘 안에서 짧은 SPARQL만 쓰면 된다. 노출되는 어휘는 R2RML mapping이 존재하는 class로 제한되고(tier2/ontop/tbox_context.py의 isMapped 게이트), 테이블 선택과 JOIN 경로는 사람이 승인한 mapping이 결정하는 번역 규칙으로 처리된다. 환각이 가능한 구간이 "물리 스키마 전체 위의 SQL 작성"에서 "승인된 개념 위의 의미 질의 작성"으로 좁아지고, 같은 SPARQL은 항상 같은 SQL이 된다.

2.4 온톨로지는 단일 의미 좌표계로 기능한다

이 에셋에서 온톨로지의 역할은 Tier 2의 번역 재료에 그치지 않는다. 발행된 온톨로지 하나를 아키텍처의 구성요소들이 서로 다른 방식으로 소비한다.

소비자온톨로지의 사용 방식
Tier 2 (VKG)T-Box가 SPARQL 어휘를, R2RML이 SQL 번역 규칙을 제공한다
Tier 3 (지식 검색)class·property embedding이 그래프 탐색의 시작점(seed)이 되고, 탐색 자체가 온톨로지 그래프 위에서 이루어진다
Governed metric지표 정의가 온톨로지와 같은 Neptune 그래프에 저장된다. 계산이 개념 체계에 부착된다
AI agent (MCP)describe_schema가 물리 스키마 대신 온톨로지 스키마를 노출한다. REST와 같은 소스를 읽는다
의미 라우팅class·property embedding(OpenSearch)이 자연어 질문과 개념을 매칭한다
문서 지식grounding이 표준 온톨로지(Schema.org, FIBO 등)와의 정렬을 만들고, 문서에서 추출한 lexical graph가 같은 그래프 저장소에서 개념과 만난다

온톨로지를 제거하면 이 에셋은 metric 카탈로그, text-to-SQL 도구, 문서 검색기의 서로 접점 없는 묶음으로 남는다. 온톨로지가 있어서 "이 지표는 어느 개념의 계산인지, 이 문서는 어느 개념에 관한 것인지, 이 SQL은 어느 개념을 조회한 것인지"가 하나의 좌표계에서 답해진다. 이 에셋의 이름이 text-to-SQL accelerator가 아니라 Context Ontology Accelerator인 이유이며, semantic context layer라는 규정의 실체다.

2.5 접근 비교 종합

Text-to-SQLText-to-SPARQL
(트리플 스토어)
GraphRAGCOA
(온톨로지 + VKG)
의미의 형태암묵적(LLM 내부)명시적(온톨로지)통계적 추출물명시적 + 검증 + 사람 승인
데이터 위치원본트리플로 복제그래프·인덱스로 적재정형은 원본, 문서만 적재
집계 정확성생성된 SQL 품질에 종속가능하나 성능 불리부적합(근사)SQL 실행으로 정확
JOIN 결정LLM 추론, hop마다 오류 누적그래프 구조 내장해당 없음R2RML 규칙, 결정적
재현성낮음생성부는 동일 문제낮음번역·실행부는 결정적
거버넌스(검토·버전·감사)불가가능제한적검증·승인·버전이 파이프라인에 내장
주 대상정형정형비정형정형과 비정형을 tier로 병용

2.6 비용과 한계

온톨로지는 공짜가 아니다. 모델링과 리뷰에 사람의 시간이 들어간다. COA는 induction 자동화와 자동 검증으로 이 비용을 줄이지만, steward와 승인자의 공수는 남는다. 따라서 효용은 질의의 복잡도에 비례해서 나타난다. 단일 테이블 조회라면 온톨로지 없는 text-to-SQL과의 차이가 작고, 다중 hop 조인, class 계층, 다중 소스 통합, agent 접근 통제, 감사 요구가 있을 때 격차가 벌어진다.

이 판단은 코드의 기본값에도 반영되어 있다. Tier 2의 기본 전략은 NL-to-SQL 우선이고 (DEFAULT_STRATEGY = NL_TO_SQL_FIRST, tier2/strategy.py:46), VKG는 폴백으로 동작한다. 단순 질의에는 직접 생성이 빠르고 충분하다는 실용적 선택이다. options.strategy로 VKG 우선(ontop_first)이나 병렬 경쟁(best)으로 전환할 수 있으며, 어떤 질의 유형에서 어느 전략이 우세한지는 워크로드별 실측 대상이다.

3. SCAN: 데이터 소스 연결과 메타데이터 구축

SCAN plane 상세
그림 2. SCAN plane. Step Functions가 Discovery, Federation, Enrichment를 순차 실행하고, 결과는 DataZone catalog에 모인다. steward 리뷰가 파이프라인의 종점이다.

3.1 지원 소스

유형엔진discovery 방식서빙 시 쿼리 방식
JDBCPostgreSQL, Redshift, MySQL, SQL ServerDB 직접 접속, information_schema 조회직접 driver 또는 Athena federation
JDBC (discovery만)Oracle, Snowflake동일Athena federation만 (jdbc.py:61 DIRECT_QUERY_ENGINES)
Glue Data CatalogAthena로 쿼리 가능한 모든 것Glue GetTables API(메타데이터만)Athena
DocumentsS3, 파일 업로드(PDF, DOCX 등)별도 파이프라인. 본 가이드 범위 외

SAP, Salesforce 등 애플리케이션 커넥터는 없다. 이런 소스는 추출 파이프라인(Glue connector, AppFlow 등)으로 S3에 내리고 Glue Catalog에 등록한 뒤 GLUE_DATABASE 소스로 연결하는 구성이 표준이다. Glue 소스는 등록 시점에 이미 Athena로 쿼리 가능한 상태여야 한다.

3.2 파이프라인 구조

소스를 등록하면 Step Functions state machine이 Discovery, Federation, Enrichment 세 단계를 순차 실행한다. 상태는 DynamoDB에 기록되며 REGISTERED → SCANNING → ENRICHING → PENDING_REVIEW로 전이한다. 단계 간 데이터 전달은 Step Functions payload가 아니라 공유 저장소를 경유하는데, 저장소는 단계마다 다르다. Discovery의 산출물(테이블·컬럼 명세)은 DataZone catalog에 asset으로 모이고 Enrichment가 이를 읽어 같은 asset에 덧쓴다. 이 구간이 catalog hub 구조다. 반면 Federation은 DataZone을 읽거나 쓰지 않는다. 입력(발견된 스키마 목록)은 Discovery가 DynamoDB 소스 레코드에 남긴 값이고, 산출물(Glue connection과 federated catalog 참조, queryable 플래그)도 DynamoDB와 Glue/Lake Formation에 기록된다(database/pipeline/federation_handler.py).

3.3 Discovery

Discovery Lambda는 소스 유형에 따라 다르게 동작한다(database/pipeline/discovery_handler.py). JDBC 소스에는 Secrets Manager에서 자격증명을 받아 DB에 직접 접속해 information_schema와 native catalog를 읽는다. 테이블, 컬럼, 타입, comment, PK/FK constraint를 수집하고, 저카디널리티 문자열 컬럼은 distinct 값을 샘플링한다(COUNT(DISTINCT)로 enum성 판별 후 수집). Glue 소스에는 데이터에 접근하지 않고 GetTables API로 카탈로그 메타데이터만 읽으며, enum 샘플링만 Athena를 경유한다(실패해도 스캔은 계속된다).

출력은 테이블당 1개의 DataZone asset이다. 컬럼 메타데이터는 form으로 저장되고, 재스캔 시 asset revision으로 idempotent하게 갱신된다. DB constraint에서 나온 PK/FK는 DETERMINISTIC provenance로 기록된다. asset 쓰기는 병렬화되어 있고(기본 10)(database/metadata_writer.py), 테이블 수 상한(MAX_TABLES_PER_SOURCE)을 넘으면 스캔이 조기 실패한다(discovery_handler.py).

3.4 Federation

Federation Lambda는 서빙 시점의 쿼리 통로를 미리 등록한다(database/pipeline/federation_handler.py). DB에는 접속하지 않으며 AWS 관리 API만 호출한다.

이 Lambda를 Discovery와 분리한 이유는 권한 격리다. federated catalog 생성에는 Lake Formation data-lake-admin 권한이 필요한데, 이 권한을 고객 DB에 접속하는 Discovery Lambda가 아닌 단일 목적 함수의 role에만 부여한다. 또한 자격증명 secret을 connector role로 assume하여 읽기 사전 점검을 수행해, 등록은 성공했지만 쿼리 시점에 조용히 실패하는 유형의 장애를 스캔 단계에서 조기 발견한다.

3.5 Enrichment

Enrichment는 ECS Fargate task로 실행된다. 테이블 수백 개 규모에서 Bedrock 호출이 Lambda의 15분 제한을 초과하기 때문이다. 원본 DB에는 재접속하지 않고 DataZone의 명세만으로 작업한다. 두 pass로 구성된다(database/enrichment/table_enricher.py, relationship_inferrer.py).

  1. Pass 1(테이블 단위, 병렬): 테이블·컬럼 이름, 타입, comment를 입력으로 비즈니스 설명, synonym, 용어, tag를 생성하고 naming pattern으로 PK를 추론한다. 모든 호출을 공유 thread pool에 제출하며, 컬럼이 많은 테이블은 배치로 분할했다가 병합한다.
  2. Pass 2(테이블 간, Pass 1 완료 후): 소스 전체의 압축 스키마 요약(테이블당 약 50 token)을 한 프롬프트에 넣고 naming 기반으로 FK를 추론한다(customer_id → customers.id 유형). 각 추론에 confidence를 부여하고, 테이블 250개 초과 시 배치로 나눈다. 데이터 값의 overlap 분석은 수행하지 않는다. 순수하게 이름 기반이므로 무의미한 legacy naming에서는 추론이 약하며, 이 경우 steward의 직접 지정이 해법이다.

DETERMINISTIC 또는 steward 지정 FK는 "재추론 금지" 목록으로 프롬프트에 전달되고, guard 로직이 AI 결과의 덮어쓰기를 차단한다. Pass 2가 실패해도 Pass 1 결과는 보존된다. LLM이 형식은 유지한 채 테이블 설명만 누락하는 조용한 실패를 잡기 위해 TablesMissingDescription CloudWatch metric을 별도 발행한다. 결과는 asset revision에 기록되고 소스는 PENDING_REVIEW 상태가 된다. 서술 메타데이터(설명, synonym, 용어, tag)는 AI_GENERATED로, AI가 추론한 PK/FK는 AI_INFERRED로 마킹된다(table_enricher.py:360·377, relationship_inferrer.py:211).

3.6 Steward 리뷰와 provenance

steward는 웹 UI에서 세 종류의 입력을 제공한다.

  1. 승인/거부(필수): 테이블·컬럼 단위. 승인분만 이후 induction의 재료가 된다.
  2. 메타데이터 수정(선택): 설명, synonym, 용어 교정. STEWARD_EDITED로 마킹된다.
  3. 구조 지정(선택): PK/FK의 추가·수정·삭제. STEWARD_SPECIFIED로 마킹된다. 실제로 변경된 키만 재스탬프되고 나머지는 기존 provenance(DETERMINISTIC 또는 AI_INFERRED)를 유지한다(api/database_routes.py:935 이하).

provenance 우선순위는 STEWARD_* > DETERMINISTIC > AI_GENERATED·AI_INFERRED로 고정이며, 이후 어떤 재스캔·재추론도 상위 provenance를 덮어쓰지 못한다.

3.7 두 카탈로그의 역할 분담

Glue Data Catalog에는 COA가 필요로 하는 구조화 메타데이터(provenance, confidence, 리뷰 상태, synonym)를 담을 자리가 없고, JDBC 소스는 스캔 시점의 Glue에 존재하지 않는다(federation이 나중에 등록하는 것은 메타데이터 사본이 아니라 라이브 DB 포인터다). 또한 Glue Catalog는 Athena와 ETL이 사용하는 운영 인프라이므로 검증 전 AI 생성물을 쓰는 것이 부적절하다. COA는 Glue Catalog에 비즈니스 메타데이터를 쓰지 않고(스캔은 읽기, federation은 쿼리 통로 등록만), 작업 카탈로그로 DataZone을 사용한다. namespace가 DataZone project와 1:1로 대응하므로 테넌트 격리도 이 구조에서 나온다.

4. MODEL: 온톨로지 유도와 발행

MODEL plane 상세
그림 3. MODEL plane. Ontology Engine이 induction, grounding, 자동검증을 거쳐 proposal을 만들고, 사람이 accept하면 S3·Neptune·OpenSearch에 발행되며 VKG service가 기동된다.

4.1 Ontology Engine

ontology-engine은 상시 실행되는 ECS Fargate service다(FastAPI, desiredCount 1). induction 실행, proposal 관리, 온톨로지 카탈로그 CRUD, 서빙용 스키마 응답(DescribeSchema)을 모두 이 서비스가 담당한다. induction과 같은 장시간 작업은 서비스 프로세스 내 background thread로 실행되고 진행 상태는 DynamoDB job 레코드로 추적된다(coa_ontology/main.py, induce_catalog.py). 스캔마다 새로 뜨는 Enrichment task(batch형)와 실행 형태가 다르다.

4.2 Induction

기본 전략(table_to_ontology)에서 induction의 골격은 코드가 결정적으로 수행한다(induce_catalog.py:215 기본값, inducer/strategies/base.py). 테이블을 owl:Class로, 컬럼을 owl:DatatypeProperty로, FK를 owl:ObjectProperty로 변환하고, class와 테이블을 잇는 R2RML mapping을 생성한다(소스 라우팅용 coa:datasourceId 주석 포함). class와 property의 설명·synonym은 새로 생성하지 않고, 3.5절 enrichment가 만들고 steward가 확정한 메타데이터를 이어받는다. 이 경로에서 LLM 판단이 개입하는 지점은 4.3절 grounding의 reranker다.

생성 LLM과 심판 LLM이 협업하는 RIGOR 전략(rigor_ontology)은 API로만 선택할 수 있는 대안이다(inducer/strategies/rigor_ontology.py). 테이블별 온톨로지 조각을 생성 LLM이 제안하고 심판 LLM이 정제하며, FK 위상 순서(참조되는 테이블 먼저)로 진행한다. 단 이 전략은 grounding 옵션을 무시하므로 4.3절의 정렬은 실행되지 않는다.

4.3 Grounding

생성된 class를 grounding pool의 온톨로지와 정렬한다. pool은 induction 요청에서 사용자가 선택한 온톨로지로 한정되며(grounding_ontology_ids), Schema.org, Dublin Core, PROV-O, FIBO 같은 표준 온톨로지는 선택한 경우에만 정렬 대상이 된다. 서버가 표준 온톨로지를 자동으로 추가하지 않으므로 선택이 없으면 모든 class가 신규(novel)로 처리되는데, 이는 오류가 아니라 스코프 선택의 결과다(induce_catalog.py:800-808). 정렬은 class·property의 이름과 설명을 Cohere Embed v4로 embedding하여 후보를 벡터 매칭으로 찾고, 기본 모드(ENHANCED)에서 LLM reranker가 최종 후보를 판정해 rdfs:subClassOf, skos:*Match 링크를 추가하는 순서로 진행된다. grounding 인프라 오류(벡터 검색 실패 등)는 job 실패로 처리된다. 오판된 "전부 신규 class"를 조용히 반환하는 대신 실패를 드러내는 fail-loud 설계다.

4.4 검증 (온디맨드)

induction은 proposal을 저장할 때 검증을 실행하지 않는다. 검증은 proposal 생성 후 리뷰어가 웹 UI 또는 POST /proposals/{id}/validate로 실행하는 선택적 비동기 job이고, 결과는 job report로 조회한다(proposals.py, ontology-induction.smithy: ValidateProposal). 검증 자체에는 LLM이 개입하지 않으며 결정적 도구만 사용한다(validation/validators/).

검증도구검사 내용
Tier 1 (심각도 분류: blocking)HermiT reasoner(owlready2), pyshacl논리 일관성, 만족 불가능 class, subclass 순환, 연결성, SHACL 형태 규칙, XSD datatype 위생
Tier 2OntoQA구조적 풍부도 지표
Tier 3OoPS! 원격 API 등온톨로지 설계 pitfall, 라벨 누락

"blocking"은 심각도 분류의 이름일 뿐 시스템이 무언가를 차단하지는 않는다. Tier 1 error는 report에 passed=false로 기록될 뿐이고, 검증을 실행하지 않아도 accept는 가능하다. 발행의 게이트는 사람의 accept 하나이므로, "accept 전에 검증을 실행하고 report를 확인한다"는 규칙은 운영 절차로 정해야 한다.

4.5 발행과 VKG 기동

사람이 proposal을 accept하면 ontology-engine이 세 곳에 기록한다(proposals.py).

  1. S3 artifacts bucket: ontology.ttl, mappings.r2rml, schema.sql
  2. Neptune: T-Box를 namespace별 named graph에 저장. 서빙 시 LLM의 참조 사전이 된다
  3. OpenSearch: class·property embedding. 서빙 시 질문과 개념의 매칭에 쓰인다

기록 후 ontology.published 이벤트를 EventBridge에 발행하고, VKG reload Lambda가 이를 받아 해당 namespace의 Ontop ECS service를 생성하거나 강제 재배포한다(deployment circuit breaker 포함)(infra/lambda/vkg-reload/index.py). VKG service는 기동 시 S3에서 아티팩트를 내려받아 번역 엔진을 조립한다. 온톨로지를 발행한 적 없는 namespace에는 VKG service가 존재하지 않는다.

4.6 Metric

metric은 이름, 설명, synonym, SQL expression으로 구성된 governed 계산 정의다. 등록 시 hard 검증(소스·테이블 존재, DML/DDL 차단)과 soft 검증(SQL 파싱, 컬럼 존재)을 거치며, Trino·PostgreSQL·Redshift 등 dialect별 expression을 병기할 수 있다. 발행되면 Neptune에 저장되고 OpenSearch에 embedding된다. OSI 포맷의 대량 import를 지원한다.

4.7 암묵지의 처리와 한계

스키마에 기록되지 않은 지식, 곧 암묵지를 COA가 어떻게 다루는지는 도입 판단에서 자주 나오는 질문이다. 결론부터 말하면 COA는 암묵지를 자동으로 발견하는 시스템이 아니다. 흔적이 있는 지식은 AI가 표면화하고, 나머지는 사람이 주입할 통로를 제공한다. provenance 체계(STEWARD가 AI를 항상 이긴다)가 이 설계의 증거다.

제공되는 수단

수단커버 범위근거
LLM 표면화스키마에 흔적이 남은 의미. 이름과 comment에서 설명·synonym을 생성하고, naming pattern으로 FK를 추론하며, enum성 컬럼의 값 목록을 샘플링한다enrichment 2-pass
Grounding보편적 도메인 지식. Schema.org, FIBO 등 표준 온톨로지와의 정렬inducer + LLM reranker
제약 추론proposal 온톨로지로부터 LLM이 semantic constraint를 추론하는 비동기 작업이 별도 API로 존재한다ontology-induction.smithy: InferConstraints, CompileConstraints
Proposal 직접 편집사람의 지식 전반. accept 전에 T-Box Turtle과 R2RML을 각각 독립적으로 수정해 업로드할 수 있다UpdateProposal (smithy:552), proposals.py:1484
사람 입력 채널steward의 메타데이터 편집과 PK/FK 지정(Scan), metric 수동 등록, 온톨로지 업로드3.6절, 4.6절

해결하지 못하는 것

  1. 흔적 없는 의미. 무의미한 legacy naming(gubun, col1)과 코드 값의 의미(status=3이 무엇을 뜻하는지)는 추론할 수 없다. enum 샘플링은 값 목록만 제공하고 각 값의 의미는 주지 못한다.
  2. 데이터 프로파일링 부재. 두 컬럼의 값 overlap을 분석해 조인 키를 발견하는 기능이 없다. 관계 추론은 순수하게 이름 기반이다.
  3. 비즈니스 규칙의 자동 유도 없음. "매출은 VAT 제외", "우수 딜러의 기준" 같은 규칙을 데이터에서 발견하지 않는다. metric은 전량 사람이 등록한다.
  4. 업로드 온톨로지와 정형 질의의 단절. mapped class는 R2RML TriplesMap의 rr:class로만 서버가 파생하며 업로드로 위조할 수 없다(ontologies.md의 신뢰 경계). 암묵지를 담은 OWL을 업로드해도 mapping이 없으면 Tier 2 SQL 질의에 반영되지 않고 Tier 3 검색 재료로만 쓰인다. 암묵지를 SQL 질의까지 반영하는 경로는 induced proposal의 편집(위 표)에 한정된다.
  5. 조건부 의미의 자동 반영 없음. induction이 생성하는 mapping은 테이블 단위 대응이다. "is_deleted=0만 유효" 같은 규칙은 자동으로 담기지 않으며, R2RML을 수동 편집하는 수단만 있다.
  6. 문서에서 정형 메타데이터로의 파이프 없음. 위키나 매뉴얼에 적힌 컬럼의 의미가 카탈로그 설명으로 흘러가는 자동 경로가 없다. 문서는 Tier 3 검색으로만 소비된다.
  7. 시간 변화 추적 없음. drift 재스캔이 미지원이므로 코드 값 추가나 규칙 변경 같은 암묵지의 변화를 시스템이 알지 못한다.

따라서 결과 품질의 상한은 사람 개입 지점(steward 리뷰, proposal 편집, metric 등록)의 충실도가 정한다. 도입 계획에서 이 세 지점의 담당자와 공수를 명시적으로 배정하는 것이 합리적이다.

5. SERVE: 질의 계층

SERVE plane 상세
그림 4. SERVE plane. Context Manager가 3-tier로 질의를 해석하고, 모든 SQL은 SQL Firewall과 권한 검사를 거쳐 Athena 또는 직접 JDBC로 실행된다.

5.1 질의 인터페이스

인터페이스대상특징
Playground웹 사용자SSE 스트리밍 채팅. 해석 단계, 생성된 SQL, 결과, trace를 실시간 표시. 세션은 DynamoDB에 지속
REST API애플리케이션POST /namespaces/{id}/query. execute:false(SQL만 반환), tierOverride, maxResults 등의 옵션
MCPAI agentAgentCore Runtime 위 Streamable HTTP. tool 6종: query, translate_sparql, list_metrics, describe_schema, rag_retrieval, graph_traversal

세 인터페이스 모두 사용자의 OIDC ID token으로 인증하며 같은 권한 경로를 지난다. MCP tool 호출도 Cedar 사전 인가를 통과해야 하고, grant가 없으면 하드 에러로 처리된다(fail-closed)(coa_mcp/auth/grant_resolver.py). 로컬 IDE(Claude Desktop, Kiro, Cursor)는 stdio를 Streamable HTTP로 변환하는 mcp-proxy로 접속한다.

5.2 실행 환경

질의 오케스트레이터인 Context Manager는 Bedrock AgentCore Runtime에서 실행된다. ECS가 아니다. JWT 검증을 런타임이 처리하고 세션 격리와 AgentCore Memory(대화 이력)를 제공하므로, agent 워크로드에 필요한 기반을 자체 구현하지 않는다. MCP server는 별도의 AgentCore Runtime에 배포된다.

5.3 권한 모델

Layer 1: CedarLayer 2: SQL Firewall
시점API Gateway authorizer(요청 진입)SQL 실행 직전
단위행위(query, manageSource 등)데이터(테이블 allowlist, 컬럼 denylist, metric allowlist)
결합 규칙여러 role 중 하나라도 permit이면 허용여러 grant의 제한이 누적. 완화되지 않음
우회role 추가로 확장 가능불가. platform-admin도 적용 대상

firewall은 sqlglot AST 수준에서 검사한다. SELECT 외 구문은 차단되고, grant에 명시된 테이블·컬럼 제한이 어느 tier에서 생성된 SQL이든 동일하게 적용된다.

6. Context Manager 상세와 질의 처리 예시

Context Manager 상세
그림 5. Context Manager 상세. Tier 1은 지표 SQL을 그대로, Tier 2는 NL-to-SQL(기본)과 VKG(폴백) 두 전략으로 SQL을 생성하며, Tier 3는 문서 검색과 그래프 탐색을 병렬 수집해 합성한다.

6.1 Context Manager의 정체

Context Manager는 질의 해석을 총괄하는 오케스트레이터로, Python 컨테이너로 빌드되어 Bedrock AgentCore Runtime에서 실행된다(packages/context-manager). ECS가 아니다. AgentCore Runtime을 실행 기반으로 선택한 이유는 agent형 워크로드에 필요한 기반이 내장되어 있어서다. 사용자 JWT의 검증을 런타임이 수행하고, 세션 격리와 AgentCore Memory(멀티턴 대화 맥락), Observability를 플랫폼이 제공하므로 이를 자체 구현하지 않는다.

세 인터페이스가 모두 이 하나의 컨테이너로 수렴한다. REST 질의는 data-layer Lambda가 AgentCore endpoint로 프록시하고, Playground는 AgentCore Runtime의 /invocations endpoint에 HTTP POST로 직접 연결되어 응답을 SSE로 스트리밍 받으며, MCP는 별도 AgentCore Runtime의 MCP server가 실행형 tool(query 등) 호출을 Context Manager 호출로 변환한다. 탐색형 tool(list_metrics, describe_schema)은 해당 서비스 API를 직접 호출한다. 어느 경로든 사용자의 ID token이 함께 전달되어 권한 검사의 주체가 된다.

6.2 제어 흐름은 코드가 결정한다

tier의 진행과 폴백은 LLM이나 agent의 판단이 아니다. orchestrator는 Tier 1, 2, 3을 순차 실행하는 결정적 코드이며(coa_serve/orchestrator.py), 전이 조건이 전부 규칙으로 정의되어 있다. Tier 1의 사양은 매칭 개수와 잔여 한정어 검사로, Tier 2의 폴백은 전략 수준 실패 판정 4종(생성 실패, unsafe SQL, 재시도 후 실행 에러, 저신뢰 0행)과 tier 수준 저신뢰 판정(행이 있어도 confidence < 0.3이면 Tier 3로)으로, Tier 3 진입은 상위 tier의 miss로 결정된다. LLM은 각 tier의 내부에서 도구로만 호출된다. SQL과 SPARQL을 생성하고 답변을 합성하지만, 어느 tier로 갈지를 결정하지 않는다.

이 구분은 실무적 결과를 낳는다. 라우팅이 결정적이므로 같은 질문은 같은 경로를 지나고, 모든 전이 사유가 trace에 남아 재현과 디버깅이 가능하다. LLM 라우터가 tier를 고르는 설계였다면 "오늘은 왜 다른 경로로 갔는가"를 설명할 수 없다. 예외는 agentic 모드 하나다. Tier 3 내부의 옵션이 아니라 요청 전체를 인수하는 실행 정책으로, 켜면(tierOverride가 없을 때) Tier 1·2 cascade를 아예 실행하지 않고 planner LLM이 구조적 도구(nl_to_sql, nl_to_sparql)를 포함한 도구 집합에서 다음 행동을 선택한다(orchestrator.py:241-270). 이때도 controller가 도구 우선순위(검증된 전략, 템플릿, 즉석 질의 순)를 결정적으로 강제하고 시간·스텝·분기 예산을 공유 clock으로 통제한다(tier3/agentic/controller.py).

tier 순서 자체는 신뢰도의 내림차순이다. Tier 1은 LLM이 아무것도 생성하지 않고, Tier 2는 질의문만 생성하며, Tier 3는 답변 내용을 생성한다. 아래로 갈수록 정확성 보증이 약해지므로 보증이 강한 경로를 먼저 소진한다. tierOverride 옵션으로 특정 tier를 고정할 수 있고, 소스 구성에 따른 자동 생략이 있다. 문서 전용 namespace는 Tier 1·2를 건너뛰고, DB 전용 namespace에서는 Tier 3의 문서 chunk 검색만 자체 생략된다(온톨로지 그래프 탐색과 합성은 동작한다).

6.3 Tier 1: Metric Resolution

metric resolver는 기동 시 Neptune에서 metric 정의를 읽어 인메모리 인덱스 세 개(by_id, by_name, by_synonym)를 구성하고 주기적으로 refresh한다. 질의 시점의 매칭은 이름·synonym의 정확 일치와, 오타·복수형 수준의 fuzzy 매칭(confidence < 1.0)이다. 의미 기반 벡터 검색은 의도적으로 수행하지 않는다(tier1/metric_resolver.py). 애매한 매칭을 허용하면 잘못된 지표를 확신 있게 반환할 수 있기 때문이다.

Tier 1이 응답하는 조건은 두 가지다.

  1. 매칭된 metric이 정확히 1개일 것. 2개 이상이면 모호하므로 fall-through한다.
  2. 질문에 잔여 한정어가 없을 것. metric SQL은 사전 컴파일된 문장을 그대로 실행하므로 질문의 필터, 기간, 그룹핑을 반영할 수 없다. "지난 분기 총 매출"은 매칭되더라도 Tier 2로 넘긴다. 그대로 실행하면 전체 매출을 반환하는 오답이 되기 때문이다. 이 재라우팅은 trace에 residual_qualifier_bypass로 기록된다. 인사말 등은 한정어로 취급하지 않는다.

options.dimensions로 필터 값을 파라미터로 전달하면 Tier 1 경로를 유지한 채 필터를 적용할 수 있다. 매칭된 SQL도 실행 전 firewall과 metric allowlist 검사를 통과한다.

6.4 Tier 2: Structured Query

Tier 2는 전략 두 개를 가진 플러거블 구조다(tier2/strategy.py). 실행 정책은 best(병렬), ontop_first, nl_to_sql_first, 단일 고정 중 호출자가 선택하며, 기본값은 nl_to_sql_first다.

기본 경로: NL-to-SQL

벡터 검색으로 질문과 관련된 테이블(steward가 검증한 설명·synonym 포함)을 찾아 프롬프트에 넣고, Bedrock이 대상 dialect의 SQL을 직접 생성한다. 실행 에러가 나면 에러 메시지를 포함해 1회 재생성한다(two-shot)(tier2/nl_to_sql/strategy.py).

폴백 판정

다음 경우 NL-to-SQL을 포기하고 VKG 경로로 넘어간다.

전략과 별개로 tier 수준의 판정이 하나 더 있다. 어느 전략이 성공해 행을 반환했더라도 최종 confidence가 0.3 미만이면 orchestrator가 결과를 버리고 Tier 3로 넘긴다(orchestrator.py:648). tierOverride: 2로 고정한 경우에만 저신뢰 결과가 그대로 반환된다.

폴백 경로: VKG

  1. OpenSearch에서 질문과 관련된 class·property를 선별한다. namespace의 class가 200개 미만이면 선별을 생략하고 T-Box 전체를 사용한다(tier2/ontop/tbox_context.py).
  2. Neptune의 발행 그래프에 SPARQL로 해당 class들의 전체 정의를 조회해 프롬프트 컨텍스트를 조립한다. R2RML mapping이 있는 class만 포함되고(isMapped 게이트), enum성 컬럼의 distinct 값이 property당 최대 12개까지 첨부되어 WHERE 절 값의 정확도를 높인다.
  3. Bedrock이 SPARQL을 생성하고 문법 검증을 거친다.
  4. VKG service가 SPARQL을 SQL로 번역한다. 번역은 R2RML 규칙 기반의 결정적 과정이며, 대상 dialect로 transpile되고 데이터소스 라우팅 정보가 함께 반환된다.

어느 경로로 생성됐든 SQL은 firewall과 Cedar 데이터 인가를 거친 후 실행된다. 단일 소스이고 직접 driver가 가능한 엔진이면 native driver로(약 20~50ms), 그 외에는 Athena federation으로(약 500~800ms) 실행한다. Glue 소스가 queryEngine=REDSHIFT로 등록된 경우 Redshift Data API 경로도 있다(clients/composite_executor.py).

6.5 Tier 3: Knowledge Retrieval

Tier 3는 두 개의 검색 소스를 병렬로 실행하고 결과를 한 번의 LLM 호출로 합성한다(tier3/knowledge_retriever.py:184-206). 각 소스는 10초 timeout을 가지며, 하나가 실패하거나 비어도 나머지로 진행한다.

소스내용
1. 문서 chunkOpenSearch chunk index에 k-NN 검색(tier3/vector_retriever.py). 문서 소스가 없는 namespace에서는 자체 생략된다.
2. 그래프 탐색라우팅 벡터 검색에서 매칭된 온톨로지 class·property의 URI를 시작점(seed)으로 Neptune에서 1~2 hop 탐색한다(entity당 관계 최대 10개). 맥락 수집용이며 집계가 아니다.
모듈 docstring과 함수 시그니처에는 소스 두 개가 더 예약되어 있으나 본 문서의 코드 기준 시점에는 미구현이다. VKG 정형 컨텍스트(Tier 2가 남긴 SPARQL 힌트로 부분 정형 데이터 확보)는 파라미터만 존재하고 orchestrator가 항상 None을 전달하며(orchestrator.py:1141), catalog summary(metric 목록)는 수집 함수가 placeholder로 항상 None을 반환한다(orchestrator.py:1014-1019). 따라서 표준 모드의 Tier 3는 정확한 수치를 확보하지 않는다. 수치가 필요한 질문은 Tier 2로 재질의하는 것이 답이다.

합성은 Bedrock Converse 1회 호출이다. 수집된 조각들이 하나의 프롬프트로 조립되며, 문서 chunk에는 datamark를 부여해 프롬프트 주입을 방어하고, 질문의 언어로 답변하도록 강제하는 지시가 프롬프트 마지막에 배치된다(tier3/synthesizer.py). 설정으로 agentic 모드를 켜면 Tier 1·2 cascade를 거치지 않고 planner LLM이 tool을 선택하는 반복 탐색 루프가 요청 전체를 처리한다. 이 루프는 nl_to_sql과 nl_to_sparql을 도구로 보유하므로 sub-question 단위로 구조적 질의와 문서 검색을 조합할 수 있다. 이때도 controller가 tool 우선순위(검증된 전략, 템플릿, 즉석 질의 순)를 결정적으로 강제하고 시간·스텝·분기 예산을 공유 clock으로 통제한다(tier3/agentic/controller.py).

6.6 질의 처리 예시

아래 네 예시는 제조사 시나리오(딜러, 보증 클레임, 부품, 로트)를 가정한다. 응답 구조는 Smithy 정의의 QueryResult를 따른다(models/src/main/smithy/serve.smithy:364). Playground에서는 같은 내용이 SSE로 스트리밍되어 해석 단계가 실시간으로 표시되고, REST에서는 아래 JSON이 한 번에 반환된다. synthesizedAnswer는 Tier 3 전용 필드다. Tier 1·2는 결과 표(resultRows)와 실행 질의문을 반환하고 자연어 문장을 만들지 않으며, 직렬화가 null 필드를 생략하므로 아래 예시에는 각 경로에서 실제로 채워지는 필드만 담았다(response_assembler.py, main.py의 exclude_none).

예시 1. Tier 1에서 종결: "총 클레임 건수는?"

단계컴포넌트동작
1Metric Resolver인메모리 인덱스에서 "총 클레임 건수"가 metric total_claim_count의 synonym과 정확 일치. 매칭 1개, 잔여 한정어 없음
2SQL Firewall등록 SQL 검사. 사용자 grant의 metric allowlist 확인
3Executor등록·검증된 SQL을 그대로 실행. LLM 개입 없음
{
  "tier": 1,
  "confidence": { "score": 1.0, "rationale": "exact synonym match: total_claim_count" },
  "queryUsed": "SELECT COUNT(*) AS claim_count FROM warranty_claims",
  "resultRows": [ { "claim_count": "18342" } ],
  "trace": [
    { "step": "t1.metric_match", "status": "success", "durationMs": 3 },
    { "step": "t1.firewall",     "status": "success", "durationMs": 1 },
    { "step": "t1.execute",      "status": "success", "durationMs": 41, "toolUsed": "postgres" }
  ]
}

사용자에게는 결과 표의 숫자가 반환된다. Playground는 "governed metric으로 응답했다"는 해석 단계와 실행된 SQL을 함께 표시하므로, 사용자는 이 숫자가 즉석 생성이 아니라 조직이 승인한 정의에서 나왔음을 확인할 수 있다.

예시 2. Tier 2, NL-to-SQL 경로: "지난달 제품군별 클레임 건수는?"

단계컴포넌트동작
1Metric Resolvertotal_claim_count가 매칭되지만 "지난달"(기간)과 "제품군별"(그룹핑)이 잔여 한정어로 남음. residual_qualifier_bypass를 기록하고 Tier 2로
2NL-to-SQL(기본 전략)벡터 검색으로 관련 테이블 조회(warranty_claims, products). steward 검증 설명과 함께 프롬프트 구성, Bedrock이 SQL 생성
3SQL FirewallSELECT 확인, 테이블 allowlist·컬럼 denylist 검사
4Executor단일 PostgreSQL 소스이므로 직접 driver로 실행
{
  "tier": 2,
  "confidence": { "score": 0.87 },
  "queryUsed": "SELECT p.product_line, COUNT(*) AS claim_count
                FROM warranty_claims c JOIN products p ON c.product_id = p.id
                WHERE c.filed_at >= date_trunc('month', now()) - interval '1 month'
                  AND c.filed_at < date_trunc('month', now())
                GROUP BY p.product_line ORDER BY claim_count DESC",
  "resultRows": [
    { "product_line": "트랙터 X", "claim_count": "212" },
    { "product_line": "콤바인 C", "claim_count": "97" }
  ],
  "trace": [
    { "step": "t1.metric_match", "status": "failed", "detail": "residual_qualifier_bypass: 지난달, 제품군별" },
    { "step": "t2.sql.generate", "status": "success", "durationMs": 1830, "toolUsed": "bedrock",
      "detail": "confidence: 0.87, tables: [warranty_claims, products]" },
    { "step": "t2.sql.firewall", "status": "success", "durationMs": 4 },
    { "step": "t2.sql.execute",  "status": "success", "durationMs": 38, "toolUsed": "postgres" }
  ]
}

사용자에게는 결과 표가 반환된다. trace의 첫 항목이 Tier 1의 사양 사유를 담고 있어, 같은 질문에서 "총 클레임 건수는?"과 다른 경로를 탄 이유를 추적할 수 있다.

예시 3. Tier 2, VKG 폴백 경로: "지난 분기 리콜된 로트를 출하받은 딜러별 클레임 건수는?"

클레임, 부품, 로트, 출하, 딜러의 5개 테이블을 잇는 multi-hop 조인이 필요한 질문이다. NL-to-SQL이 조인 경로를 잘못 구성해 폴백이 발생하는 시나리오를 보인다.

단계컴포넌트동작
1Metric Resolver매칭 없음. Tier 2로
2NL-to-SQLSQL 생성했으나 lots와 shipments의 조인 키를 잘못 추론. 실행 에러 발생, 에러 메시지로 1회 재생성했으나 재차 실패. 전략 포기
3VKG: class 선별OpenSearch에서 관련 class 조회. 이 namespace는 class 47개로 200개 미만이므로 선별을 생략하고 전체 T-Box 사용
4VKG: T-Box 조립Neptune에서 WarrantyClaim, Part, Lot, Shipment, Dealer의 정의와 관계 조회. Lot.status의 distinct 값("RECALLED", "NORMAL")이 컨텍스트에 포함됨
5VKG: SPARQL 생성Bedrock이 온톨로지 어휘로 SPARQL 작성. 조인 경로는 SPARQL에 등장하지 않음(관계명만 사용)
6VKG: 번역Ontop이 R2RML 규칙으로 5-테이블 JOIN SQL을 결정적으로 생성. 조인 키는 사람이 승인한 mapping에서 나옴
7Firewall, Executor검사 후 Athena federation으로 실행(클레임은 PostgreSQL, 출하 이력은 S3 curated의 크로스 소스 조인)
{
  "tier": 2,
  "confidence": { "score": 0.91 },
  "sparqlGenerated": "SELECT ?dealerName (COUNT(?claim) AS ?claimCount) WHERE {
      ?claim a dd:WarrantyClaim ; dd:refersToPart ?part .
      ?part dd:producedInLot ?lot .
      ?lot dd:status \"RECALLED\" ; dd:shippedVia ?shipment .
      ?shipment dd:receivedBy ?dealer .
      ?dealer dd:name ?dealerName .
      FILTER(?filedAt >= \"2026-04-01\"^^xsd:date && ?filedAt < \"2026-07-01\"^^xsd:date)
    } GROUP BY ?dealerName",
  "queryUsed": "SELECT d.name, COUNT(c.id) FROM claims c
                JOIN parts p ON c.part_id = p.id
                JOIN lots l ON p.lot_id = l.id AND l.status = 'RECALLED'
                JOIN shipments s ON s.lot_id = l.id
                JOIN dealers d ON s.dealer_id = d.id
                WHERE c.filed_at >= DATE '2026-04-01' AND c.filed_at < DATE '2026-07-01'
                GROUP BY d.name",
  "resultRows": [ { "name": "A딜러", "count": "34" }, { "name": "B딜러", "count": "19" } ],
  "trace": [
    { "step": "t2.sql.generate",  "status": "success", "toolUsed": "bedrock" },
    { "step": "t2.sql.execute",   "status": "error",  "detail": "column lots.shipment_id does not exist" },
    { "step": "t2.sql.failed",    "status": "failed", "detail": "correction shot also failed" },
    { "step": "t2.vkg.translate", "status": "success", "durationMs": 2140, "toolUsed": "bedrock" },
    { "step": "t2.vkg.compile",   "status": "success", "toolUsed": "ontop" },
    { "step": "t2.vkg.authorize", "status": "success", "toolUsed": "cedar" },
    { "step": "t2.vkg.firewall",  "status": "success", "toolUsed": "sql-firewall" },
    { "step": "t2.vkg.execute",   "status": "success", "durationMs": 640, "toolUsed": "sql-engine", "detail": "engine: athena" }
  ]
}

응답에 sparqlGeneratedqueryUsed가 모두 채워진 점이 이 경로의 특징이다. 사용자에게는 결과 표와 함께 두 질의문이 제공되므로, LLM이 쓴 것(SPARQL)과 기계 번역이 만든 것(SQL)을 구분해 검증할 수 있다. trace에는 첫 전략의 실패와 폴백 전 과정이 남는다.

예시 4. Tier 3: "A딜러 클레임이 왜 늘었나?"

"왜"는 하나의 SQL로 답할 수 없다. Tier 1은 매칭이 없고, Tier 2는 두 전략 모두 질문을 질의문으로 표현하지 못해 miss가 된다. Tier 3가 두 검색 소스를 병렬로 수집한다.

단계컴포넌트동작
1라우팅질문 embedding으로 OpenSearch 온톨로지 인덱스 검색. seed entity: dd:Dealer, dd:WarrantyClaim
2a문서 chunkchunk index k-NN. 품질 회보 문서에서 관련 조각 2건 검색(문서 소스가 없다면 이 소스는 자체 생략)
2b그래프 탐색seed에서 1~2 hop. Dealer, Claim, Part, Lot의 연결 구조와 인접 개념 수집
3Synthesizer수집된 조각을 datamark와 함께 조립, Bedrock 1회 호출로 한국어 서술 생성
{
  "tier": 3,
  "confidence": { "score": 0.68, "rationale": "synthesis over document and graph retrieval" },
  "synthesizedAnswer": "A딜러의 클레임은 부품과 로트 L-2231을 거쳐 출하·딜러와
    연결되는 개념이다. 2026년 5월 품질 회보는 해당 로트의 유압 부품 결함
    가능성을 언급했다. 증가 원인의 정량 확인은 '딜러별 X제품군 클레임 건수'
    같은 집계 질의로 구체화할 수 있다.",
  "supportingContent": [
    { "chunkId": "doc-qb-0526#c12", "text": "L-2231 로트 유압 실린더의 조기 마모 사례가 보고되어…", "relevanceScore": 0.83 }
  ],
  "graphContext": {
    "entities": [ { "uri": "dd:Dealer", "label": "딜러" }, { "uri": "dd:WarrantyClaim", "label": "보증 클레임" } ]
  },
  "partial": false,
  "trace": [
    { "step": "t3.vector_search",  "status": "success", "parallelGroup": "t3.retrieve", "durationMs": 420, "toolUsed": "opensearch" },
    { "step": "t3.graph_traverse", "status": "success", "parallelGroup": "t3.retrieve", "durationMs": 610, "toolUsed": "neptune" },
    { "step": "t3.synthesize",     "status": "success", "durationMs": 3900, "toolUsed": "bedrock" }
  ]
}

사용자에게는 근거가 첨부된 서술형 답변이 반환된다. supportingContent가 인용된 문서 조각을, graphContext의 entities가 탐색된 개념을 담으므로 답변의 출처를 역추적할 수 있다 (Smithy 계약의 최상위 relationships 리스트는 현 런타임이 채우지 않는다). Tier 1·2와 달리 이 답변은 LLM이 작성한 것이며, 정확한 집계가 아니라 수집된 맥락의 종합이라는 점이 confidence rationale과 trace에 드러난다. 후속으로 정확한 수치가 필요하면 사용자는 "A딜러의 제품군별 클레임 건수는?"과 같이 Tier 2가 처리할 수 있는 형태로 재질의하면 된다.

7. 제약사항

항목내용
리전검증된 리전은 us-east-1뿐이다. AgentCore Runtime과 DataZone의 가용성이 제약이며, Bedrock 모델 ID가 US cross-region inference profile로 대부분 하드코딩되어 있어 타 리전 배포에는 코드 수정이 필요하다.
유휴 비용Neptune(db.r8g.large 프로비저닝)과 OpenSearch Serverless로 유휴 시에도 월 약 $930이 발생한다. namespace마다 VKG Fargate service 비용이 추가된다.
처리량스캔·유도·서빙의 병목은 컴퓨팅이 아니라 Bedrock 모델별 TPM quota다.
스키마 변경drift 감지와 정기 재스캔은 미지원이다. 재스캔은 SCAN_FAILED 상태의 복구 동작으로만 허용된다.
질의 시간API Gateway 경유 질의는 29초 제한이 있다.
버전릴리즈는 v0.2.x로 진행 중이며(2026-08-21 v0.2.1), 본 문서의 코드 기준은 main mirror 2026-08-18이다. 운영 배포에는 tagged release 사용이 권장된다. 저장소는 read-only mirror로 PR을 받지 않는다.
본 문서는 COA 0.1.0 소스코드, 공식 문서(external-docs/content), Smithy API 정의를 근거로 작성했다. 동작 서술 중 문서와 코드가 상이한 항목(Tier 2 기본 전략 등)은 코드를 기준으로 기술했다. 배포 절차와 운영 함정은 별도의 배포 가이드에서 다룬다.