
Предыстория
Смотря на столь бурное развитие направления по применении агентов с ИИ для автоматизации OpenClaw убрала поддержку бесплатного использования модель Qwen.
Это было ограниченное количество запросов, но тем не менее - работало весьма хорошо.
Теперь это только платная подписка.
Аналогичным образом поступают и другие вендоры ИИ - какие-то LLM просто не умеют работать как агенты, какие-то подлежат кастомизации (Claude). Опять же - всё хорошее за деньги, средний лимит - 1 млн токенов для демонстрации.
Опыт Apple
Пока все смеются над тем, что только это компания где-то опоздала, на самом деле Apple заключает соглашение с Google об использовании квантованных (сжатых) версий топовых моделей от техно-гиганта.
Недавно Apple подтвердила стратегическое партнерство с Google для интеграции ИИ Gemini в свои устройства. Это решение связано со сложностями в создании нейросети такого масштаба для конкуренции с лидерами рынка.
Основные детали сделки:
Интеграция с Siri: Gemini станет основой обновленной Siri.
Модель Google будет отвечать за понимание контекста, планирование задач и обобщение информации.
Работа внутри смартфона: Apple получила доступ к технологиям Google для дистилляции. Это позволит Apple обучить собственные компактные модели на базе Gemini, которые будут работать напрямую на устройстве, обеспечивая скорость и конфиденциальность.
Вот именно это мы сейчас и опробуем, только на примере с Android.
Эмуляция
Не секрет, что на Android можно запускать Linux-форки приложений и работать на телефоне в полноценной Linux-среде. Для этого энтузиастами был разработан эмулятор - имя ему Termux Termux Wiki
Я использую это решения для запуска веб-приложений backend/frontend с Mongodb, reactjsб javascript через node.js и nginx, а также для подключения к linux/unix-системам напрямую с телефона. Всё работает локально на устройстве.
Основные плюшки:
Пакетный менеджер: Упрощает установку и управление программами с помощью pkg, аналогичного apt.
Поддержка языков программирования: Termux поддерживает Python, Ruby, Node.js, PHP, Go, Rust и другие языки.
Интеграция с Git и контроля версий: Позволяет работать с репозиториями и контролем версий.
Доступ к файловой системе Android: Удобно работает с файловой системой Android, включая внешнюю SD-карту.
Интеграция с внешними клавиатурами и терминальными клиентами: Поддержка SSH, VNC и других терминальных клиентов.
Автоматизация задач: Позволяет автоматизировать выполнение команд и скриптов.
Таким образом, Termux является мощным инструментом для пользователей Android, которые ищут возможность использовать Linux-подобную среду на своем устройстве.
Установка
Установка на Android устройство проста до безобразия не требует особых умственных усилий, в отличие от вычисления над полями Галуа Finite field - Wikipedia
Шаг 1. Скачиваем Termux с Google Play
В качестве альтернативы и для получения более свежей версии можно выполнить установку, скачав предварительно F-Droid с официального сайта.
Шаг 1.2: Первый запуск
Открываете Termux. Видите чёрный экран с белым текстом и приглашением $ — это командная строка. Теперь вы — пользователь Linux на своём телефоне.
Вводим команду: uname - a

Консоль отзывается - всё хорошо.
Шаг 2. Ставим LLM
Шаг 2.1: Обновляем всё
pkg update && pkg upgrade -y
Процесс должен выглядеть примерно так:

Шаг 2.2: Ставим необходимые инструменты
pkg install -y curl wget git nodejs nginx
Это инструменты для работы с web-фреймворками. Они нам потребуются для запуска WebUI.
Шаг 2.3: Скачиваем Ollama
Многие кто погружен в эту историю уже знают про HuggingFace и аналогичные сервисы.
Одним из популярных также является Ollama - это софт для установки и запуска больших языковых моделей локально на устройство. Как правило - на серверы, ПК, ноутбуки.
Но мало кто пробовал ставить на Android - вы будете в числе первых!
wget https://github.com/ollama/ollama/releases/download/v0.5.4/ollama-linux-arm64 chmod +x ollama-linux-arm64 mv ollama-linux-arm64 $PREFIX/bin/ollama
Шаг 2.4: Проверяем установку
ollama --version
Ожидаемый вывод: ollama version 0.5.4
Также можно просто набрать ollama - программа перейдет в интерактивный режим:

