Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
GOOGLE_API_KEY="YOUR_GEMINI_API_KEY"
GTFS_API_KEY="YOUR_GTFS_API_KEY"
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# HOUSING CHATBOT

## Overview

The housing chatbot aims to recommend the most suitable properties for the users based on their preferences. Besides basic information such as property type (House, Apartment), rental fee, number of bedrooms/bathrooms, etc, it can handle queries that include distance and travelling time from the properties to other places (such as Deakin University), in both driving and public transportation mode, which listing sites such as [realestate.com.au](realestate.com.au) currently do not support. In trimester 2 - 2025, our team has built the chatbot that could recommend properties based on a static crawled listing, it can handle multi-turn conversations where the user query is too vague (e.g. "I want to find a room") so that it could understand more about user's preferences. Based on the preferences, including basic information and distance/time to other places, the chatbot uses a simple ranking model with linear regression to rank the properties. The chatbot is built with LangGraph, with Gemini Flash 2.0 as the core LLM. The UI is build with Chainlit.

**Flowchat**:

![Flow chart of the chatbot](./Housing%20chatbot%20flowchart.png)

## Code structure
- `graph.py`: Core components of the chatbot compiled with LangGraph.
- `json_schema.py`: Pre-defined housing schema to help the LLM extract housing entities from user's query.
- `prompts.py`: System prompt for the LLM to extract housing entities.
- `utils.py`: Core functions for the components, including data processing, house ranking, and distance calculation.
- `runner.py`: Simple runner for the chatbot in the local terminal for testing.
- `app.py`: UI for the chatbot using Chainlit.

## How to run the code

### 1. Setup the environment
- `Python >=3.11`.
- `pip install langgraph langchain-google-genai dotenv chainlit geopy scipy openrouteservice pandas folium geopandas`.
- Add your Gemini API key and GTFS API key in the `.env` file.

### 2. Run the code on terminal (for testing purposes)
`python runner.py`

### 3. Run on UI
`chainlit run app.py -w`

## Future development
Future plans for the chatbot might include:
1. Live listing data through API or crawling pipelines;
2. Full distance/travelling time for public transportation, including trams and buses (Right now, it only handles walking, driving, and travelling by trains);
3. Enable for buying use case (it's only built for rental right now).
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from typing import cast
import chainlit as cl

from langchain.schema.runnable.config import RunnableConfig
from langchain_core.messages import AIMessage, HumanMessage

from langgraph.graph.state import CompiledStateGraph
from langgraph.types import Command

from graph import create_graph


@cl.on_chat_start
async def on_chat_start():
cl.user_session.set("messages", [])
agent = await get_chat_agent()
cl.user_session.set("agent", agent)

@cl.on_message
async def on_message(message: cl.Message):
agent = cast(CompiledStateGraph, cl.user_session.get("agent"))
config = RunnableConfig(
configurable={"thread_id": cl.context.session.id},
callbacks=[cl.LangchainCallbackHandler()],
)

messages = cl.user_session.get("messages")
messages.append(HumanMessage(content=message.content))
cl.user_session.set("messages", messages)

interrupt = None
response = cl.Message(content="")

stream = agent.astream(
{"messages": messages},
config=config,
stream_mode=['messages', 'updates'],
)

while stream:
async for stream_mode, pack in stream:
if stream_mode == 'messages':
msg, metadata = pack
if (
msg.content
and not isinstance(msg, HumanMessage)
and metadata["langgraph_node"] in ["show_data_output", "end_query"]
):
await response.stream_token(msg.content)
stream = None

else:
if '__interrupt__' in pack:
interrupt = pack['__interrupt__'][0]
res = await cl.AskUserMessage(content=str(interrupt.value)).send()

cmd = Command(resume=res["output"])

stream = agent.astream(
cmd,
config=config,
stream_mode=['messages', 'updates'],
)
else:
stream = None

messages.append(AIMessage(content=response.content))
cl.user_session.set("messages", messages)

await response.send()

async def get_chat_agent() -> CompiledStateGraph:
graph = create_graph()
return graph
Loading