본문 바로가기
개발

랭체인(LangChain) SQL Agent로 자연어로 SQL 쿼리 생성하기 - Text-To-SQL

by developer's warehouse 2024. 6. 7.

구조화된 데이터들은 SQL을 지원하는 DBMS에 저장되어 있습니다. 하지만, SQL로 DBMS와 소통하는 것은 쉽지가 않습니다.

LLM 시스템은 자연어로 질문하고 답을 받을 수 있는데, 이는 벡터 데이터베이스라고 하는 벡터 임베딩을 통해 유사성 검색을 통해 진행할 수 있게 됩니다.

하지만, LLM 시스템에서 구조화된 데이터를 쿼리하는 것은 비정형 데이터와 질적으로 다를 수 있습니다. 구조화된 데이터에 대한 접근 방식은 종종 LLM이 SQL과 같은 DSL(Domain-Specific Language)에서 쿼리를 작성하고 실행해야 합니다.

그러므로, LLM에서 자연어로 입력받은 질문을 구조화된 SQL 데이터베이스(RDBMS)에서 질의하고 사용자에게 출력하려면 자연어를 SQL로 변환할 수 있어야 하며, 결과를 다시 자연어로 변환할 수 있어야 합니다.

랭체인(LangChain) SQL Agent로 자연어로 SQL 쿼리 생성하기 - Text-To-SQL 썸네일

오늘 배우는 것

이 포스팅에서는 데이터베이스의 행과 열 형식 데이터에 대한 Q&A 시스템을 만드는 기본적인 방법을 알아보겠습니다.

체인(chain)과 에이전트(agent)를 모두 사용한 구현을 다루도록 하겠습니다.

 

이러한 시스템을 사용하면 데이터베이스의 데이터에 대해 자연어로 질문하고 자연어로 답변을 얻을 수 있습니다. SQL과 자연어 Q&A 시스템의 가장 큰 차이점은 에이전트와 필요한 만큼 반복해서 데이터베이스에 사람의 언어로 쿼리 할 수 있다는 것입니다.

 

주의사항

SQL 데이터베이스의 Q&A 시스템을 구축하려면 모델에서 생성된 SQL 쿼리를 실행해야 합니다. 하지만, LLM이 생성한 SQL은 환영과 같은 문제로 잘못된 SQL 질의문을 수행할 가능성이 있습니다.

그러므로, 실제 데이터베이스 연결이나 작업 권한은 이 시스템을 사용하는 사용자의 필요에 따라 가능한 한 적은 범위로 설정해야 합니다. 예를들어, Select만 가능하게 한다던지, 데이터 삭제는 못하게 한다던지 하는 권한을 제한할 수 있겠습니다.

이렇게 하면 모델 중심 시스템 구축의 위험을 완화할 수는 있지만 제거할 수는 없습니다. 보안을 강화하는 더 자세한 방법은 다음 링크를 참고하세요.

https://python.langchain.com/v0.2/docs/security/

 

아래 링크의 LangChain 공식 튜토리얼의 SQL Data를 통한 자연어 질문 및 답변 예제를 참고해서 테스트를 진행합니다.

https://python.langchain.com/v0.2/docs/tutorials/sql_qa/

 

Build a Question/Answering system over SQL data | 🦜️🔗 LangChain

Enabling a LLM system to query structured data can be qualitatively different from unstructured text data. Whereas in the latter it is common to generate text that can be searched against a vector database, the approach for structured data is often for the

python.langchain.com

 

시스템 아키텍처

오늘 설명할 시스템의 단계를 간단히 설명하면 다음과 같습니다.

  1. LLM 모델이 사용자의 질문을 SQL 쿼리로 변환합니다.
  2. SQL 쿼리를 실행합니다
  3. 모델이 쿼리 결과를 사용하여 사용자 입력에 자연어로 응답합니다.

시스템 아키텍처 그림

시스템 셋업

먼저 필요한 패키지를 가져오고 환경 변수를 설정해야합니다.

