# Что такое Инструменты?

Одним из важнейших аспектов AI Агентов является их способность предпринимать **действия**. Как мы видели, это происходит благодаря использованию **Инструментов**.

В этом разделе мы узнаем, что такое Инструменты, как их эффективно разработать и как интегрировать их в вашего Агента с помощью Системного Сообщения.

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

## Что такое AI Инструменты?

** Инструмент - это функция, предоставленная LLM**. Эта функция должна выполнять **четкую цель**.

Вот некоторые часто используемые в AI агентах инструменты:

| Инструмент | Описание |
|----------------|---------------------------------------------------------------|
| Веб-поиск | Позволяет агенту получать актуальную информацию из Интернета. |
| Генерация изображений | Создает изображения на основе текстовых описаний. |
| Извлечение      | Извлекает информацию из внешнего источника.                 |
| | Интерфейс API | Взаимодействие с внешним API (GitHub, YouTube, Spotify и т. д.). |

Это лишь примеры, поскольку на самом деле вы можете создать инструмент для любого случая использования!

Хороший инструмент должен быть чем-то, что **дополняет возможности LLM**. 

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

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

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

- Инструмент должен:

  - **иметь текстовое описание того, что делает функция**.
  - *быть Вызываемым (Callable)* (чем-то, что выполняет действие).
  - *иметь Аргументы* с типизацией.
  - (Необязательно) иметь Выходные данные с типизацией.

## Как работают инструменты?

Как мы видели, **LLM могут только получать текстовые данные на вход и генерировать текстовые данные на выход. У них нет возможности самостоятельно вызывать инструменты. Когда мы говорим о _предоставлении инструментов агенту_, мы имеем в виду, что мы **обучаем** LLM существованию инструментов и просим модель генерировать текст, который будет вызывать инструменты, когда это необходимо. Например, если мы предоставим инструмент для проверки погоды в определенном месте из Интернета, а затем спросим LLM о погоде в Париже, LLM распознает этот вопрос как релевантную возможность использовать инструмент "weather", которой мы его научили. LLM сгенерирует _текст_ в виде кода, чтобы вызвать этот инструмент. Ответственность **Агента** заключается в том, чтобы проанализировать вывод LLM, распознать, что требуется вызов инструмента, и вызвать его от имени LLM. Выходные данные от инструмента будут отправлены обратно в LLM, которая составит окончательный ответ для пользователя.

Выходные данные после вызова инструмента - это еще один тип сообщений в диалоге. Шаги вызова инструмента обычно не демонстрируются пользователю: агент извлекает диалог, вызывает инструмент(ы), получает выходные данные, добавляет их в новое сообщение диалога и снова отправляет обновленный диалог в LLM. С точки зрения пользователя это выглядит так, как будто LLM использовал инструмент, но на самом деле это сделал наш код приложения (**Агент**).

Мы поговорим об этом процессе подробнее на следующих занятиях.

## Как мы даем инструменты LLM?

Полный ответ может показаться непомерно сложным, но мы, по сути, используем системную подсказку для предоставления текстовых описаний доступных модели инструментов:

Чтобы это сработало, мы должны быть очень точны и аккуратны в отношении:

1. **Что делает инструмент**.
2. **Каких именно входных данных он ожидает**.

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

Если это кажется слишком теоретическим, давайте разберемся на конкретном примере.

Мы реализуем упрощенный **калькулятор**, который будет просто перемножать два целых числа. Это может быть наша реализация на Python:

```python
def calculator(a: int, b: int) -> int:
    """Multiply two integers."""
    return a * b
```

Итак, наш инструмент называется `calculator`, он **перемножает два целых числа**, и ему требуются следующие входные данные:

- **`a`** (*int*): Целое число.
- **`b`** (*int*): Целое число.

На выходе получается другое целое число, которое можно описать следующим образом:
- (*int*): Произведение `a` и `b`.

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

```text
Tool Name: calculator, Description: Multiply two integers., Arguments: a: int, b: int, Outputs: int
```

> **Напоминание:** Это текстовое описание - *то, что мы хотим, чтобы LLM знала об инструменте*.

Когда мы передадим предыдущую строку как часть входных данных в LLM, модель распознает ее как инструмент, и будет знать, что ему нужно передавать в качестве входных данных и что ожидать от выходных данных.

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

Есть ли лучший способ?

### Автоформатирование секции Инструменты

Наш инструмент написан на Python, и его реализация уже предоставляет все, что нам нужно:

- Описательное название того, что он делает: `calculator`.
- Более длинное описание, представленное в комментарии к docstring функции: `Multiply two integers.`.
- Входные данные и их тип: функция явно ожидает два `int`.
- Тип выходных данных.

Люди не просто так используют языки программирования: они выразительны, кратки и точны.

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

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

После этого нам останется только использовать декоратор Python, чтобы указать, что функция `calculator` является инструментом:

```python
@tool
def calculator(a: int, b: int) -> int:
    """Multiply two integers."""
    return a * b

print(calculator.to_string())
```

Обратите внимание на декоратор `@tool` перед определением функции.

С помощью реализации, которую мы рассмотрим далее, мы сможем автоматически извлекать следующий текст из исходного кода с помощью функции `to_string()`, предоставляемой декоратором:

```text
Tool Name: calculator, Description: Multiply two integers., Arguments: a: int, b: int, Outputs: int
```

Как видите, это то же самое, что мы уже писали вручную!

