Меня зовут Алексей Петров, я solution-инженер в компании Just AI. Сегодня многие разработчики столкнулись с необходимостью внедрения LLM в свои корпоративные сервисы. В этой статье я попробую доказать, почему интеграция API в данном случае гораздо эффективнее использования AI-плагинов, а также расскажу о том, как интегрировать API Jay Copilot в бизнес-процессы на примере интеграции amoCRM.

AI-плагины не про автоматизацию

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

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

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

Мы решали похожие рутинные задачи и не переставали верить в идеальный мир с одним эндпоинтом. Как вы уже поняли из названия, решение нашлось, благодаря сервису Jay Copilot и его внешнему API.

Что такое Jay Copilot?

Это платформа для удобного использования генеративного ИИ в работе и повседневных задачах. Под капотом в Jay Copilot используются нейросети и сервисы, например: GPT-3.5 и GPT-4, YaGPT-2, GigaChat, Stable Diffusion и DALL·E 3, Whisper, Aimyvoice и собственная языковая модель Just AI, JustGPT.

Jay Copilot объединяет функции этих нейросетей в виде готовых приложений: Анализ файла, Подкасты, Создание документа по образцу, Иллюстратор, Краткое содержание, Поиск по сайту, Программист, Маркетинговое письмо и т.д. И несколько отраслевых бизнес-решений: Аналитик данных, База знаний, Написание вакансии и Анализ отзывов.
Про механику работы этих приложениями можно почитать в документации: https://help.jaycopilot.com/docs/

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

C чего начать работу с API?

В первую очередь стоит начать с изучения возможностей: https://help.jaycopilot.com
Напишите нам в техподдержку: support@just-ai.com для получения ключа API Jay Copilot, указав ваш аккаунт на платформе.

Пример интеграции с CRM

Давайте разберем интеграцию с CRM и Jay Copilot на примере бизнес-кейса.

Описание:

  • Менеджер создает лид в CRM;

  • Документы загружены в раздел «Файлы» в CRM;

  • Cкрипт обращается в CRM и получает URL документа(ов);

  • В Jay создается навык, подходящий для анализа документа;

  • При создании навыка в параметрах указывается URL Документа из CRM и вопросы, на которые необходимо ответить навыку;

  • В результате выполнения скрипта в сделку записывается примечание с ответами на вопросы по документу.

Преднастройки и вспомогательные функции:

  • Python 3;

  • requests для отправки запросов в сервисы;

  • Функция для чтения токенов;

  • Доступ в amo crm и работа с token;

  • Доступ в Jay API и работа с token;

  • Опционально: используем библиотеку amocrm.v2 для работы с amocrm (в примере будет использоваться Tokens, Lead) https://github.com/andrey-tech/amocrm-api-v2-docs

Кодим?

Функции для работы с запросами:

import requests

def handle_response(response):
    if response.status_code == 200:
        return response.json()
    else:
        return response.text  
    
## Блок кода проверяет ответ от сервера после отправки запроса. 
## Если все прошло хорошо (код ответа 200), то он возвращает данные этого ответа в формате JSON. 
## Если что-то пошло не так (код ответа отличается от 200), то возвращает текст ответа, который может содержать информацию об ошибке.
    
def read_token_from_file(filename):
    with open(filename, 'r') as file:
        return file.read().strip()
    
## Блок кода для работы с токенами которые хранятся в файлах: открывает файл с указанным именем и читает из него информацию. 
## Информация, которую он прочитал из файла, возвращается как результат работы функции. 
## Все лишние пробелы в начале и конце строки удаляются.

Сделаем доступ по API до CRM

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

Справа сверху есть раздел webhooks, нужно нажать на троеточие и выбрать «создать интеграцию» и выбрать «внешняя интеграция».

При создании из обязательных полей нам нужен только адрес. Можно даже написать localhost. Но мы возьмем адрес Jay Copilot. Так как AMO проверяет доступность домена, как обязательное условие для работы интеграции.

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

Кстати, небольшой оффтоп: вы можете также найти информацию про подключение своих интеграций в AMO, где говорится, что нужно идти в настройки ЛК и там выпускать bearer токен, но это чуть устаревшая информация. Раньше тут действительно был механизм API ключей, но со слов поддержки CRM он устарел и имел множество недостатков. Поэтому они перешли на механизм [[oAuth2.0]].

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

Далее мы уже можем через API интерфейс обращаться к нашей CRM. Но тут есть нюанс.

В документации разработчиков AMO CRM пишут, что нормальная библиотека, которую поддерживает вендор этой CRM самостоятельно, есть только для PHP, но, думаю, писать код сейчас на PHP мы не будем, поэтому для скорости демонстрации возьмем примеры на Python и готовую библиотеку. Благо, что комьюнити разработчиков у этой CRM действительно большое, и примеры всегда можно найти.

Например, ниже будет пару примеров из мануала, который я нашел на Youtube на канале Сеньор Джуниор: https://www.youtube.com/watch?v=rbwvOOwZxGo