이 시스템은 파이썬을 이용해 구현하게 됩니다. 그러므로 파이썬에서 필요한 패키지를 아래 명령으로 설치합니다.

pip install --upgrade --quiet langchain langchain-community langchain-openai

 

이 문서에서는 OpenAI 모델을 사용하여 질문을 SQL로 변환합니다.

 

테스트를 하기 위해서는 OpenAI(chatGPT)의 챗지피티 사이트에서 API KEY를 받아야 합니다.

api key는 아래 링크에서 무료로 받으실 수 있습니다. 하지만, 실제 사용하려면 유료 결제를 해야 하는 것 같습니다.

https://platform.openai.com/api-keys

 

위의 링크로 들어가서 로그인을 하시면 아래와 같은 화면이 나타납니다. "Create new secret key"를 눌러서 chatgpt의 API Key를 할당받습니다.

API Key 할당 받는 첫 화면

create new secret key를 누르면 아래와 같은 화면이 나타나는데, 이름을 적으시면 알아보기 편리합니다. 원하는 Key 이름을 적은 후 "Create secret key" 버튼을 누르면 바로 생성됩니다.

api key 생성생성 완료된 api key

 

 

그러면 오른쪽 화면과 같이 API Key가 발행되면 Copy 버튼을 눌러서 복사할 수 있습니다. 이 키는 다시 볼 수 없으므로 이 화면에서 잘 복사해서 어딘가에 저장해 두시길 바랍니다.

 

테스트 database 설치

오늘 보여드리는 예시는 Chinook 데이터베이스와의 SQLite 연결을 사용합니다.

Chinook DB란?

Chinook.db는 SQL Server, Oracle, MySQL 등을 위한 샘플 데이터베이스입니다. 이 데이터베이스는 하나의 SQL 스크립트를 실행하여 생성할 수 있습니다. Chinook.db는 Northwind 데이터베이스의 대안으로, 단일 및 다중 데이터베이스 서버를 대상으로 하는 ORM 도구를 테스트하고 데모하는 데 이상적입니다.

Chinook.db는 디지털 미디어 스토어를 나타내는 데이터 모델을 가지고 있으며, 아티스트, 앨범, 미디어 트랙, 인보이스, 고객 등에 대한 테이블을 포함하고 있습니다. 미디어 관련 데이터는 실제 iTunes 라이브러리에서 생성되었으며, 고객 및 직원 정보는 가상의 이름, Google 지도에서 찾을 수 있는 주소 등을 사용하여 수동으로 생성되었습니다. 판매 정보는 4년 기간 동안 무작위 데이터를 사용하여 자동 생성되었습니다.

Chinook DB의 공식 소스는 아래의 github에서 확인하실 수 있습니다.

https://github.com/lerocha/chinook-database

Chinook.db는 다음과 같은 11개의 테이블을 가지고 있습니다.

employees 테이블: 직원 데이터(예: id, 성, 이름 등)를 저장합니다.

customers 테이블: 고객 데이터를 저장합니다.

invoices & invoice_items 테이블: 인보이스 데이터를 저장합니다. invoices 테이블은 인보이스 헤더 데이터를, invoice_items 테이블은 인보이스 항목 데이터를 저장합니다.

artists 테이블: 아티스트 데이터를 저장합니다.

albums 테이블: 트랙 목록에 대한 데이터를 저장합니다. 각 앨범은 한 명의 아티스트에 속하며, 하나의 아티스트는 여러 앨범을 가질 수 있습니다.

media_types 테이블: MPEG 오디오 및 AAC 오디오 파일과 같은 미디어 유형을 저장합니다.

genres 테이블: 록, 재즈, 메탈 등과 같은 음악 유형을 저장합니다.

tracks 테이블: 노래 데이터를 저장합니다. 각 트랙은 하나의 앨범에 속합니다.

