Openclaw, eine kleine Antwort

(edited)

Ich konnte der Versuchung nicht widerstehen, trotz entsprechender Warnungen, dass es die gefährlichste Software der Welt sei, diese ein wenig auszutesten. Vorweg kann ich schonmal sagen, die Kosten sind ernüchternd. Binnen von 4 Tagen habe ich anscheinend das Kontingent von OpenAI go aufgebraucht. Anscheinend war es das Verfügbare innerhalb von 10 Tagen. Dann habe ich 1x 5 USDT aufgeladen, wobei die Kosten pro Antwort in etwa 1/2 ct. war.

Oh, da muss ich sagen das war eigentlich schon das günstigere Deepseek V3.2. Bei meiner Art der Verwendung war mir das viel zu teuer und ich bin auf gpt4 mini umgestiegen. Kostenfaktor 8x günstiger, aber beschränkteres Modell. Viele berichten von über 100 USD am Tag, mit großen Modellen. Da bin ich mit meinen 1,5 USD eh froh, die ich in den paar Tagen verbraucht habe.

Was habe ich damit gemacht?

Ich habe getestet
-dass er Dateien schreibt
-bestimmte Dateien liest
-als Sprachmodell
-Programme öffnet
-als Analysetool

Am allermeisten hat mich beeindruckt, dass ich das KI-Tool Openvino endlich installieren konnte. Da hatte ich schon etliche Versuche hinter mir, auch mit KI, das zu machen. Openclaw hat mein System untersucht und mir Hilfe bei der Installation angeboten.

Openclaw läuft bei mir über einen Bottoken und BenutzerID in Telegram.


Nachdem ich mich aber nicht auf Dauer mit Kosten beschäftigen möchte, habe ich mithilfe von ChatGPT selbst ein Tool entwickelt, das eingeschränkt bestimmte Dinge tun kann. Es läuft unter anderem mit einem lokalen KI Modell, das bei Openclaw im Gegensatz ein nicht unbeachtliches Problem mit dem Datenschutz war. Eine lokale AI, etwas das Openclaw so nicht erlaubt. Damit kann ich nun in Telegram

-erlaubte Verzeichnisse mit list auflisten
-mit send kann man sich Dateien schicken lassen.
-ausgewählte Befehle wie df, free, whoami, top und uptime ausführen und nach Telegram übertragen. Diese werden mit cmd getriggert.
-Mit analyze kann man Dateien, zB logs, analysieren lassen. Das funktioniert mit einem KI Fallback. Alles andere ist eine Zuweisung per Programmierung.
-mit show kann man sich Textdateien anzeigen lassen.

ChatGPT hat mich davor gewarnt, zu viele Befehle einzubauen, weil ich ansonsten bald eine Remoteshell habe, diesen Rat habe ich beherzigt.

Als KI Modell habe ich ein kleines Qwen2.5-7B-Instruct-Q4_K_M, damit es nicht zu lange dauert. Die Geschwindigkeit ist in Ordnung und hängt an Vulcan, weil SYCL, das besser geeignet wäre, nicht funktioniert. Man kann jetzt nicht allzuviel davon erwarten, aber es erfüllt seinen Zweck.

Derzeit ist es so eingestellt, dass es sich innerhalb der selben Session an Nachrichten erinnern kann.

Zum Verständnis, das kann ich auch vom Smartphone aus machen. Es ist eine Spielerei, die durchaus noch ausbaufähig ist. Momentan fehlt mir aber ein wenig Fantasie, um da mehr daraus zu machen.

https://odysee.com/@jeyf123:d/Minibot:c

import requests
import subprocess
import os
from telegram import Update
from telegram.ext import ApplicationBuilder, MessageHandler, ContextTypes, filters

# ==============================
# KONFIGURATION
# ==============================

TELEGRAM_BOT_TOKEN = "xxx"
ALLOWED_USER_ID = xxx

LLAMA_URL = "http://127.0.0.1:8080/v1/chat/completions"
MODEL_NAME = "Qwen2.5-7B-Instruct-Q4_K_M.gguf"

BASE_DIR = "/home/gerald"

ALLOWED_COMMANDS = {
    "df": ["df", "-h"],
    "free": ["free", "-h"],
    "uptime": ["uptime"],
    "whoami": ["whoami"],
    "top": ["top", "-b", "-n", "2"]
}


# ==============================
# SICHERHEIT
# ==============================

def is_safe_path(path):
    real = os.path.realpath(path)

    if not real.startswith(BASE_DIR):
        return False

    if "/." in real:
        return False

    if os.path.islink(real):
        return False

    return True


