Руководство по использованию nзапросов на естественном языке (NLQ) для запроса структурированных данных для Insights.

Понимание текста на естественном языке с его непосредственными проблемами двусмысленности и взаимной ссылки было давней проблемой в обработке естественного языка, также известной как НЛП.

Одним из проявлений этой проблемы является то, как можно надежно обеспечить ответы на вопросы в наборе данных путем преобразования естественного разговорного языка в формат структурированного текстового запроса SQL.

Учитывая, что огромное количество современной информации хранится в реляционных базах данных, таких как медицинские записи, финансовые данные, записи клиентов и анализ больших данных, возможность извлечения информации из этих баз данных и хранилищ данных ограничена отчасти из-за необходимости пользователям понять мощные, но сложные языки структурированных запросов.

С появлением ChatGPT и быстрым развитием моделей LLM решение проблемы преобразования текста в SQL стало намного проще. Не технически грамотные пользователи могут использовать декларативный язык для описания предполагаемого запроса. Детали построения запроса делегируются LLM.

Что это значит для тебя?

Возможность использовать естественный язык, чтобы задавать вопросы о вашей базе данных, имеет много потенциальных вариантов использования, таких как:

  • Предоставление информации на основе данных для пользователей без навыков программирования
  • Сокращение времени получения информации в домене
  • Повышение ценности накопленных данных
  • Знание SQL не требуется
  • Никаких знаний в области кодирования не требуется — достаточно базового понимания оперативной инженерии.

По своей простоте мы хотим перейти от этой подсказки

Показать все случаи COVID-19 в Нью-Йорке за последние шесть месяцев

Который будет переведен с помощью GPT в следующий SQL, где выходной оператор SQL адаптирован для целевой базы данных.

SELECT *
FROM covid_cases
WHERE [date] > dateadd(month, -6,  cast(getdate() as date)) 
AND city in ('New York');

Хотя использование моделей LLM делает этот процесс относительно проще, больше внимания будет уделяться следующему:

  1. Инжиниринг подсказок, как и качество извлеченных данных, будет в значительной степени определяться тем, насколько хороши подсказки ваших запросов.
  2. Насколько хорошо помечены ваши структурированные данные.
  3. Структура вашей таблицы и соглашения об именовании, которые вы используете для именования таблиц и столбцов. Неграмотно написанные структуры данных будет трудно понять даже человеку. Следовательно, вам нужно будет свести к минимуму двусмысленность и загадочные разговоры об именах.

Если вы использовали ChatGPT достаточно долго, вы бы поняли, насколько краткими и описательными должны быть данные и входные данные, чтобы в полной мере воспользоваться преимуществами модели. Использование NLP в SQL имеет много преимуществ; он добавляет дополнительный уровень абстракции, который может замедлить процесс поиска. Написание необработанных собственных запросов, которые выполняются к базе данных с хорошо оптимизированными индексами, всегда будет более эффективным, поскольку запросы выполняются непосредственно РСУБД.

В приведенном ниже примере мы воспользуемся пошаговым подходом, чтобы предоставить вашему ChatGPT доступ к вашей базе данных и написать запросы к вашей БД, используя естественный язык.

Начиная

LlamaIndex будет нашей основной библиотекой, поскольку она предоставляет оболочки, которые упрощают процесс подключения к любой базе данных по вашему выбору.

Создайте новый проект Python в своей среде IDE и установите необходимые зависимости PyPi. Мы будем использовать явные версии пакетов, чтобы у вас не возникло никаких проблем.

pip install langchain==0.0.219

pip install transformers==4.30.2

pip install llama-index==0.6.37

Загрузите копию D beaver, чтобы вы могли просматривать данные базы данных RDMS. Пример, который мы будем использовать, можно легко перенести в любую базу данных RDMS по вашему выбору, поскольку мы будем использовать библиотеку SQLachemy для подключения к нашей базе данных.

Давайте заполним наши данные в нашу БД; скорее всего, у вас уже есть существующая база данных, которую вы хотели бы использовать в своей кодовой базе.

Мы будем использовать SQLAlchemy, так как он позволяет нам подключаться к различным типам баз данных.

import time

from langchain import SQLDatabaseChain, PromptTemplate
from langchain.llms import OpenAI
from llama_index import SQLDatabase, GPTSQLStructStoreIndex, LLMPredictor, ServiceContext
from llama_index.indices.struct_store.sql import SQLQueryMode
from llama_index.prompts.prompts import TextToSQLPrompt
from sqlalchemy import create_engine, MetaData, Table, Column, String, Integer, select
from sqlalchemy import insert


engine = create_engine("sqlite:///my_gpt.sqlite3")
metadata_obj = MetaData()

table_name = "city_stats"

city_stats_table = Table(
    table_name,
    metadata_obj,
    Column("city_name", String(16), primary_key=False),
    Column("population", Integer),
    Column("country", String(16), nullable=False),
)
metadata_obj.create_all(engine)