playlists & playlist_track 테이블: playlists 테이블은 재생 목록 데이터를 저장하며, 각 재생 목록에는 여러 트랙이 포함됩니다. 각 트랙은 여러 재생 목록에 속할 수 있습니다 playlists와 tracks 테이블 사이의 관계는 다대다 관계이며, playlist_track 테이블은 이 관계를 반영하는 데 사용됩니다.

Chinook DB

 

이 데이터베이스는 SQL, 특히 SQLite와 함께 연습하기 좋은 샘플 데이터베이스입니다. 이 데이터베이스를 사용하면 SQL 쿼리 작성 및 실행, 데이터 조작 및 분석 등의 기본적인 데이터베이스 작업을 연습할 수 있습니다.

특히, SQLITE에서도 이 DB를 샘플 데이터베이스로 소개하고 있습니다.

 

sqlite sample database 소개 자료

SQLOTE Chinook DB 페이지

SQLITE에 Chinook.db 만들기

다음 설치 단계에 따라 이 노트북과 같은 디렉터리에 Chinook.db를 만드시면 됩니다.

sqlite에 Chinook.db 만드는 법 보러 가기

 

1. Chinook.sql 다운로드

아래의 링크에서 Chinook.sql을 다운로드합니다.

Chinook_Sqlite.sql 다운 받기

저의 경우 리눅스에서 커맨드라인으로 테스트 중이어서 wget을 이용하여 다운로드하였습니다.

:~/langchain$ wget https://raw.githubusercontent.com/lerocha/chinook-database/master/ChinookDatabase/DataSources/Chinook_Sqlite.sql
--2024-06-04 17:53:30--  https://raw.githubusercontent.com/lerocha/chinook-database/master/ChinookDatabase/DataSources/Chinook_Sqlite.sql
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.108.133, 185.199.110.133, 185.199.111.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.108.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 611447 (597K) [text/plain]
Saving to: 'Chinook_Sqlite.sql'

Chinook_Sqlite.sql                    100%[=======================================================================>] 597.12K  --.-KB/s    in 0.05s

2024-06-04 17:53:31 (11.1 MB/s) - 'Chinook_Sqlite.sql' saved [611447/611447]

 

2. sqlite3로 Chinook.db 생성

다음의 명령어를 수행하여 sqlite3에서 Chinook.db를 생성하고 sqlite3의 대화형 DB 접근 창을 실행할 수 있습니다.

sqlite3 Chinook.db

 

3. Chinook.sql 로드하기

sqlite3에서 파일을 읽어 들여서 수행하는 다음의 명령을 실행합니다.

.read Chinook.sql

4. 데이터 로드 확인

다음과 같은 SQL을 수행하여 데이터가 잘 들어갔는지 확인합니다.

SELECT * FROM Artist LIMIT 10;

 

이를 순서대로 실행하면 다음과 같습니다.

:~/langchain$ sqlite3 Chinook.db
SQLite version 3.37.2 2022-01-06 13:25:41
Enter ".help" for usage hints.
sqlite> .read Chinook_Sqlite.sql
sqlite> SELECT * FROM Artist LIMIT 10;
1|AC/DC
2|Accept
3|Aerosmith
4|Alanis Morissette
5|Alice In Chains
6|Antônio Carlos Jobim
7|Apocalyptica
8|Audioslave
9|BackBeat
10|Billy Cobham
sqlite>

 

이제 Chinook.db가 잘 수행된 것을 확인하실 수 있습니다.

 

 

파이썬에서 SQLAlchemy로 DB 접근하기

이제 Chinhook.db가 디렉터리에 생성되었습니다. 파이썬으로 SQLAlchemy 라이브러리 기반 SQLDatabase 클래스를 사용하여 DB에 접근할 수 있습니다.

다음의 코드를 사용하여 sqlite에 접근하는 파이썬 프로그램을 작성해서 테스트해 보도록 하겠습니다.

from langchain_community.utilities import SQLDatabase

