데이터베이스 문서를 에이전트가 활용할 수 있는 지식으로 변환하기 (OKF 시리즈, 파트 3)
요약
본 글은 데이터베이스의 원시 문서를 에이전트가 활용할 수 있는 지식 기반으로 변환하는 방법을 다룹니다. '컴파일러'를 통해 문서화된 비즈니스 규칙을 구조화하고, 이 정보를 읽는 오픈 모델 기반 SQL 에이전트를 구축하여 성능을 비교합니다.
핵심 포인트
- 데이터베이스 AI 에이전트는 스키마 외에 비즈니스 용어 지식이 필요하다.
- 컴파일러가 원시 문서를 [OKF] 번들로 구조화하는 역할을 한다.
- 에이전트는 인덱스를 탐색하며 필요한 파일만 가져와 SQL을 작성한다.
- 구조화된 지식(번들)과 모든 원시 문서 전달 방식의 성능을 비교 테스트한다.
데이터베이스에 대한 SQL을 작성하는 AI 에이전트는 스키마만으로는 부족합니다. 또한 비즈니스 용어의 의미를 알아야 합니다. 예를 들어, 어떤 컬럼들이 "자원 활용률(Resource Utilization Ratio)"을 구성하는지, 또는 어떤 세 가지 조건이 운영을 "고위험(high-risk)"으로 만드는지를 알아야 합니다. 대부분의 팀은 이미 이러한 지식을 스키마 덤프, 컬럼 사전, 또는 메트릭 정의 페이지와 같은 곳에 문서화해 놓았습니다. 문제는 이 정보가 에이전트가 접근할 수 있는 형태가 아니라는 것입니다.
다음 부분과 이어지는 내용을 통해 하나의 실제 데이터베이스를 위한 격차를 해소하는 구성 요소들을 구축하고 이를 종단 간(end to end)으로 실행합니다:
- 컴파일러(Compiler). 데이터베이스의 원시 문서를 읽어 [OKF] 번들로 작성하는 작은 Python 패키지입니다. 이 번들은 테이블 및 비즈니스 규칙별 개념 파일 하나와 각 폴더에 인덱스 파일을 포함하여 상호 연결됩니다.
- 에이전트(Agent). 오픈 모델 기반의 간단한 SQL 에이전트입니다. 이 에이전트는 위키를 탐색하는 방식처럼 번들을 읽습니다. 즉, 먼저 인덱스를 확인하고 질문에 필요한 파일만 가져옵니다. 그런 다음 SQL을 작성하고 실행합니다.
- 병렬 실행(Side-by-side run). 동일한 질문을 두 번 던집니다. 한 번은 번들과 함께, 다른 한 번은 모든 원시 문서를 프롬프트에 붙여넣어 전달합니다. 그리고 에이전트가 무엇을 읽었는지, 몇 개의 토큰을 사용했는지, 작성된 SQL을 비교합니다.
이 부분에서는 첫 번째 구성 요소인 컴파일러를 구축하고 벤치마크의 모든 18개 데이터베이스에 대해 실행합니다. 다음 부분에서는 에이전트를 구축하고 비교 테스트를 진행합니다. 이 두 과정을 모두 마치면, 여러분의 노트북에서 작동하는 시스템을 갖게 될 것입니다: 하나의 데이터베이스, 그 번들, 그리고 직접 질문할 수 있는 에이전트입니다.
저희 데이터베이스는 LiveSQLBench의 공개 텍스트-to-SQL 벤치마크인 disaster입니다. 이 데이터베이스에는 재난 대응 작전에 관한 10개 테이블과 54개의 비즈니스 규칙이 포함되어 있습니다. 만약 이전 파트를 따라오셨다면, 이미 세 개의 개념 파일을 직접 작성하셨을 겁니다. 이제 이 모든 것을 생성할 차례입니다. 만약 아직 하지 않으셨다면, 필요한 모든 내용은 본문에 있습니다.

