Skill + DB Query Plugin 패턴

이 문서는 다음 세 가지를 함께 도입하는 권장 설계를 설명합니다.

  • Core 안의 skill manifest 계층
  • 안전한 DB 접근을 담당하는 db-query plugin
  • 자연어 요청을 구조화된 질의 계획으로 바꾸는 natural-language-to-query skill

왜 이 패턴이 필요한가

사용자가 다음처럼 물을 수 있게 하려는 목적입니다.

  • “최근 24시간 실패 주문 보여줘”
  • “오늘 heartbeat 놓친 runner 수 알려줘”
  • “실패 job 이 10개 넘는 tenant 목록 보여줘”

하지만 LLM이 임의 SQL을 직접 실행하게 두면 위험합니다.

안전한 경로는 다음과 같습니다.

  1. 자연어 요청이 Core로 들어옴
  2. Core가 적절한 skill을 선택
  3. LLM이 구조화된 query intent 생성
  4. Core가 정책 검증 후 planned_job 으로 변환
  5. Runner가 DB plugin 호출
  6. plugin이 제한된 read-only 질의 실행
  7. Core가 결과 표와 요약을 반환

역할 분리

Skill

skill은 실행기가 아니라 제어 평면의 판단 자산입니다.

책임:

  • 요청이 DB 질의인지 분류
  • filter, 집계, 시간 범위, limit 추출
  • 허용된 datasource 선택
  • 승인 필요 여부 판단
  • 가능하면 raw SQL 대신 구조화된 query intent 생성

Core

Core는 정책과 오케스트레이션 계층을 유지합니다.

책임:

  • skill 선택
  • 스키마 정보와 datasource 메타데이터 제공
  • LLM 출력 검증
  • 출력 결과를 planned_job 으로 변환
  • 실행 전 정책 적용
  • decision trace 와 감사 로그 저장

Runner

Runner는 실행 경계를 유지합니다.

책임:

  • planned job 실행
  • 내부 HTTP/JSON 계약으로 plugin 호출
  • timeout, retry, 네트워크 제어 적용

Plugin

plugin은 실제 DB 접근을 수행합니다.

책임:

  • 내부 인증 헤더 검증
  • datasource allowlist 강제
  • read-only 동작 강제
  • 제한된 질의 실행
  • 결과를 표준 형태로 정규화

권장 skill manifest

권장 파일 위치:

  • core/skills/natural-language-to-query.yaml

예시:

api_version: skill/v1
id: natural-language-to-query
display_name: Natural Language to Query
description: 안전한 read-only DB 질문을 구조화된 질의 계획으로 변환합니다.
mode: plan
action_kind: db
default_risk: read_only
allowed_targets:
  - planned_job
supported_plugins:
  - postgres-query
  - mysql-query
requires_approval:
  prod: true
  non_prod: false
constraints:
  allow_raw_sql: false
  max_limit: 200
  require_time_range_for_large_tables: true
  require_datasource_allowlist: true
output_schema: db_query_intent/v1

권장 skill 출력 형식

LLM은 구조화된 JSON만 반환하는 것이 좋습니다.

{
  "intent": "db_read_query",
  "datasource": "orders-prod-ro",
  "driver": "postgres",
  "target": {
    "table": "orders"
  },
  "select": ["id", "status", "created_at"],
  "filters": [
    {"field": "status", "op": "eq", "value": "FAILED"},
    {"field": "created_at", "op": "gte", "value": "now-24h"}
  ],
  "sort": [
    {"field": "created_at", "direction": "desc"}
  ],
  "limit": 50,
  "risk": "read_only",
  "requires_approval": true,
  "rationale": "운영 데이터소스이며 고객 영향 가능성이 있는 데이터입니다."
}

그 다음 Core가 이것을 다음 중 하나로 변환합니다.

  • 검증된 SQL
  • plugin 전용 구조화 질의 payload

둘 중에서는 두 번째가 더 바람직합니다.

