Каждый раз, когда вы совершаете покупки в Интернете, утомительная задача снова и снова заполнять свое имя, адрес и информацию о кредитной карте может стать немного скучной. Что ж, в Chrome есть довольно удобное расширение автозаполнения, которое поможет вам в этом. Аналогичным образом я разработал расширение с функциями автозаполнения для веб-сайта AIID, чтобы объединить возможности генеративного искусственного интеллекта с полезным и эффективным инструментом. Этот инструмент позволит пользователю легко получить информацию о новостной статье по URL-адресу и быстро автоматически заполнить назначенные поля одним нажатием кнопки!

Но сначала немного предыстории AIID…

База данных инцидентов с искусственным интеллектом (AIID) — это проект с открытым исходным кодом, который был публично запущен в ноябре 2020 года в рамках Responsible AI Collaborative. Он систематически собирает инциденты, когда разведывательные системы вызвали безопасность, справедливость или другие реальные проблемы. Собрав все эти инциденты в одном месте, создатели надеются, что более широкое сообщество сможет использовать эту базу данных для информирования о любых решениях, связанных с ИИ, которые они принимают.

Для получения дополнительной информации об этом проекте вот несколько ссылок:





Как отправить отчет о происшествии

Отчет об инциденте для AIID занимает 3 страницы и включает в себя следующие основные поля (среди прочих): URL-адрес, название, авторы, дата публикации, полнотекстовое содержимое, описание инцидента, предполагаемый развертыватель, предполагаемый разработчик и предполагаемые пострадавшие стороны.

Обзор отчета об инциденте

Вот подробный вид страницы 3 отчета (где применимо создаваемое нами расширение):

Вы можете задаться вопросом: почему я перешел сразу на третью страницу? А что насчет страниц 1 и 2?

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

Страница 2 относительно не важна для всего отчета об инциденте.

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

Как это работает? Это расширение предлагает пользователю ввести URL-адрес (соответствующий новостной статье, описывающей инцидент с искусственным интеллектом). Затем он отправит запрос к конечной точке API, размещенной локально, чтобы выполнить следующие действия с учетом URL-адреса:

  • веб-скрапинг для получения текстового содержимого новостной статьи в формате HTML
  • использование LLM для обобщения текстового содержания
  • использование еще одного LLM с ответами на вопросы, чтобы проверить сводку на наличие ответов на 3 вопроса: предполагаемый развертыватель, предполагаемый разработчик и предполагаемые пострадавшие стороны.

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

Основы расширений Chrome

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

Каждое расширение Chrome должно начинаться с файла manifest.json, содержащего различную информацию.

manifest.json

{
    "manifest_version": 3,
    "name": "AIID Incident Report Form Filler",
    "version": "0.0.1",
    "description": "Populate fields of an AIID Incident Report",
    "homepage_url": "https://incidentdatabase.ai/apps/submit/",
    "icons": {
        "16": "clipboard.png",
        "32": "clipboard.png",
        "48": "clipboard.png",
        "128": "clipboard.png"
      },
    "background": {
        "service_worker": "background/background.js"
    },
    "action": {
        "default_icon": {              
          "16": "clipboard.png",   
          "24": "clipboard.png",   
          "32": "clipboard.png"
        },
        "default_title": "AIID Incident Report Form Filler",   
        "default_popup": "popup/popup.html" 
      },
    "content_security_policy": {
        "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
      },
    "content_scripts": [
        {
        "matches": ["<all_urls>"],
        "js": ["content.js"]
        }
    ],
    "permissions": ["storage","activeTab","declarativeContent"],
    "host_permissions": [
      "http://127.0.0.1:5000/"
    ]
    
}

Вот некоторые важные ключи манифеста, которые следует включить, и их назначение:

  1. action
  • default_icon: определяет словарь путей к изображениям значков разного размера.
  • default_title: указывает заголовок по умолчанию, который пользователь может видеть при наведении курсора на значок расширения на панели инструментов.
  • default_popup: указывает путь к html-файлу для всплывающего окна, которое появляется, когда пользователь щелкает значок расширения.

2. content_security_policy

  • extension_pages: минимальная политика безопасности контента для страниц расширений, дальнейшее смягчение не допускается.

3. content_scripts

  • matches: указывает, в какие шаблоны URL-адресов следует вставлять сценарии содержимого.
  • js: пути к файлам JavaScript для внедрения.

4. permissions: указывает намерение использовать перечисленные API-интерфейсы Chrome.

5. host_permissions: указывает список шаблонов соответствия URL-адресов, которым разрешено взаимодействовать с расширением.

Чтобы получить дополнительную информацию о манифесте и узнать, какие еще ключи доступны, перейдите по следующей ссылке ⤵️



Главное всплывающее окно

После создания файла manifest.json нам нужно добавить HTML-скрипт и функции для всплывающей страницы, которая отображается, когда пользователь щелкает значок расширения AIID на панели инструментов.

Начнем с определения html-кода 🗒

popup.html

<!DOCTYPE html>
<html>
  <head>
    <style>
      html, body {
        height: 200px;
        width: 400px;
      }
    </style>
  </head>
  <body>
    <h1>Chrome extension demo</h1>
    <h2>Let's fill out forms!</h2>
    <p>Url for the Report: <input id="input_url" type="text" name="value"></p>
    <button id='fillForm'>Fill form</button>
    <script type="module" src='popup.js'></script>
  </body>
</html>

Этот простой HTML-код создает одно текстовое поле, в котором пользователь может ввести URL-адрес новостной статьи. Затем они могут нажать кнопку Fill form, которая заполнит поля отчета на текущей веб-странице.

Теперь нам нужно добавить функциональность (то есть код JavaScript) для кнопки Fill form.

popup.js

async function getResponse(url) {
 const big_url = "http://127.0.0.1:5000/getInfo/" + url 
 const response = await fetch(big_url);
 console.log("finished fetch function")

 const data = await response.json();
 chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
  chrome.tabs.sendMessage(tabs[0].id, {
   url: data.url,
   text: data.text,
   summary: data.summary,
   deployer: data.deployer,
   developer: data.developer,
   harmed: data.harmed
  }, function(response) {
   console.log(response.status);
  });
 });

 return data
}

document.getElementById("fillForm").addEventListener("click", () => {
 /* Auto fill form */
 console.log("Filled form button pressed")
 const url = document.getElementById("input_url").value

 const returned_data = getResponse(url)
 console.log(returned_data)
 console.log(returned_data.url)
 console.log(returned_data.text)
 console.log(returned_data.summary)
 console.log(returned_data.deployer)
 console.log(returned_data.developer)
 console.log(returned_data.harmed)
});

Как видите, мы добавляем eventListener для кнопки fillForm, которая захватывает введенный нами URL-адрес и выполняет функцию getResponse.

Функция getResponse выполняет следующие действия:

  • объединяет введенный URL-адрес с URL-адресом локального сервера, на котором размещена наша конечная точка API, содержащая код веб-скрапинга и LLM.
  • вызывает функцию fetch с большим объединенным URL-адресом, который отправляет запрос к нашему API и возвращает response
  • использует API chrome.tabs для отправки сообщения из всплывающего окна на страницу контента (т. е. текущую веб-страницу) с данными из response, которые в конечном итоге будут использоваться для заполнения отчета об инциденте

Функциональность автозаполнения

После того, как сообщение было отправлено из файла popup.html, нам необходимо закодировать часть автозаполнения в сценарии содержимого, который находится в контексте текущей веб-страницы.

content.js

chrome.runtime.onMessage.addListener(
    function(request, sender, sendResponse) {
        try {
            var summary = document.getElementsByName("description")[0];
            summary.innerHTML = request.summary
            summary.dispatchEvent(new Event('input', { bubbles: true }));

            var deployer = document.getElementById("deployers")
            deployer.setAttribute("value", request.deployer);
            deployer.dispatchEvent(new Event('input', { bubbles: true }));

            var developer = document.getElementById("developers")
            developer.setAttribute("value", request.developer);
            developer.dispatchEvent(new Event('input', { bubbles: true }));

            var harmed = document.getElementById("harmed_parties")
            harmed.setAttribute("value", request.harmed);
            harmed.dispatchEvent(new Event('input', { bubbles: true }));

            console.log("received!")
            sendResponse({status: "Success!"});
        } catch (error) {
            console.log(error)
            sendResponse({status: "Exception occurred!"});
        }
    }
);

