Добро пожаловать во вторую часть статьи о фреймворке LangChain. В предыдущей части мы научились добавлять память в диалоги с LLM, а также создали простого агента, который подключался к поисковой системе Google. Некоторые запросы вызвали беспокойство модераторов на Хабре и они попросили добавить немного цензуры отредактировать статью. Но мы знаем, что рукописи не горят, поэтому с оригинальным фрагментом можно познакомиться здесь
Как уже отмечено в предыдущей части, агенты являются одним из самых мощных инструментов фреймворка LangChain. Они позволяют подключаться к внешним ресурсам, что значительно расширяет возможности языковой модели.
В этой части мы перейдем к более продвинутым возможностям агентов и узнаем, как использовать их для работы с собственной базой данных и моделирования.
Cоздаем базу данных
Чтобы подключить агента к базе данных, необходимо сначала создать саму базу данных. Представим, что наш заказчик бота — небольшой магазин, специализирующийся на продаже футболок с принтами. Допустим, его база имеет следующую структуру:
Таблица tshirts:
-
Поля:
-
id: Уникальный идентификатор футболки
-
type: Тип футболки (например, «Type 1», «Type 2» и т.д.)
-
color: Цвет футболки
-
size: Размер футболки
-
quantity: Количество футболок в наличии
-
print_id: Внешний ключ на таблицу «prints»
-
price: Цена футболки с учетом атрибутов
-
Таблица prints:
-
Поля:
-
id: Уникальный идентификатор принта
-
name: Название принта
-
image_url: URL изображения принта
-
Таблица clients:
-
Поля:
-
id: Уникальный идентификатор клиента
-
name: Имя клиента
-
address: Адрес клиента
-
contact_info: Контактная информация клиента
-
Таблица orders :
-
Поля:
-
id: Уникальный идентификатор заказа
-
order_date: Дата заказа
-
client_id: Внешний ключ на таблицу «clients»
-
price: Общая стоимость заказа
-
Структура не идеальна, но лучше пока ее не усложнять.
Теперь можно воплотить идею в коде. Я буду использовать SQLAlchemy. С помощью SQLAlchemy можно создавать объекты, которые представляют таблицы в базе данных, и работать с ними через python. Она упрощает взаимодействие с базами данных и помогает избежать ошибок, связанных с написанием SQL‑запросов.
Возможно, у вас возникнет вопрос: зачем заморачиваться, если можно просто скачать готовую базу данных и протестировать ее. Действительно, это более простой способ. Но за простоту мы заплатим свободой и гибкостью. К тому же инструменты LangChain работают с алхимией, поэтому будет полезно попробовать этот фреймворк в работе.
Код для создания базы:
from sqlalchemy import Column, Integer, String, ForeignKey, Date, Numeric, Table from sqlalchemy.orm import relationship from sqlalchemy.orm import declarative_base from sqlalchemy import create_engine Base = declarative_base() class TShirt(Base): __tablename__ = 'tshirts' id = Column(Integer, primary_key=True) type = Column(String) # Тип футболки (например, "Type 1", "Type 2" и т.д.) color = Column(String) size = Column(String) quantity = Column(Integer) # Количество футболок в наличии print_id = Column(Integer, ForeignKey('prints.id')) # Внешний ключ на таблицу "Prints" print = relationship("Print") # Отношение между футболками и принтами price = Column(Numeric(10, 2)) # Цена футболки с учетом атрибутов def calculate_tshirt_price(self): base_price = 500.0 type_multiplier = 1.2 color_multiplier = 1.3 size_multiplier = 1 if self.size in ['XL', 'XXL']: size_multiplier = 1.5 price = base_price * type_multiplier * color_multiplier * size_multiplier self.price = round(price, 2) class Print(Base): __tablename__ = 'prints' id = Column(Integer, primary_key=True) name = Column(String) # Название принта image_url = Column(String) # URL изображения принта class Client(Base): __tablename__ = 'clients' id = Column(Integer, primary_key=True) name = Column(String) # Имя клиента address = Column(String) # Адрес клиента contact_info = Column(String) # Контактная информация клиента orders = relationship("Order", back_populates="client") # Отношение order_tshirt_table = Table('order_tshirt', Base.metadata, Column('order_id', Integer, ForeignKey('orders.id'), primary_key=True), Column('tshirt_id', Integer, ForeignKey('tshirts.id'), primary_key=True) ) class Order(Base): __tablename__ = 'orders' id = Column(Integer, primary_key=True) order_date = Column(Date) # Дата заказа client_id = Column(Integer, ForeignKey('clients.id')) # Внешний ключ на таблицу "Clients" client = relationship("Client", back_populates="orders") # Отношение между заказами и клиентами tshirts = relationship("TShirt", secondary=order_tshirt_table) quantity = Column(Integer) price = Column(Numeric(10, 2)) # Общая стоимость заказа def calculate_order_price(self): total_price = 0.0 self.quantity = len(self.tshirts) for tshirt in self.tshirts: total_price += tshirt.price self.price = round(total_price, 2) * self.quantity def create_tables(engine): Base.metadata.create_all(engine) print('Tables created successfully!') if __name__ == '__main__': # Создаем экземпляр движка базы данных engine = create_engine('sqlite:///tshirt_store.db') print('Сreate engine') # Создаем таблицы create_tables(engine)
Заполняем базу
Хорошо, давайте заполним базу данных футболками. Вот пример кода, который поможет нам насемплировать любое количество футболок с использованием SQLAlchemy:
import random from random import randint from create_database import TShirt, Print from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker def random_color(): colors = ['Red', 'Blue', 'Green', 'Yellow', 'Black', 'White'] return random.choice(colors) def random_size(): sizes = ['S', 'M', 'L', 'XL', 'XXL'] return random.choice(sizes) def create_tshirt_samples(session, num_samples): tshirt_samples = [] print_samples = [] for _ in range(num_samples): tshirt = TShirt( type=f'Type {randint(1, 5)}', color=random_color(), size=random_size(), quantity=randint(1, num_samples // 5), print_id=0, # Заглушка для внешнего ключа, будет обновлено ниже price=0.0 ) tshirt.calculate_tshirt_price() # Вычисление цены футболки tshirt_samples.append(tshirt) print = Print( name=f'Print_{randint(1, num_samples * 2)}', image_url=f'https://example.com/print_{randint(1, 10)}.jpg' ) print_samples.append(print) session.add_all(print_samples) session.flush() # Получение сгенерированных ID принтов for i, tshirt in enumerate(tshirt_samples): tshirt.print_id = print_samples[i].id session.add_all(tshirt_samples) session.commit() if __name__ == '__main__': # Создаем экземпляр движка базы данных engine = create_engine('sqlite:///tshirt_store.db') print('Сreate engine') Session = sessionmaker(bind=engine) session = Session() create_tshirt_samples(session, 50) print('Shirts successfully added into DB') session.close()
Если ничего не упадет, то у вас должна появиться табличка с футболками:
Дальше нужно сформировать заказы. Попробуем сделать это согласно определенной функциональной зависимости: например периодической с наличием небольшого тренда.
import random from datetime import datetime, timedelta from random import randint from create_database import Order, Client, TShirt from sqlalchemy.orm import sessionmaker from sqlalchemy import create_engine, func from math import sin, pi def create_random_client(n): # Создание случайного клиента с использованием случайно сгенерированных имени, адреса и контактной информации name = f'Client_{randint(1, n)}' address = f'Address_{randint(1, n)}' contact_info = f'Contact_{randint(1, n)}' return Client(name=name, address=address, contact_info=contact_info) def get_random_client(session, r_id=1000, client_proba=0.5): # Получение случайного клиента из базы данных client = session.query(Client).order_by(func.random()).first() if client is None or client_proba < 0.5: # Создание случайного клиента, если база данных клиентов пуста или вероятность меньше 0.5 client = create_random_client(r_id) session.add(client) session.commit() return client def get_random_tshirts(session, num_tshirts): # Получение случайного количества футболок из базы данных tshirts = session.query(TShirt).order_by(func.random()).limit(num_tshirts).all() return tshirts def create_orders( session, start_date, end_date, order_function, ): current_date = start_date while current_date <= end_date: # Получение количества заказов на текущую дату с использованием функции order_function num_orders = order_function(current_date) for _ in range(num_orders): # Получение случайного клиента client = get_random_client(session, client_proba=random.random()) # Создание экземпляра класса Order с указанной датой и клиентом order = Order(order_date=current_date, client=client) order.client = client # Получение случайных футболок tshirts = get_random_tshirts(session, randint(1, num_orders)) order.tshirts = tshirts # Вычисление общей цены заказа order.calculate_order_price() session.add(order) current_date += timedelta(days=1) session.commit() def order_function(date): day_of_year = date.timetuple().tm_yday amplitude = 10 # Амплитуда количества заказов period = 7 # Период синусоиды в днях angle = 2 * pi * (day_of_year % period) / period num_orders = amplitude * (sin(angle) + 1) return int(num_orders * day_of_year / 365) if __name__ == '__main__': # Создаем экземпляр движка базы данных engine = create_engine('sqlite:///tshirt_store.db') print('Сreate engine') Session = sessionmaker(bind=engine) session = Session() start_date = datetime(2023, 5, 1) end_date = datetime(2023, 6, 30) create_orders(session, start_date, end_date, order_function) print('Orders successfully added into DB') session.close()
Должна появится похожая на эту таблица:
Теперь у нас есть фундамент для работы агента.
Создаем агента
Для удобства дальнейшего воспроизведения, код я буду писать в сниппетах, а результаты выводить в виде скринов ячеек jupyter notebook. Напомню три источника, три составные части агента:
-
Языковая модель
-
Инструмент
-
Тело агента
import os from langchain.sql_database import SQLDatabase from langchain.chains import SQLDatabaseChain from langchain.chat_models import ChatOpenAI from langchain.agents import Tool from langchain.agents import initialize_agent # Инициализируем языковую модель llm = ChatOpenAI( openai_api_key=open_ai_api_key, model_name='gpt-3.5-turbo', temperature=0 ) # Создаем движок для работы с БД engine = create_engine('sqlite:///tshirt_store.db') # Регистрируем движок в langchain db = SQLDatabase(engine) # Создаем цепочку для работы с базой sql_chain = SQLDatabaseChain( llm=llm, database=db, verbose=True ) # Создаем на основе цепочки инструмент db_tool = Tool( name='tshirt store db', func=sql_chain.run, description='Use for extaract data from database' ) # Ну и наконец создаем самого агента(пока без использования памяти) db_agent = initialize_agent( agent='zero-shot-react-description', tools=[db_tool], llm=llm, verbose=True, max_iterations=5, )
Пора начинать задавать вопросы нашему эксперту:
Неплохо, попробуем задать вопрос посложнее:
Видно, что цепочка рассуждений верна. Агент даже правильно понял, как связать различные таблицы, чтобы получить верный ответ.
На самом деле, запрос к нашей базе можно сделать и без агента, а напрямую через цепочку sql_chain:
Но вот уже со вторым запросом цепочка не справилась:

Посмотрим, как выглядит шаблон промпта нашего агента:
А так выглядит промпт для цепочки:
Этот пример еще раз напоминает нам о важности промптов для языковой модели. Если вы хотите познать грамоту промптинга, то Эндрю Ын запилил бесплатный курс, в котором основной акцент делается на основах, но все равно он довольно полезен.
Кажется, что агент работает идеально, но сломать его оказалось довольно просто:
Что же делать? Можно попытаться модифицировать промпт. Однако, есть и другой путь — воспользоваться более мощным инструментом. В LangChain есть SQLDatabaseToolkit, под капотом этого швейцарского ножа сразу 4 инструмента:
-
QuerySQLDataBaseTool
-
InfoSQLDatabaseTool
-
ListSQLDatabaseTool
-
QueryCheckerTool
Посмотрим, смогут ли они нам помочь. Но сначала нужно инициализировать нового агента:
from langchain.agents import create_sql_agent from langchain.agents.agent_toolkits import SQLDatabaseToolkit toolkit = SQLDatabaseToolkit(db=db, llm=llm) db_agent_toolkit = create_sql_agent( llm=llm, toolkit=toolkit, verbose=True )
Задаем тот же вопрос:
Преимущества налицо: во‑первых, агент понимает, какие таблицы ему доступны; во‑вторых, работает обработчик запросов, который не позволяет агенту упасть при неправильной формулировке.
Мы научились извлекать информацию из базы данных. Почему бы нам не научить агента строить модели на основе этих данных? Изначально я планировал написать свой собственный инструмент для этой задачи, но оказалось, что уже существует готовая реализация в виде PythonREPLTool. Ну что ж, давайте добавим и этот инструмент в наш арсенал.
from langchain.agents.agent_toolkits import create_python_agent from langchain.tools.python.tool import PythonREPLTool agent_model = create_python_agent( llm=llm, tool=PythonREPLTool(), verbose=True )
Попробуем спрогнозировать заказы на футболки:
Ну что, кожаные, впечатлены? Время эльфов людей прошло, настало время агентов. На это й оптимистичной ноте с агентами можно пока закончить. В заключительной части мы попробуем написать собственные инструменты для работы с агентами. Спасибо за внимание!
Пишу про AI и NLP в телеграм.
ссылка на оригинал статьи https://habr.com/ru/articles/734146/
Добавить комментарий