Я возьму одноименную библиотеку amocrm-api https://pypi.org/project/amocrm-api/2.6.1/

Судя по обновлениям она поддерживается и довольно легко устанавливается.

Теперь кодим

Итак, давайте уже напишем немного кода а-ля приложение или AI-агента, который будет ходить как в CRM, так и в API Jay Copilot. Приложению нужно авторизоваться.
Далеко ходить не будем, возьмем метод с токенами из библиотеки для авторизации в amocrm:

##     Данный блок кода предназначен для подключения к сервису AMO CRM через API.
##     Переменные в начале кода - это уникальные ключи и адреса, которые нужны для того, чтобы программа могла войти в ваш аккаунт на сервисе AMO CRM.
##     Библиотека amocrm.v2 помогает программе работать с этими ключами и авторизоваться в системе AMO CRM.
##     Функция default_token_manager создает менеджер токенов, который используется для авторизации в AMO CRM.
##     После выполнения кода, программа сохранит два ключа доступа в два файла: access_token.txt и refresh_token.txt. 
##     Эти ключи будут использоваться для авторизации в AMO CRM при следующих запросах к API.

subdomain="" ##субдомен
client_id="" ## ID интеграции 
client_secret="" ##Секретный ключ
redirect_url="" ## URL который указан в интеграции (в нашем примере https://app.jaycopilot.com/)
refresh_token = ''
## в refresh_token - указывается тот длиииинный код авторизации (действительный 20 мин)"

from amocrm.v2 import tokens

if __name__ == '__main__':

    tokens.default_token_manager( client_id, client_secret, subdomain, redirect_url, 
        storage=tokens.FileTokensStorage(), ## by default FileTokensStorage
        )

    tokens.default_token_manager.init(refresh_token, skip_error=False) 

# Получение нового access token с использованием refresh token
def refresh_access_token():
    token_url = f'https://{subdomain}.amocrm.ru/oauth2/access_token'
    data = {
        'client_id': client_id,
        'client_secret': client_secret,
        'grant_type': 'refresh_token',
        'refresh_token': read_token_from_file('refresh_token.txt')
    }
    response = requests.post(token_url, data=data)
    return response.json().get('access_token')

# Формируем заголовки для запроса в AMOCRM
headers = {
    'Content-Type': 'application/json',
    'Authorization': f'Bearer {read_token_from_file("access_token.txt")}'
}

