AI 개발 독학 4주 - 4주차 SQL과 AI 에이전트 설계

타리스만에서는 유익한 AI 기술을 전달합니다
제휴 링크로 판매시 수수료를 제공받습니다

드디어 마지막 주입니다. 지난 3주 동안 파이썬 기초, 데이터 처리, RAG 검색 구조를 함께 훑어봤습니다. 이제 이 조각들을 하나로 묶어 실제로 쓸 수 있는 서비스를 만드는 단계입니다. 데이터를 구조적으로 묻는 SQL, 자연어를 SQL로 바꾸는 txt2SQL, 그리고 여러 모듈을 지휘하는 에이전트 오케스트레이션이 이번 주의 핵심입니다.

많은 AI 애플리케이션의 백엔드는 이미 에이전트 오케스트레이션 구조를 갖추고 있습니다. 이 글에서는 이해를 돕기 위해 작은 예제 프로젝트(간단한 데이터 분석 도구)를 예로 듭니다. 예를 들어 이 프로젝트의 backend/pipeline.py는 여러 개의 모듈이 순서대로 돌아가고, 각 모듈은 agents 폴더의 지침 파일(.md)을 따르는 구조라고 가정해 봅시다. 이번 주는 이런 구조를 이해하고, 마지막 퍼즐인 외부 LLM APIproviders.py에 어떻게 붙일지 설계도를 그리는 것으로 마무리합니다.

이번 주 목표

기본 SQL 4종(SELECT, WHERE, GROUP BY, JOIN)을 예제 데이터 테이블로 직접 돌려봅니다. txt2SQL의 작동 원리와 치명적인 위험 요소를 파악합니다. "모듈=에이전트+지침" 구조를 이해하고, 외부 LLM API를 안전하게 붙이는 보안 설계도를 완성합니다.

이번 주 계획

요일 학습 내용 시간
SQL 기초: 테이블 생성, SELECT, WHERE로 데이터 필터링 1h
SQL 심화: GROUP BY로 집계, JOIN으로 두 테이블(도서·리뷰) 연결 1h
txt2SQL 원리: 자연어→SQL 변환 흐름과 검증(Hallucination 방지) 1.5h
에이전트와 오케스트레이션: pipeline.py의 모듈 파이프라인 구조 분석 1h
외부 LLM API 구조와 통합: providers.py 설계 및 민감정보 보호 원칙 1.5h
토·일 주말 과제: agents/spec.md 지침 파일 구체화 및 개선 3h
시간 없으면 이것만

1. 아래 SQL 위젯 2개를 실행해 결과를 확인하세요.
2. txt2SQL 검증 3원칙(스키마 제공, 읽기전용, LIMIT)을 기억하세요.
3. 예시로 든 agents/spec.md 형태의 지침 파일이 어떤 5개 절 구조를 갖는지 눈으로 익혀 두세요.

1. SQL: 데이터를 다루는 표준 언어

SQL(Structured Query Language)은 표(테이블) 형태의 데이터베이스에서 원하는 행을 뽑거나 집계하는 질의 언어입니다. 파이썬은 sqlite3 라이브러리를 기본으로 내장하고 있어서, 별도 설치 없이 메모리 위에서 SQL을 바로 연습할 수 있습니다.

먼저 간단한 도서 테이블을 만들고 데이터를 조회해 보겠습니다. SELECT는 열을 선택하고, WHERE는 조건에 맞는 행만 필터링합니다.

import sqlite3

# 1. 메모리 DB 연결 및 커서 생성
conn = sqlite3.connect(':memory:')
c = conn.cursor()

# 2. 테이블 생성 (제목, 장르, 가격, 재고)
c.execute('''CREATE TABLE books
             (title TEXT, genre TEXT, price REAL, stock INTEGER)''')

# 3. 데이터 삽입
data = [
    ('바람의 언덕', '소설', 15000, 12),
    ('파이썬 첫걸음', '기술', 22000, 30),
    ('별을 세는 밤', '시집', 9000, 5)
]
c.executemany('INSERT INTO books VALUES (?,?,?,?)', data)

# 4. 조회: 모든 책의 제목과 가격
print("--- 전체 도서 ---")
c.execute('SELECT title, price FROM books')
for row in c.fetchall():
    print(row)