db = SQLDatabase.from_uri("sqlite:///Chinook.db")
print(db.dialect)
print(db.get_usable_table_names())
print(db.run("SELECT * FROM Artist LIMIT 10;"))

 

vscode 화면

 

vscode에서 위와 같이 작성 후 실행을 하면 아래와 같은 결과가 출력됩니다.

~/langchain$ /bin/python3 /home/altibase/langchain/sqliteTest.py
sqlite
['Album', 'Artist', 'Customer', 'Employee', 'Genre', 'Invoice', 'InvoiceLine', 'MediaType', 'Playlist', 'PlaylistTrack', 'Track']
[(1, 'AC/DC'), (2, 'Accept'), (3, 'Aerosmith'), (4, 'Alanis Morissette'), (5, 'Alice In Chains'), (6, 'Antônio Carlos Jobim'), (7, 'Apocalyptica'), (8, 'Audioslave'), (9, 'BackBeat'), (10, 'Billy Cobham')]

 

이제 Chinook.db를 가져와서 파이썬으로 질의를 수행하고 결과를 얻을 수 있습니다.

이제 LLM에 연결해 보도록 하겠습니다.

Chain (체인)

체인(즉, LangChain 런처블의 구성)은 단계를 예측할 수 있는 애플리케이션을 지원합니다. 질문을 받아 다음을 수행하는 간단한 체인을 만들 수 있습니다:

  • 자연어 질문을 SQL 쿼리로 변환합니다.
  • SQL 쿼리를 DB에 실행합니다.
  • DB로부터 받은 SQL 질의 결과를 사용하여 원래 질문에 자연어로 답합니다.

이 흐름에서 지원되지 않는 시나리오도 있습니다. 예를 들어, 이 시스템은 모든 사용자 입력에 대해 SQL 쿼리를 실행합니다. 심지어 "hello"도 마찬가지입니다. 중요한 것은 아래에서 살펴보겠지만 일부 질문에 답변하기 위해서는 두 개 이상의 쿼리가 필요하다는 것입니다. 이러한 시나리오는 Agents 섹션에서 다루겠습니다.

질문을 SQL 쿼리로 변환하기

SQL 체인 또는 에이전트의 첫 번째 단계는 사용자 입력을 받아 SQL 쿼리로 변환하는 것입니다. LangChain에는 이를 위한 기본 제공 체인인 create_sql_query_chain이 있습니다.

pip install -qU langchain-openai

위의 pip 명령은 lanchain-openai 패키지를 설치하라는 명령입니다. 그중 옵션으로 준 qU의 의미는 다음과 같습니다.

 

-q 옵션: "quiet"를 의미하며, 이 옵션을 사용하면 pip가 출력하는 메시지의 양을 줄입니다.
-U 옵션: "upgrade"를 의미하며, 이 옵션을 사용하면 pip가 이미 설치된 패키지를 최신 버전으로 업그레이드합니다. 이 옵션 없이 pip install 명령을 실행하면, 패키지가 이미 설치되어 있다면 pip는 패키지가 이미 설치되어 있다는 메시지를 출력하고 종료합니다.

이렇게 설치를 한 후에 다음의 파이썬 코드를 convertQuestionToSQL.py 파일로 작성합니다.

import getpass
import os

from langchain_community.utilities import SQLDatabase

db = SQLDatabase.from_uri("sqlite:///Chinook.db")

os.environ["OPENAI_API_KEY"] = getpass.getpass()

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-3.5-turbo-0125")

from langchain.chains import create_sql_query_chain

chain = create_sql_query_chain(llm, db)

response = chain.invoke({"question": "How many employees are there"})

db.run(response)

 

이제 위의 코드를 실행하면 Password라는 항목이 나타나면서 openai에서 할당받은 API Key를 요청합니다.

여기에 openai api key를 넣으면 됩니다. 그러나, 저는 무료 플랜을 사용하고 있어서 다음과 같은 에러가 발생합니다.