rows = [
    {"city_name": "Toronto", "population": 2930000, "country": "Canada"},
    {"city_name": "Tokyo", "population": 37194000, "country": "Japan"},
    {"city_name": "Chicago", "population": 2679000, "country": "United States"},
    {"city_name": "Seoul", "population": 9776000, "country": "South Korea"},
    {"city_name": "Kuala Lumpur", "population": 1808000, "country": "Malaysia"},
    {"city_name": "Singapore", "population": 5979599, "country": "Singapore"},
    {"city_name": "Jakarta", "population": 11249000, "country": "Indonesia"},
    {"city_name": "Rio de Janeiro", "population": 13728000, "country": "Brazil"},
    {"city_name": "Tianjin", "population": 14239000, "country": "China"},
    {"city_name": "Manila", "population": 11249000, "country": "Philippines"},
    {"city_name": "Kinshasa", "population": 16315534, "country": "Democratic Republic of Congo"},
    {"city_name": "Lagos", "population": 15946000, "country": "Nigeria"},
    {"city_name": "Kolkata", "population": 15333000, "country": "India"},
    {"city_name": "Buenos Aires", "population": 15490000, "country": "Argentia"},
    {"city_name": "Istanbul", "population": 15847768, "country": "Turkey"},
    {"city_name": "Chongqing", "population": 17341000, "country": "China"},
    {"city_name": "Karachi", "population": 17150000, "country": "Pakistan"},
    {"city_name": "Osaka", "population": 19013000, "country": "Japan"},
    {"city_name": "Mumbai", "population": 21297000, "country": "India"},
    {"city_name": "Beijing", "population": 21766214, "country": "China"},
    {"city_name": "Cairo", "population": 22183000, "country": "Egypt"},
    {"city_name": "Dhaka", "population": 23210000, "country": "Bangladesh"},
    {"city_name": "Mexico City", "population": 22281000, "country": "Mexico"},
    {"city_name": "Sao Paulo", "population": 22620000, "country": "Brazil"},
    {"city_name": "Shanghai", "population": 29210808, "country": "China"},
    {"city_name": "Delhi", "population": 32941000, "country": "India"},
]

with engine.begin() as connection:
    for row in rows:
        stmt = insert(city_stats_table).values(**row)
        connection.execute(stmt)

Как использовать Dbeaver со следующими деталями подключения:

Вы можете просмотреть вывод автоматически созданной таблицы.

Внедрение контекста таблицы

Как внедрить контекст для каждой таблицы в приглашение преобразования текста в SQL. Контекст можно добавить вручную или извлечь из неструктурированных документов. По умолчанию мы напрямую вставляем контекст в подсказку. Иногда это невозможно, если контекст большой. Ниже показано, как мы используем структуру данных LlamaIndex для хранения контекста таблицы:

llm_predictor = LLMPredictor(llm=OpenAI(temperature=0, model_name="gpt-3.5-turbo"))
sql_database = SQLDatabase(engine, include_tables=["city_stats"])
service_context = ServiceContext.from_defaults(llm_predictor=llm_predictor)

# The table_name specified here is the table that you
# want to extract into from structured documents.
index = GPTSQLStructStoreIndex.from_documents(
    [],
    service_context=service_context,
    sql_database=sql_database,
    table_name="city_stats",
)

# view current table
with engine.begin() as connection:
    stmt = select(
        city_stats_table.c["city_name", "population", "country"]
    ).select_from(city_stats_table)
    results = connection.execute(stmt).fetchall()
    print(results)

query_engine = index.as_query_engine(query_mode=SQLQueryMode.NL)

response = query_engine.query("Which city has the highest population?")
print(response)

time.sleep(60*3)

response = query_engine.query("How did you derive this answer?")
print(response)

Под капотом GPTSQLStructStoreIndex находится индекс, который использует базу данных SQL. Во время построения индекса данные могут быть извлечены
из неструктурированных документов с учетом запроса на извлечение схемы
или могут быть предварительно загружены в базу данных.

Во время запроса пользователь может указать необработанный SQL-запрос или запрос на естественном языке для получения своих данных.

В нашем случае мы будем явно указывать естественный язык query_mode=SQLQueryMode.NL

Прежде чем запускать код, определите ключ OpenAI API через переменные среды, которые вы можете найти здесь. Как только мы выполним наш код, мы получим следующий вывод:

Мы можем отказаться от индексации и напрямую взаимодействовать с базой данных с помощью класса-оболочки SQLDatabaseChain.

llm = OpenAI(temperature=0)

# Chain for interacting with SQL Database.
db_chain = SQLDatabaseChain(llm=llm, database=sql_database)

response = db_chain.run("Which city has the highest population?")
print(response)

