A newer version of the Gradio SDK is available: 6.14.0
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):
rooms– Anzahl Zimmerarea_m2– Wohnfläche in m²pop– Bevölkerung der Gemeindepop_dens– Bevölkerungsdichte (Einwohner/km²)frg_pct– Ausländeranteil in Prozentemp– Anzahl Beschäftigte in der Gemeindetax_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,
nullfü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:
- Ob die Antwort nicht leer ist
- Ob allfällige Markdown-Code-Blöcke (
```json) entfernt werden - Ob der String valides JSON ist (
json.loads) - 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
- Nutzer gibt deutschen Wohnungswunsch ein (z.B. «Ich suche eine 3.5-Zimmer-Wohnung mit 85 m² in Winterthur.»)
extract_preferences(): LLM (Claude Haiku) extrahiertrooms,area_m2,townals JSONparse_json_response()validiert das JSON und prüft alle Pflichtfelderrun_pipeline()prüft aufnull-Werte und gibt Fehlermeldung bei fehlenden Angabenmatch_town()ordnet den Ortsnamen einem kanonischen BFS-Gemeindenamen zupredict_apartment_price()lädt Gemeindedaten und übergibt alle 7 Features an das Modellgenerate_explanation(): LLM generiert eine deutsche Erklärung des Preises- 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 explizitenValueErrormit 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, nichtareawie 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.pyrandom_forest_regression.pklbfs_municipality_and_tax_data.csvrequirements.txtREADME.mddocumentation.md
9.2 Secrets / Environment Variables
OPENAI_API_KEY– Pflicht: OpenAI API KeyOPENAI_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
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
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.