На принимающей стороне chrome.runtime.onMessage необходимо настроить прослушиватель (т. е. addListener), который, как видно из его тезки, прослушивает и обрабатывает сообщения.

Чтобы идентифицировать элементы HTML на веб-странице, которые необходимо автоматически заполнить данными, отправленными сообщением, нам необходимо сначала проверить веб-страницу. HTML-элементы можно идентифицировать по их id, name, type и многим другим параметрам.

Следующий рисунок демонстрирует, как это сделать для наших целей. 👩‍🏫

Как только мы узнаем, как получить элемент html, мы можем установить для его атрибута любое желаемое значение, используя либо функцию setAttribute, либо свойство innerHTML.

Мы будем использовать данные из request, чтобы соответствующим образом установить значения этих html-элементов.

Затем dispatchEvent(new Event(‘input’, { bubbles: true })) вызовет изменение события, которое фактически обновит содержимое элементов html на текущей веб-странице в соответствии с нашим кодом. Это автоматически заполняет поля.

Веб-скрапинг URL-адресов и LLM

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

Но как нам получить различные поля данных из URL-адреса новостной статьи? Имейте в виду, что нам необходимо получить описание инцидента, предполагаемого развертывателя, предполагаемого разработчика и предполагаемых пострадавших сторон.

Во-первых, нам нужно использовать веб-скрапинг, чтобы получить текстовое содержимое новостной статьи по URL-адресу. Затем мы можем использовать LLM обобщения, чтобы получить краткое описание поля описания инцидента. И, наконец, мы можем использовать LLM с ответами на вопросы, чтобы проверить резюме на наличие ответов на 3 предполагаемых вопроса. Эти результаты будут использованы для заполнения соответствующих полей в форме отчета об инциденте.

Описанная выше функциональность инкапсулирована в следующем файле Python.

content_parser.py

import requests
from bs4 import BeautifulSoup
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
from transformers import pipeline


def getTextContent(url):
    res = requests.get(url)
    soup = BeautifulSoup(res.content, 'html.parser')
    text = ""
    for data in soup.find_all("p"):
        text = text + " " + data.get_text()
    return text

def getSummary(text):
    tokenizer = AutoTokenizer.from_pretrained("facebook/bart-large-cnn")
    model = AutoModelForSeq2SeqLM.from_pretrained("facebook/bart-large-cnn")

    tokenized_text = tokenizer(text, return_tensors="pt")
    input_tensor = tokenized_text['input_ids'][0:1, 0:1024]

    outputs = model.generate(input_tensor)

    summary = tokenizer.decode(outputs[0], skip_special_tokens=True)
    return summary

def getDetails(summary):
    qa_model_name = "deepset/roberta-base-squad2"
    qa = pipeline('question-answering', model=qa_model_name, tokenizer=qa_model_name)
    QA_input1 = {
    'question': "Who employed or was responsible for this technology?",
    'context': summary
    }
    res1 = qa(QA_input1)
    deployer = res1['answer']

    QA_input2 = {
        'question': "Who built or created the technology?",
        'context': summary
    }
    res2 = qa(QA_input2)
    developer = res2['answer']

    QA_input3 = {
        'question': "Who experienced negative impacts?",
        'context': summary
    }
    res3 = qa(QA_input3)
    harmed = res3['answer']
    return (deployer, developer, harmed)

Веб-скрейпинг: getTextContent()

  • BeautifulSoup4 и запросы пакетов

Обобщение: getSummary()

  • BART от Facebook: предварительно обученная модель кодировщика-кодера трансформатора (seq2seq) с двунаправленным (BERT-подобным) кодером и авторегрессионным (GPT-подобным) декодером.

Вопрос-ответ: getDetails()

  • roberta-base от deepset: модель трансформеров, предварительно обученная на большом массиве английских данных с самоконтролем и точно настроенная на наборе данных SQuAD2.0 пар вопрос-ответ.