# 5. 필터링: 소설 장르만 조회
print("\n--- 소설 장르 ---")
c.execute("SELECT title, stock FROM books WHERE genre = '소설'")
print(c.fetchone()) # 결과는 하나이므로 fetchone 사용

실무에서는 단순 조회보다 '그룹별 집계'와 '테이블 결합'이 훨씬 자주 필요합니다. GROUP BY는 장르별 평균 가격처럼 묶어서 계산할 때 쓰고, JOIN은 도서 목록과 리뷰 목록처럼 서로 다른 두 테이블을 공통 키로 연결할 때 씁니다.

import sqlite3

conn = sqlite3.connect(':memory:')
c = conn.cursor()

# 도서 테이블과 리뷰 목록 테이블 생성
c.execute('CREATE TABLE books (id INTEGER, title TEXT, genre TEXT)')
c.execute('CREATE TABLE reviews (book_id INTEGER, reviewer TEXT, rating REAL)')

# 데이터 삽입
c.executemany('INSERT INTO books VALUES (?,?,?)', [
    (1, '바람의 언덕', '소설'), (2, '파이썬 첫걸음', '기술')
])
c.executemany('INSERT INTO reviews VALUES (?,?,?)', [
    (1, '민지', 4.5), (1, '준호', 5.0),
    (2, '서연', 4.0), (2, '도윤', 3.5)
])

# 1. GROUP BY: 장르별 도서 수 집계
print("--- 장르별 도서 수 ---")
c.execute('SELECT genre, COUNT(*) FROM books GROUP BY genre')
for row in c.fetchall():
    print(f"{row[0]}: {row[1]}권")

# 2. JOIN: 책 제목과 해당 리뷰 목록 결합
print("\n--- 바람의 언덕 리뷰 목록 ---")
# books.id와 reviews.book_id가 같은 행끼리 연결
c.execute('''
    SELECT b.title, r.reviewer, r.rating
    FROM books b
    JOIN reviews r ON b.id = r.book_id
    WHERE b.title LIKE '바람%'
''')
for row in c.fetchall():
    print(f"[{row[0]}] 리뷰어: {row[1]}, 평점: {row[2]}")

2. txt2SQL: 자연어로 DB 질의하기

txt2SQL(Text-to-SQL)은 사용자가 "평점 4점 넘는 소설 찾아줘"라고 말하면, LLM이 이를 SELECT ... WHERE rating > 4로 번역해 실행하는 기술입니다. 흐름은 다음과 같습니다.
① DB 스키마(테이블/컬럼 정의)를 프롬프트에 주입 → ② 자연어 질문 입력 → ③ LLM이 SQL 생성 → ④ 생성된 SQL 실행 → ⑤ 결과를 자연어로 요약.

주의

실패 케이스와 검증: LLM은 존재하지 않는 컬럼을 지어내거나(Hallucination), DELETE 같은 위험한 쿼리를 만들 수 있습니다. 그래서 반드시 ① 생성된 SQL을 사용자에게 먼저 보여주고 확인받거나, ② DB 계정을 읽기전용(Read-only)으로 제한하고, ③ 결과 행 수를 LIMIT로 강제해야 합니다.

아래 예제는 LLM이 SQL을 생성했다고 가정하고, 이를 실행하는 과정과 검증(에러 처리)의 중요성을 보여줍니다.

import sqlite3

conn = sqlite3.connect(':memory:')
c = conn.cursor()
c.execute('CREATE TABLE books (title TEXT, price REAL, stock INTEGER)')
c.execute("INSERT INTO books VALUES ('바람의 언덕', 15000, 12)")

# 시나리오 1: LLM이 올바른 SQL을 생성함
generated_sql_1 = "SELECT title, price FROM books WHERE price > 10000"
print("--- 정상 쿼리 실행 ---")
try:
    c.execute(generated_sql_1)
    print("결과:", c.fetchall())
except Exception as e:
    print("에러:", e)

# 시나리오 2: LLM이 없는 컬럼(discount)을 지어냄
generated_sql_2 = "SELECT title, discount FROM books"
print("\n--- hallucination 쿼리 실행 ---")
try:
    c.execute(generated_sql_2)
    print("결과:", c.fetchall())