Шаг 3: Загрузка модели Gemma
Gemma 3 — это новое поколение открытых мультимодальных моделей искусственного интеллекта от Google DeepMind, представленное весной 2025 года.
Она оптимизирована для эффективной работы на обычных пользовательских устройствах.
Основные характеристики
Мультимодальность: Начиная с версии 4B, Gemma 3 способна одновременно понимать и обрабатывать текст, изображения и короткие видеоролики.
Доступные версии: Семейство включает модели разного размера для разных задач:
1B (1 млрд параметров): Эта модель работает только с текстом и английским языком. Подходит для мобильных устройств.
4B, 12B и 27B: Это мультимодальные модели, поддерживающие более 140 языков.
Контекстное окно: Модели поддерживают до 128 000 токенов, что позволяет анализировать очень длинные документы.
Открытость: Веса моделей доступны для свободного скачивания и использования разработчиками.
Технические преимущества
Производительность: Старшие версии (27B) в ряде тестов превосходят более массивные модели, такие как Llama-3 405B.
Оптимизация: Модель 27B требует около 62 ГБ видеопамяти при полной точности, но может работать на 15.5 ГБ в квантованном режиме.
Инструменты: Модель хорошо справляется со структурированным выводом и вызовом внешних функций, что делает её подходящей для создания автономных ИИ-агентов.
Шаг 3.1: Запускаем сервер Ollama
Открываем ПЕРВОЕ окно Termux (основное) и запускаем:
OLLAMA_ORIGINS="*" ollama serve

💡 Важно: флаг OLLAMA_ORIGINS="*" включает CORS. Без него веб-интерфейс не сможет обращаться к Ollama. Так мы запускаем сервер Ollama в фоне.
Шаг 3.2: Скачиваем модель
Открываем ВТОРОЕ окно Termux (свайп от левого края → New session):
ollama pull gemma3:1b
Увидим процесс скачивания модели:

Шаг 3.3: Проверяем модель
ollama run gemma3:1b
Модель загрузится и можно к ней можно обращаться с консоли: пишем наш запрос.

Добавляем удобство
Поскольку так работать вполне можно, но немного не удобно. Поэтому нам нужно научиться работать с моделькой как мы привыкли, как через приложение.
Я попробовал запустить все возможные и рекомендуемые форки для WebUI для Ollama - но ничего из этого не заработало или требовало серьезных танцев с бубнами.
Пишем своё
Написание полноценного приложения это весьма затратный путь - я написал WebUI на базе html, javascript, который запускается в termux через nginx, а пользоваться можно просто через любой браузер в смартфоне.
Но для начала нужно немного подготовиться.
Проверяем через curl
Убедимся, что наш сервис отвечат на http-запросы.
curl -X POST http://127.0.0.1:11434/api/generate \ -d '{"model":"gemma3:1b","prompt":"Привет! Как тебя зовут?","stream":false}' \ -H "Content-Type: application/json"

