LangChain для бывалых: создаем свои инструменты

—

от автора

Наконец мы добрались до заключительной части сериала о фреймворке 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 в телеграм.


ссылка на оригинал статьи https://habr.com/ru/articles/735920/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *