Hallo meine lieben Brotfische,
lange ist es her.
Neulich war ich auf einem Server, die hatten kein Tutorial und keine Erklärungen.
Dann hat man sich selbst durch die Menüs gekämpft, nur um dann Befehle zu finden, wo es hieß, den kannst du ohne xyz nicht nutzen. Findest du am Schwarzmarkt.
Glaubt ihr, ich habe diesen Schwarzmarkt gefunden? -Nein
Gab es ein Navi? -Nein
Wusste irgendwer wo das war? -Nein
Das fand ich sehr nervig.
Und wie cool wäre es, wenn jeder Server einen AI-Assistenten völlig kostenlos und ohne GPU hätte?
Da will ich euch heute mal einen Ansatz vorstellen.
Vielleicht noch kurz zur Erklärung, wieso ich kein LLM nutzen mag. Natürlich wäre der Output dann besser, allerdings ist es dann auch massiv langsamer, teurer, aufwändiger und anfälliger für falsche Infos (Stichwort Halluzination).
Die Theorie
Die Grundidee nennt sich Word2Vector.
Aber um es zu vereinfachen, jedem Wort ordnen wir quasi eine Zahl zu. Und je ähnlicher die Wörter sind, desto ähnlicher die Zahlen. Zum Beispiel Mann (3) und Frau (4). Aber Mann (3) und Apfel (50). Hier sehen wir, dass die Wörter kontextuell sehr unterschiedlich sind. Es gibt sog. Embedding Modelle, die übernehmen genau diese Aufgabe für uns. Sie wurden auf riesige Textmengen trainiert und können gekonnt den Wörtern entsprechende Zahlen zuweisen.
Nun haben wir eine Textdatei, z.B.
/duty = Damit können Sie OnDuty gehen. (Wert: 5)
/open = Damit können jegliche Art von Tor/Schranke geöffnet werden. (Alternativ auch H im Auto drücken) (Wert: 90)
/su = Um einem Spieler Wanteds zu geben. (Wert: 20)
Wenn wir jetzt eine Frage stellen, zum Beispiel: Wie gebe ich jmd Wanteds? Dann kommt auch hier unser Embedding Modell und gibt dem Satz einen Wert, in unserem Fall wäre das 18. Und jetzt gehen wir einfach alle Möglichkeiten durch und finden, dass wir wohl nach dem /su Befehl suchen, da er dem Spieler Wanteds gibt und geben ihm diese Zeile aus.
Implementierung
Die Implementierung ist in python sehr einfach. Wieso python? Weil das der einfachste und gängigste Weg für "Machine Learning" ist. Hier gibt es viele optimierte Bibliotheken, die unter der Haube C++/Rust nutzen und so sehr performant sind.
Die Idee ist nun sehr einfach, wir setzen in python einen lokalen Web-Server (der auf der selben Maschine läuft wie unser Gameserver, aber nicht von extern erreichbar ist) auf.
Dann führen wir von unserem Gameserver (egal ob GTA SA/GTA 5/MTA oder Arc oder sonstiges Game) einfach einen HTTP Request aus.
Gameserver -> ruft per HTTP Anfrage + Frage unseren Server auf -> Der Server nutzt Word2Vec und gleicht das mit der DB ab -> gibt das Ergebnis wieder.
Wenn man wirklich große Infos/Daten hat, dann sollte man hier eine Datenbank, wie Chroma o.ä. verwenden und nicht nur eine Textdatei, da sonst ggfs viel RAM gefressen wird.
Aber für die "normale" Nutzung sollte das erstmal kein Problem sein.
from sentence_transformers import SentenceTransformer, util
from flask import Flask, request
with open("data.txt", "r", encoding="utf-8") as f:
data = [line.strip() for line in f if line]
model = SentenceTransformer("distiluse-base-multilingual-cased-v2", cache_folder="brain") # Gutes Embedding-Modell vor Allem für Deutsch! :)
data_embedding = model.encode(data, convert_to_tensor=True)
def find_top_line(query):
global model, data_embedding, data
query_embedding = model.encode(query, convert_to_tensor=True)
scores = util.cos_sim(query_embedding, data_embedding)[0]
idx = scores.argmax().item()
return "Keine Ergebnisse in der Datenbank gefunden :(" if scores[idx] < 0.15 else data[idx]
app = Flask(__name__)
@app.route('/ai', methods=['GET', 'POST'])
def handle_ai():
question = request.form.get("text") or request.args.get("text") or ""
question = question.strip()
response_text = find_top_line(question) if question else ""
response_bytes = response_text.encode("cp1252", errors="replace")
return response_bytes, 200, {"Content-Type": "text/plain; charset=windows-1252"}
if __name__ == '__main__':
app.run(host="127.0.0.1", port=1337)
Alles anzeigen
Zuerst die Abhängigkeiten installieren:
pip install sentence-transformers flask
Und anschließend kann das Skript einfach mit python main.py.
PS: Bei Linux heißt es pip3 oder python3. Hier natürlich mit nohup o.ä. arbeiten.
Unsere data.txt sieht einfach so z.B. aus:
/switchcolor = Um Ihre Autofarbe anzupassen.
/members = Zeigt die Member an, die online sind.
/duty = Damit können Sie OnDuty gehen.
/open = Damit können jegliche Art von Tor/Schranke geöffnet werden. (Alternativ auch H im Auto drücken)
/r = Damit schreiben Sie Ihren Team-Kollegen.
/d = Damit können Sie allen Staatsfraktionen schreiben.
/gov = Damit können Sie Regierungsnachrichten an den Server senden.
/m = Steht für Megaphone, damit können Sie einen Text aus einem Polizei Auto schreien.
/su = Um einem Spieler Wanteds zu geben.
/cuff = Fesselt einen Spieler.
/uncuff = Entfesselt einen Spieler.
/arrest = Sperrt einen Verbrecher ein.
/frisk = Durchsucht einen Spieler nach illegalen Items.
/take = Damit kannst du einem Spieler illegale Items abnehmen.
/Bluttest = Um Drogen- oder Alkoholtest durchzuführen.
/orten = Damit können Sie einen Spieler suchen.
/hide = Damit entfernen Sie einen Marker.
/rb = Das steht für RoadBlock und erstellt eine Straßensperre.
Um zum Schwarzmarkt zu gelangen "/find blackmarket".
Alles anzeigen
Also unser Algorithmus sucht in jeder Zeile, nach der entsprechenden Antwort von unserer Frage.
Dort müssen nicht nur Befehle drinnen stehen, sondern es können auch Erklärungen/Texte o.ä. reinpassen. Wichtig ist nur, dass es in einer Zeile steht.
Man kann das System beliebig erweitern, wie vllt eine Datei nur für Befehle, die Andere für Erklärungen und dann gibt es mehrere cmds, wie /ai_cmds und /ai_explain...o.ä.
In Pawn (hehe, good old SA:MP) sieht es dann z.B. so aus:
#include <open.mp>
#include <makro>
#include <cmd>
#include <strlib>
CMD:ai(playerid, params[])
{
if(GetPVarType(playerid,"ai") != PLAYER_VARTYPE_NONE) return 0; // um (D)DOS zu unterbinden
SetPVarInt(playerid, "ai", 1);
new out[256];
strurldecode(out, params);
new string[256];
format(string,sizeof(string),"text=%s",out);
HTTP(playerid, HTTP_POST, "http://127.0.0.1:1337/ai", string, "OnHttpResponse");
return 1;
}
forward OnHttpResponse(index, response_code, data[]);
public OnHttpResponse(index, response_code, data[])
{
new playerid = index;
new buffer[128];
static const prefix[] = "* [AI]: ";
new len = strlen(data);
new i = 0;
while (i < len) // so kompliziert, da in SAMP nur 128 Zeichen in eine Zeile passen. Somit loopen wir hier durch den Output, bis alles ausgegeben wurde in jeder Zeile. Würden wir alles in eine Zeile schreiben, würde es leider abgeschnitten werden.
{
new chunkSize = ((i+chunkSize-1) > (len-1)) ? (len - i) : (sizeof(buffer) - 1);
if (i == 0)
{
chunkSize -= strlen(prefix); // leave space for prefix
strmid(buffer, data, i, i + chunkSize - 1);
strins(buffer, prefix, 0, sizeof(buffer));
}
else
{
strmid(buffer, data, i, i + chunkSize - 1);
}
SendClientMessage(playerid, 0xFF8000FF, buffer);
i += chunkSize;
}
DeletePVar(playerid, "ai");
return 1;
}
Alles anzeigen
InGame sieht das dann so aus:
FAQ
- Ja, das läuft auf Linux oder Windows.
- Nein, man brauch keine GPU.
- Der Python-Web-Server soll nur lokal verfügbar sein! (Auch aus Sicherheitsgründen!)
- Eine Anfrage braucht ungefähr ~35ms
- Wichtig ist URLEncode zu nutzen bei der HTTP Anfrage!
- Wenn man Quatsch eingibt, bekommt man Quatsch zurück oder eben, dass nichts in der DB gefunden wurde.
- Wieso kein LLM? Wurde oben erklärt.
- Werden meine Änderungen sofort übernommen, wenn ich sie in der data.txt ergänze? Nein, da das unnötig wäre immer on the fly für alle Zeilen den Vektor zu berechnen. Man muss dann kurz alles Neustarten. Aber das sollte ja auch in 5 Sekunden erledigt sein. Alternativ kann man hier ansetzen eine Schnittstelle noch zu implementieren, die den Datenvektor erweitert bzw das direkt in einer .db ablegt. Dafür eignet sich chroma gut.
Bei Fragen wendet euch gerne an ChatGPT und wenn der nicht weiterkommt, dann gerne an mich
Hoffe es inspiriert vielleicht den Ein oder Anderen hier im Forum sowas zu implementieren, fände ich mega stark!
In diesem Sinn, bei Anregungen, gerne einen Kommentar, falls euch der Beitrag gefallen hat, gerne einen Daumen nach oben