except sqlite3.OperationalError as e:
    # 검증 로직: 에러가 나면 사용자에게 재질문하거나 수정 요청
    print(f"[검증 실패] 실행 불가: {e}")
    print("-> 시스템: LLM에게 스키마를 다시 알려주고 수정을 요청해야 함.")

3. 에이전트와 오케스트레이션

에이전트(Agent)는 명확한 역할과 지침을 가진 작은 일꾼입니다. 오케스트레이션(Orchestration)은 이들을 순서대로, 또는 조건에 따라 지휘하는 것입니다. 앞서 예로 든 예제 프로젝트의 backend/pipeline.py를 전형적인 오케스트레이터라고 생각해 봅시다. 여러 개의 모듈(intake → spec → ... → report)을 순서대로 실행하고, 각 모듈은 backend/agents/<id>.md라는 지침 파일을 가진 독립된 에이전트입니다.

지금은 파이썬 규칙엔진으로 동작하지만, 나중에는 이 .md 파일의 지침이 곧 LLM의 시스템 프롬프트가 됩니다. agents/spec.md의 구조를 살펴보겠습니다.

# backend/agents/spec.md (예시 구조)

## 1. 역할 (Role)
너는 상품 정보 분석가다. 입력된 텍스트에서 상품 종류, 용량, 등급 정보를 추출한다.

## 2. 입력 (Input)
- 상품 설명 텍스트 (비정형)
- 표준 분류 코드 목록 (JSON)

## 3. 판단 기준 (Rules)
- "500밀리"와 "500ml"는 모두 "500"으로 통일한다.
- 등급 정보가 없으면 "표준"으로 간주한다.
- 한정판 상품은 가격 보정 계수를 1.2배로 적용한다.

## 4. 출력 계약 (Output Contract)
반드시 아래 JSON 형식으로만 출력한다.
{
  "volume": int,
  "grade": string,
  "price_adj": float
}

## 5. LLM 연결 시 지침 (Future)
(추후 외부 LLM API 연동 시 사용할 프롬프트 엔지니어링 가이드)

4. 외부 LLM API 구조와 통합 설계

대부분의 호스팅형 LLM API는 비슷한 방식으로 동작합니다. 먼저 서비스에서 모델(사용할 파운데이션 모델)을 고르고, 그 모델을 API 엔드포인트(REST URL)로 노출한 다음, 발급받은 API 키로 인증합니다. 실제 호출은 이 엔드포인트에 messagesmax_tokens 같은 파라미터를 POST 방식으로 보내는 형태입니다.

설계 포인트

backend/providers.py가 바로 이 LLM API를 호출하는 어댑터 자리입니다. 여기서 가장 중요한 것은 민감정보 보호 원칙입니다. 고객 정보·결제 내역·개인정보처럼 민감한 데이터는 절대 외부 LLM으로 그대로 보내지 않습니다. 꼭 필요하다면 데이터가 밖으로 나가지 않는 사설(On-premise/Private Cloud) 환경의 LLM을 쓰고, 일반적인 외부 LLM에는 오직 "표현 표준화", "요약", "근거 문장 추출" 같은 비민감성 업무만 맡깁니다. 실제 민감한 계산 로직은 여전히 rules.py의 규칙엔진이 담당하도록 분리합니다.

챗 서비스를 제대로 완성하려면 다음 요소도 함께 고려해야 합니다.

  • 인증(Auth): 로그인(SSO 등) 연동으로 누가 쓰는지 확인.
  • 세션(Session): 대화 기록을 DB에 저장해 문맥 유지.
  • 컨텍스트(Context): 직전 대화 + RAG 검색 결과를 프롬프트에 조립.
  • 스트리밍(Streaming): LLM 응답을 타자기처럼 끊어서 전송(Server-Sent Events).

주말 과제 — agents/spec.md 지침 개선

실습

코드를 짜는 것이 아니라, 예시로 든 agents/spec.md 형태의 지침 파일을 열어 내용을 직접 다듬어 보세요. 특히 5번 "LLM 연결 시 지침(추후)" 섹션을 구체화해 봅니다. LLM이 이 모듈에 붙었을 때 무엇을 판단하고 무엇을 출력해야 하는지 3~5줄로 명시하면 됩니다.

