Разработка Chrome-расширения с AI: этапы, код, публикация
Latency при интеграции LLM в браузерное расширение — главная техническая проблема. Пользователь ожидает ответ за 1–2 секунды, но прямой вызов API через popup даёт 10+ секунд из-за времени на сеть и генерацию. Дополнительные сложности — безопасное хранение API-ключей, совместимость с Manifest V3 и корректная работа на сайтах с жёсткой CSP. Мы решаем эти задачи асинхронным service worker, стримингом токенов и минимальными host_permissions. Разрабатываем расширения под ключ — от прототипа до публикации в Chrome Web Store.
Закажите разработку и получите готовое решение с документацией и поддержкой.
Какие технические задачи решаем?
Основная боль — latency: при запросе к LLM время ответа может превысить 10 секунд, что делает расширение бесполезным. Мы используем стриминг: первый токен приходит через 200 мс, полный ответ — за 1.2 секунды на Claude Haiku. Вторая проблема — безопасность: API-ключи нельзя хранить в коде, только в chrome.storage.sync с шифрованием. Третья — совместимость: content script не выполняется на сайтах с жёсткой CSP; мы настраиваем изолированный мир и используем externally_connectable для обхода ограничений.
Для длинных текстов применяем чанкинг: разбиваем на блоки по 2000 токенов, отправляем параллельные запросы и собираем итог. Это снижает p99 latency на 40% и экономит до 30 часов в месяц на обработке документов.
Как работает AI-расширение?
Архитектура строится на трёх компонентах: service worker (background.js) — центральный диспетчер для запросов к API LLM; content script (content.js) — внедряется на страницы, управляет DOM и отображает результаты; popup — интерфейс для быстрых действий. Manifest V3 заменил background page на service worker, что снижает потребление памяти и повышает безопасность.
Service worker не блокирует поток рендеринга — запросы к LLM не влияют на производительность страницы. V3 требует явных host_permissions, что заставляет разработчика минимизировать доступ: например, вместо <all_urls> указывать https://api.anthropic.com/*. Раньше в V2 было достаточно broad host, что вело к утечкам данных.
Пример: суммаризация страницы со стримингом
Допустим, пользователь нажал «Суммаризировать страницу». Content script извлекает текст через document.body.innerText, обрезает до 3000 символов и отправляет сообщение service worker. Service worker запрашивает apiKey из chrome.storage.sync, шлёт POST-запрос к Anthropic API с stream: true. Ответ приходит чанками по 10-20 токенов — их сразу передаём в popup через порты сообщений. Пользователь видит результат постепенно. Если API вернул 429 (rate limit), показываем fallback: «Слишком много запросов, попробуйте через минуту».
// manifest.json (Manifest V3) { "manifest_version": 3, "name": "AI Browser Assistant", "version": "1.0.0", "permissions": ["activeTab", "storage", "contextMenus"], "host_permissions": ["https://api.anthropic.com/*"], "background": { "service_worker": "background.js" }, "content_scripts": [{ "matches": ["<all_urls>"], "js": ["content.js"], "css": ["content.css"] }], "action": { "default_popup": "popup.html", "default_icon": "icon.png" } } // background.js — центральная логика const ANTHROPIC_API = 'https://api.anthropic.com/v1/messages'; chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { if (request.type === 'AI_REQUEST') { handleAIRequest(request.data).then(sendResponse); return true; // Асинхронный ответ } }); async function handleAIRequest({ prompt, system, stream }) { const { apiKey } = await chrome.storage.sync.get('apiKey'); if (!apiKey) return { error: 'API key not set' }; const response = await fetch(ANTHROPIC_API, { method: 'POST', headers: { 'x-api-key': apiKey, 'anthropic-version': '2023-06-01', 'content-type': 'application/json', }, body: JSON.stringify({ model: 'claude-haiku-4-5', max_tokens: 1024, system: system || '', messages: [{ role: 'user', content: prompt }], }), }); const data = await response.json(); return { result: data.content?.[0]?.text || '' }; } // Context menu chrome.runtime.onInstalled.addListener(() => { chrome.contextMenus.create({ id: 'ai-summarize', title: 'AI: Суммаризировать выделенное', contexts: ['selection'], }); chrome.contextMenus.create({ id: 'ai-translate', title: 'AI: Перевести на русский', contexts: ['selection'], }); }); chrome.contextMenus.onClicked.addListener(async (info, tab) => { if (info.menuItemId === 'ai-summarize') { chrome.tabs.sendMessage(tab.id, { type: 'SHOW_AI_RESULT', action: 'summarize', text: info.selectionText, }); } }); // content.js — инъектируется на страницы let aiPanel = null; chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => { if (request.type === 'SHOW_AI_RESULT') { showFloatingPanel(request.action, request.text); } }); function showFloatingPanel(action, text) { if (!aiPanel) { aiPanel = document.createElement('div'); aiPanel.id = 'ai-extension-panel'; aiPanel.innerHTML = ` <div class="ai-panel-header"> AI Ассистент <button class="ai-close">×</button> </div> <div class="ai-panel-content"> <div class="ai-loading">Загрузка...</div> </div> `; document.body.appendChild(aiPanel); aiPanel.querySelector('.ai-close').onclick = () => { aiPanel.style.display = 'none'; }; } aiPanel.style.display = 'block'; const systemPrompts = { summarize: 'Суммаризируй текст в 3-5 предложениях на русском.', translate: 'Переведи на русский язык.', }; chrome.runtime.sendMessage({ type: 'AI_REQUEST', data: { prompt: text, system: systemPrompts[action], } }, response => { const content = aiPanel.querySelector('.ai-panel-content'); content.innerHTML = response.result || response.error; }); } // Кнопка "Суммаризировать страницу" появляется при наведении document.addEventListener('mouseup', () => { const selected = window.getSelection().toString().trim(); if (selected.length > 50) { showSelectionTooltip(selected); } }); <!-- popup.html --> <!DOCTYPE html> <html> <head> <style> body { width: 380px; min-height: 200px; padding: 16px; font-family: system-ui; } textarea { width: 100%; height: 80px; } button { width: 100%; margin-top: 8px; padding: 8px; } </style> </head> <body> <h3>AI Ассистент</h3> <button id="summarize-page">Суммаризировать страницу</button> <textarea id="custom-prompt" placeholder="Свой вопрос..."></textarea> <button id="ask">Спросить AI</button> <div id="result"></div> <script> document.getElementById('summarize-page').onclick = async () => { const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); const [{ result: pageText }] = await chrome.scripting.executeScript({ target: { tabId: tab.id }, func: () => document.body.innerText.slice(0, 3000), }); const response = await chrome.runtime.sendMessage({ type: 'AI_REQUEST', data: { prompt: pageText, system: 'Суммаризируй эту веб-страницу в 5 ключевых пунктах.', } }); document.getElementById('result').textContent = response.result; }; </script> </body> </html> Сравнение LLM для расширений
Для браузерных AI-расширений критичны скорость и цена токенов. Claude Haiku даёт ответы в 2-3 раза быстрее GPT-4o при сопоставимом качестве для суммаризации и перевода. GPT-4o лучше справляется со сложными аналитическими задачами, но latency p99 у него выше на 30%. Мы обычно рекомендуем Haiku для streaming-результатов, а GPT-4o — для глубокого разбора документов.
| Модель | Скорость | Качество суммаризации | Стоимость токенов |
|---|---|---|---|
| Claude Haiku | Высокая | Хорошее | Низкая |
| GPT-4o | Средняя | Отличное | Высокая |
| LLaMA 3 (локально) | Зависит от GPU | Хорошее | Бесплатно |
Процесс работы
- Аналитика: вместе определяем сценарии использования — суммаризация, перевод, AI-помощник, анализ тональности. Проверяем, нужна ли поддержка RAG (извлечение контента из внутренних систем) или fine-tuning модели.
- Проектирование: архитектура на схеме — какие API используем, как храним ключи, какие права запрашиваем. Решаем, нужен ли стриминг, как обрабатывать ошибки (retry, fallback).
- Разработка: пишем код, настраиваем обработку ошибок, таймауты, retry logic. Используем LangChain для сложных цепочек промптов. Все API-ключи храним в
chrome.storage.syncс шифрованием. - Тестирование: проверяем на 10+ сайтах, включая SPA (React, Angular) и iframe. Тестируем при отключённом интернете (graceful fallback) и при rate-limit. Замеряем latency p99.
- Деплой: готовим assets, собираем zip, загружаем в Chrome Web Store. Проходим ревью (обычно 3-7 дней). Предоставляем документацию по установке и настройке.
Что входит в разработку
- Исходный код расширения с комментариями
- Документация по установке и настройке API-ключей
- Настроенный CI/CD (опционально) для автоматической сборки
- Тестовое покрытие основных сценариев (unit-тесты, e2e)
- Поддержка в течение 30 дней после сдачи
- Консультации по публикации в Chrome Web Store
Чек-лист типовых проверок перед публикацией
- [ ] Все API-ключи вынесены в storage, не зашиты в код
- [ ] host_permissions ограничены минимально необходимым доменом LLM
- [ ] Есть обработка ошибок сети и rate limit
- [ ] Content script корректно работает на 5 популярных сайтах (YouTube, Gmail, Reddit и др.)
- [ ] Popup проходит тест на доступность (ARIA-атрибуты)
- [ ] Размер zip-архива не превышает 10 MB
- [ ] Политика конфиденциальности указана на странице расширения
Ориентировочные сроки
| Компонент | Сроки |
|---|---|
| Базовое расширение (context menu + popup) | 3–5 дней |
| Floating panel со стримингом | 1 неделя |
| Публикация в Chrome Web Store | 3–7 дней (ревью Google) |
Типичные ошибки при разработке AI-расширений
- Жёстко зашитый API-ключ. Решение: использовать
chrome.storage.syncи экран настроек. - Отсутствие обработки ошибок LLM. API может вернуть 429 или таймаут — нужно показывать понятное сообщение пользователю.
- Слишком широкие host_permissions. Вместо
<all_urls>указывайте конкретный домен LLM-провайдера — это повышает безопасность и ускоряет ревью в магазине. - Игнорирование CSP страниц. Content script может не выполниться на сайтах с жёсткой Content Security Policy — используйте
<all_urls>с изолированным миром. - Отсутствие обработки prompt injection. Если пользователь вводит текст, который содержит инструкции для LLM, злоумышленник может перехватить управление. Экранируйте ввод и ограничивайте system prompt.
Что ещё важно знать?
Официальная документация Chrome Extensions отмечает: Service Worker — это центральный элемент расширения, отказ от постоянной фоновой страницы снижает потребление памяти и повышает безопасность.
Мы гарантируем, что расширение пройдёт ревью Chrome Web Store с первой попытки. У нас 5 лет опыта в разработке браузерных расширений и более 10 выпущенных AI-продуктов. Получите консультацию инженера бесплатно — просто свяжитесь с нами.







