Flaskx2+MongoDB

Django, Flask, Bottle, WSGI, CGI…
Antworten
Andreas22
User
Beiträge: 31
Registriert: Donnerstag 5. Januar 2023, 16:51

Liebe alle,

ich habe eine Flask-Datei für einen Chat geschrieben, die ganz ordentlich funktioniert.
Der Chat speist sich aus einer großen Zahl von Json-Dateien, die verschiedene Themen behandeln.
Der Name dieser Json-Dateien soll nun als Topic (Dateiname minus ".json") über einen intermediate_file.json an eine zweite Flask-Datei weitergeben werden.
Im intermediate_file.json soll verzeichnet werden, wer spricht (Bot oder User), Systemzeit, der Ort des Gespräches und die Chat-Message selbst.
Leider kommt Sprecher, timestamp und message prima in der zweiten Flask-Datei an, nicht aber das Topic. Nun bin ich ziemlich sicher, dass der Fehler ein schön blöder ist. Nur komme ich seit 4 Tagen einfach nicht drauf, welcher.

Mit diesem Codeblock versuche ich es:

Code: Alles auswählen

data_cache = {}
last_category_name = ""
last_json_filename = ""
recent_messages = []
last_matched_pattern = ""
last_responses = set()
speaker = ""


def remove_punctuation_for_matching(input_text):
    pattern = f"[{re.escape(string.punctuation)}]"
    return re.sub(pattern, '', input_text)

def log_message_to_file(speaker, message, json_filename=None):
    if not json_filename:
        print("Warnung: json_filename ist nicht gesetzt!")
    else:
        print(f"json_filename ist gesetzt: {json_filename}")

    topic = os.path.splitext(json_filename)[0] if json_filename else 'Unbekannt'
    placestamp = session.get('user_location', 'Unbekannt')
    record = {
        "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
        "speaker": speaker,
        "message": message,
        "location": placestamp,
        "topic": topic
    }

    file_path = 'C:/Users/andre/Desktop/MALDIX/GPT/intermediate_file.json'
   
    try:
        if os.path.exists(file_path):
            with open(file_path, 'r+', encoding='utf-8') as file:
                try:
                    data = json.load(file)
                    if not isinstance(data, list):
                        data = [data]
                except json.JSONDecodeError:
                    data = []
                data.append(record)
                file.seek(0)
                json.dump(data, file, ensure_ascii=False, indent=4)
                file.truncate()
        else:
            with open(file_path, 'w', encoding='utf-8') as file:
                json.dump([record], file, ensure_ascii=False, indent=4)
    except Exception as e:
        print(f"Error beim Schreiben: {e}")

@app.route("/start_conversation", methods=["GET"])
def start_conversation():
    standortErmittlung = StandortErmittlung()
    standortFrage = standortErmittlung.stelle_standortfrage()
    ort = standortErmittlung.get_location()
    if ort:
        session['user_location'] = ort
    return jsonify({
        "message": standortFrage
    })

emotions = MaldixEmotions()
emotional_memory = EmotionalMemory()
maldix_interface = MaldixEmotionInterface(emotions, emotional_memory)
emoDix = EmoDix(api_key)

@app.route("/chat", methods=["POST"])
def chat():
    data = request.json
    user_input_original = data.get("input_text", "").strip().lower()

    # Find matching category and set last_json_filename
    matching_category = find_matching_category(user_input_original)
    if matching_category:
        global last_category_name, last_json_filename
        last_category_name = matching_category['pattern']
        last_json_filename = matching_category.get('filename', '')
        print(f"Set last_json_filename to: {last_json_filename}")
    else:
        print("No matching category found, last_json_filename remains unchanged.")

    if "wie geht es dir?" in user_input_original:
        emotion, ort = emoDix.get_emotion_based_on_weather()
        if emotion and ort:
            response = f"Ich fühle im Augenblick {emotion} aufgrund des Wetters in {ort}."
        else:
            response = "Keine Ahnung, wie ich mich gerade fühle! Versuch es später nochmal."
        return jsonify({"response": response})

    log_message_to_file("IC", user_input_original, last_json_filename)
    cleaned_input = remove_punctuation_for_matching(user_input_original.lower())
    response = ""

    if ist_wetteranfrage(cleaned_input):
        response = wetterService.check_and_respond(cleaned_input)
    else:
        global recent_messages, last_matched_pattern, last_responses
        if last_category_name and last_json_filename in data_cache:
            current_category = next((cat for cat in data_cache[last_json_filename] if cat['pattern'] == last_category_name), None)
            if current_category:
                matched_response = get_refined_response(current_category, cleaned_input, stopwords_de)
                if matched_response:
                    response = matched_response
                    

        if not response:
            matching_category = find_matching_category(user_input_original)
            if matching_category:
                global last_category_name, last_json_filename
                last_category_name = matching_category['pattern']
                last_json_filename = os.path.splitext(matching_category.get('filename', ''))[0]
                print(f"Set last_json_filename to: {last_json_filename}")
            else:
                print("No matching category found, last_json_filename remains unchanged.")
                last_json_filename = ''

    log_message_to_file("MX", response, last_json_filename)

    recent_messages.append(f"User: {user_input_original}")
    recent_messages.append(f"Bot: {response}")

    return jsonify({"response": response})

