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

Сегодня попробуем разобраться в инструментах(Tool). Инструменты являются исполнительной частью агента, которая как раз и добавляет языковой модели дополнительную функциональность. Например, LLM может наврать в базовых арифметических операциях, и лучше доверить вычисления калькулятору. В этот момент и приходит на помощь tool. В самом фреймворке уже есть готовые реализации для популярных задач, но они, естественно, не могут покрыть весь спектр потребностей, поэтому разработчики предусмотрели создание пользовательских типов.

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

Решаем уравнения с помощью LLM

Попробуем обучить ChatGPT(в агентах я пока буду использовать только эту модель) решать кубические уравнения.

Сначала зададим вопрос напрямую:

from langchain.chat_models import ChatOpenAI
from langchain.prompts.chat import HumanMessage

llm = ChatOpenAI(openai_api_key=open_ai_api_key, model_name='gpt-3.5-turbo', temperature=0.0)

answer = llm([HumanMessage(content='Реши кубическое уравнение 3*x**3 - 5*x**2 + 2*x - 13')])

Ответ ChatGPT:

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

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

from langchain.tools import BaseTool
from numpy.polynomial import Polynomial

from langchain.agents import initialize_agent


class PolynomRootsTool(BaseTool):
    name = 'Решатель полиномиальных уравнений'
    # В описании указываем модели, когда следует вызывать этот инструмент
    description = 'Используй этот иструмент для решения линейных, квадратных и уравнений более высоких степеней.\
    На вход подавай коэффициенты уравнения(только числа). Если какого-то коэффициента нет, значит его значение равно 0'

	def _run(self, inp):
        coefs = [int(coef.strip()) for coef in inp.split(',')]
        # В Polynomial надо подавать коэффициенты в обратном порядке
        p = Polynomial(coefs[::-1])
        return p.roots()
    
    def _arun(self, *coefs):
        raise NotImplementedError('Does not support async')

Здесь стоит обратить внимание на description - подсказка для LLM о том, как правильно использовать инструмент. Можете проэкспериментировать с разными вариациями промпта и самостоятельно убедиться, насколько важно грамотно продумывать такие описания.

Теперь можно создать агента и вызвать наш инструмент:

tools = [PolynomRootsTool()]

agent = initialize_agent(
    agent='zero-shot-react-description',
    tools=tools,
    llm=llm,
    verbose=True,
    max_iterations=3,
    early_stopping_method='generate',
)
agent.run('Реши кубическое уравнение x**3 + 5*x**2 + 2*x - 12')

Неплохо справился. Правда иногда он не воспринимает комплексные числа, поэтому промпт требует доработки. Напомню, что можно всегда вытащить ваш шаблон для модели и отредактировать.

Учим бота рисовать графики

В прошлый раз наш агент получил доступ к python и попробовал написать простенькую модельку для анализа продаж. Вроде даже что-то получилось, но качество модели было так себе. Будет значительно удобней, если мы явно укажем агенту, какую модель стоит использовать для прогноза. Но сначала нам нужно получить данные:

from langchain.agents import create_sql_agent
from langchain.agents.agent_toolkits import SQLDatabaseToolkit

from sqlalchemy import create_engine

engine = create_engine('sqlite:///tshirt_store.db')
db = SQLDatabase(engine)

toolkit = SQLDatabaseToolkit(db=db, llm=llm)

db_agent_toolkit = create_sql_agent(
    llm=llm,
    toolkit=toolkit,
    verbose=True
)

db_data = db_agent_toolkit.run('Выведи количество заказов за каждый день мая 2023 года, игнорируй лимит. В качестве ответа предоставь список из кортежей (дата, количество)')

Отлично. Данные в кармане. Теперь попробуем визуализировать цифры:

import matplotlib.pyplot as plt
from ast import literal_eval

class SalesVisualizerTool(BaseTool):
    name = 'Визуализатор данных о продажах'
    description = 'Используй этот инструмент для визуализации данных о продажах магазина. В качестве входных данных используй данные о продажах'
    def _run(self, inp):
        # Преобразование строки в список кортежей
        sales_data = literal_eval(inp)
        # Распаковка данных в два списка для дат и продаж
        dates, sales = zip(*sales_data)
        # Создание графика
        plt.figure(figsize=(10, 6))
        plt.plot(dates, sales, marker='o')
        
        plt.xlabel('Дата')
        plt.xticks(range(0, len(dates), 5), dates[::5])
        
        plt.ylabel('Количество заказов')
        
        plt.title('Данные о продажах')
        plt.grid(True)
        plt.show()

    def _arun(self, *sales_data):
        raise NotImplementedError('Does not support async')

Хорошо, инструмент мы создали, но как передать в него данные, которые мы запрашиваем из базы данных?

Когда мы создаем агента, LangChain позволяет добавить в него не один, а множество инструментов : tools = [PolynomRootsTool()]. Однако наш случай немного отличается. Дело в том, что мы воспользовались специальным типом агента create_sql_agent и добавили в него тулкит из коробки toolkit = SQLDatabaseToolkit(db=db, llm=llm). Это уже не обычный список, в который можно просто так добавить новый элемент.

Давайте посмотрим на объект SQLDatabaseToolkit:

class SQLDatabaseToolkit(BaseToolkit):  
    """Toolkit for interacting with SQL databases."""  
  
    db: SQLDatabase = Field(exclude=True)  
    llm: BaseLanguageModel = Field(exclude=True)  
  
    @property  
    def dialect(self) -> str:  
        """Return string representation of dialect to use."""  
        return self.db.dialect  
  
    class Config:  
        """Configuration for this pydantic object."""  
  
        arbitrary_types_allowed = True  
  
    def get_tools(self) -> List[BaseTool]:  
        """Get the tools in the toolkit."""  
        return [  
            QuerySQLDataBaseTool(db=self.db),  
            InfoSQLDatabaseTool(db=self.db),  
            ListSQLDatabaseTool(db=self.db),  
            QueryCheckerTool(db=self.db, llm=self.llm),  
        ]

Ага, в методе get_tools как раз и передаются все инструменты для работы с базой данных. Давайте попробуем просто создать дочерний класс и переопределить метод get_tools:

class SQLDatabaseToolkitViz(SQLDatabaseToolkit):
        def get_tools(self):
            return [
                QuerySQLDataBaseTool(db=self.db),
                InfoSQLDatabaseTool(db=self.db),
                #ListSQLDatabaseTool(db=self.db),
                QueryCheckerTool(db=self.db, llm=self.llm),
                SalesVisualizerTool()
            ]

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

Пора пробовать обновленный инструмент:

toolkit = SQLDatabaseToolkitViz(db=db, llm=llm)

db_agent_toolkit = create_sql_agent(
    llm=llm,
    toolkit=toolkit,
    verbose=True
)

db_agent_toolkit.run('сначала найди, а потом визуализируй количество заказов за каждый день мая 2023 года, не используй LIMIT')

Работает. Но в конце агент все равно падает. Дело в том, что при добавлении нашего инструмента мы нарушили "когерентность" промптов, которые закладывали разработчики, и теперь выводы модели не согласуются с запрограмированной логикой.

Нужно будет либо переписывать SQLDatabaseToolkit дальше, либо создавать свой собственный пайплайн инструментов. В конце концов, можно просто передать вывод агента базы на вход другого агента.

Аналогично можно создать инструмент и для моделирования наших данных для получения прогноза. Оставим это в качестве домашнего задания.

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

Пишу про AI и NLP в телеграм.

Комментарии (0)