Размещение конечной точки API

Теперь у нас есть код расширения Chrome и код URL-адреса анализа. Как мы можем общаться между этими двумя и отправлять данные туда и обратно?

Ответ: разместите URL-адрес синтаксического анализа в конечной точке API на локальном сервере, и наш код расширения Chrome сделает запрос к этому API → 🆒

В предыдущем разделе мы уже создали функциональность API. Теперь нам нужно закодировать возможности хостинга.

app.py

from flask import Flask, jsonify
from flask_restful import Resource, Api
from content_parser import getTextContent, getSummary, getDetails

app = Flask(__name__)
api = Api(app)

class getInfo(Resource):
    def get(self, encoded_url):
        extract = dict()
        extract["url"] = encoded_url
        print("url:", encoded_url)

        text = getTextContent(encoded_url)
        extract["text"] = text
        print("text: " + text)

        summary = getSummary(text)
        extract["summary"] = summary
        print("summary: " + summary)

        (deployer, developer, harmed) = getDetails(summary)
        extract["deployer"] = deployer
        print("deployer: " + deployer)

        extract["developer"] = developer
        print("developer: " + developer)

        extract["harmed"] = harmed
        print("harmed: " + harmed)

        return jsonify(extract)

api.add_resource(getInfo, '/getInfo/<path:encoded_url>')

if __name__ == '__main__':
    app.run()

Flask Restful — это пакет, который поддерживает создание REST API с использованием Flask в качестве бэкэнда. Используя этот пакет, мы определяем класс ресурсов под названием getInfo, внутри которого могут быть такие методы, как GET, POST, PUT, DELETE. Для наших целей мы определим метод GET, который принимает URL-адрес новостной статьи и возвращает объект словаря json, содержащий следующие ключи и их значения:

  • url: URL-адрес, переданный в качестве входных данных.
  • text : полное текстовое содержание новостной статьи на url.
  • summary : обобщенная версия text, которая будет использоваться для заполнения поля описания инцидента.
  • deployer : предполагаемый развертыватель системы ИИ.
  • developer : предполагаемый разработчик системы искусственного интеллекта.
  • harmed : предполагаемые стороны, пострадавшие из-за системы искусственного интеллекта

Затем мы добавляем ресурс в наш API и указываем для него URL-путь. Этот URL-путь включает имя ресурса, за которым следует /, а затем параметр для фактического URL-адреса новостной статьи.

(По сути, у нас есть URL внутри URL 😲)

Собираем все это вместе

Теперь мы создали все наши движущиеся части. Вот как все это сочетается.

  1. пользователь вводит URL-адрес новостной статьи на всплывающей странице, а затем нажимает кнопку заполнения формы.
  2. всплывающая страница получает ответ на запрос на получение нашего API с URL-адресом новостной статьи в качестве входных данных.
  3. всплывающая страница отправляет ответ в виде данных с использованием API chrome.tabs
  4. сценарий содержимого прослушивает это сообщение с данными
  5. скрипт содержимого обновляет поля текущей веб-страницы (страница 3 формы отчета об инциденте AIID) соответствующей информацией, полученной из сообщения

Та-да! Поля заполнены автоматически ✅

Надеюсь, к настоящему моменту вы узнали, как создать расширение Chrome с автозаполнением, подключающееся к REST API, встроенному в Python. Чтобы собрать весь код в одном месте, вот ссылка на репозиторий GitHub:



Несмотря на то, что это расширение имеет множество функций, есть еще несколько областей для улучшения:

  • включение отзывов пользователей о точности ответов в обучение более совершенных моделей
  • точная настройка LLM, используемых для обобщения и ответов на вопросы
  • заполнение дополнительных полей формы отчета об инциденте (некоторые также на страницах 1 и 2)
  • размещение конечной точки API НЕ локально (имея реальный домен, поэтому он всегда остается активным)

Вот и все!

Если вам понравилась эта статья, поставьте лайк 👍, прокомментируйте 💬 и поделитесь ⤴️.