Ich wäre sehr dankbar, wenn mir jemand von euch den Star stechen könnte.
Herzliche Grüße
von
Andreas
Benutzeravatar
__blackjack__
User
Beiträge: 13270
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Andreas22: Was ist Flaskx2 und wo hat die Frage bezug zu MongoDB?

Da sind globale Variablen zwischen Funktionen verteilt. Das sollte nicht sein. Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. ``global`` sollte in einem sauberen Programm nicht vorkommen.

Bei WSGI-Anwendungen muss man in der Regel davon ausgehen, dass die über mehrere Prozesse verteilt laufen. ”Globaler” Zustand auf Modulebene über Anfragen hinweg geht also nicht wirklich weil man nicht weiss welcher Prozess die nächste Anfrage beantwortet. Zustand in Webanwendungen geht deshalb über den Client und/oder eine Datenbank oder zumindest einen ähnlichen Dienst.

Statt `print()` sollte man etwas verwenden was fürs Protokollieren gedacht ist. Beispielsweise das `logging`-Modul aus der Standardbibliothek oder etwas externes wie `loguru` oder `structlog`. Dann hat man zum einen Level nach denen man filtern kann, und zum anderen bekommt man bei Ausnahmen auch den Traceback, was für die Fehlersuche wichtig ist.

`os.path` & Co verwendet man nicht mehr seit es das `pathlib`-Modul gibt. So etwas wie ``os.path.splitext(json_filename)[0]`` wird dann beispielsweise zu ``json_file_path.stem``.

Auf die Existenz einer Datei prüfen ist an sich schon relativ ungewöhnlich, in so einem nebenläufigen Programm ist das nicht sicher. Das kann sich direkt nach dem Test ja bereits geändert haben.

Textdateien in einem Modus mit "+" zu öffnen macht in aller Regel keinen Sinn. Das verkleinert im vorliegenden Fall *vielleicht* das Zeitfenster in dem ein konkurierender Lesezugriff ein kaputtes JSON lesen kann, weil das gerade in dem Augenblick um einen weiteren Datensatz erweitert wird, aber das ist den Aufwand nicht wert, wenn man das auch *sicher* haben kann, in dem man erst in eine temporäre Datei schreibt und die am Ende umbenennt. Das ist auf den gängigen Dateisystemen eine atomare Operation.

Andererseits ist das lesen, erweitern, und schreiben eines JSON-Array jetzt auch nicht wirklich die effizienteste Art immer mehr Datensätze in eine Datei zu schreiben. Wenn man bei Datei und JSON bleiben möchte, böte sich „JSON Lines“ als Format an. Allerdings hat man auch da noch das Problem mit Nebenläufigkeit, nämlich bei mehreren Schreibern. Wie schon gesagt: Zustand in Webanwendungen kommt eigentlich in eine Datenbank, weil man da diese ganzen Probleme mit Nebenläufigkeit gelöst bekommt.

Dieses bedingte umverpacken vom Inhalt der JSON-Datei in eine Liste bedeutet ja, dass irgendwo auch einzelwerte in diese Datei geschrieben werden. Das sollte man an *der* Stelle lösen, und nicht beim einlesen.

Die `chat()`-Funktion ist ziemlich wirr. Da kommt Code nahezu doppelt vor, aber eben doch nicht ganz identisch. Nach der Ausgabe das `last_json_filename` unverändert bleibt, wird es dann *doch* verändert‽ Was denn nun?

Die Zeile ``global recent_messages, last_matched_pattern, last_responses`` macht keinen Sinn und kann einfach ersatzlos gestrichen werden.

`remove_punctuation_for_matching()` geht etwas effizienter wenn man den regulären Ausdruck einmal kompiliert. Die Funktion kann man sich dann mit `functools.partial()` aus der `sub()`-Methode basteln.

Sowohl „Standortermittlung“ als auch „Standortfrage“ sind im Deutschen *ein* Wort.

Zwischenstand (ungtestet):

Code: Alles auswählen

import json
import re
import string
from datetime import datetime
from functools import partial
from pathlib import Path

from flask import Flask, jsonify, request, session
from loguru import logger

app = Flask(__name__)
#
# XXX Nein.  Einfach nur Nein.
# 
data_cache = {}
recent_messages = []

PUNCTUATION_RE = re.compile(f"[{re.escape(string.punctuation)}]")

