- BrainTools - https://www.braintools.ru -

LLM для Meshtastic на Orange PI 5 8G

Однажды встретились Orange PI 5, Heltect v3, свободное время и J4F и в Саратове появился второй LLM бот для Meshtastic. Сегодня расскажу как все это повторить если у вас в одном месте и в одно время появится примерно такое же.

Кратенько про Meshtastic. Сейчас у нас в Саратове по данным https://map.onemesh.ru/ [1] 114 нод, по данным моей ноды – 150 из которых около ~40 постоянно онлайн). В качестве железа этого проекта используется стационарный Heltect v3 с увеличенной антенной закрепленный на окне и подключенный к WiFi и MQTT. Так как нода Meshtastic не умеет мультиконнект, то к ноде подключена интеграция Home Assistant которая умеет работать как прокси. Но это не обязательно, то же самое умеет meshmonitor [2], его можно запускать как угодно, даже есть инсталяторы под разные OS.

Eще забавный факт, вчера человек летел из Махачкалы с LILYGO T-Echo, судя по flightradar24 в 245км (в районе фролово) от Саратова на высоте примерно 10 км, и мы перекидывались сообщениям с ним почти до его подлета к Тамбову, и даже удалось перекинуться сообщениями с Пензой.

LLM нода – Orange PI 5 8G RAM c 513G m2 SSD. На нем крутится Ubuntu 22.04.5 c ollama и c закаченной моделькой phi4-mini

gals@orangepi5-8g:~ $ ollama list
NAME                ID              SIZE      MODIFIED
phi4-mini:latest    78fad5d182a7    2.5 GB    2 weeks ago

Типичная трата ресурсов при обработке запроса из meshtastic такая

Prometheus

Prometheus
А это сам запрос

А это сам запрос

А теперь к основной части, самому скрипту бота на python который работает как прослойка между Meshatastic и Ollama . Вообще скрипт задумывался как тупой пересыльщик сообщений из Meshatastic в телегу, а так же записыватель базы всех сообщений и нод в sqlite3 базу. Там все тупо, скучно и не интересно. На интересных особенностях остановимся далее. Актуальная версия доступна на github [3], вместе с systemd service, env и requirements.txt

Пробежимся по интерeсным моментам

Кроме основного дефолтного канала у меня в Meshtastic настроены каналы со своими отдельными ключами шифрования. 1 – семья, 2 – друзья, они зашиты в константу CHANNELS

Так как в Meshtastic есть ограничение на размер в сообщении в 230 байт, притом что кириллица это 2 байта, а emoji могут быть больше, а так же потому что ресурсы llm ноды ограничены мы добавляем системный промпт “Ты чатбот для Meshtastic. Отвечай по-русски. Ответ строго <= 110 символов. Без списков, без пояснений, без приветствий.” в функцию ollama_reply, так же туда же добавляем “temperature”: 0.4, “top_p”: 0.9, что бы ответ был быстрее. Так как LLM может выдать ответ длиннее чем можно, и сообщения тогда вообще не уйдет ответ еще проходит через функцию clamp_200 которая обрезает сообщение до 110 символов. У нас в Cаратове как-то сложилось что обе модели LLM отвечают только на сообщения которые начинаются с !llm и это тупо захардкожено прямо в скрипте.

Скрытый текст
def ollama_reply(prompt: str) -> str:
    # Request to model to be very short
    system = (
        "Ты чатбот для Meshtastic. "
        "Отвечай по-русски. "
        "Ответ строго <= 110 символов. "
        "Без списков, без пояснений, без приветствий."
    )

    payload = {
        "model": OLLAMA_MODEL,
        "prompt": f"{system}nnВопрос: {prompt}nОтвет:",
        "stream": False,
        # Not speed up
        "options": {
            "temperature": 0.4,
            "top_p": 0.9,
        },
    }

Второй интересный момент – функция send_response. В meshtastic есть 3 вида текстовых сообщений, обычные, ответы (ака thread) и так же реакции [4] на сообщения. При этом API sendText из API умеет только первый тип, для обхода этого ограничения send_response использует _sendPacket из API напрямую. Что забавно, в реакции можно пихать не только emoji но и текст. В скрипте для примера тоже захордкожены реакции на hi и пинг

Выглядит это примерно так

Скрин c andorid

Скрин c andorid

Говорят что это работает только в приложении на Android, а на IOS этого нет, но сам не проверял.

Собственно у нас в саратове 2 llm, одна моя, вторая вроде крутится на видюхе (к сожалению и модель и модель видюхи забыл), и сама посерьезнеe и побольше, на скринах дальше можно глянуть ответы и сравнить их скорость. sg-n это описываемая тут нода, а NWAY – другая. Ради забавы ради позадавал вопросы и на китайском.

