Die AI und ich–Winning Team und keiner bekommt es mit

In der Reihe, werde Supercoder bei absoluter Ahnungslosigkeit, heute das Produktivitätswerkzeug. Lies die IBAN aus der Rechnung, damit ich sie nicht abtippen muss. Damit es niemand erfährt, nutze ich ein lokales LLM auf einem Mac Mini.  
Auf diesem läuft das Programm LMStudio. Der Zugriff erfolgt über einen OpenAPI kompatiblen REST http Zugriff. LLM Modelle brauchen viel Speicher. 64 GB hat die Apfel Kiste.

macmini
Auf meinem lokalen Windows Rechner nutze ich Visual Studio Code um ein Python Script auszuführen.

Konkret wird der Benutzer aus einer Rechnung per Snippet Tool einen Teil Screenshot erstellen und dann das Programm laufen lassen.

clip0

Ich bin selber Mac Bediendepp. Ich habe mir jedenfalls VNC eingerichtet (als Remote Desktop alternative) um auf die Apple Maschine zugreifen zu können. Dort läuft LMStudio als Host für beliebige Modelle. Für dieses Beispiel wird Qwen 3.5 genutzt.

clip1

Der Name Qwen3.5-35B-A3B-8bit (MLX Community) beschreibt Architektur, Größe und Optimierung „Qwen3.5“ ist die Modellgeneration, „35B“ steht für die Gesamtanzahl der Parameter (≈35 Milliarden), während „A3B“ aus der Mixture-of-Experts-Architektur kommt und bedeutet, dass nur etwa 3 Milliarden Parameter pro Token aktiv genutzt werden – das Modell verhält sich also rechnerisch wie ein deutlich kleineres Modell bei gleicher Wissensbasis. Der Zusatz „8bit“ beschreibt die Quantisierung (Speicher- und Performance-Optimierung durch niedrigere Präzision), und „mlx-community“ zeigt, dass es sich um eine speziell für Apples MLX-Runtime angepasste Community-Portierung handelt.

Hat man das Modell in LMStudio geladen (erst download- dann laden), kann man im Chat dieses auch direkt nutzen. Das ist zwar nicht unser Anwendungsfall, aber für einen ersten Test perfekt.

clip2

Wir erkennen, das Ding kann Vision und Text.

Allerdings brauchen wir für den Remote Zugriff einen sogenannten API Key. Dieser ist eine Art Benutzername/Passwort. Das nutzen Service Anbieter wie OpenAI, Gemini oder Anthropic um dir von deiner Kreditkarte Nutzungsbasiert passend abzubuchen. Basis sind immer Token die rein oder raus gehen. Ein Token ist eine Nummer die ein sehr kleines Wort darstellt.
Nutzt du LM Studio lokal, brauchst du keinen Token. Aber da die wenigstens Menschen einen PC mit 128GB in der Hand haben, die Aufteilung. Der Mac Mini macht das Qwen Tiny LM und der Windows PC ist deiner.
Du musst nun in der Console des MAC oder Remote per VNC, in LMStudio einen Api Key erstellen.

Developer links in der Toolleiste -links oben [local Server] - Mitte oben [Server Settings]- Popup Window - Zeile Active API Keys [Manage Tokens]- neues Popup LM Studio API Tokens [Create New token]- weiteres Window [Create Token] und am Ende [Copy]

clip4b

Da war es wieder das Problem. Ich bekomme es nicht hin die Zwischenablage von Mac und Windows per VNC zu teilen. Abtippen des Keys ist auch doof. Also Screenshot aus Windows! und meiner neue App übergeben um den Text zu erhalten.

Ich habe das durch meine Python App laufen lassen und es funktioniert!

clip5

Achtung: Qwen ist ein sogenanntes Thinking Modell. Das macht die Erstanwort zunächst langsam auch wenn dann die Tokens echt schnell fließen. Ein netter Effekt von LM Studio vs Ollama (eine andere Runtime oder auch Inferenz-Engine genannt) ist das Logging der Geschehnisse in der UI von LMStudio.
clip6

Wir sind ready to go. Start your Visual Studio Code. File- New – Type Python und….
Ich will ehrlich sein, alles von ChatGPT generiert. Aber ich erklär es trotzdem. Import lädt Bibliotheken, die eigentlich Stärke von Python. Diese müssen vorhanden sein. Sind sie es nicht landete man bei einem PIP Install xxx. Aber Zeile 1-3 sollten auch so da sein. Hier wird die Library Imagegrab genutzt um das Bild aus der Zwischenablage zu holen. Für den späteren Zugriff zur OpenAPI kompatiblen REST API auf unserem Mini Mac Server muss ein transportierbares Format existieren und das ist eben Base64. Das heißt ein binäres Bild wird in eine andere Zeichensatz umgesetzt. Das ist alles Internet Standard.

   1:  import base64
   2:  import io
   3:  import requests
   4:  from PIL import ImageGrab
   5:   
   6:  img = ImageGrab.grabclipboard()
   7:   
   8:  if img is None or not hasattr(img, "save"):
   9:      raise SystemExit("Kein Bild in der Zwischenablage gefunden.")
  10:   
  11:  buffer = io.BytesIO()
  12:  img.save(buffer, format="PNG")
  13:  image_b64 = base64.b64encode(buffer.getvalue()).decode("utf-8")