프로젝트 및 데이터베이스 설정
어떤 코드를 작성하기 전에 두 가지가 필요합니다. 컴파일러가 위치할 공간과 에이전트가 SQL을 실행할 실제 데이터베이스입니다.
프로젝트 레이아웃
data/ 폴더와 bundles/ 폴더 옆에 Python 패키지인 okf_compiler를 추가합니다. (여기서 시작하는 경우, 튜토리얼 가이드에 설명된 대로 프로젝트를 설정하고 데이터를 다운로드하세요.)
우리가 나아갈 방향은 이렇습니다. 각 모듈은 필요한 단계에서 완전히 작성됩니다:
okf-sql-knowledge/
├── data/
│ └── livesqlbench-base-lite/
...
okf_compiler/ 폴더를 만들고 그 안에 빈 __init__.py 파일을 넣으세요.
데이터베이스
나중에 구축할 에이전트는 실제 SQL을 실행하므로, 실제 disaster 데이터베이스가 필요합니다. LiveSQLBench 팀은 18개의 Base-Lite 데이터베이스가 이미 로드된 PostgreSQL 이미지를 게시했습니다. 이를 실행하려면 Docker Desktop이 필요하며, 튜토리얼 가이드를 참고하세요.
프로젝트 폴더에 docker-compose.yml을 생성합니다:
services:
postgres:
image: docker.io/shawnxxh/bird-interact-postgresql:latest
...
실행하고 로그를 확인하세요:
docker compose up -d
docker logs -f livesqlbench_postgresql
로그 메시지 출력(import messages)이 멈출 때까지 기다린 후, Ctrl+C를 누르세요. 컨테이너는 계속 실행됩니다. 에이전트를 구축할 때까지 데이터베이스가 필요하지 않으므로, 로딩되는 동안 다음 단계로 넘어가도 됩니다.
각 벤치마크 데이터베이스에는 이름에 _template이 추가되어 있으므로, 저희의 것은 disaster_template이라고 불립니다. 테이블들이 제대로 있는지 확인해 보세요:
docker exec -it livesqlbench_postgresql psql -U root -d disaster_template -c "\dt"
다음 열 개의 테이블이 보일 것입니다:
Schema | Name | Type | Owner
--------+-----------------------------+-------+-------
...
열 개의 테이블은 disaster_schema.txt에 있는 열 개의 CREATE TABLE 구문과 일치합니다. 데이터베이스는 데이터를 담고 있지만, 그 안에 hubutilpct를 Resource Utilization Ratio로 변환하는 방법은 아무것도 나와 있지 않습니다. 그것이 바로 이 번들(bundle)의 목적입니다.
포트가 이미 사용 중인가요? 만약 PostgreSQL이 이미 노트북에서 실행되고 있다면, 포트 라인을 `
- 스키마는 빈 줄이 아닌
CREATE TABLE구문으로 분리됩니다. 각 테이블 뒤에는 샘플 행(sample rows)이 이어지며, 샘플 행이 없는 테이블은 다음 테이블과 합쳐지는 문제를 방지합니다. - 이름은 소문자로 일치시킵니다. 파일들은 대소문자가 섞여 있습니다. 스키마의
impactmetrics컬럼은 설명(description)에서는impactMetrics로 되어 있고, 일부 스키마는 대문자로 된 컬럼 이름을 가지고 있습니다. - JSONB 컬럼에는 단 하나의 문장이 아닌 모든 중첩 필드에 대한 설명이 포함됩니다. 저희는 이 필드 설명을
json_fields에 유지하는데, 에이전트는 존재하지 않는 JSON 필드를 쿼리할 수 없기 때문입니다. children_knowledge는 리스트이거나-1입니다. 저희는 이를 항상 리스트인depends_on으로 변환하여, 나중에 코드가-1을 확인할 필요가 없게 만듭니다.
add_column_meanings 함수 역시 설명을 찾지 못한 컬럼들을 실패하며 조용히 넘어가는 대신 반환합니다. 이것이 소스 문서에 누락된 정보에 대한 저희의 첫 번째 보고서입니다.
읽어온 내용 확인하기
프로젝트 루트 디렉터리(okf_compiler 폴더 내부가 아님)에 docker-compose.yml 옆으로 inspect_db.py를 생성하세요. 이 스크립트는 하나의 데이터베이스를 읽고 요약 정보를 출력합니다:
import sys
from pathlib import Path
...
disaster 데이터베이스로 실행해 보세요:
python inspect_db.py disaster
출력 결과:
tables: 10
columns: 123
columns without a meaning: none
...
총 열 개의 테이블이 나타났는데, 이는 CREATE TABLE 구문이 있었던 열 개와 같습니다. 그리고 disaster_kb.jsonl의 각 줄마다 하나의 규칙(rule)이 있어 총 54개의 규칙이 있습니다. 모든 컬럼에 설명이 붙어있습니다.
하지만 모든 데이터베이스가 이렇지는 않습니다. mental 데이터베이스로 실행해 보세요:
python inspect_db.py mental
출력 결과 (테이블 목록은 생략됨):
tables: 9
columns: 105
columns without a meaning: patients.clinleadref
...
patients.clinleadref라는 한 컬럼에는 소스에 설명이 없습니다. 컴파일러는 임의로 설명을 만들어낼 수 없기 때문에, 이 컬럼은 이름과 타입만 가지고 번들(bundle)에 포함됩니다. 이것이 저희가 소스 문서에서 발견한 첫 번째 누락된 부분이며, 컴파일러는 이를 숨기는 대신 이런 공백을 보고해야 합니다.
이제 프로젝트 구조는 다음과 같습니다:
okf-sql-knowledge/
├── data/
│ └── livesqlbench-base-lite/...
테이블 개념 생성하기
각 테이블은 하나의 개념 파일이 됩니다. 이 파일은 에이전트가 특정 테이블의 컬럼, 그 의미, 그리고 조인 관계가 필요할 때 열어보는 파일입니다.
모든 개념 파일을 위한 헬퍼
컴파일러가 작성하는 모든 개념 파일은 동일한 형태를 가집니다: YAML 프론트매터(frontmatter)와 마크다운 본문(body). 하나의 작은 모듈이 이 형태를 작성하므로, 테이블 모듈과 규칙 모듈에서 이를 반복할 필요가 없습니다.
okf_compiler/concept.py를 생성합니다:
"""YAML 프론트매터와 마크다운 본문을 가진 하나의 개념 파일을 작성합니다."""
from datetime import datetime
...
이것이 생성하는 프론트매터에 대해 두 가지 점에 주목하세요. (각 필드의 의미는 OKF 소개에서 다룹니다.)
generated.by는 사람이 아니라 컴파일러와 그 버전입니다. 번들(bundle)의 독자는 이 파일들이okf_compiler/0.1에 의해, 그리고 언제 작성되었는지 알 수 있습니다.verified필드는 없습니다. 아무도 이 파일들을 검토하지 않았으며, 번들도 그렇게 명시합니다. 만약 누군가 나중에 개념을 확인한다면, 그 사람이 해당 파일에verified를 추가합니다.
테이블 모듈
okf_compiler/tables.py를 생성합니다:
"""테이블당 하나의 개념 파일을 구축합니다."""
from pathlib import Path
...
본문은 모든 컬럼의 의미를 글자 그대로 유지합니다. 왜냐하면 enum 값 같은 세부 정보가 여기에 존재하기 때문입니다 (warehousestate는 Fair, Excellent, Good 또는 Poor입니다). JSONB 컬럼은 impactmetrics.population.affected와 같이 모든 중첩된 필드를 경로로 나열하는 추가 섹션을 얻게 되어, 쿼리에서 바로 사용할 수 있습니다.
디자인 선택: 테이블 설명은 LLM이 아닌 컬럼 이름에서 가져옵니다.
description은 에이전트가 파일을 열지 결정하기 전에 인덱스에서 보는 한 줄입니다. 사람이 작성한다면 "배송 허브별로, 그 용량과 저장 공간을 가진 하나의 행"과 같이 쓸 것입니다. 컴파일러는 LLM 없이는 이 문장을 작성할 수 없고, LLM은 소스 문서에 없는 단어를 추가하여 다른 설정들이 얻지 못하는 '번들 지식(bundle knowledge)'을 제공하게 됩니다. 따라서 describe는 테이블의 컬럼 이름과 연결되는 테이블 목록을 나열합니다: 평범하지만, 출처에서 곧바로 가져옵니다. 만약 에이전트가 적절한 테이블을 선택하는 데 어려움을 겪는다면, 이것이 개선해야 할 첫 번째 부분입니다.
write_table은 또한 관련 비즈니스 규칙(business rules) 목록을 받습니다. 현재는 비어 있으며, 다음 단계에서 채워질 것이므로, 각 테이블은 자신의 컬럼을 사용하는 규칙들과 연결됩니다.
명령어 (The command)
okf_compiler/cli.py를 생성합니다. 현재는 테이블 개념만 작성합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기