Скрытый текст
LLM для Meshtastic на Orange PI 5 8G - 4
LLM для Meshtastic на Orange PI 5 8G - 5
LLM для Meshtastic на Orange PI 5 8G - 6
LLM для Meshtastic на Orange PI 5 8G - 7
LLM для Meshtastic на Orange PI 5 8G - 8

По идее если добавить в скрипте в ответ бота !llm то можно устроить теоретически бесконечный диалог :) но канал не резиновый и небольшую рекурсию можно устроить и одним запросом, напоминаю что sg-h это бот

LLM для Meshtastic на Orange PI 5 8G - 9

В целом модель phi4-mini ведет себя отлично, даже без ограничений которые наложены запросом из мештастик бота. Эта же модель подключена к телеграм боту основанному на вот этом проекте [5] где ограничений нет, и там она вполне хорошо держит конекст в диалоге, выдает довольно релевантные ответы, но иногда может призадуматься.

Загрузка системы во время диалога в телеге.

Загрузка системы во время диалога в телеге.

Если, вдруг, вы дочитали до конца и у вас есть Home Assistant c настроенной интеграцией с Meshtastci, то вот вам бонусом HA автоматизация которая по запросу !w выдает текущую погоду в канал

Скрытый текст
alias: Meshatic Weather
description: ""
triggers:
  - domain: meshtastic
    type: channel_message.received
    entity_id: meshtastic.gateway_sg_h_channel_primary
    trigger: device
conditions:
  - condition: template
    value_template: "{{ "!w" in (trigger.event.data.message | lower) }}"
actions:
  - action: weather.get_forecasts
    metadata: {}
    data:
      type: daily
    enabled: false
  - delay:
      hours: 0
      minutes: 0
      seconds: 12
      milliseconds: 0
  - action: meshtastic.broadcast_channel_message
    metadata: {}
    data:
      ack: true
      channel: meshtastic.gateway_sg_h_channel_primary
      message: >-
        Погода: 🌡️ {{state_attr('weather.home', 'temperature')
        }}{{state_attr('weather.home', 'temperature_unit')}} 💧
        {{state_attr('weather.home', 'humidity')}}% 💨
        {{state_attr('weather.home', 'wind_speed')}}{{state_attr('weather.home',
        'wind_speed_unit')}} / {{ ((state_attr('weather.home', 'wind_speed') |
        float(0)) / 3.6) | round(1) }}m/s {% set deg =
        state_attr('weather.home', 'wind_bearing') | float(0) %}{% set dirs = [
          "С",
          "СВ",
          "В",
          "ЮВ",
          "Ю",
          "ЮЗ",
          "З",
          "СЗ"
        ] %}{% set idx = ((deg + 22.5) // 45) | int %} 💨↗️ {{ dirs[idx % 8]
        }}({{ deg }})° 🌥️ {{state_attr('weather.home', 'cloud_coverage')}}%
        ☀️(uv_index) {{state_attr('weather.home', 'uv_index')}} 🏋️
        {{state_attr('weather.home', 'pressure')}}{{state_attr('weather.home',
        'pressure_unit')}}{% set hpa = state_attr('weather.home', 'pressure') |
        float(0) %}{% set mmhg = (hpa * 0.750064) | round(0) %}{% if mmhg < 745
        %}{% set level = "низкое" %}{% elif mmhg <= 765 %}{% set level = "норм"
        %}{% else %}{% set level = "высокое" %}{% endif %}/{{ mmhg }}mm ({{
        level }}). {% set t = states('weather.home') %}{% set map = {
          'sunny': 'клево и солнечно',
          'clear-night': 'Темно и ясно',
          'cloudy': 'облачно немношк',
          'partlycloudy': 'Иногда облачность',
          'rainy': 'лужи и дождь',
          'pouring': 'не забываем зонт, ливень',
          'snowy': 'готовьте лыжи, снег',
          'snowy-rainy': 'херовато, снег с дождём',
          'windy': 'ветрено нафик',
          'windy-variant': 'может сдуть нафик',
          'fog': 'загадочно и туманно',
          'hail': 'не забудьте каску, град',
          'lightning': 'в укрвтие, гроза',
          'lightning-rainy': 'Гроза с дождём'
        } %}Воопщем - {{ map[t] if t in map else t }}
mode: single

в тексте выглядит это примерно так

LLM для Meshtastic на Orange PI 5 8G - 11

Автор: sergeygals

Источник [6]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/24926

URLs in this post:

[1] https://map.onemesh.ru/: https://map.onemesh.ru/

[2] meshmonitor: https://github.com/Yeraze/meshmonitor/

[3] на github: https://github.com/sergeygalkin/meshtastic/tree/main/bot

[4] реакции: http://www.braintools.ru/article/1549

[5] вот этом проекте: https://github.com/rikkichy/ollama-telegram

[6] Источник: https://habr.com/ru/articles/990588/?utm_source=habrahabr&utm_medium=rss&utm_campaign=990588

www.BrainTools.ru

Rambler's Top100