Локальные нейросети — это не только эксперименты в терминале. Их можно интегрировать в реальные веб-проекты: чат-боты, помощники на сайте, генерация текстов, ответы на вопросы пользователей. Всё это работает без облачных сервисов, без ежемесячных платежей и без передачи данных третьим лицам.
В этой статье мы подробно разберём:
- Архитектуру подключения локальной нейросети к сайту.
- Подготовку Ollama: запуск, настройка CORS, доступ по сети.
- Пример на PHP: отправка запроса к Ollama через cURL.
- Пример на JavaScript (Node.js) для бэкенда.
- Создание простого чат-бота на фронтенде.
- Потоковую передачу (streaming) для живого ответа.
- Обработку ошибок и таймаутов.
- Оптимизацию: кеширование, ограничение длины.
- Безопасность: как не выставить Ollama в интернет.
- Частые ошибки и их решение.
1. Архитектура подключения
Типичная схема выглядит так:
Пользователь → Браузер (фронтенд) → Веб-сервер (бэкенд) → Ollama → Модель- Фронтенд — HTML/JavaScript, который отправляет запросы к вашему бэкенду.
- Бэкенд — PHP, Node.js, Python или другой язык. Он принимает запрос от фронтенда, обращается к Ollama и возвращает ответ.
- Ollama — запущена на том же сервере или на отдельной машине. Слушает порт 11434 (по умолчанию).
- Модель — загружена в Ollama и готова к генерации.
Почему не обращаться к Ollama напрямую из браузера?
- Ollama не имеет встроенной аутентификации.
- CORS по умолчанию отключён (можно включить, но это небезопасно).
- Вы раскрываете порт 11434 всему интернету.
- Лучше делать прокси через бэкенд, где можно добавить аутентификацию, ограничения и кеширование.
2. Подготовка Ollama
2.1 Запуск сервера
Если Ollama ещё не запущена:
ollama serveИли через systemd:
sudo systemctl start ollama
sudo systemctl enable ollama2.2 Проверка доступности
curl http://localhost:11434/api/tagsЕсли сервер работает, вы увидите список моделей.
2.3 Настройка доступа по сети (если нужно)
По умолчанию Ollama слушает только 127.0.0.1:11434. Если вы хотите обращаться к ней с другого сервера (например, с отдельного бэкенда), измените переменную окружения OLLAMA_HOST.
Linux (systemd):
Отредактируйте /etc/systemd/system/ollama.service:
ini
[Service]
Environment="OLLAMA_HOST=0.0.0.0:11434"Перезапустите:
sudo systemctl daemon-reload
sudo systemctl restart ollamaВажно: если вы открываете Ollama в интернет, обязательно настройте фаервол и аутентификацию (или прокси через бэкенд).
2.4 Настройка CORS (если нужно обращаться из браузера)
Ollama поддерживает CORS, но по умолчанию он отключён. Включать его не рекомендуется, если вы не контролируете всех клиентов. Лучше делать прокси через бэкенд.
Если всё же нужно:
export OLLAMA_ORIGINS="https://example.com"Или в systemd:
ini
Environment="OLLAMA_ORIGINS=https://example.com"3. Пример на PHP: отправка запроса к Ollama
PHP — один из самых популярных языков для веб-разработки. Рассмотрим, как отправить запрос к Ollama через cURL.
3.1 Простой запрос (без streaming)
<?php
function askOllama(string $prompt, string $model = 'mistral'): string {
$url = 'http://localhost:11434/api/generate';
$data = [
'model' => $model,
'prompt' => $prompt,
'stream' => false
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_TIMEOUT, 120); // таймаут 2 минуты
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
throw new Exception("Ollama вернула код $httpCode");
}
$result = json_decode($response, true);
return $result['response'] ?? '';
}
// Использование
try {
$answer = askOllama('Объясни, что такое HTTP/2, в двух предложениях.');
echo $answer;
} catch (Exception $e) {
echo "Ошибка: " . $e->getMessage();
}
?>3.2 Чат с историей (эндпоинт /api/chat)
<?php
function chatWithOllama(array $messages, string $model = 'mistral'): string {
$url = 'http://localhost:11434/api/chat';
$data = [
'model' => $model,
'messages' => $messages,
'stream' => false
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_TIMEOUT, 120);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response, true);
return $result['message']['content'] ?? '';
}
// Пример
$messages = [
['role' => 'system', 'content' => 'Ты — помощник по веб-разработке.'],
['role' => 'user', 'content' => 'Как настроить HTTPS на Nginx?']
];
echo chatWithOllama($messages);
?>3.3 Потоковая передача (streaming) на PHP
Для длинных ответов можно использовать streaming, чтобы пользователь видел текст по мере генерации.
<?php
function streamOllama(string $prompt, string $model = 'mistral'): void {
$url = 'http://localhost:11434/api/generate';
$data = [
'model' => $model,
'prompt' => $prompt,
'stream' => true
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
// потоковая обработка с защитой от разрыва JSON-пакетов
curl_setopt($ch, CURLOPT_WRITEFUNCTION, function ($ch, $chunk) {
// Используем статическую переменную для хранения незавершенных кусков строки между пакетами
static $buffer = '';
$buffer .= $chunk;
$lines = explode("\n", $buffer);
// Последний элемент массива оставляем в буфере, так как он может быть недописан
$buffer = array_pop($lines);
foreach ($lines as $line) {
$line = trim($line);
if (empty($line)) continue;
$json = json_decode($line, true);
if (isset($json['response'])) {
echo $json['response'];
// КРИТИЧЕСКИ ВАЖНО для мгновенного выталкивания буфера в браузер:
if (ob_get_level() > 0) {
ob_flush();
}
flush();
}
}
return strlen($chunk);
});
curl_exec($ch);
curl_close($ch);
}
// Использование
header('Content-Type: text/plain; charset=utf-8');
streamOllama('Расскажи про HTTP/3');
?>Важно: для streaming нужно отключить буферизацию вывода в PHP и веб-сервере.
4. Пример на Node.js (бэкенд)
Node.js — отличный выбор для асинхронной работы с API.
4.1 Установка зависимостей
npm init -y
npm install express node-fetch4.2 Простой сервер
const express = require('express');
const fetch = require('node-fetch');
const app = express();
app.use(express.json());
app.post('/api/ask', async (req, res) => {
const { prompt, model = 'mistral' } = req.body;
try {
const response = await fetch('http://localhost:11434/api/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
model,
prompt,
stream: false
})
});
const data = await response.json();
res.json({ answer: data.response });
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Ошибка при обращении к Ollama' });
}
});
app.listen(3000, () => {
console.log('Сервер запущен на порту 3000');
});4.3 Streaming на Node.js
// Актуальный асинхронный стриминг для современных версий Node.js и Node-Fetch v3
app.post('/api/stream', async (req, res) => {
const { prompt, model = 'mistral' } = req.body;
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.setHeader('Transfer-Encoding', 'chunked');
try {
const response = await fetch('http://127.0.0', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ model, prompt, stream: true })
});
if (!response.ok) throw new Error(`Ollama error: ${response.statusText}`);
// Исправлен синтаксис итерации по нативному ReadableStream
for await (const chunk of response.body) {
const lines = chunk.toString().split('\n');
for (const line of lines) {
if (!line.trim()) continue;
try {
const json = JSON.parse(line);
if (json.response) {
res.write(json.response);
}
} catch (e) {}
}
}
res.end();
} catch (error) {
console.error(error);
res.write('Ошибка потока генерации');
res.end();
}
});5. Создание чат-бота на фронтенде
Теперь, когда у нас есть бэкенд, сделаем простой интерфейс чата.
HTML + JavaScript:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Чат с локальной нейросетью</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
#chat { border: 1px solid #ccc; height: 400px; overflow-y: scroll; padding: 10px; margin-bottom: 10px; }
.message { margin: 5px 0; padding: 8px; border-radius: 5px; }
.user { background: #e3f2fd; text-align: right; }
.bot { background: #f1f1f1; }
#input { width: 80%; padding: 10px; }
#send { padding: 10px 20px; }
</style>
</head>
<body>
<h1>Чат с локальной нейросетью</h1>
<div id="chat"></div>
<input type="text" id="input" placeholder="Введите сообщение...">
<button id="send">Отправить</button>
<script>
const chat = document.getElementById('chat');
const input = document.getElementById('input');
const sendBtn = document.getElementById('send');
function addMessage(text, sender) {
const div = document.createElement('div');
div.className = `message ${sender}`;
div.textContent = text;
chat.appendChild(div);
chat.scrollTop = chat.scrollHeight;
}
async function sendMessage() {
const text = input.value.trim();
if (!text) return;
addMessage(text, 'user');
input.value = '';
try {
const response = await fetch('/api/ask', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt: text })
});
const data = await response.json();
addMessage(data.answer, 'bot');
} catch (error) {
addMessage('Ошибка: не удалось получить ответ', 'bot');
}
}
sendBtn.addEventListener('click', sendMessage);
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') sendMessage();
});
</script>
</body>
</html>6. Обработка ошибок и таймаутов
При работе с локальными моделями возможны ошибки:
- Модель не загружена.
- Недостаточно памяти.
- Превышен таймаут.
- Ollama не запущена.
Рекомендации:
- Установите таймаут на запрос (например, 120 секунд).
- Обрабатывайте HTTP-коды (500, 503).
- Показывайте пользователю понятное сообщение об ошибке.
- Логируйте ошибки для отладки.
Пример на PHP:
curl_setopt($ch, CURLOPT_TIMEOUT, 120);
$response = curl_exec($ch);
if ($response === false) {
throw new Exception('Ошибка cURL: ' . curl_error($ch));
}7. Оптимизация
7.1 Кеширование
Если один и тот же вопрос задаётся часто, кешируйте ответы.
$cacheKey = 'ollama_' . md5($prompt);
$cached = $redis->get($cacheKey);
if ($cached) {
return $cached;
}
$answer = askOllama($prompt);
$redis->setex($cacheKey, 3600, $answer);
return $answer;7.2 Ограничение длины ответа
Укажите num_predict, чтобы модель не генерировала слишком длинный ответ.
{
"model": "mistral",
"prompt": "...",
"options": {
"num_predict": 512
}
}7.3 Выбор модели
- Для быстрых ответов — Mistral 7B, Phi, Gemma 2B.
- Для сложных — Llama 3 8B, DeepSeek Coder.
- Для русского — Mistral, Qwen.
7.4 Использование GPU
Если на сервере есть GPU, Ollama автоматически его использует. Это ускоряет генерацию в 3–10 раз.
8. Безопасность
Никогда не выставляйте Ollama напрямую в интернет. Порт 11434 не имеет аутентификации, и любой сможет использовать вашу модель.
Правильная схема:
- Ollama слушает только
127.0.0.1. - Бэкенд (PHP/Node.js) обращается к Ollama локально.
- Бэкенд имеет аутентификацию (сессии, JWT, API-ключи).
- Бэкенд ограничивает частоту запросов (rate limiting).
- Бэкенд логирует запросы.
Если нужно обращаться к Ollama с другого сервера:
- Используйте SSH-туннель.
- Или настройте VPN.
- Или поставьте прокси с аутентификацией (Nginx + Basic Auth).
Пример Nginx как прокси с аутентификацией:
server {
listen 443 ssl;
server_name ollama.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
auth_basic "Restricted Access";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://127.0.0.1:11434;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# КРИТИЧЕСКИ ВАЖНО ДЛЯ СТРИМИНГА: отключаем буферизацию прокси и выставляем бесконечный таймаут
proxy_buffering off;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
}9. Частые ошибки
| Ошибка | Причина | Решение |
|---|---|---|
Connection refused | Ollama не запущена | Запустите ollama serve. |
CORS error | Браузер блокирует запрос | Делайте прокси через бэкенд. |
| Таймаут | Модель долго генерирует | Увеличьте таймаут, уменьшите num_predict. |
| Пустой ответ | Модель не загружена | Проверьте ollama list, запустите модель. |
| Медленно | Нет GPU, большая модель | Используйте меньшую модель или квантизацию Q4_0. |
| Ошибка JSON | Неверный формат запроса | Проверьте Content-Type: application/json. |
10. Заключение
Подключение локальной нейросети к сайту — это реально и доступно каждому. Вы можете создать чат-бота, помощника по контенту, генератор текстов — и всё это будет работать на вашем сервере, без облаков и платежей.
Мы разобрали:
- Архитектуру подключения.
- Подготовку Ollama.
- Примеры на PHP и Node.js.
- Streaming для живого ответа.
- Создание чат-бота на фронтенде.
- Обработку ошибок, оптимизацию и безопасность.
