Локальный ИИ-ассистент на Raspberry Pi 5: гайд по FunctionGemma | AiManual
AiManual Logo Ai / Manual.
31 Дек 2025 Гайд

Создаём персонального ИИ-ассистента на Raspberry Pi 5: читаем почту и управляем календарём

Подробное руководство по созданию приватного ИИ-ассистента на Raspberry Pi 5 для управления почтой и календарём без облаков. FunctionGemma, Python, автономность

Почему вашему Raspberry Pi 5 нужен ИИ-ассистент?

Каждый день мы тратим часы на рутинные задачи: проверяем почту, планируем встречи, ищем важные письма. Облачные ассистенты вроде Google Assistant или Siri удобны, но они отправляют ваши данные на сервера корпораций. Что если собрать персонального помощника, который работает полностью на вашем устройстве, уважает вашу приватность и выполняет именно те задачи, которые нужны вам?

Проблема: Облачные ИИ-сервисы имеют доступ ко всей вашей переписке, историю запросов хранят неопределённое время, а их функционал ограничен тем, что решила добавить компания.

Решение: Локальный ИИ-агент на Raspberry Pi 5 с моделью FunctionGemma. Это не просто чат-бот, а полноценный агент, который умеет выполнять функции (functions) — например, подключаться к вашему почтовому ящику через API и читать письма, или создавать события в календаре. Всё обрабатывается на устройстве, данные никуда не уходят.

💡
Этот проект — логичное развитие идеи локальных ассистентов. Если вас интересуют другие подходы, например, голосовой ассистент с интеграцией через n8n, рекомендую статью «Как построить локальный голосовой ассистент с бесконечными инструментами на n8n».

Что такое FunctionGemma и почему она идеальна для Pi 5?

FunctionGemma — это специальная версия модели Gemma 2B от Google, оптимизированная для вызова функций (function calling). В отличие от обычных языковых моделей, она обучена понимать, когда нужно выполнить внешнее действие (например, отправить запрос к API), и правильно формировать структурированный ответ для этого.

Компонент Рекомендация Примечание
Raspberry Pi 5 4GB или 8GB RAM 8GB предпочтительнее для работы с ИИ
Карта памяти MicroSD 64GB Class 10 Или SSD через USB3 для скорости
Охлаждение Активный кулер ИИ-модели нагружают CPU
Модель ИИ FunctionGemma 2B (Q4_K_M) Оптимальный баланс скорости/качества

Raspberry Pi 5 с его 2.4 GHz quad-core CPU и поддержкой PCIe 2.0 (для быстрого SSD) достаточно мощён для запуска квантованных версий моделей размером 2-3 миллиарда параметров. FunctionGemma 2B в формате Q4_K_M занимает около 1.5 GB RAM и работает с приемлемой скоростью (2-5 токенов в секунду).

1 Подготовка Raspberry Pi 5 и установка базового ПО

Начнём с чистой установки Raspberry Pi OS (64-bit). Важно использовать именно 64-битную версию, так как современные ИИ-библиотеки требуют этого.

# Обновляем систему и устанавливаем ключевые пакеты
sudo apt update && sudo apt upgrade -y
sudo apt install -y python3-pip python3-venv git curl wget build-essential

# Устанавливаем Ollama — самый простой способ запускать модели
curl -fsSL https://ollama.com/install.sh | sh

# Проверяем установку
ollama --version

Важно: Если вы планируете использовать Pi 5 для других ИИ-экспериментов, рекомендую прочитать глубокий анализ форматов квантования моделей. Для нашего случая Q4_K_M — оптимальный выбор.

2 Установка и настройка FunctionGemma

Ollama уже содержит FunctionGemma в своём репозитории. Загружаем квантованную версию:

# Скачиваем модель (это займёт время, ~1.5 GB)
ollama pull functiongemma:2b-q4_K_M

# Запускаем модель в фоне как сервис
ollama serve &

# Проверяем, что модель работает
curl http://localhost:11434/api/generate -d '{
  "model": "functiongemma:2b-q4_K_M",
  "prompt": "Hello"
}'

Теперь у нас работает локальный ИИ, который понимает function calling. Но пока он не умеет делать ничего полезного — ему нужны инструменты (functions).