remove_punctuation_for_matching = partial(PUNCTUATION_RE.sub, "")


def log_message_to_file(speaker, message, json_file_path=None):
    if json_file_path:
        logger.debug("json_file_path ist gesetzt: {!r}", json_file_path)
    else:
        logger.warning("json_file_path ist nicht gesetzt!")

    file_path = Path(
        "C:/Users/andre/Desktop/MALDIX/GPT/intermediate_file.json"
    )
    with logger.catch():
        #
        # BUG Hier können Datensätze verloren gehen und je mehr Datensätze in
        #     der Datei sind, desto ineffizienter wird das.  Das gehört eher in
        #     eine Datenbank.
        #
        with file_path.open("rb") as file:
            data = json.load(file)

        data.append(
            {
                "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                "speaker": speaker,
                "message": message,
                "location": session.get("user_location", "Unbekannt"),
                "topic": (
                    json_file_path.stem if json_file_path else "Unbekannt"
                ),
            }
        )
        temporary_path = file_path.with_name(file_path.name + ".part")
        with temporary_path.open("w", encoding="utf-8") as file:
            json.dumps(data, ensure_ascii=False, indent=4)

        temporary_path.rename(file_path)


@app.route("/start_conversation", methods=["GET"])
def start_conversation():
    standortermittlung = Standortermittlung()
    standortfrage = standortermittlung.stelle_standortfrage()
    ort = standortermittlung.get_location()
    if ort:
        session["user_location"] = ort
    return jsonify({"message": standortfrage})


@app.route("/chat", methods=["POST"])
def chat():
    original_user_input = request.json.get("input_text", "").strip().lower()

    matching_category = find_matching_category(original_user_input)
    if matching_category:
        session["last_category_name"] = matching_category["pattern"]
        session["last_json_filename"] = matching_category.get("filename", "")
        logger.debug(
            "Set last_json_filename to: {!r}", session["last_json_filename"]
        )
    else:
        logger.debug(
            "No matching category found, last_json_filename remains unchanged."
        )

    if "wie geht es dir?" in original_user_input:
        emotion, ort = EmoDix(api_key).get_emotion_based_on_weather()
        return jsonify(
            {
                "response": (
                    (
                        f"Ich fühle im Augenblick {emotion} aufgrund des"
                        f" Wetters in {ort}."
                    )
                    if emotion and ort
                    else (
                        "Keine Ahnung, wie ich mich gerade fühle! Versuch es"
                        " später nochmal."
                    )
                )
            }
        )

    log_message_to_file(
        "IC", original_user_input, Path(session["last_json_filename"])
    )
    cleaned_input = remove_punctuation_for_matching(
        original_user_input.lower()
    )
    response = ""
    if ist_wetteranfrage(cleaned_input):
        response = wetterdienst.check_and_respond(cleaned_input)
    else:
        #
        # BUG Potentiell toter Code weil nirgends etwas in `data_cache`
        #     gespeichert wird und damit diese Bedingung nie wahr wird.
        #     `data_cache` dürfte so als globales Wörterbuch auch nicht
        #     existieren weil WSGI-Anwendungen in mehr als einem Prozess laufen
        #     können → Zustand in Datenbank!
        #
        if (
            "last_category_name" in session
            and session.get("last_json_filename") in data_cache
        ):
            current_category = next(
                (
                    category
                    for category in data_cache[session["last_json_filename"]]
                    if category["pattern"] == session["last_category_name"]
                ),
                None,
            )
            if current_category:
                matched_response = get_refined_response(
                    current_category, cleaned_input, stopwords_de
                )
                if matched_response:
                    response = matched_response

        if not response:
            #
            # XXX Das was hier stand, passiert alles weiter oben schon mal,
            #     dafür bleibt aber `response` unverändert leer und wird dann
            #     auch so protokolliert und zurück geschickt‽  Also was soll(te)
            #     hier *eigentlich* passieren‽
            #
            pass
    #
    # XXX `response` kann/darf hier leer sein‽
    # 
    log_message_to_file("MX", response, Path(session["last_json_filename"]))
    recent_messages.append(f"User: {original_user_input}")
    recent_messages.append(f"Bot: {response}")
    return jsonify({"response": response})
Please call it what it is: copyright infringement, not piracy. Piracy takes place in international waters, and involves one or more of theft, murder, rape and kidnapping. Making an unauthorized copy of a piece of software is not piracy, it is an infringement of a government-granted monopoly.
Andreas22
User
Beiträge: 31
Registriert: Donnerstag 5. Januar 2023, 16:51

Lieber Blackjack,
ich danke dir sehr für hilfreiche und umfassende Antwort.
Ich bin gerade dabei, sie so gut ich kann umzusetzen
Dir und euch allen ein schönes Wochenende
von
Andreas
Antworten