Каждый раз, когда вы совершаете покупки в Интернете, утомительная задача снова и снова заполнять свое имя, адрес и информацию о кредитной карте может стать немного скучной. Что ж, в 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/" ] }
Вот некоторые важные ключи манифеста, которые следует включить, и их назначение:
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 😲)
Собираем все это вместе
Теперь мы создали все наши движущиеся части. Вот как все это сочетается.
- пользователь вводит URL-адрес новостной статьи на всплывающей странице, а затем нажимает кнопку заполнения формы.
- всплывающая страница получает ответ на запрос на получение нашего API с URL-адресом новостной статьи в качестве входных данных.
- всплывающая страница отправляет ответ в виде данных с использованием API chrome.tabs
- сценарий содержимого прослушивает это сообщение с данными
- скрипт содержимого обновляет поля текущей веб-страницы (страница 3 формы отчета об инциденте AIID) соответствующей информацией, полученной из сообщения
Та-да! Поля заполнены автоматически ✅
Надеюсь, к настоящему моменту вы узнали, как создать расширение Chrome с автозаполнением, подключающееся к REST API, встроенному в Python. Чтобы собрать весь код в одном месте, вот ссылка на репозиторий GitHub:
Несмотря на то, что это расширение имеет множество функций, есть еще несколько областей для улучшения:
- включение отзывов пользователей о точности ответов в обучение более совершенных моделей
- точная настройка LLM, используемых для обобщения и ответов на вопросы
- заполнение дополнительных полей формы отчета об инциденте (некоторые также на страницах 1 и 2)
- размещение конечной точки API НЕ локально (имея реальный домен, поэтому он всегда остается активным)
Вот и все!
Если вам понравилась эта статья, поставьте лайк 👍, прокомментируйте 💬 и поделитесь ⤴️.