3 Создаём инструменты для работы с почтой и календарём

Мы будем использовать Google API для Gmail и Google Calendar. Это безопаснее, чем парсить почту напрямую, и даёт полный контроль над правами доступа.

  1. Создаём проект в Google Cloud Console
  2. Включаем API Gmail и Google Calendar
  3. Создаём OAuth 2.0 credentials (тип "Desktop app")
  4. Скачиваем JSON-файл с учётными данными и сохраняем как credentials.json

Устанавливаем необходимые Python-библиотеки:

pip install google-auth google-auth-oauthlib google-auth-httplib2 \
  google-api-python-client python-dotenv

Создаём файл tools.py с нашими инструментами:

import os
import pickle
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from datetime import datetime, timedelta

# Если изменяете SCOPES, удалите файл token.pickle
SCOPES = [
    'https://www.googleapis.com/auth/gmail.readonly',
    'https://www.googleapis.com/auth/calendar'
]

def get_gmail_service():
    """Аутентификация и создание сервиса Gmail"""
    creds = None
    if os.path.exists('token.pickle'):
        with open('token.pickle', 'rb') as token:
            creds = pickle.load(token)
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                'credentials.json', SCOPES)
            creds = flow.run_local_server(port=0)
        with open('token.pickle', 'wb') as token:
            pickle.dump(creds, token)
    return build('gmail', 'v1', credentials=creds)

def get_calendar_service():
    """Аутентификация и создание сервиса Calendar"""
    creds = None
    if os.path.exists('token.pickle'):
        with open('token.pickle', 'rb') as token:
            creds = pickle.load(token)
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                'credentials.json', SCOPES)
            creds = flow.run_local_server(port=0)
        with open('token.pickle', 'wb') as token:
            pickle.dump(creds, token)
    return build('calendar', 'v3', credentials=creds)

def read_unread_emails(limit=5):
    """Чтение непрочитанных писем"""
    service = get_gmail_service()
    results = service.users().messages().list(
        userId='me', labelIds=['INBOX', 'UNREAD'], maxResults=limit).execute()
    messages = results.get('messages', [])
    
    emails = []
    for msg in messages:
        txt = service.users().messages().get(userId='me', id=msg['id']).execute()
        subject = next(h['value'] for h in txt['payload']['headers'] if h['name'] == 'Subject')
        sender = next(h['value'] for h in txt['payload']['headers'] if h['name'] == 'From')
        emails.append({'subject': subject, 'from': sender})
    return emails

def create_calendar_event(summary, start_time, duration_hours=1):
    """Создание события в календаре"""
    service = get_calendar_service()
    start = datetime.fromisoformat(start_time)
    end = start + timedelta(hours=duration_hours)
    
    event = {
        'summary': summary,
        'start': {'dateTime': start.isoformat(), 'timeZone': 'Europe/Moscow'},
        'end': {'dateTime': end.isoformat(), 'timeZone': 'Europe/Moscow'},
    }
    
    created_event = service.events().insert(calendarId='primary', body=event).execute()
    return f"Создано событие: {created_event['summary']} в {start.strftime('%H:%M %d.%m.%Y')}"
💡
Если вы хотите реализовать более сложную логику работы с почтой, например, автосортировку или шаблонные ответы, изучите опыт создания Privemail — локального email-клиента с ИИ.

4 Создаём агента, который связывает FunctionGemma с инструментами

Теперь напишем основной скрипт, который будет принимать запросы на естественном языке, определять, какие инструменты нужны, и выполнять их.

import json
import requests
from tools import read_unread_emails, create_calendar_event

OLLAMA_URL = "http://localhost:11434/api/chat"

# Описание инструментов для модели в формате JSON Schema
TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "read_unread_emails",
            "description": "Читает непрочитанные письма из Gmail",
            "parameters": {
                "type": "object",
                "properties": {
                    "limit": {
                        "type": "integer",
                        "description": "Количество писем (по умолчанию 5)"
                    }
                }
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "create_calendar_event",
            "description": "Создаёт событие в Google Calendar",
            "parameters": {
                "type": "object",
                "properties": {
                    "summary": {
                        "type": "string",
                        "description": "Название события"
                    },
                    "start_time": {
                        "type": "string",
                        "description": "Время начала в формате YYYY-MM-DDTHH:MM:SS"
                    },
                    "duration_hours": {
                        "type": "number",
                        "description": "Продолжительность в часах"
                    }
                },
                "required": ["summary", "start_time"]
            }
        }
    }
]

