수민 '-'

플오그래밍

제가 작성하는 모든 글은 절대 상업적인 이용이 아니며, 그저 개인적인 공부 용도로만 사용하는 것임을 밝힙니다.

쿼리문을 작성하는 RAG - SQLite와 ReAct 에이전트

문서를 임베딩해 찾는 RAG와 달리, 구조화된 데이터는 SQL로 직접 묻는 편이 정확할 때가 많다. 자연어 질문을 받아 안전한 읽기 전용 쿼리를 만들고 실행한 뒤 결과를 풀어서 답하는 흐름을 LangChain·LangGraph로 실습해보자. 이번 글(1부)은 SQLite 샘플 DB 준비부터 SQLDatabaseToolkitcreate_react_agent 로 한 번에 돌려보는 단계까지 다룬다. 원문 흐름은 쿼리문을 작성하는 RAG를 바탕으로 정리했다.


1. SQLite란

SQLite는 서버 없이 동작하는 가볍고 독립형(self-contained) 인 관계형 DBMS다. 애플리케이션 안에 파일 하나로 DB를 두기 때문에 별도 설치·설정 부담이 적다. ANSI-SQL 대부분, 트랜잭션·인덱스·뷰·트리거 등을 지원하지만, MySQL·PostgreSQL 같은 서버형 DB에 비해 동시 접속·대규모 분산에는 한계가 있다.

작은 웹·모바일 앱, 프로토타입, 내장 기기 등에 널리 쓰이고, 파일만 복사하면 DB 전체를 옮길 수 있을 정도로 이식성이 좋다.


2. Chinook 샘플 DB 받기

벤치마크용 Chinook DB를 내려받는 예시다.

import requests

url = "https://storage.googleapis.com/benchmarks-artifacts/chinook/Chinook.db"
response = requests.get(url)

if response.status_code == 200:
    with open("Chinook.db", "wb") as file:
        file.write(response.content)
    print("Chinook.db 다운로드")
else:
    print(f"다운로드 실패: {response.status_code}")

3. LangChain으로 SQLite 연결하기

!pip install langchain_community
from langchain_community.utilities import SQLDatabase
import os

db_path = "./Chinook.db"
print("경로 존재 여부:", os.path.exists(db_path))

db = SQLDatabase.from_uri(f"sqlite:///{db_path}")
print(f"Dialect: {db.dialect}")
print(f"Available tables: {db.get_usable_table_names()}")
print(f'Sample output: {db.run("SELECT * FROM Artist LIMIT 5;")}')

SQLDatabase.from_uri("sqlite:///Chinook.db")처럼 상대 경로 없이 쓸 수도 있다. 환경에 맞게 URI만 맞추면 된다.


4. LLM 준비

import getpass
import os

def _set_env(var: str):
    if not os.environ.get(var):
        os.environ[var] = getpass.getpass(f"{var}: ")

_set_env("OPENAI_API_KEY")
!pip install langchain_openai
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o", temperature=0, max_retries=3)

모델 이름은 사용 중인 API에 맞게 바꾸면 된다.


5. SQLDatabaseToolkit이 제공하는 도구

SQLDatabaseToolkit은 LLM이 DB와 상호작용할 때 쓰는 도구 묶음을 만든다. 대표적으로 다음이 포함된다(이름·역할은 버전에 따라 문구가 조금 다를 수 있다).

  • sql_db_query: SQL을 실행하고 조회 결과(또는 오류 메시지)를 반환
  • sql_db_schema: 지정한 테이블의 스키마와 샘플 행
  • sql_db_list_tables: 테이블 목록
  • sql_db_query_checker: 실행 전 쿼리 검토용(제공되는 경우)
from langchain_community.agent_toolkits import SQLDatabaseToolkit

toolkit = SQLDatabaseToolkit(db=db, llm=llm)
tools = toolkit.get_tools()

for tool in tools:
    print(f"{tool.name}: {tool.description}\n")

6. create_react_agent로 한 번에 돌려보기

LangGraph의 create_react_agent 에 LLM과 위 tools를 넘기면, ReAct 패턴으로 “도구 호출 → 관찰 → 다시 생각” 루프가 구성된다. 시스템 프롬프트에서는 DML 금지, 먼저 테이블 확인, 관련 컬럼만 조회, 결과는 최대 5행 등 운영 규칙을 명시하는 것이 안전하다.

!pip install langgraph
from langgraph.prebuilt import create_react_agent

system_prompt = f"""
You are an agent designed to interact with a SQL database.
Given an input question, create a syntactically correct {db.dialect} query to run,
then look at the results of the query and return the answer. Unless the user
specifies a specific number of examples they wish to obtain, always limit your
query to at most 5 results.

You can order the results by a relevant column to return the most interesting
examples in the database. Never query for all the columns from a specific table,
only ask for the relevant columns given the question.

You MUST double check your query before executing it. If you get an error while
executing a query, rewrite the query and try again.

DO NOT make any DML statements (INSERT, UPDATE, DELETE, DROP etc.) to the
database.

To start you should ALWAYS look at the tables in the database to see what you
can query. Do NOT skip this step.

Then you should query the schema of the most relevant tables.
"""

agent = create_react_agent(
    llm,
    tools,
    prompt=system_prompt,
)
question = "2009년에 가장 많은 매출을 올린 영업 사원은 누구인가요?"

for step in agent.stream(
    {"messages": [{"role": "user", "content": question}]},
    stream_mode="values",
):
    step["messages"][-1].pretty_print()

디버깅할 때는 정의해 둔 tools 객체를 그대로 출력해 이름과 설명을 다시 확인해도 좋다.


마치며

  • SQLite는 파일 기반 RDB로, 샘플·교육·경량 앱에 적합하다.
  • SQLDatabase로 스키마·실행을 LangChain 쪽에서 다루고, SQLDatabaseToolkit으로 에이전트용 도구를 한꺼번에 꺼낼 수 있다.
  • create_react_agent는 최소 코드로 텍스트 → SQL → 결과 해석 파이프라인을 검증하기 좋다.
  • 2부에서는 같은 도구를 쪼개 노드·엣지로 직접 그래프를 짜고, 쿼리 오류 시 재생성 루프를 넣는 방식을 정리한다.

참고