exercise3 / documentation.md
nbacchi's picture
Upload documentation.md with huggingface_hub
51f94af verified

A newer version of the Gradio SDK is available: 6.14.0

Upgrade

Documentation

Abgabe 3: Conversational Agent – Apartment Price Prediction


1. Project Summary

Die App ermöglicht es Nutzern, ihren Wohnungswunsch auf Deutsch zu beschreiben und erhält eine Mietpreisschätzung für den Kanton Zürich. Ein LLM (Claude von Anthropic) extrahiert strukturierte Daten (Zimmer, Fläche, Ort) aus dem Freitext. Diese werden mit Gemeindedaten aus dem BFS-Datensatz ergänzt und an ein trainiertes Random-Forest-Regressionsmodell übergeben. Das Modell liefert die Preisschätzung; ein zweiter LLM-Schritt erklärt das Ergebnis in natürlicher Sprache auf Deutsch.


2. Files Used

Datei Zweck
app.py Hauptanwendung: Pipeline, LLM-Calls, Gradio-Interface
random_forest_regression.pkl Gespeichertes Regressionsmodell (aus GitHub-Repo)
bfs_municipality_and_tax_data.csv Gemeindedaten Kanton Zürich (Bevölkerung, Steuern, etc.)
requirements.txt Python-Abhängigkeiten
documentation.md Diese Dokumentation
README.md Hugging Face Space Konfiguration und Beschreibung

3. Numeric Prediction Part

3.1 Reused Model

Verwendetes Modell: random_forest_regression.pkl

Was das Modell vorhersagt:
Die monatliche Bruttomiete (CHF) einer Wohnung im Kanton Zürich, basierend auf Wohnungsmerkmalen und Gemeindedaten.

Input Features (exakt 7, in dieser Reihenfolge):

  1. rooms – Anzahl Zimmer
  2. area_m2 – Wohnfläche in m²
  3. pop – Bevölkerung der Gemeinde
  4. pop_dens – Bevölkerungsdichte (Einwohner/km²)
  5. frg_pct – Ausländeranteil in Prozent
  6. emp – Anzahl Beschäftigte in der Gemeinde
  7. tax_income – Steuerbares Einkommen (Medianeinkommen der Gemeinde)

3.2 Prediction Logic

Der Ortsname aus der LLM-Extraktion wird mittels match_town() einem kanonischen BFS-Gemeindenamen zugeordnet (zunächst exakter Vergleich in Kleinbuchstaben, dann Teilstring-Matching). Die entsprechende Zeile aus bfs_municipality_and_tax_data.csv liefert die Gemeindedaten. Diese werden zusammen mit Zimmer und Fläche als DataFrame an model.predict() übergeben.


4. LLM Extraction Part

4.1 Goal

Das LLM soll aus einem deutschen Freitext drei strukturierte Werte extrahieren: die Anzahl Zimmer, die Wohnfläche in m² und den Gemeindenamen. Diese drei Werte werden benötigt, um die Modellvorhersage zu erstellen.

4.2 Prompt Design

Es wurde ein System-Prompt mit einer Developer-Instruction verwendet. Der Prompt:

  • Definiert die Rolle explizit als «Datenextraktionssystem»
  • Fordert strikte JSON-Ausgabe ohne Markdown und ohne Erklärung
  • Nennt alle drei Pflichtfelder mit Typ und Beispiel
  • Gibt an, null für fehlende Werte zu verwenden
Du bist ein Datenextraktionssystem für Schweizer Wohnungsanfragen.
Extrahiere aus dem Benutzertext genau drei Werte und gib sie als JSON zurück:
- "rooms": Anzahl Zimmer als Dezimalzahl (z.B. 3.5)
- "area_m2": Wohnfläche in m² als Zahl
- "town": Name der Gemeinde oder Stadt als String
Gib NUR gültiges JSON zurück, kein Markdown, keine Erklärung.
Beispiel: {"rooms": 3.5, "area_m2": 85, "town": "Winterthur"}
Falls ein Wert fehlt oder unklar ist, setze ihn auf null.

4.3 Expected Output Format

{"rooms": 3.5, "area_m2": 85, "town": "Winterthur"}

4.4 Validation