response = db_chain.run("How did you derive this answer and what is your source")
print(response)

def ask_my_db():
    print("Type 'exit' to quit app")

    while True:
        question = input("Enter a NLQ prompt: ")

        if question.lower() == 'exit':
            print('Exiting...')
            break
        else:
            try:
                print(db_chain.run(question))
            except Exception as e:
                print(e)

ask_my_db()

Одна вещь, на которую вы должны обратить внимание, это то, что в классе SQLDatabaseChain нет ни предварительной проверки, ни защиты, чтобы предотвратить отправку злоумышленником запроса, такого как “Drop table <tablename>”. Вы должны разработать способ перехвата и проверки операторов SQL перед их отправкой в ​​базу данных, чтобы предотвратить нежелательные инъекции SQL, которые могут быть потенциально опасными.

На Langchain GitHub есть открытый PR, который вы можете посмотреть для вдохновения. В качестве альтернативы вы можете ограничить доступ к базе данных только для чтения и определить соответствующие тайм-ауты для длительного выполнения запросов, чтобы не перегружать базу данных.

Вы также можете добавить возможность выполнять пробные прогоны, например, при построении запросов к другим типам баз данных, таким как BigQuery, у которых есть стоимость, связанная с каждым выполнением запроса. При работе с большими наборами данных вы можете показать пользователям, что этот запрос будет сканировать XXX ГБ и будет стоить примерно X,XX долларов. Вы хотите продолжить? Да/Нет/Изменить. Вы также можете пойти дальше и внедрить симуляции затрат ChatGPT, которые показывают, сколько будет стоить быстрое выполнение перед фактической цепочкой выполнения.



«Как рассчитать и оптимизировать затраты ChatGPT с помощью Langchain
Рассмотрите, как смоделировать первоначальные затраты и свести к минимуму вызовы API, не разорившись при разработке пользовательского ChatGPT…лучшее программирование .паб"



Как бороться с искаженными SQL-запросами

По мере того, как вы больше работаете с NLQ, если вы можете столкнуться с тем, что некоторые запросы генерируются неправильно или нуждаются в исправлении, каждая СУБД может иметь небольшие различия в ожидаемом синтаксисе SQL. Быстрое решение для этого:

  1. Изменение системной подсказки библиотеки Langchain по умолчанию.
  2. Расширение кода Langchain и анализ входных и выходных ответов в соответствии с вашими требованиями.

Вот пример того, как мы настраиваем подсказки, чтобы указать GPT, как сгенерировать оператор SQL перед отправкой в ​​вашу СУБД, чтобы предотвратить выполнение искаженных запросов SQL.

PROMPT_SUFFIX = """Only use the following tables:
{table_info}

Question: {input}"""

_DEFAULT_TEMPLATE = """Given an input question, first create a syntactically correct {dialect} query to run, then look at the results of the query and return the answer. Unless the user specifies in his question a specific number of examples he wishes to obtain, always limit your query to at most {top_k} results. You can order the results by a relevant column to return the most interesting examples in the database.

You are forbidden to use semicolons (;)

Never query for all the columns from a specific table, only ask for a the few relevant columns given the question.

Pay attention to use only the column names that you can see in the schema description. Be careful to not query for columns that do not exist. Also, pay attention to which column is in which table.

Use the following format:

Question: Question here
SQLQuery: SQL Query to run
SQLResult: Result of the SQLQuery
Answer: Final answer here

"""

PROMPT = PromptTemplate(
    input_variables=["input", "table_info", "dialect", "top_k"],
    template=_DEFAULT_TEMPLATE + PROMPT_SUFFIX,
)

llm = OpenAI(temperature=0)

# Chain for interacting with SQL Database.
db_chain = SQLDatabaseChain.from_llm(llm=llm, db=sql_database, prompt=PROMPT)

response = db_chain.run("Which city has the highest population?")
print(response)

response = db_chain.run("How did you derive this answer and what is your source")
print(response)

Приведенный выше код корректирует исходное приглашение Langchain по умолчанию. Мы указываем GPT не использовать точку с запятой со следующей инструкцией You are forbidden to use semicolons (;)

Последние мысли

Преобразование NLP в SQL — действительно мощная идея, которая дает больше гибкости нетехническим пользователям, а также сводит к минимуму необходимость создания обширных информационных панелей для поиска специальных данных.

Используя LLM, мы намного ближе к абстрагированию больших данных и позволяем владельцам бизнеса и нетехническим пользователям быстро искать данные, не тратя значительное время и ресурсы на создание полноценных аналитических информационных панелей.

Я не рассматриваю это как средство для замены групп бизнес-аналитики, а для расширения нашей работы, где можно исключить специальные задачи и запросы данных, оптимизируя нашу работу.

Я надеюсь, что статья натолкнула вас на новые идеи для интересных вещей, которые вы можете построить на основе преобразования NLP в SQL.