권장 planned_job 형태

이 부분은 현재의 LLM plan 흐름과 맞추는 것이 좋습니다.

{
  "workflow_id": "llm-ad-hoc-db",
  "kind": {
    "tag": "JobDb",
    "contents": {
      "driver": "postgres",
      "dsn_ref": "orders-prod-ro",
      "query": "select id, status, created_at from orders where status = $1 and created_at >= now() - interval '24 hours' order by created_at desc limit 50",
      "params": ["FAILED"],
      "query_timeout_seconds": 15,
      "allowlist_tag": "read"
    }
  }
}

향후 제품이 확장되면 이 use case 에는 JobPlugin 변형이 더 깔끔할 수 있습니다.

권장 DB plugin API

권장 plugin 이름:

  • postgres-query
  • mysql-query
  • 필요하면 통합형 db-query

권장 endpoint:

POST /query/read

요청:

{
  "trace_id": "tr_123",
  "tenant_id": "default",
  "datasource": "orders-prod-ro",
  "driver": "postgres",
  "statement": "select id, status, created_at from orders where status = $1 limit 50",
  "params": ["FAILED"],
  "timeout_seconds": 15,
  "max_rows": 50
}

응답:

{
  "ok": true,
  "columns": ["id", "status", "created_at"],
  "rows": [
    [101, "FAILED", "2026-03-14T10:00:00Z"]
  ],
  "row_count": 1,
  "truncated": false,
  "duration_ms": 42
}

POST /query/explain

정책 검토나 디버깅용으로 선택적으로 둘 수 있습니다.

GET /healthz

plugin readiness 확인용 기본 health endpoint 입니다.

보안 모델

필수 제어:

  • v1 에서는 read-only credential만 사용
  • datasource allowlist
  • table/view allowlist
  • row limit 강제
  • timeout 강제
  • multi-statement 금지
  • DDL/DML 금지
  • 로그에 secret 값 남기지 않기
  • 모든 요청에 signed internal header 적용

강력 권장:

  • 직접 테이블보다 승인된 view 사용
  • tenant 별 datasource 매핑
  • 민감 컬럼 결과 마스킹
  • trace ID, datasource, row count 를 포함한 query audit log

natural-language-to-query 흐름

  1. 사용자가 자연어로 질문
  2. Core가 natural-language-to-query 선택
  3. Core가 다음 컨텍스트 제공
    • tenant
    • environment
    • datasource catalog
    • 허용된 table/view
    • schema 힌트
  4. LLM이 구조화된 query intent 반환
  5. Core가 다음을 검증
    • datasource 허용 여부
    • read-only 여부
    • row limit 적절성
    • env/approval 정책
  6. Core가 이를 planned_job 으로 변환
  7. Runner가 DB plugin 으로 dispatch
  8. plugin이 실행 후 정규화된 결과 반환
  9. Core가 다음을 렌더링
    • 결과 표
    • 짧은 자연어 요약

승인 정책

초기 권장 규칙:

  • prod DB 실행은 전부 승인 필요
  • write intent 는 v1 에서 전부 거부
  • datasource 를 결정할 수 없는 요청은 거부
  • row/timeout 한도를 넘는 요청은 거부

권장 v1 범위

우선 출시:

  • PostgreSQL read-only
  • datasource allowlist
  • 구조화된 filter, sort, limit, aggregate count
  • 결과 표 + 요약

후순위:

  • write query
  • 자유 SQL 모드
  • 임의 스키마 간 join
  • 최종 사용자용 schema discovery
  • spreadsheet export

왜 AisOpsFlow에 잘 맞는가

이 패턴은 현재 제품 경계와 잘 맞습니다.

  • skill은 판단
  • Core는 검증과 정책
  • Runner는 통제된 실행
  • plugin은 외부 시스템 접근

이 분리를 유지하면 LLM을 유용하게 쓰면서도 DB superuser처럼 행동하게 만들지 않을 수 있습니다.