# ==============================
# HAUPTFUNKTION
# ==============================

async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):

    user_id = update.effective_user.id
    if user_id != ALLOWED_USER_ID:
        return

    user_text = update.message.text.strip()
    print("DEBUG:", repr(user_text))

    # ==============================
    # ANALYZE DATEI
    # ==============================

    if user_text.lower().startswith("analyze "):
        relative_path = user_text[8:].strip()
        candidate = os.path.join(BASE_DIR, relative_path)

        if not is_safe_path(candidate):
            await update.message.reply_text("Pfad nicht erlaubt.")
            return

        if not os.path.exists(candidate):
            await update.message.reply_text("Datei existiert nicht.")
            return

        with open(candidate, "r", encoding="utf-8", errors="ignore") as f:
            content = f.read(16000)

        # Datei im User-Kontext speichern
        context.user_data["last_file_content"] = content
        context.user_data["last_file_name"] = relative_path

        try:
            response = requests.post(
                LLAMA_URL,
                json={
                    "model": MODEL_NAME,
                    "messages": [
                        {
                            "role": "system",
                            "content": "Du analysierst Dateiinhalt sachlich und strukturiert. Erfinde nichts."
                        },
                        {
                            "role": "user",
                            "content": content
                        }
                    ],
                    "temperature": 0.2,
                    "max_tokens": 2048
                },
                timeout=300
            )

            result = response.json()

            if "choices" not in result:
                await update.message.reply_text(f"Server-Fehler:\n{result}")
                return

            answer = result["choices"][0]["message"]["content"].strip()
            await update.message.reply_text(answer[:3500])

        except Exception as e:
            await update.message.reply_text(f"Analyse-Fehler: {e}")

        return


    # ==============================
    # CMD WHITELIST
    # ==============================

    if user_text.lower().startswith("cmd "):
        cmd_name = user_text[4:].strip().lower()

        if cmd_name not in ALLOWED_COMMANDS:
            await update.message.reply_text("Befehl nicht erlaubt.")
            return

        try:
            result = subprocess.run(
                ALLOWED_COMMANDS[cmd_name],
                capture_output=True,
                text=True,
                timeout=10
            )

            output = result.stdout or result.stderr
            if not output:
                output = "Keine Ausgabe."

            await update.message.reply_text(
                f"```\n{output[:3500]}\n```",
                parse_mode="Markdown"
            )

        except Exception as e:
            await update.message.reply_text(f"Fehler: {e}")

        return


        # ==============================
    # DATEI SENDEN
    # ==============================

    if user_text.lower().startswith("send "):
        relative_path = user_text[5:].strip()
        candidate = os.path.join(BASE_DIR, relative_path)

        if not is_safe_path(candidate):
            await update.message.reply_text("Pfad nicht erlaubt.")
            return

        if not os.path.exists(candidate):
            await update.message.reply_text("Datei existiert nicht.")
            return

        if os.path.getsize(candidate) > 20 * 1024 * 1024:
            await update.message.reply_text("Datei zu groß.")
            return

        try:
            with open(candidate, "rb") as f:
                await update.message.reply_document(f)
        except Exception as e:
            await update.message.reply_text(f"Sende-Fehler: {e}")

        return

        # ==============================
    # DATEIEN AUFLISTEN
    # ==============================

    if user_text.lower().startswith("list"):
        relative_path = user_text[4:].strip() or "."
        candidate = os.path.join(BASE_DIR, relative_path)

        if not is_safe_path(candidate):
            await update.message.reply_text("Pfad nicht erlaubt.")
            return

        if not os.path.isdir(candidate):
            await update.message.reply_text("Kein Verzeichnis.")
            return

        try:
            entries = [
                e for e in os.listdir(candidate)
                if not e.startswith(".")
            ]

            if not entries:
                await update.message.reply_text("Verzeichnis ist leer.")
                return

            dirs = sorted(
                e for e in entries
                if os.path.isdir(os.path.join(candidate, e))
            )

            files = sorted(
                e for e in entries
                if not os.path.isdir(os.path.join(candidate, e))
            )

            output_lines = []

            for d in dirs:
                output_lines.append(f"<DIR>  {d}")

            for f in files:
                output_lines.append(f"       {f}")

            output = "\n".join(output_lines)

            await update.message.reply_text(
                f"```\n{output[:3500]}\n```",
                parse_mode="Markdown"
            )

        except Exception as e:
            await update.message.reply_text(f"Fehler: {e}")

        return

        # ==============================
    # DATEI ANZEIGEN (TEXT)
    # ==============================

    if user_text.lower().startswith("show "):
        relative_path = user_text[5:].strip()
        candidate = os.path.join(BASE_DIR, relative_path)

        ALLOWED_SHOW_EXT = (".txt", ".log", ".md", ".conf")

        if not is_safe_path(candidate):
            await update.message.reply_text("Pfad nicht erlaubt.")
            return

        if not os.path.exists(candidate):
            await update.message.reply_text("Datei existiert nicht.")
            return

        if not candidate.lower().endswith(ALLOWED_SHOW_EXT):
            await update.message.reply_text("Dateityp nicht erlaubt.")
            return

        try:
            with open(candidate, "r", encoding="utf-8", errors="ignore") as f:
                content = f.read(4000)

            if not content.strip():
                await update.message.reply_text("Datei ist leer.")
                return

            await update.message.reply_text(
                f"```\n{content}\n```",
                parse_mode="Markdown"
            )

        except Exception as e:
            await update.message.reply_text(f"Fehler: {e}")

        return


    # ==============================
    # KI FALLBACK MIT DATEIKONTEXT
    # ==============================

    last_content = context.user_data.get("last_file_content")
    last_name = context.user_data.get("last_file_name")

    try:
        response = requests.post(
            LLAMA_URL,
            json={
                "model": MODEL_NAME,
                "messages": [
                    {
                        "role": "system",
                        "content": "Du bist ein hilfreicher Linux- und Technik-Assistent."
                    },
                    {
                        "role": "user",
                        "content": f"""Aktuelle Anfrage:
{user_text}

Falls sich die Anfrage auf eine zuvor analysierte Datei bezieht:

Dateiname: {last_name}
Dateiinhalt:
{last_content}
"""
                    }
                ],
                "temperature": 0.7,
                "max_tokens": 2048
            },
            timeout=300
        )

        result = response.json()

        if "choices" not in result:
            await update.message.reply_text(f"Server-Fehler:\n{result}")
            return

        answer = result["choices"][0]["message"]["content"].strip()
        await update.message.reply_text(answer[:3500])

    except Exception as e:
        await update.message.reply_text(f"KI-Fehler: {e}")