Im nächsten Schritt muss die Nachricht, wie wir sie im Chat eintippen würden zusammen gestellt werden. International seit langem üblich, sie in einen sogenannten JSON Datentyp zu pressen. Da steht drinnen welches Modell (das ich vorher in LMStudio geladen hatte). Wie genau (Temp).
Die Rolle User ist Standard für einfache Dinge. Dann der Prompt und die Daten. Payload weil da müsste man zahlen. Nein Payload ist ein willkürlicher Name der Variable.

   1:  payload = {
   2:      "model": "qwen3.5-35b-a3b-8bit",
   3:              "temperature": 0.2,
   4:      "messages": [
   5:          {
   6:              "role": "user",
   7:              "content": [
   8:                  {
   9:                      "type": "text",
  10:                      "text": "extrahiere den text aus diesem bild und gib ihn als json antwort zurück"
  11:                  },
  12:                  {
  13:                      "type": "image_url",
  14:                      "image_url": {
  15:                          "url": f"data:image/png;base64,{image_b64}"
  16:                      }
  17:                  }
  18:              ]
  19:          }
  20:      ]
  21:  }

Und nun machen wir das, was der Benutzer üblicherweise im Browser macht. Es wird eine URL aufgerufen, die API und die Daten übergeben.
Üblicherweise schickt ein normaler Benutzer ein sogenannten HTTP Get im Browser. Erst wenn ein Formular und dessen Inhalt gesendet wird, nutzt der Browser ein POST Kommando. Das sieht man alles nicht. Aber idente Technologie für den Aufruf eines REST Services. HTTP Post, Url (IP unseres MAC Mini, Port und der Rest ist halt so bei LMStudio). Zeile 3, was wollen wir eigentlich und in Zeile 4 wer wir sind. Also der API Key den ich vorher im Blog generiert habe, als Parameter in der Autorisierung. Das Wort Bearer verweist auf die Art der Authentifizierung. Da gibt es verschiedene. 

   1:  r = requests.post(
   2:      "http://192.168.255.17:1234/v1/chat/completions",
   3:      json=payload,
   4:      headers={"Content-Type": "application/json","Authorization": "Bearer sk-lm-FI3UAhHh:QKdvJnxCeLsqvMHTPg7J"   }
   5:  )
   6:   
   7:  r.raise_for_status()
   8:  print(r.json()["choices"][0]["message"]["content"])
Zeile 7 wird dich über hoffentlich nicht auftretende Fehler informieren und die letzte Python Code Zeile  macht die Ausgabe in Visual Studio Code.

Für die ganz faulen der komplette Code für OCR Erkennung der Zwischenablage in einem Stück. Austauschen ServerIP und APIKey.

import base64
import io
import requests
from PIL import ImageGrab
img = ImageGrab.grabclipboard()
if img is None or not hasattr(img, "save"):
    raise SystemExit("Kein Bild in der Zwischenablage gefunden.")
buffer = io.BytesIO()
img.save(buffer, format="PNG")
image_b64 = base64.b64encode(buffer.getvalue()).decode("utf-8")
payload = {
    "model": "qwen3.5-35b-a3b-8bit",
            "temperature": 0.2,
    "messages": [
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": "extrahiere den text aus diesem 
bild und gib ihn als json antwort zurück"
                },
                {
                    "type": "image_url",
                    "image_url": {
                        "url": f"data:image/png;base64,{image_b64}"
                    }
                }
            ]
        }
    ]
}
r = requests.post(
    "http://ServerIP:1234/v1/chat/completions",
    json=payload,
    headers={"Content-Type": "application/json","Authorization": "Bearer ApiKey"   }
)

r.raise_for_status()
print(r.json()["choices"][0]["message"]["content"])

Noch ein Wort zur Kaufentscheidung. Wir haben hier im Test auch dickere Nvidia Grafikkarten. Problem ist der Stromverbrauch eines derartigen Servers und auch vorhandenes RAM. Ein Modell muss komplett in den Speicher geladen werden können. Mein Demo Qwen kommt auf rund 30G und ist eher klein klein. Noch eine Alternative Nvidia DGX Spark. VRAM bis 128GB. Allerdings soll das relativ langsam sein. Dazu kommt der hohe Preis und mangelnde Nachnutzung.
Die Token/Sekunde Leistung des Mac Mini ist jedenfalls beeindruckend, solange nur ein Nutzer drauf ist.

Beindruckend: eine einfache Änderung am Prompt “. Übersetze ins Deutsche.” mit einem Screenshot eines Twitter Scherzkeks funktioniert.

clip8

{
  "text": [
    "Die automatische Übersetzungsfunktion ist die großartigste Innovation auf der X-Plattform. Danke an @nikitabier und @elonmusk. Sie verbindet die Welt enger zusammen und bringt völlig neue Inspirationen und Erkenntnisse.",echt
    "Dennoch wage ich zu wetten, dass in Deutschland der Anteil derer am höchsten ist, die diese Funktion ausgeschaltet haben. @nikitabier, hast du dazu Daten?"
  ]
}

Alles lokal ohne das meine Daten jemals das Haus verlassen hätten!

Kommentare sind geschlossen