Модель отвечает. Идём дальше!
Создаём веб-интерфейс
По умолчанию Nginx на termux хранит данные по следующему пути:
/usr/share/nginx/html
Создадим файл нашего веб-приложения:
nano /usr/share/nginx/html/chat.html
Код приложения
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <title>Gemma 3 WebUI</title> <style> :root { --bg-color: #0f172a; --chat-bg: #1e293b; --user-msg: #3b82f6; --bot-msg: #334155; --text-main: #f8fafc; --text-muted: #94a3b8; --accent: #60a5fa; --border: #475569; } body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; background-color: var(--bg-color); color: var(--text-main); margin: 0; padding: 0; display: flex; flex-direction: column; height: 100vh; box-sizing: border-box; } header { background-color: var(--chat-bg); padding: 15px 20px; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border); box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); z-index: 10; } header h1 { margin: 0; font-size: 1.2rem; color: var(--accent); } select { background-color: var(--bg-color); color: var(--text-main); border: 1px solid var(--border); padding: 8px 12px; border-radius: 6px; outline: none; font-size: 0.9rem; } #chat-container { flex-grow: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 15px; scroll-behavior: smooth; } .message { max-width: 85%; padding: 12px 16px; border-radius: 16px; font-size: 1rem; line-height: 1.5; word-wrap: break-word; animation: fadeIn 0.3s ease-out; } @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } .user { background-color: var(--user-msg); color: white; align-self: flex-end; border-bottom-right-radius: 4px; } .bot { background-color: var(--bot-msg); color: var(--text-main); align-self: flex-start; border-bottom-left-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.2); } .error { background-color: #ef4444; color: white; align-self: center; font-size: 0.85rem; } .typing-indicator { display: inline-flex; gap: 4px; align-items: center; height: 20px; } .typing-indicator span { width: 6px; height: 6px; background-color: var(--text-muted); border-radius: 50%; animation: bounce 1.4s infinite ease-in-out both; } .typing-indicator span:nth-child(1) { animation-delay: -0.32s; } .typing-indicator span:nth-child(2) { animation-delay: -0.16s; } @keyframes bounce { 0%, 80%, 100% { transform: scale(0); } 40% { transform: scale(1); } } #input-area { background-color: var(--chat-bg); padding: 15px; border-top: 1px solid var(--border); display: flex; gap: 10px; align-items: flex-end; } textarea { flex-grow: 1; background-color: var(--bg-color); color: var(--text-main); border: 1px solid var(--border); border-radius: 12px; padding: 12px; font-size: 1rem; resize: none; outline: none; min-height: 24px; max-height: 120px; font-family: inherit; transition: border-color 0.2s; } textarea:focus { border-color: var(--accent); } button { background-color: var(--accent); color: #000; border: none; border-radius: 12px; padding: 0 20px; height: 50px; font-size: 1rem; font-weight: 600; cursor: pointer; transition: opacity 0.2s, transform 0.1s; display: flex; align-items: center; justify-content: center; } button:active { transform: scale(0.95); } button:disabled { opacity: 0.5; cursor: not-allowed; transform: none; } </style> </head> <body> <header> <h1>Local LLM</h1> <select id="model-select"> <option value="">Загрузка моделей...</option> </select> </header> <div id="chat-container"> <div class="message bot">Привет! Я готова к общению.</div> </div> <div id="input-area"> <textarea id="prompt" rows="1" placeholder="Введите сообщение..."></textarea> <button id="send-btn">➔</button> </div> <script> const OLLAMA_URL = 'http://127.0.0.1:11434'; const chatContainer = document.getElementById('chat-container'); const promptInput = document.getElementById('prompt'); const sendBtn = document.getElementById('send-btn'); const modelSelect = document.getElementById('model-select'); let chatHistory = []; // Массив для хранения контекста диалога // Автоматическое изменение высоты поля ввода promptInput.addEventListener('input', function() { this.style.height = 'auto'; this.style.height = (this.scrollHeight) + 'px'; }); // Отправка по Enter (без Shift) promptInput.addEventListener('keydown', function(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }); sendBtn.addEventListener('click', sendMessage); // Асинхронная загрузка доступных моделей async function fetchModels() { try { const response = await fetch(`${OLLAMA_URL}/api/tags`); if (!response.ok) throw new Error('Network error'); const data = await response.json(); modelSelect.innerHTML = ''; if (data.models.length === 0) { modelSelect.innerHTML = '<option>Нет установленных моделей</option>'; return; } data.models.forEach(model => { const option = document.createElement('option'); option.value = model.name; option.textContent = model.name; // Автовыбор gemma3 если она есть if(model.name.includes('gemma3')) option.selected = true; modelSelect.appendChild(option); }); } catch (error) { console.error('Ошибка загрузки моделей:', error); modelSelect.innerHTML = '<option value="gemma3">gemma3 (Оффлайн)</option>'; } } function createMessageElement(sender) { const msgDiv = document.createElement('div'); msgDiv.className = `message ${sender}`; chatContainer.appendChild(msgDiv); return msgDiv; } function scrollToBottom() { chatContainer.scrollTop = chatContainer.scrollHeight; } async function sendMessage() { const text = promptInput.value.trim(); const selectedModel = modelSelect.value; if (!text || !selectedModel) return; // Блокируем ввод promptInput.value = ''; promptInput.style.height = 'auto'; promptInput.disabled = true; sendBtn.disabled = true; // Добавляем сообщение пользователя const userMsgDiv = createMessageElement('user'); userMsgDiv.textContent = text; chatHistory.push({ role: 'user', content: text }); scrollToBottom(); // Создаем блок для ответа бота с индикатором печати const botMsgDiv = createMessageElement('bot'); botMsgDiv.innerHTML = '<div class="typing-indicator"><span></span><span></span><span></span></div>'; scrollToBottom(); try { const response = await fetch(`${OLLAMA_URL}/api/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: selectedModel, messages: chatHistory, stream: true }) }); if (!response.ok) throw new Error(`HTTP Error: ${response.status}`); // Очищаем индикатор загрузки botMsgDiv.innerHTML = ''; let fullResponse = ''; // Асинхронное чтение потока const reader = response.body.getReader(); const decoder = new TextDecoder('utf-8'); while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value, { stream: true }); const lines = chunk.split('\n').filter(line => line.trim() !== ''); for (const line of lines) { const json = JSON.parse(line); if (json.message && json.message.content) { fullResponse += json.message.content; // Простая замена переносов строк для HTML botMsgDiv.innerHTML = fullResponse.replace(/\n/g, '<br>'); scrollToBottom(); } } } // Сохраняем ответ в историю chatHistory.push({ role: 'assistant', content: fullResponse }); } catch (error) { console.error(error); botMsgDiv.className = 'message error'; botMsgDiv.textContent = 'Ошибка подключения. Проверьте OLLAMA_ORIGINS="*" и запущен ли сервер.'; } finally { // Разблокируем ввод promptInput.disabled = false; sendBtn.disabled = false; promptInput.focus(); scrollToBottom(); } } // Инициализация window.onload = fetchModels; </script> </body> </html>
Вставляем код из вложения (можно взять на моём GitHub BlackJackBander/Ollama-WebUI: Ollama-WebUI interface for works from Browser on smartphone).
🔑 Ключевые фрагменты кода:
Отправка запроса к Ollama:
// Асинхронная загрузка доступных моделей async function fetchModels() { try { const response = await fetch(`${OLLAMA_URL}/api/tags`); if (!response.ok) throw new Error('Network error'); const data = await response.json();
Обработка потокового ответа (текст появляется постепенно):
// Асинхронное чтение потока const reader = response.body.getReader(); const decoder = new TextDecoder('utf-8'); while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value, { stream: true }); const lines = chunk.split('\n').filter(line => line.trim() !== ''); for (const line of lines) { const json = JSON.parse(line); if (json.message && json.message.content) { fullResponse += json.message.content; // Простая замена переносов строк для HTML botMsgDiv.innerHTML = fullResponse.replace(/\n/g, '<br>'); scrollToBottom(); } } }
Шаг 4.4: Запускаем Nginx
nginx -t nginx
Если ничего не пишет, проверяем работает ли сервис и если нет - запускаем:
sv status nginx sv up nginx
🚀 Запускаем всё вместе
Открываем свой любимый браузер. Лично мой - Firefox за возможность отрезания рекламы везде где можно и широкой кастомизации:
В адресной строке браузера указываем адрес для доступа:
http://<ip адрес вашего устройства>:8080/chat.html
WebUI сам подхватит текущую запущенную модель.
Если всё ОК - будет выглядеть так:

Типичные проблемы и их решение
Проблема 1: CORS ошибка в браузере
Решение: Убедитесь, что Ollama запущен с OLLAMA_ORIGINS="*".
Проблема 2: Ошибка 405 при curl
Решение: Используйте -X POST.
Проблема 3: Порт уже занят
Решение: pkill -9 ollama или pkill -9 nginx.
Проблема 4: Termux убивает процесс в фоне
Решение: Отключите оптимизацию батареи для Termux в настройках телефона.
Советы по оптимизации
Выбирайте правильную модель
Я выбрал gemma3:1b потому что только она поместилась на моё устройство.
Модель | Параметры | Требования к ОЗУ |
|---|---|---|
| 1.5 млрд | 3+ ГБ |
| 4 млрд | 6+ ГБ |
| 1.5 млрд | 3+ ГБ |
Экономим память
du -sh ~/.ollama/models/ # проверить занятое место ollama rm <имя_модели> # удалить модель
🔮 Что дальше?
Теперь у вас есть собственный локальный ИИ-ассистент в телефоне и вы уже обошли Apple!
Вы можете:
Общаться с ним в дороге (даже в самолёте)
Использовать как офлайн-помощника для работы с текстом
Ставить другие модели (Mistral, Phi-3, CodeLlama)
Интегрировать его в свои приложения через API
Полезные команды:
ollama list # список установленных моделей ollama pull mistral:7b # скачать другую модель ollama rm gemma3:1b # удалить модель ollama show gemma3:1b # информация о модели ollama ps # Выведет информацию о запущенной модели
🔒 P.S. Насколько это безопасно?
Вся обработка данных происходит локально на вашем устройстве. Ollama не отправляет ваши запросы никуда. Вы можете выключить интернет, и всё будет работать. Ваши диалоги остаются только у вас.
Хотите знать больше?
Подписывайтесь на мой канал в VC.ru и Telegram
Будьте осторожны! Статья сгенерирована человеком :-D
Созданное решение это больше демонстрация, чем полноценное использование. Например, я не стал писать сохранение сессий чатов- это уже отдельная история.