# ==============================
# START
# ==============================

if __name__ == "__main__":
    app = ApplicationBuilder().token(TELEGRAM_BOT_TOKEN).build()
    app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
    print("Bot läuft...")
    app.run_polling()


0.10474324 BEE
3 comments

⚠️⚠️⚠️ ALERT ⚠️⚠️⚠️

HIVE coin is currently at a critically low liquidity. It is strongly suggested to withdraw your funds while you still can.

0E-8 BEE
(edited)

Ich hab doch das SYCL Programm gebaut fuer sowas.^^

Lass dir mal helfen das zu starten.^^ Das waere auch Interessant ob die Klaue das kann.

Du bist schon Krass. :-)

Bau er einfach voll rein!
Applaus! Ich kann mit sowas leider nix anfangen, aber ich kann es Testen wenn Du magst.

Edit warum Funktioniert das SYCL Zeug nicht bei dir das Juckt mich.

Die Geraeteerkennung hab ich schon gebaut... Das war das Hauptproblem!!!

Mir faellt sonst nur das Zeug auf deinem System ein das nicht harmoniert...

0E-8 BEE

SYCL hängt mit oneapi zusammen, oder? Das habe ich schon x mal versucht zu installieren. Da kann ich entweder auf das Repo von Intel nicht zugreifen, bzw. bietet OpenSuse kein vollständiges Paket. Ich hatte noch nicht den Nerv, etwas wie Garuda zu installieren. Da fällt mir ein, offenbar ist meine C++ Zeit nun auch zu Ende. Das ist genau was ich befürchtet habe. Eine Zeitlang geht es gut, vielleicht sogar sehr gut, dann kommt es zum Stillstand und ich kann mich nicht mehr überwinden, etwas zu tun.

8.3E-7 BEE

Wenn mich der Rappel packt schreib ich den Suse Waschlappen mal die sollen das einbauen.

Ansonsten lass dich doch nicht stressen von dir selber das geht jedem so. Nur wieder anzufangen, wenn man Lustig ist, wo man aufgehoert hat vorher, ist der Trick, hab ich mir sagen lassen. :-)

Mach doch Nix! Das war sowieso krass die letzte Zeit von dir, ich habe dafuer weniger gemacht wieder mal. Lass dich nicht aergern. :-)

0E-8 BEE

Pass auf, auf welche Daten und Accounts du den Datenkraken Zugriff gibst...

0E-8 BEE

ja

0E-8 BEE