### Универсальная реализация Инструмента

Мы создаем общий класс `Tool`, который мы можем использовать каждый раз, когда нам нужно использовать инструмент.

> **Отказ от ответственности:** Этот пример реализации является вымышленным, но очень похож на реальные реализации в большинстве библиотек.

```python
class Tool:
    """
    Класс, представляющий многократно используемый фрагмент кода (инструмент).

    Атрибуты:
        name (str): Имя инструмента.
        description (str): Текстовое описание того, что делает инструмент.
        func (вызываемый): Функция, которую оборачивает этот инструмент.
        arguments (список): Список аргументов.
        outputs (str или list): Возвращаемые обернутой функцией типы.
    """
    def __init__(self, 
                 name: str, 
                 description: str, 
                 func: callable, 
                 arguments: list,
                 outputs: str):
        self.name = name
        self.description = description
        self.func = func
        self.arguments = arguments
        self.outputs = outputs

    def to_string(self) -> str:
        """
        Возвращает строковое представление инструмента, 
        включая его название, описание, аргументы и выходные данные.
        """
        args_str = ", ".join([
            f"{arg_name}: {arg_type}" for arg_name, arg_type in self.arguments
        ])
        
        return (
            f"Tool Name: {self.name},"
            f" Description: {self.description},"
            f" Arguments: {args_str},"
            f" Outputs: {self.outputs}"
        )

    def __call__(self, *args, **kwargs):
        """
        Вызов базовой функции (вызываемой) с указанными аргументами.
        """
        return self.func(*args, **kwargs)
```

Это может показаться сложным, но если мы медленно пройдемся по нему, то сможем понять, что он делает. Мы определяем класс **`Tool`**, который включает в себя:

- **`name`** (*str*): Название инструмента.
- **`description`** (*str*): Краткое описание того, что делает инструмент.
- **`function`** (*callable*): Функция, которую выполняет инструмент.
- **`arguments`** (*list*): Ожидаемые входные параметры.
- **`outputs`** (*str* или *list*): Ожидаемые выходные данные инструмента.
- **`__call__()`**: Вызывает функцию при вызове экземпляра инструмента.
- **`to_string()`**: Преобразует атрибуты инструмента в текстовое представление.

Мы можем создать инструмент с помощью этого класса, используя следующий код:

```python
calculator_tool = Tool(
    "calculator",                   # имя
    "Multiply two integers.",       # описание
    calculator,                     # функция для вызова
    [("a", "int"), ("b", "int")],   # водные данные (имена и типы)
    "int",                          # выходные данные
)
```

Но мы также можем использовать модуль Python `inspect`, чтобы получить всю информацию за нас! Вот что делает декоратор `@tool`.

> Если вам интересно, вы можете посмотреть на реализацию декоратора в следующем разделе.

 код декоратора

```python
def tool(func):
    """
    Декоратор, создающий экземпляр Tool из заданной функции.
    """
    # Получение сигнатуры функции
    signature = inspect.signature(func)
    
    # Извлеките пары (param_name, param_annotation) для входных данных
    arguments = []
    for param in signature.parameters.values():
        annotation_name = (
            param.annotation.__name__ 
            if hasattr(param.annotation, '__name__') 
            else str(param.annotation)
        )
        arguments.append((param.name, annotation_name))
    
    # Определите аннотацию возврата
    return_annotation = signature.return_annotation
    if return_annotation is inspect._empty:
        outputs = "No return annotation"
    else:
        outputs = (
            return_annotation.__name__ 
            if hasattr(return_annotation, '__name__') 
            else str(return_annotation)
        )
    
    # Используйте строку документации функции в качестве описания (по умолчанию, если None)
    description = func.__doc__ or "Описание не представлено."
    
    # Имя функции становится именем Инструмента
    name = func.__name__
    
    # Возвращаем новый экземпляр Инструмента
    return Tool(
        name=name, 
        description=description, 
        func=func, 
        arguments=arguments, 
        outputs=outputs
    )
```

Повторимся, что с этим декоратором мы можем реализовать наш инструмент следующим образом:

```python
@tool
def calculator(a: int, b: int) -> int:
    """Multiply two integers."""

    return a * b

print(calculator.to_string())
```

И мы можем использовать метод `Tool` `to_string` для автоматического получения текста, подходящего для использования в качестве описания инструмента для LLM:

```text
Tool Name: calculator, Description: Multiply two integers., Arguments: a: int, b: int, Outputs: int
```

Описание **вставляется** в системную подсказку. Если взять пример, с которого мы начали этот раздел, то вот как он будет выглядеть после замены `tools_description`:

В разделе [Действия](actions) мы узнаем, как агент может **вызвать** инструмент, который мы только что создали.

---

Инструменты играют решающую роль в расширении возможностей AI агентов.

Подводя итоги, мы узнали:

- *Что такое инструменты*: Функции, которые предоставляют LLM дополнительные возможности, такие как выполнение вычислений или доступ к внешним данным.

- *Как определить инструмент*: Предоставить четкое текстовое описание, входы, выходы и вызываемую функцию.

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

Теперь мы можем перейти к [Рабочему процессу Агента](agent-steps-and-structure), где вы увидите, как Агент наблюдает, думает и действует. Это **собирает воедино все, что мы изучили до сих пор**, и закладывает основу для создания вашего собственного полнофункционального AI Агента.

Но сначала - еще один короткий тест!