Заполним параметры, где client id = ID интеграции, client_secret = Секретный ключ, subdomain = ваш домен, т.е. те данные, которые в браузере в строке написаны до .amocrm.ru/.., redirect_url это наш ранее указанный адрес, и самое основное это tokens.default_token_manager.init(code="" = временный код авторизации.

Например, так:

Когда первый раз запустите код, у вас в папке проекта появится access token и refresh token.

После этого нам уже можно будет не обновлять каждый раз 20-ти минутный токен, поэтому закомментим его:

##    tokens.default_token_manager.init(refresh_token, skip_error=False) 

Все, вы авторизовались. Можно написать первый тестовый запрос, допустим, проверить какие у нас там есть сделки.

## выполним простую проверку для того чтобы убедиться что мы получаем данные из AMO CRM.
## получим информацию о созданных сделках при помощи библиотеки amocrm.v2

from amocrm.v2 import Lead

leads = Lead.objects.all()

for _ in leads:
    print(_.name, _.id)

Еще один пример:

# Получение сущности сделки по её ID
# lead_id = 888423  # Замените на реальный ID вашей сделки
# lead = Lead.objects.all(object_id=lead_id)

# Мы просто возьмем первый
lead = list(Lead.objects.all())[0]
lead_id = lead.id

# Вывод основной информации о сделке
print("ID сделки:", lead.id)
print("Название сделки:", lead.name)
print("Создана:", lead.created_at)
print("Обновлена:", lead.updated_at)
print("Стоимость:", lead.price)

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

Вернемся в код. Получим информацию о файлах в CRM:

# Формируем URL для запроса. Найдем файлы в сделке
url = f'https://{subdomain}.amocrm.ru/api/v4/leads/{lead_id}/files'

# Отправляем GET-запрос
response = requests.get(url, headers=headers)
response = handle_response(response)
response

Увидим на выходе что-то вроде:

{'_links': {'self': {'href': '

https://hostname.amocrm.ru/api/v4/leads/1738915/files?limit=50

'}},
'_embedded': {'files': [{'file_uuid': '8ab921e7-40c1-4323-9849-e87f48c5d9ad', 'id': 208995}]}}

# Получим uuid файлов 
file_uuids = [file["file_uuid"] for file in response["_embedded"]["files"]]
file_uuids
# получаем домен сервиса файлов 
url = f'https://{subdomain}.amocrm.ru/api/v4/account?with=drive_url'
response = requests.get(url, headers=headers)
response = handle_response(response)

drive_url = response['drive_url']
print(f"домен сервиса файлов: {drive_url}")

# получаем ссылочки на файлы по uuid
files = []
url = drive_url + '/v1.0/files/'
for uuid in file_uuids:
    response = requests.get(url + uuid, headers=headers)
    response = handle_response(response)
    files.append({
        'name': response['name'],
        'url': response['_links']['download']['href']
    })

files

Этот блок кода должен вернуть в ответ имя и точный Url Документа из сделки в CRM.
Если все успешно, можно идти дальше уже в Jay.

Получим краткое содержание файлов при помощи Jay:

### Настроим доступ к Jay Copilot API

BASE_URL = 'https://app.jaycopilot.com/api/appsAdapter/' ### base url прода 
API_KEY = read_token_from_file('jay_api_key.txt') ## ключ

headers = {
    'Content-Type': 'application/json',
    'Accept': 'application/json',
    'X-API-KEY': API_KEY
}

В этом коде токена нет.

Возьмите его у техподдержки Just AI: support@just-ai.com

Взяли? Отлично! Положите в txt файл в папку с проектом access_token.txt или укажите в коде.

Можно идти дальше.

Опциональный шаг:

# получим темплейты для приложений
response = requests.get(BASE_URL + "templates/", headers=headers)
print(response
response = handle_response(response)
print(str(response)[:500] + "<...>")
templates = response["templates"]

Еще один опциональный шаг:

# посмотрим список темплейтов
for (templateName, templateData) in templates.items():
        print(templateName + ": " + templateData["info"]["title"])

Последний опциональный шаг, обещаю:

# Посмотрим настройки темплейта Jay: Краткое содержание. 
templateName = "summarizer"
templates[templateName]

Все, можно создавать приложение с диалогом:

# создадим диалог с приложением "Краткий пересказ" и запустим приложение c первым файлом из сделки (Без предварительной загрузки файлов)
body = {
    'app': {
        'template': 'summarizer',
        'params': {
            'language': 'Russian',
            'fields': 'Содержание',
            'sentences': 4,
            'documentToSummarize': [files[0]['url']]
        }
    }
}
response = requests.post(BASE_URL + "conversations/", headers=headers, json=body)
response = handle_response(response)
response

Видите ответ? Посмотрите внимательно на этот Json — это параметры. Далее:

# запомним id диалога и приложения
conversationId = response["id"]
appId = response["app"]["id"]
appId
# сохраним ответ приложения из response в переменную summary и немного отформатируем

summary = []
summary.append(files[0]["name"] + ":\n" + response["history"][0]["content"][0]["text"])
print(summary[0])

Получили саммари. Можно идти обратно в CRM и записывать его.
Уберите пальцы от ctrl+c, запишем по API :)

## Запишем ответ от Jay в notes нашей сделки
lead.notes(text="\n\n".join(summary)).save()

## Прочитаем записанное
list(lead.notes.objects.all())[-1]

Все, вы молодец! Вы получаете бонусный код.

Создайте новый имя-файл.py

Можно пообщаться напрямую с ChatGPT, как настоящий hackerman в командной строке.

import requests
import os

def read_token_from_file(filename):
    with open(filename, 'r') as file:
        return file.read().strip()
    
API_KEY = read_token_from_file('jay_api_key.txt') ## ключ
#или
#API_KEY = 'your token'
BASE_URL = 'https://app.jaycopilot.com/api/appsAdapter/'


headers = {
    'Content-Type': 'application/json',
    'Accept': 'application/json',
    'X-API-KEY': API_KEY
}

def handle_response(response):
    if response.status_code == 200:
        return response.json()
    else:
        return response.text 
    

# проверим, есть ли уже диалог с приложением
conversations = requests.get(BASE_URL + "conversations", headers=headers)
conversations = handle_response(conversations)
gpt_conversations = list(filter(lambda conv: conv["app"]["template"] == "directLLM", conversations["conversations"]))

if len(gpt_conversations) > 0:
    conversationId = gpt_conversations[0]["id"]
    print("Найден существующий диалог. id: " + conversationId)
else:
    # создадим диалог с приложением (запустим приложение)
    body = {
        "app": {
            "template": "directLLM",
            "params": {'model': 'gpt-3.5-turbo'}
        }
    }
    response = requests.post(BASE_URL + "conversations/", headers=headers, json=body)
    response = handle_response(response)
    conversationId = response["id"]
    print("Создан новый диалог. id: " + conversationId)

print("Напишите exit, чтобы выйти")

while True:
    message = input("Клиент: ")
    if message == "exit":
        break
    response = requests.post(BASE_URL + "conversations/" + conversationId + "/message", headers=headers, json={"text": message})
    response = handle_response(response)
    print("Бот: " + response["content"][0]["text"])

FAQ

Как ничего не работает? У меня на компьютере все работает :)

Но я могу поделиться jupyter notebook c этим кодом — пишите на почту: apetrov@just-ai.com или @palexe на Хабре.