작성 예시:
"입력 텍스트에 '한정판'이라는 키워드가 있으면 등급을 'Limited'로 매핑하라. 누락된 필드는 'Unknown'으로 채우되, 확신이 80% 이상일 때만 값을 확정하라. 출력은 반드시 4절의 JSON 계약을 따르며, 마크다운 코드블록(```json) 없이 순수 텍스트만 반환하라."

좋은 지침의 조건: 역할이 명확한가? 입력 데이터 형식이 정의되었는가? 출력 형식(JSON 등)이 고정되었는가? 예외 상황(데이터 누락 등)에 대한 판단 규칙이 있는가?

자가 체크리스트

  • sqlite3를 이용해 SELECT, WHERE, GROUP BY, JOIN 쿼리를 각각 한 번 이상 실행해 봤어요.
  • txt2SQL의 4단계 흐름(스키마→질문→SQL→실행)을 설명할 수 있어요.
  • LLM이 잘못된 SQL을 생성할 위험과 검증 방법(읽기전용, LIMIT 등)을 알아요.
  • 에이전트(일꾼)와 오케스트레이션(지휘)의 개념을 pipeline.py 구조에 대입해 이해했어요.
  • 외부 LLM API의 핵심 요소(엔드포인트, 인증 키, 요청/응답 형식)를 알아요.
  • 민감한 계산은 LLM 없이 규칙엔진으로, 텍스트 처리만 LLM으로 분리하는 보안 원칙을 확인했어요.
  • 예시로 든 agents/spec.md 지침 파일의 5번 섹션을 구체적으로 작성해 봤어요.
4주 지도

이제 예제 프로젝트의 코드를 열면 각 파일이 어떤 역할을 하는지 눈에 들어올 것입니다.

  • 1주차 (파이썬 기초): rules.py의 딕셔너리 처리와 함수 구조.
  • 2주차 (데이터·LLM): providers.py의 messages 페이로드와 rules.py의 정규식.
  • 3주차 (RAG): rag.py의 청킹/메타데이터 필터링과 embed.py의 코사인 유사도.
  • 4주차 (SQL·에이전트): pipeline.py의 모듈 파이프라인과 agents/*.md 지침 파일.

다음 단계는 스스로 해 보는 것입니다. 위젯 예제의 데이터를 여러분이 직접 다루는 데이터(가계부, 판매 기록, 설문 응답 등)로 바꿔 실행해 보거나, agents 폴더의 다른 모듈 지침 파일도 다듬어 보세요.

이번 주 용어

용어설명
SQL구조화된 질의 언어. 데이터베이스와 대화하는 표준 방법.
테이블/컬럼/행표/열(속성)/행(데이터 레코드). 엑셀의 시트/헤더/데이터와 유사.
SELECT / WHERE데이터를 선택하는 명령어 / 조건에 맞는 행만 필터링하는 절.
GROUP BY특정 컬럼 값을 기준으로 데이터를 묶어 집계(SUM, AVG 등)할 때 사용.
JOIN두 개 이상의 테이블을 공통된 키(Key)로 연결하여 데이터를 합침.
스키마(Schema)데이터베이스의 구조(테이블명, 컬럼명, 데이터 타입) 정의서.
txt2SQL자연어 질문을 SQL 쿼리로 자동 변환하는 기술.
에이전트(Agent)주어진 목표와 도구를 사용해 자율적으로 작업을 수행하는 AI 단위.
오케스트레이션여러 에이전트의 작업 순서와 상호작용을 관리·조정하는 것.
LLM API학습된 대형 언어 모델을 REST 요청으로 호출하는 서비스 엔드포인트.
디플로이먼트학습된 모델을 API 형태로 서비스 환경에 배포하는 것.
스트리밍데이터를 한 번에 받지 않고 생성되는 대로 실시간 전송하는 방식.

4주간의 여정이 끝났습니다. 이제 남이 만든 커리큘럼을 그대로 따라가는 것이 아니라, 여러분이 직접 만든 코드를 열고 고칠 차례입니다. 에러가 나면 로그를 읽고, 로직이 부족하면 agents의 지침을 보강하세요. 개발은 더 이상 개발자만의 영역이 아닙니다.

댓글 남기기