def run_agent(user_query):
    """Основная функция агента"""
    # Первый запрос к модели с описанием инструментов
    messages = [
        {
            "role": "user",
            "content": user_query
        }
    ]
    
    payload = {
        "model": "functiongemma:2b-q4_K_M",
        "messages": messages,
        "tools": TOOLS,
        "stream": False
    }
    
    response = requests.post(OLLAMA_URL, json=payload)
    response_data = response.json()
    
    # Проверяем, хочет ли модель вызвать инструмент
    if 'tool_calls' in response_data['message']:
        tool_call = response_data['message']['tool_calls'][0]
        function_name = tool_call['function']['name']
        function_args = json.loads(tool_call['function']['arguments'])
        
        # Вызываем соответствующий инструмент
        if function_name == "read_unread_emails":
            result = read_unread_emails(**function_args)
        elif function_name == "create_calendar_event":
            result = create_calendar_event(**function_args)
        else:
            result = "Неизвестный инструмент"
        
        # Отправляем результат выполнения обратно модели
        messages.append(response_data['message'])
        messages.append({
            "role": "tool",
            "content": json.dumps(result),
            "tool_call_id": tool_call['id']
        })
        
        # Получаем финальный ответ от модели
        payload["messages"] = messages
        final_response = requests.post(OLLAMA_URL, json=payload)
        return final_response.json()['message']['content']
    else:
        return response_data['message']['content']

# Пример использования
if __name__ == "__main__":
    print(run_agent("Какие у меня непрочитанные письма?"))
    print(run_agent("Создай встречу 'Обсуждение проекта' на завтра 15:00"))

5 Запуск и тестирование системы

Запускаем наш агент и тестируем основные сценарии:

# В первом терминале запускаем Ollama
ollama serve

# Во втором терминале запускаем нашего агента
python3 assistant.py

При первом запуске откроется браузер для авторизации в Google. После этого создастся файл token.pickle с токенами доступа.

Предупреждение: Файл token.pickle содержит ключи доступа к вашей почте и календарю. Храните его в безопасном месте и не коммитьте в Git! Добавьте token.pickle и credentials.json в .gitignore.

Возможные ошибки и их решение

Ошибка Причина Решение
"Out of memory" при загрузке модели Недостаточно оперативной памяти Используйте swap-файл: sudo fallocate -l 4G /swapfile && sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile
Медленная работа модели CPU перегревается или throttling Установите активное охлаждение, проверьте температуру: vcgencmd measure_temp
Ошибка аутентификации Google Истёк токен или неверные scope Удалите token.pickle и перезапустите аутентификацию
Модель не вызывает инструменты Неправильное описание tools в JSON Сверьтесь с документацией Ollama Function Calling

Куда развивать проект дальше?

  • Голосовой интерфейс: Добавьте VOSK или Whisper для распознавания речи и синтеза ответов. В статье о голосовом ассистенте на n8n есть полезные идеи.
  • Больше инструментов: Подключите управление умным домом (Home Assistant), проверку погоды, напоминания.
  • Автоматизация сценариев: Например, «если пришло письмо от начальника с пометкой „срочно“ — создай событие в календаре на ближайшее время».
  • Веб-интерфейс: Создайте простой UI на Flask или FastAPI для управления ассистентом из браузера.
💡
Если вы столкнулись с ошибками при программировании с ИИ, изучите 10 типичных ошибок новичков при работе с ИИ-помощниками — это сэкономит вам много времени.

Теперь у вас есть полностью локальный ИИ-ассистент, который работает на Raspberry Pi 5, уважает вашу приватность и может быть расширен под любые ваши задачи. Это не просто игрушка, а практичный инструмент для автоматизации рутины, который вы контролируете от начала до конца.