[ 에러메시지 ]
{'message': 'You exceeded your current quota, please check your plan and billing details. For more information on this error, read the docs: https://platform.openai.com/docs/guides/error-codes/api-errors.', 'type': 'insufficient_quota', 'param': None, 'code': 'insufficient_quota'}
~/langchain$ python3 convertQuestionToSQL.py
Password:
Traceback (most recent call last):
  File "/home/altibase/langchain/convertQuestionToSQL.py", line 18, in <module>
    response = chain.invoke({"question": "How many employees are there"})
  File "/home/altibase/.local/lib/python3.10/site-packages/langchain_core/runnables/base.py", line 2399, in invoke
    input = step.invoke(
  File "/home/altibase/.local/lib/python3.10/site-packages/langchain_core/runnables/base.py", line 4433, in invoke
    return self.bound.invoke(
  File "/home/altibase/.local/lib/python3.10/site-packages/langchain_core/language_models/chat_models.py", line 170, in invoke
    self.generate_prompt(
  File "/home/altibase/.local/lib/python3.10/site-packages/langchain_core/language_models/chat_models.py", line 599, in generate_prompt
    return self.generate(prompt_messages, stop=stop, callbacks=callbacks, **kwargs)
  File "/home/altibase/.local/lib/python3.10/site-packages/langchain_core/language_models/chat_models.py", line 456, in generate
    raise e
  File "/home/altibase/.local/lib/python3.10/site-packages/langchain_core/language_models/chat_models.py", line 446, in generate
    self._generate_with_cache(
  File "/home/altibase/.local/lib/python3.10/site-packages/langchain_core/language_models/chat_models.py", line 671, in _generate_with_cache
    result = self._generate(
  File "/home/altibase/.local/lib/python3.10/site-packages/langchain_openai/chat_models/base.py", line 537, in _generate
    response = self.client.create(messages=message_dicts, **params)
  File "/home/altibase/.local/lib/python3.10/site-packages/openai/_utils/_utils.py", line 277, in wrapper
    return func(*args, **kwargs)
  File "/home/altibase/.local/lib/python3.10/site-packages/openai/resources/chat/completions.py", line 590, in create
    return self._post(
  File "/home/altibase/.local/lib/python3.10/site-packages/openai/_base_client.py", line 1240, in post
    return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls))
  File "/home/altibase/.local/lib/python3.10/site-packages/openai/_base_client.py", line 921, in request
    return self._request(
  File "/home/altibase/.local/lib/python3.10/site-packages/openai/_base_client.py", line 1005, in _request
    return self._retry_request(
  File "/home/altibase/.local/lib/python3.10/site-packages/openai/_base_client.py", line 1053, in _retry_request
    return self._request(
  File "/home/altibase/.local/lib/python3.10/site-packages/openai/_base_client.py", line 1005, in _request
    return self._retry_request(
  File "/home/altibase/.local/lib/python3.10/site-packages/openai/_base_client.py", line 1053, in _retry_request
    return self._request(
  File "/home/altibase/.local/lib/python3.10/site-packages/openai/_base_client.py", line 1020, in _request
    raise self._make_status_error_from_response(err.response) from None
openai.RateLimitError: Error code: 429 - {'error': {'message': 'You exceeded your current quota, please check your plan and billing details. For more information on this error, read the docs: https://platform.openai.com/docs/guides/error-codes/api-errors.', 'type': 'insufficient_quota', 'param': None, 'code': 'insufficient_quota'}}
altibase@altibase-KVM:~/langchain$ vi convertQuestionToSQL.py

 

위의 에러메시지를 잘 분석해 보니 llm에 요청할 때 에러가 발생하고 있습니다.

 

실제 정상적인 결과는 다음과 같이 나온다고 합니다.

'[(8,)]'

 

유료플랜 있으신 분들은 해보시고 댓글 남겨주시면 감사하겠습니다.

api key만 정상적이면 잘 될 것 같습니다.

facebook twitter kakaoTalk kakaostory naver band shareLink