Die Funktion parse_json_response() prüft:

  1. Ob die Antwort nicht leer ist
  2. Ob allfällige Markdown-Code-Blöcke (```json) entfernt werden
  3. Ob der String valides JSON ist (json.loads)
  4. Ob alle drei Pflichtfelder vorhanden sind

Zusätzlich prüft run_pipeline(), ob alle drei Felder den Wert null haben und gibt eine benutzerfreundliche Fehlermeldung aus.


5. LLM Explanation Part

5.1 Goal

Nach der Preisberechnung soll das LLM eine kurze, verständliche Antwort auf Deutsch generieren. Das Modell erklärt den vorhergesagten Preis in Zusammenhang mit den Wohnungsmerkmalen – ohne einen eigenen neuen Preis zu berechnen.

5.2 Prompt Design

Der System-Prompt:

  • Definiert die Rolle als «freundlicher Schweizer Wohnungsberater»
  • Verlangt 2–3 Sätze auf Deutsch
  • Fordert Erwähnung von Zimmer, Fläche, Ort und geschätztem Preis
  • Verlangt einen Unsicherheitshinweis (Zustand, Mikrolage)
  • Untersagt eine eigene Preisberechnung
  • Fordert striktes JSON mit dem Schlüssel "answer"

5.3 Expected Output Format

{"answer": "Für eine 3.5-Zimmer-Wohnung mit 85 m² in Winterthur schätzt das Modell eine monatliche Miete von rund CHF 2'450. Winterthur ist eine gut erschlossene Stadt mit moderaten Mietpreisen. Beachte, dass Faktoren wie Zustand der Wohnung und Mikrolage nicht im Modell enthalten sind und den tatsächlichen Preis beeinflussen können."}

6. End-to-End Pipeline

  1. Nutzer gibt deutschen Wohnungswunsch ein (z.B. «Ich suche eine 3.5-Zimmer-Wohnung mit 85 m² in Winterthur.»)
  2. extract_preferences(): LLM (Claude Haiku) extrahiert rooms, area_m2, town als JSON
  3. parse_json_response() validiert das JSON und prüft alle Pflichtfelder
  4. run_pipeline() prüft auf null-Werte und gibt Fehlermeldung bei fehlenden Angaben
  5. match_town() ordnet den Ortsnamen einem kanonischen BFS-Gemeindenamen zu
  6. predict_apartment_price() lädt Gemeindedaten und übergibt alle 7 Features an das Modell
  7. generate_explanation(): LLM generiert eine deutsche Erklärung des Preises
  8. Die App gibt drei Ausgaben zurück: extrahierte JSON-Parameter, numerische Mietschätzung, natürlichsprachliche Antwort

7. Test Cases

Test Input Extraktion korrekt? Preis zurückgegeben? Erklärung zurückgegeben? Anmerkungen
ich suche eine 5 zimmer wohnung in uster mit mindestens 500 quadratmeter Ja – rooms: 5, area_m2: 500, town: "Uster" Ja – CHF 3'757/Monat Ja Auch ohne Grossschreibung und formale Sätze werden alle Felder korrekt extrahiert
ich suche eine 2 zimmer wohnung in uster mit mindestens 50 quadratmeter Ja – rooms: 2, area_m2: 50, town: "Uster" Ja – CHF 1'578/Monat Ja Kleinere Wohnung, plausibel tieferer Preis
2 zimmer 100 quadratmeter uster Ja – rooms: 2, area_m2: 100, town: "Uster" Ja – CHF 2'461/Monat Ja Sehr kurze Eingabe ohne Satzstruktur, LLM extrahiert trotzdem korrekt
Ich möchte eine Wohnung in Zürich. Teilweise – town: "Zürich", rooms: null, area_m2: null Nein Nein Fehlermeldung: fehlende Angaben Zimmer und Fläche
Ich suche etwas in Tokio. Ja – town: "Tokio" Nein Nein match_town() findet keinen Match, klare Fehlermeldung ausgegeben

8. Errors and Problems

Problem 1: LLM gibt Markdown zurück statt reines JSON

  • Ursache: Manche Claude-Varianten umhüllen JSON mit ```json ... ```
  • Fix: parse_json_response() erkennt Markdown-Blöcke und entfernt sie vor dem Parsen

Problem 2: Ortsname aus LLM stimmt nicht exakt mit BFS-Namen überein

  • Ursache: Nutzer schreibt z.B. «Zürich» statt «Zürich (Kreis 1)» oder «Zuerich»
  • Fix: match_town() versucht zunächst exakten Lowercase-Match, dann Teilstring-Matching

Problem 3: Fehlende API-Keys im Deployment

  • Ursache: Hugging Face Secrets nicht gesetzt
  • Fix: call_llm_json() wirft expliziten ValueError mit Hinweis auf fehlende Env-Variable; die App zeigt eine verständliche Fehlermeldung

Problem 4: Modell-Feature-Namen müssen exakt übereinstimmen

  • Ursache: Das gespeicherte Modell erwartet Spalten area_m2, nicht area wie im Projekt 1
  • Fix: Features in predict_apartment_price() exakt nach Vorgabe des Modells benannt (rooms, area_m2, pop, pop_dens, frg_pct, emp, tax_income)

9. Deployment Notes

9.1 Files included

  • app.py
  • random_forest_regression.pkl
  • bfs_municipality_and_tax_data.csv
  • requirements.txt
  • README.md
  • documentation.md

9.2 Secrets / Environment Variables

  • OPENAI_API_KEY – Pflicht: OpenAI API Key
  • OPENAI_MODEL – Optional: Modell-ID (Standard: gpt-4o-mini)

9.3 Deployment Result

Der Space läuft erfolgreich auf Hugging Face. Die Gradio-App startet korrekt, das Modell und die CSV werden beim Start geladen. Alle drei Ausgaben (extrahierte Parameter, Preis, Erklärung) werden korrekt angezeigt.

9.4 Screenshots

Beispiel 1: Grosse Wohnung mit informeller Eingabe

Beispiel 1

Eingabe: «ich suche eine 5 zimmer wohnung in uster mit mindestens 500 quadratmeter» – Das LLM extrahiert korrekt rooms: 5, area_m2: 500, town: "Uster", obwohl die Eingabe keine formale Satzstruktur hat. Das Modell schätzt CHF 3'757/Monat; die Erklärung weist auf Unsicherheiten durch Zustand und Mikrolage hin.

Beispiel 2: Kleine Wohnung, gleicher Ort

Beispiel 2

Eingabe: «ich suche eine 2 zimmer wohnung in uster mit mindestens 50 quadratmeter» – Alle drei Felder werden korrekt erkannt (rooms: 2, area_m2: 50, town: "Uster"). Der deutlich tiefere Preis von CHF 1'578/Monat im Vergleich zu Beispiel 1 ist plausibel und zeigt, dass das Modell auf Fläche und Zimmerzahl reagiert.


10. Reflection

Die Kombination aus Regressionsmodell und LLM funktioniert gut für strukturierte Vorhersagen mit natürlichsprachiger Ein- und Ausgabe. Der stärkste Vorteil ist die Flexibilität: Nutzer müssen keine Formulare ausfüllen, sondern können sich frei ausdrücken. Die fragile Stelle ist die Ortsnamenerkennung – das Modell kennt nur Gemeinden im Kanton Zürich, und der LLM extrahiert möglicherweise Ortsnamen aus anderen Kantonen oder Ländern. Die deutsche Eingabe ist wichtig, da das Modell auf deutschsprachigen Schweizer Kontext ausgerichtet ist. Fehlende Informationen wie Etage, Baujahr, Ausstattung oder genaue Strassenlage beeinflussen den Preis stark, sind aber nicht im Modell enthalten. Als nächste Verbesserung würde sich ein Fuzzy-Matching für Ortsnamen sowie eine Validierung der Wertebereiche (z.B. Fläche > 0) eignen.


11. Responsible Use Note

Die Preisschätzung ist ein statistischer Schätzwert auf Basis historischer Daten und dient nur zur Orientierung – sie ersetzt keine professionelle Immobilienberatung. Das Modell kennt keine aktuellen Marktentwicklungen, den Zustand der Wohnung, die Etage oder die genaue Mikrolage. Das LLM kann Angaben aus dem Text falsch interpretieren, insbesondere bei unklaren oder mehrdeutigen Formulierungen. Alle ausgegebenen Preise sollten daher kritisch hinterfragt und mit aktuellen Inseraten verglichen werden.